"""
This is a demonstration file to explain how to
use the object selector ChooseObjectAndNewName.

#. Import a dataset;
#. Create a ROI of the same shape as the dataset, where pixels included in the ROI are those
   representing the water;
#. Create a ROI of the same shape as the dataset, where pixels included in the ROI are those
   representing the air;
#. For the purpose of the demonstration, create another ROI of the same shape as the dataset
   and another ROI with a different shape (for example, by using the crop tool);
#. Select the dataset and right-click on it to open the contextual menu.
   This menu will contain the menu item named *Demo: ChooseObjectAndNewName*.
   When clicking on that menu item, a popup will open to select the ROI representing the water.
   The available ROIs are only those not empty and having the same shape as the dataset.
   Select the ROI corresponding to the water and click on the button to accept the selection.
   Another popup will open to select the ROI representing the air.
   The available ROIs are only those not empty and having the same shape as the dataset,
   and note that the ROI specified for the water has been excluded.
   Select the ROI corresponding to the air and click on the button to accept the selection.
#. The Hounsfield calibration is made on the dataset from these ROIs by adjusting the
   slope and offset.

:author: ORS Team
:contact: http://theobjects.com
:email: info@theobjects.com
:organization: Object Research Systems (ORS), Inc.
:address: 760 St-Paul West, suite 101, Montréal, Québec, Canada, H3C 1M4
:copyright: Object Research Systems (ORS), Inc. All rights reserved.
:date: Nov 02 2017 09:28
:dragonflyVersion: 3.1.0.307 (D)
:UUID: bd9152aebfd111e7b032448a5b5d70c0
"""

__version__ = '1.0.0'

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog

from ORSModel import orsObj, Channel, ROI
from OrsLibraries.workingcontext import WorkingContext
from ORSServiceClass.menuItems.contextualMenuItem import ContextualMenuItem
from ORSServiceClass.decorators.infrastructure import interfaceMethod
from ORSServiceClass.actionAndMenu.menu import Menu
from OrsHelpers.ListHelper import ListHelper
from ORSServiceClass.ORSWidget.chooseObjectAndNewName.chooseObjectAndNewName import ChooseObjectAndNewName


class DemoChooseObjectAndNewName_bd9152aebfd111e7b032448a5b5d70c0(ContextualMenuItem):

    @classmethod
    def getIsSelectionValid(cls, aCollectionOfObjects):
        """
        :param aCollectionOfObjects: a list of objects currently being selected, i.e. on which the menu item could be applied.
        :return: if True, the menu item will be displayed.
        """
        
        # The selection should be made of one Channel only
        if aCollectionOfObjects is None or len(aCollectionOfObjects) != 1:
            return False

        return isinstance(aCollectionOfObjects[0], Channel)

    @classmethod
    def getMenuItemForSelection(cls, aCollectionOfObjects):
        """
        Returns the menu item
        :param aCollectionOfObjects: a list of objects currently being selected, i.e. on which the menu item will be applied.
        :return: Menu
        """

        collectionString = ListHelper.asPythonCollectionString(aCollectionOfObjects)
        myMenu = Menu(title='Demo: ChooseObjectAndNewName',
                      id_='DemoChooseObjectAndNewName_bd9152aebfd111e7b032448a5b5d70c0',
                      section='Demos',
                      action='DemoChooseObjectAndNewName_bd9152aebfd111e7b032448a5b5d70c0.menuItemEntryPoint({collection})'.format(collection=str(collectionString)))
        return myMenu

    @classmethod
    def menuItemEntryPoint(cls, aCollectionOfObjects):
        """
        Will be executed when the menu item is selected.

        :param aCollectionOfObjects: a list of objects representation currently being selected,
            i.e. on which the menu will be applied.
        """

        listOfObjects = ListHelper.fromPythonCollection(aCollectionOfObjects)
        aChannel = listOfObjects[0]

        # Getting the ROI representing the water
        aROIWater = cls.getROI(aChannel, [], 'Choose ROI of water')
        if aROIWater is None:
            return

        # Getting the ROI representing the air
        # The ROI of the water should not be selected for the air,
        # so it is excluded from the list of available ROIs
        aROIAir = cls.getROI(aChannel, [aROIWater.getGUID()], 'Choose ROI of air')
        if aROIAir is None:
            return

        # Performing the Hounsfield calibration
        cls.hounsfieldCalibration(aChannel, aROIWater, aROIAir)

    @classmethod
    def getROI(cls, aChannel, listObjectsToExclude, windowTitle):

        def isAcceptableROI(aROI):
            # Should have the same shape
            if not aChannel.getHasSameShape(aROI):
                return False

            # The ROI should not be empty
            if aROI.getVoxelCount(0) == 0:
                return False

            return True

        chooser = ChooseObjectAndNewName(managedClass=[ROI],
                                         parent=WorkingContext.getCurrentContextWindow(),
                                         excludeObjectsGUIDS=listObjectsToExclude,
                                         getNewTitle=False,
                                         filterFunction=isAcceptableROI)
        chooser.setWindowTitle(windowTitle)
        chooser.setWindowFlags(Qt.Window | Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        if chooser.exec() == QDialog.Rejected:
            return None
        aROIGUID = chooser.getObjectGUID()
        aROI = orsObj(aROIGUID)
        return aROI

    @classmethod
    @interfaceMethod
    def hounsfieldCalibration(cls, aChannel, aROIWater, aROIAir):
        """
        Calibrates a dataset accordingly to the Hounsfield scale.

        :param aChannel: dataset to calibrate
        :type aChannel: ORSModel.ors.Channel
        :param aROIWater: ROI identifying the voxels of water in the input dataset aChannel.
            This ROI should have the same shape as the dataset.
        :type aROIWater: ORSModel.ors.ROI
        :param aROIAir: ROI identifying the voxels of air in the input dataset aChannel.
            This ROI should have the same shape as the dataset.
        :type aROIAir: ORSModel.ors.ROI
        """

        if aChannel is None or aROIWater is None or aROIAir is None:
            return

        # Validating the shapes
        if not aChannel.getHasSameShape(aROIWater):
            return

        if not aChannel.getHasSameShape(aROIAir):
            return

        # Validating the non emptiness of the ROIs
        if aROIWater.getVoxelCount(0) == 0:
            return

        if aROIAir.getVoxelCount(0) == 0:
            return

        # Hounsfield unit (HU) scale:
        # HU = 1000 * (density_data - density_water)/(density_water - density_air)

        # Getting the values of density_water and density_air in the dataset
        t = 0
        densityWater = aROIWater.getMeanSourceDataValue(t, aChannel)
        densityAir = aROIAir.getMeanSourceDataValue(t, aChannel)
        if densityWater == densityAir:
            # Bad selection in the ROIs
            # Going further with the computation would generate an error because of a division by 0
            return

        # Computing the new slope: 1000 / (density_water - density_air)
        newSlope = 1000 / (densityWater - densityAir)

        # Computing the new offset: 1000 * -density_water/(density_water - density_air)
        newOffset = 1000 * -densityWater / (densityWater - densityAir)

        # Setting the new values in the dataset
        aChannel.setSlope(newSlope)
        aChannel.setOffset(newOffset)

        # Raising the flag of data dirty
        aChannel.setDataDirty()

        # Updating all views containing the dataset
        aChannel.refreshAllParentViews()
