from typing import Union, List, Tuple, Optional
from .vector import Vector
from .data import Data
from .datetime_utils import datetime_to_str
from ._private._well_dto import EmbeddedChannelDto, TimeSeriesReferenceChannelDto, ChannelAspectDto, ChannelType, DoubleValuesDto, TimeValuesDto, CrossReferenceChannelDto
from .plots_enum import PlotDataTypesEnum, SymbolAspectEnum, LineAspectEnum, PlotProperties, DrawingStyle
from .measure_enum import MeasureEnum
from .field_data_types_catalog import FieldDataTypesCatalog
from pydantic import ValidationError
[docs]
class PlotChannel:
    """
    Build a PlotChannel dto for the :class:`Plot` object.
    .. note:: Should not be instantiated directly.
    """
[docs]
    def __init__(self, field_id: str, data_types_catalog: FieldDataTypesCatalog, pane_name: Optional[str] = None):
        self.__field_id: str = field_id
        self.__data_types_catalog: FieldDataTypesCatalog = data_types_catalog
        self.__dto: Optional[Union[EmbeddedChannelDto, TimeSeriesReferenceChannelDto, CrossReferenceChannelDto]] = None
        self.__pane_name: Optional[str] = pane_name 
[docs]
    def build_time_series(self, well_id: str, data: Data, properties: Optional[PlotProperties] = None) -> None:
        """ Build plot channel from a time series
        Parameters
        ----------
        well_id:
            Id of the well
        data:
            :class:`Data` object to be plotted
        properties:
            Properties of the channel
        """
        if properties is None:
            properties = PlotProperties()
        aspect_dto = self.__build_aspect_dto(properties, data.data_type)
        is_y_log = properties.is_y_log
        hide_in_legend = properties.hide_in_legend
        is_raw = properties.is_raw
        self.__dto = TimeSeriesReferenceChannelDto(type=ChannelType.TimeSeriesReference, xyVectorId=data.vector_id, isRaw=is_raw, aspect=aspect_dto,
                                                   name=data.name, isYLog=is_y_log, wellId=well_id, hideInLegend=hide_in_legend) 
[docs]
    def build_embedded(self, well_id: str, data: Union[Vector, Tuple[List[float], List[float]]], data_type: Optional[Union[PlotDataTypesEnum, str]] = None,
                       properties: Optional[PlotProperties] = None) -> None:
        """ Build plot channel from embedded data
         Parameters
         ----------
         well_id:
             Id of the well
         data:
             Data to build the channel could be either a Vector or a Tuple of lists
         data_type:
             Data type of the data
         properties:
             Properties of the channel
         """
        if properties is None:
            properties = PlotProperties()
        if properties.y_measure is None:
            y_measure = MeasureEnum.no_unit
        else:
            y_measure = properties.y_measure
        x_values: Union[DoubleValuesDto, TimeValuesDto]
        y_values: DoubleValuesDto
        if type(data) is tuple:
            if len(data[0]) != len(data[1]):
                raise ValueError("X inputs and Y inputs have different lengths")
            if properties.x_measure is None:
                x_measure = MeasureEnum.no_unit
            else:
                x_measure = properties.x_measure
            try:
                x_values = DoubleValuesDto.model_validate({"type": "Double", "values": data[0], "dimension": str(x_measure.value)})
            except ValidationError:
                raise ValueError("X inputs are not of type Double. If you are trying to use datetime objects as X values you must create and use a Vector object as data input instead")
            y_values = DoubleValuesDto.model_validate({"type": "Double", "values": data[1], "dimension": str(y_measure.value)})
        if isinstance(data, Vector):
            if properties.use_elapsed:
                x_values = DoubleValuesDto.model_validate({"type": "Double", "values": data.elapsed_times, "dimension": "Time"})
            else:
                x_values = TimeValuesDto.model_validate({"type": "Time", "values": [datetime_to_str(dt) for dt in data.dates]})
            y_values = DoubleValuesDto.model_validate({"type": "Double", "values": data.values, "dimension": str(y_measure.value)})
        aspect_dto = self.__build_aspect_dto(properties, data_type)
        name = properties.channel_name
        if name is None:
            name = "Channel #1"
        is_y_log = properties.is_y_log
        is_by_step = properties.is_by_step
        first_x = properties.first_x
        hide_in_legend = properties.hide_in_legend
        self.__dto = EmbeddedChannelDto(type=ChannelType.Embedded, aspect=aspect_dto, name=name, isYLog=is_y_log, xValues=x_values, yValues=y_values,
                                        isByStep=is_by_step, firstX=first_x, hideInLegend=hide_in_legend, wellId=well_id) 
[docs]
    def build_cross_plot_channel(self, well_id: str, x_vector_id: str, y_vector_id: str, data_type: Optional[Union[PlotDataTypesEnum, str]] = None,
                                 properties: Optional[PlotProperties] = None) -> None:
        """ Build plot channel from embedded data
         Parameters
         ----------
         well_id:
             Id of the well
         data:
             Data to build the channel could be either a Vector or a Tuple of lists
         data_type:
             Data type of the data
         properties:
             Properties of the channel
         """
        if properties is None:
            properties = PlotProperties()
        name = properties.channel_name
        if name is None:
            name = "Channel #1"
        aspect_dto = self.__build_aspect_dto(properties, data_type)
        self.__dto = CrossReferenceChannelDto(type=ChannelType.CrossReference, aspect=aspect_dto, name=name, isYLog=properties.is_y_log, xVectorId=x_vector_id, yVectorId=y_vector_id,
                                              hideInLegend=properties.hide_in_legend, wellId=well_id) 
    def __build_aspect_dto(self, properties: PlotProperties, data_type: Optional[Union[str, PlotDataTypesEnum]]) -> ChannelAspectDto:
        aspect_dto = self.__get_default_aspect(data_type)
        is_by_step = properties.is_by_step
        show_symbols = properties.show_symbols
        if data_type == PlotDataTypesEnum.shutin or data_type == PlotDataTypesEnum.shutin.value:
            aspect_dto.drawingStyle = DrawingStyle.as_vertical_line
            show_symbols = False
            if aspect_dto.band is not None:
                aspect_dto.band.opacity = 80
        if (is_by_step and show_symbols is None) or show_symbols is False:
            aspect_dto.symbol = None
        if not properties.show_lines:
            aspect_dto.line = None
        return aspect_dto.__deepcopy__()
    def __get_default_aspect(self, data_type: Optional[Union[str, PlotDataTypesEnum]]) -> ChannelAspectDto:
        if data_type is None:
            data_type = PlotDataTypesEnum.oil_rate_surface.value
        if type(data_type) is PlotDataTypesEnum:
            data_type = data_type.value
        for data_types in self.__data_types_catalog:
            if data_types.alias == data_type or data_types.name == data_type:
                channnel_aspect_dto = data_types.channel_aspect_dto
                if channnel_aspect_dto is None:
                    raise ValueError("Missing Aspect dto for channel {}".format(data_types.name))
                return channnel_aspect_dto.__deepcopy__()
        else:
            raise ValueError("This data type does not exist")
[docs]
    def set_lines_aspect(self, color: Optional[str] = None, width: Optional[int] = None, style: Optional[LineAspectEnum] = None) -> None:
        """
        Set up the line's aspect of the :class:`ChannelsModel`
        Parameters
        ----------
        color:
            Line color
        width:
            Line width
        style:
            Line style
        """
        if self.__dto is not None and self.__dto.aspect.line is not None:
            if color is not None:
                self.__dto.aspect.line.color = color
            if width is not None:
                self.__dto.aspect.line.width = width
            if style is not None:
                self.__dto.aspect.line.style = style
            if self.__dto.aspect.line is None:
                raise KeyError("Show line is set up at False, you cannot change lines properties")
        else:
            raise KeyError("The dto is empty, call the build_embedded or build_time_series method before changing properties") 
[docs]
    def set_band_aspect(self, color: Optional[str] = None, opacity: Optional[int] = None) -> None:
        """
        Set up the band's aspect of the :class:`ChannelsModel`
        Parameters
        ----------
        color:
            Band color
        opacity:
            Band opacity between 0 and 100
        """
        if self.__dto is not None and self.__dto.aspect.band is not None:
            if color is not None:
                self.__dto.aspect.band.color = color
            if opacity is not None:
                self.__dto.aspect.band.opacity = opacity
        else:
            raise KeyError("The dto is empty, call the build_embedded or the build_time_series method before changing properties") 
[docs]
    def set_symbols_aspect(self, color: Optional[str] = None, size: Optional[int] = None, filled: Optional[bool] = None,
                           symbol_type: Optional[SymbolAspectEnum] = None) -> None:
        """
        Set up the symbols' aspect of the :class:`ChannelsModel`
        Parameters
        ----------
        color:
            Symbols' color
        size:
            Symbols' size
        filled:
            Symbols' filled
        symbol_type:
            Symbol's type
        """
        if self.__dto is not None and self.__dto.aspect.symbol is not None:
            if color is not None:
                self.__dto.aspect.symbol.color = color
            if size is not None:
                self.__dto.aspect.symbol.size = size
            if filled is not None:
                self.__dto.aspect.symbol.filled = filled
            if symbol_type is not None:
                self.__dto.aspect.symbol.type = symbol_type
            if self.__dto.aspect.symbol is None:
                raise KeyError("Show symbols is set up at False, you cannot change symbols properties")
        else:
            raise KeyError("The dto is empty, call the build_dto method before changing properties") 
[docs]
    def set_channel_aspect(self, drawing_style: Optional[DrawingStyle] = None, is_y_log: Optional[bool] = None, hide_in_legend: Optional[bool] = None,
                           is_raw: Optional[bool] = None) -> None:
        """
        Set up some aspect of the :class:`ChannelsModel`
        Parameters
        ----------
        drawing_style
        is_y_log
        hide_in_legend
        is_raw
        """
        if self.__dto is not None:
            if drawing_style is not None:
                self.__dto.aspect.drawingStyle = drawing_style
            if is_y_log is not None:
                self.__dto.isYLog = is_y_log
            if hide_in_legend is not None:
                self.__dto.hideInLegend = hide_in_legend
            if is_raw is not None:
                if type(self.__dto) is not TimeSeriesReferenceChannelDto:
                    raise ValueError("The dto is not a time series")
                else:
                    self.__dto.isRaw = is_raw
        else:
            raise KeyError("The dto is empty, call the build_dto method before changing properties") 
    @property
    def pane_name(self) -> Optional[str]:
        """
        Get associated pane name of the :class: PlotChannel
        """
        return self.__pane_name
    @property
    def dto(self) -> Optional[Union[EmbeddedChannelDto, TimeSeriesReferenceChannelDto, CrossReferenceChannelDto]]:
        """
        Get associated dto of the :class: PlotChannel
        """
        return self.__dto