from abc import ABC
from enum import Enum
from pathlib import Path
from . import IDocument
from . import PhysicalUnits, Complex, PhysicalProperty

class PhysicalPropertyPy(Enum):
    """ Enumeration of VL PhysicalProperties
    """

    NoUnit = 0
    Length = 1
    AngleRad = 2
    AngleDeg = 3
    Time = 4
    WaveNumber = 5
    AbsorptionCoefficient = 6
    Percentage = 7
    Power = 8
    ElectricalField = 9
    TemporalFrequency = 10
    PowerPerArea = 11
    PowerPerVolume = 12
    AreaPerEnergy = 13
    VolumePerEnergy = 14
    AreaPerEnergySquared = 15
    VolumePerEnergySquared = 16
    EnergyPerArea = 17
    EnergyPerVolume = 18
    PowerPerSolidAngle = 19
    PowerPerSolidAngleAndArea = 20
    Illuminance = 21
    Luminance = 22
    LuminousIntensity = 23
    ArbitraryUnit = 24
    LogarithmicLevel = 25
    ElectricFieldStrengthSquared = 26
    MagneticFieldStrength = 27
    MagneticFieldStrengthSquared = 28
    PowerPerAreaSquared = 29
    PerVolume = 30
    Energy = 31
    Temperature = 32
    Pressure = 33
    ResolvingCapacity = 34

class PhysicalValueBase(ABC):
    """ Wrapper base class for a complex or float value with a physical unit.
    
    Args:
        physical_property (VirtualLabAPI.Core.Numerics.PhysicalProperty): The unit of the PhysicalValue.
        comment (str): A comment specifying what the PhysicalValue actually is.
    """
    
    # Slots
    __slots__ = ('_physical_property', '_comment')
    
    # Constructor
    def __init__(self, physical_property: PhysicalPropertyPy, comment: str = None):
        self.physical_property = physical_property
        self.comment = comment if comment else ''
    
    # Getter of physical_property    
    @property
    def physical_property(self) -> PhysicalPropertyPy:
        """The unit of the PhysicalValue.
        """
        return self._physical_property
    
    # Setter of physical_property   
    @physical_property.setter
    def physical_property(self, val) -> None:
        if not isinstance(val, PhysicalPropertyPy):
            raise TypeError('physical_property must be PhysicalProperty')
        self._physical_property = val
    
    # Getter of comment    
    @property
    def comment(self) -> str:
        """A comment specifying what the PhysicalValue actually is.
        """
        return self._comment
    
    # Setter of comment 
    @comment.setter
    def comment(self, val) -> None:
        if not isinstance(val, str):
            raise TypeError('comment must be str')
        self._comment = val

class PhysicalValue(PhysicalValueBase):
    """ Wrapper class for a float value with a physical unit.
    
    Args:
        value (float): The value of the PhysicalValue.
        physical_property (VirtualLabAPI.Core.Numerics.PhysicalProperty): The unit of the PhysicalValue.
        comment (str): A comment specifying what the PhysicalValue actually is.
    """
    
    # Slots
    __slots__ = ('_value',)
    
    # Constructor
    def __init__(self, value: float, physical_property: PhysicalPropertyPy, comment: str = None):
        self.value = value
        super().__init__(physical_property, comment)
    
    # Overwritten str()-method    
    def __str__(self) -> str:
        try:
            return f'{self.comment}: {PhysicalUnits.FormatPhysicalUnit(float(self.value), PhysicalProperty(self.physical_property.value))}'
        except:
            return f'{self.comment}: {PhysicalUnits.FormatPhysicalUnit(float(self.value), self.physical_property.value)}'

    
    # Getter of value    
    @property
    def value(self) -> float:
        """The value of the PhysicalValue.
        """
        return self._value
    
    # Setter of value
    @value.setter
    def value(self, val) -> None:
        if not isinstance(val, (int, float)):
            raise TypeError('value must be float')
        self._value = val
        
class PhysicalValueComplex(PhysicalValueBase):
    """ Wrapper class for a complex value with a physical unit.
    
    Args:
        value (VirtualLabAPI.Core.Numerics.Complex): The complex value of the PhysicalValue.
        physical_property (VirtualLabAPI.Core.Numerics.PhysicalProperty): The unit of the PhysicalValue.
        comment (str): A comment specifying what the PhysicalValue actually is.
    """
    
    # Slots
    __slots__ = ('_value',)
    
    # Constructor
    def __init__(self, value: Complex, physical_property: PhysicalPropertyPy, comment: str = None):
        self.value = value
        super().__init__(physical_property, comment)
    
    # Overwritten str()-method    
    def __str__(self) -> str:
        return f'{self.comment}: {PhysicalUnits.FormatComplexPhysicalUnit(self.value, self.physical_property.value)}'
    
    # Getter of value    
    @property
    def value(self) -> Complex:
        """value (VirtualLabAPI.Core.Numerics.Complex): The complex value of the PhysicalValue.
        """
        return self._value
    
    # Setter of Value
    @value.setter
    def value(self, val) -> None:
        if not isinstance(val, Complex):
            raise TypeError('value must be VirtualLabAPI.Core.Numerics.Complex')
        self._value = val
    
class Document():
    """ WrapperClass holding VL IDocument object; allows to save to file

    Args:
        vl_object (VirtualLabAPI.Core.Common.IDocument): VL IDocument object
    """
    
    # Slots
    __slots__ = ('__vl_object',)
    
    # Constructor
    def __init__(self, vl_object: IDocument):
        if not isinstance(vl_object, IDocument):
            raise TypeError('vl_object must be a class that implements VirtualLabAPI.Core.Common.IDocument')
        self.__vl_object = vl_object
        
    # Overwritten str()-method 
    def __str__(self):
        return self.__vl_object.__class__.__name__
    
    # Method for saving the Document
    def save(self, path: str, file_name: str) -> None:
        """Saves VL Document to file

        Args:
            path (str): save path
            file_name (str): file name w/o extension!

        Raises:
            NotADirectoryError: Given directory not found/existing
        """
        if path[-1] != '\\':
            path += '\\'
        if not Path(path).exists():
            raise NotADirectoryError('Cannot save to file, because the given path does not exist!')
        save_path = path + file_name + self.__vl_object.FileExtension
        self.__vl_object.Save(save_path)

