"""
This is a demonstration file to explain how to
use an object analysis statistics.

#. Create a ROI having multiple disconnected 3D objects, some of those having holes completely included;
#. Right-click on the ROI and select *New Connectivity Multi-ROI Analysis...*;
#. Open the *Statistical Properties* panel (button in the the top-left section of the *Object Analysis* window);
#. In the left column (*Available datasets:*), make sure the *No dataset required* item is selected;
#. In the right column (*Statistical properties:*), select the items *Porous volume* and *Porous volume fraction*;
#. Press the *OK* button to compute these statistics.

: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 2020.
:date: Oct 06 2017 14:42
:dragonflyVersion: 3.1.0.307 (D)
:UUID: 21fa7698aac611e787d6448a5b5d70c0
"""

__version__ = '1.0.0'

import numpy as np

from ORSModel import orsObj
from ORSModel.ors import ROI, DimensionUnit
from OrsHelpers.arrayhelper import ArrayHelper
from OrsPythonPlugins.OrsObjectAnalysis.PythonScriptsStatisticsGenerators.StatisticsGeneratorAbstract import StatisticsGeneratorAbstract
from COMWrapper.ORS_def import CxvUniverse_Dimension
from OrsLibraries.preferences import Preferences


class DemoObjectAnalysisStatisticsPorousVolume_21fa7698aac611e787d6448a5b5d70c0(StatisticsGeneratorAbstract):

    def __init__(self):
        super().__init__()

        self.outputTagsNoDataset = ['Porous volume',
                                    'Porous volume fraction']

    @classmethod
    def getUUID(cls):
        return "21fa7698aac611e787d6448a5b5d70c0"

    def getOutputTagsWithoutDataset(self):
        return self.outputTagsNoDataset

    def generateOutputsWithoutDataset(self, multiROIGUID, listTagsRequested=None, IProgress=None):
        aMultiROI = orsObj(multiROIGUID)  # Get the multi roi object from his guid

        # Compute a numpy array of all multi ROI non empty labels
        arrayNonEmptyLabels = aMultiROI.getNonEmptyLabels(None)
        npArrayNonEmptyLabels = ArrayHelper.ConvertOrsToNumpyArray(arrayNonEmptyLabels)
        arrayNonEmptyLabels.setCallbacksEnabled(False)

        # Here is an integrated cache mechanic. The function readScalarValues check if the statistical properties had
        # already been previously computed. If so, it fills the npArrayDictToReturn dictionary with the computed values
        # and remove the corresponding statistical properties tag from listTagsRemaining
        listTagsRemaining = listTagsRequested
        if listTagsRemaining is None:
            listTagsRemaining = self.getOutputTagsWithoutDataset()
        npArrayDictToReturn = self.readScalarValues(aMultiROI, npArrayNonEmptyLabels, listTagsRemaining)

        if len(listTagsRemaining) == 0:
            # All outputs were found in scalar values. Immediate return.
            arrayNonEmptyLabels.deleteObject()
            return npArrayDictToReturn

        # At last, we have to compute statistical properties that hadn't been previously computed
        nonEmptyLabelCount = len(npArrayNonEmptyLabels)

        # Filling the dictionary with the remaining tags to compute
        for tag in listTagsRemaining:
            npArrayDictToReturn[tag] = (np.zeros((nonEmptyLabelCount,), dtype=float), True)

        # Creating temporary ROIs to manipulate each object
        ROICurrentObject = ROI()
        ROICurrentObject.copyShapeFromStructuredGrid(aMultiROI)
        ROICurrentObject.setCallbacksEnabled(False)
        voxelVolume = ROICurrentObject.getXSpacing() * ROICurrentObject.getYSpacing() * ROICurrentObject.getZSpacing()

        # Progress
        if nonEmptyLabelCount <= 100:
            labelStepRefreshingProgress = 1
        else:
            labelStepRefreshingProgress = int(nonEmptyLabelCount / 100)
        nextLabelUpdateProgress = labelStepRefreshingProgress

        # Getting the arrays to fill
        arrayPorousVolume = None  # Initialization
        if "Porous volume" in listTagsRemaining:
            arrayPorousVolume = npArrayDictToReturn["Porous volume"][0]

        arrayPorousVolumeFraction = None  # Initialization
        if "Porous volume fraction" in listTagsRemaining:
            arrayPorousVolumeFraction = npArrayDictToReturn["Porous volume fraction"][0]

        # Computing the values
        tStepTempROI = 0
        fillInnerHolesConsiderDiagonal = False
        for iLabel in range(nonEmptyLabelCount):
            currentLabel = arrayNonEmptyLabels.at(iLabel)

            # Getting the current object as a ROI
            aMultiROI.addToVolumeROI(ROICurrentObject, currentLabel)

            # Getting the volume before the filling
            volumeInVoxelsBeforeFilling = ROICurrentObject.getVoxelCount(tStepTempROI)

            # Filling the empty portion of the ROI
            ROICurrentObject.fillInnerHoles(tStepTempROI, fillInnerHolesConsiderDiagonal)

            # Getting the volume after the filling
            volumeInVoxelsAfterFilling = ROICurrentObject.getVoxelCount(tStepTempROI)

            # Getting the volume of the porous portion
            volumeInVoxelsPorousPortion = volumeInVoxelsAfterFilling - volumeInVoxelsBeforeFilling

            if arrayPorousVolume is not None:
                # Value of porous volume in m3
                arrayPorousVolume[iLabel] = volumeInVoxelsPorousPortion * voxelVolume

            if arrayPorousVolumeFraction is not None:
                # Value of porous volume fraction is pure number
                arrayPorousVolumeFraction[iLabel] = volumeInVoxelsPorousPortion/volumeInVoxelsAfterFilling

            # Clearing the ROIs for the next iteration
            ROICurrentObject.clear()

            if IProgress is not None:
                if IProgress.getIsCancelled():
                    break
                else:
                    if iLabel == nextLabelUpdateProgress:
                        IProgress.updateProgress(int((iLabel + 1) / nonEmptyLabelCount * 100))
                        nextLabelUpdateProgress += labelStepRefreshingProgress

        if not IProgress.getIsCancelled():
            # Computation is complete for all remaining statistics
            for tag in listTagsRemaining:
                npArrayDictToReturn[tag] = (npArrayDictToReturn[tag][0], False)

        ROICurrentObject.deleteObject()
        arrayNonEmptyLabels.deleteObject()

        return npArrayDictToReturn

    def updateDefaultDimensionUnit(self, outputTag, view):
        if outputTag == 'Porous volume':
            # Volume value
            self.defaultDimensionUnitDict[outputTag] = view.getVolumeDimensionUnit()
        elif outputTag == 'Porous volume fraction':
            # Pure number
            pass

    def getDefaultDimensionUnitDict(self):
        if self.defaultDimensionUnitDict is None:
            self.defaultDimensionUnitDict = {'Porous volume': Preferences.getDefaultVolumeUnit()}
        return self.defaultDimensionUnitDict

    def getDataDimensionUnitDict(self):
        if self.dataDimensionUnitDict is None:
            unitFactory = DimensionUnit()
            self.dataDimensionUnitDict = \
                {'Porous volume': unitFactory.getUnitForID(CxvUniverse_Dimension.CXV_DIMENSION_METER_VOLUME)}
            unitFactory.deleteObject()
        return self.dataDimensionUnitDict
