from yta_editor_time.specifications import FrameIndexSpecification, TimeSpecification, ProgressSpecification
from yta_editor_time.specifications.types import FrameIndex, TimeMoment, ProgressValue, FramesNumber, Fps, TimeDuration
from yta_editor_time.specifications.resolved import FrameIndexSpecificationResolved
from yta_validation import PythonValidator
from typing import Union


class SpecificationResolver:
    """
    *Static class*

    Class to simplify the way we resolve the
    different specifications.
    """

    @staticmethod
    def resolve(
        specification: Union[ProgressSpecification, TimeSpecification, FrameIndexSpecification],
        fps: Union[Fps, None] = None,
        total_frames: Union[FramesNumber, None] = None,
        duration: Union[TimeDuration, None] = None
    ):
        return (
            SpecificationResolver._resolve_progress_specification(
                progress_specification = specification,
                fps = fps,
                duration = duration
            )
            if PythonValidator.is_instance_of(specification, ProgressSpecification) else
            SpecificationResolver._resolve_time_specification(
                time_specification = specification,
                fps = fps
            )
            if PythonValidator.is_instance_of(specification, TimeSpecification) else
            SpecificationResolver._resolve_frame_index_specification(
                frame_index_specification = specification,
                total_frames = total_frames
            )
        )

    @staticmethod
    def _resolve_frame_index_specification(
        frame_index_specification: FrameIndexSpecification,
        total_frames: FramesNumber
    ) -> FrameIndexSpecificationResolved:
        """
        *For internal use only*

        Resolve the `frame_index_specification` provided
        considering the `total_frames` provided.
        """
        if (
            frame_index_specification.start_frame and
            frame_index_specification.end_frame
        ):
            start_frame = frame_index_specification.start_frame
            end_frame = frame_index_specification.end_frame
        elif (
            frame_index_specification.start_frame and
            frame_index_specification.total_frames
        ):
            start_frame = frame_index_specification.start_frame
            end_frame = FrameIndex(start_frame.value + frame_index_specification.total_frames.value)
        elif (
            frame_index_specification.end_frame and
            frame_index_specification.total_frames
        ):
            end_frame = frame_index_specification.end_frame
            start_frame = FrameIndex(end_frame.value - frame_index_specification.total_frames.value)
        else:
            raise AssertionError('Unreachable')

        if (
            start_frame.value < 0 or
            end_frame.value > int(total_frames)
        ):
            raise ValueError('Resolved values out of bounds')

        return FrameIndexSpecificationResolved(
            start_frame = start_frame,
            end_frame = end_frame
        )
    
    def _resolve_time_specification(
        time_specification: TimeSpecification,
        fps: Fps
    ) -> FrameIndexSpecificationResolved:
        """
        *For internal use only*

        Resolve the `time_specification` provided
        considering the `fps` provided.
        """
        def to_frame(
            t: TimeMoment
        ) -> FrameIndex:
            from yta_video_frame_time.t_media import TMedia

            return FrameIndex(
                value = TMedia(
                    t = float(t),
                    fps = float(fps)
                ).frame_index
            )

        if (
            time_specification.start_t and
            time_specification.end_t
        ):
            start_frame = to_frame(time_specification.start_t)
            end_frame = to_frame(time_specification.end_t)
        elif (
            time_specification.start_t and
            time_specification.duration
        ):
            start_frame = to_frame(time_specification.start_t)
            end_frame = to_frame(TimeMoment(time_specification.start_t.value + time_specification.duration.value))
        elif (
            time_specification.end_t and
            time_specification.duration
        ):
            end_frame = to_frame(time_specification.end_t)
            start_frame = to_frame(TimeMoment(time_specification.end_t.value - time_specification.duration.value))
        else:
            raise ValueError

        return FrameIndexSpecificationResolved(
            start_frame = start_frame,
            end_frame = end_frame
        )

    def _resolve_progress_specification(
        progress_specification: ProgressSpecification,
        fps: Fps,
        duration: TimeDuration
    ) -> FrameIndexSpecificationResolved:
        """
        *For internal use only*

        Resolve the `progress_specification` provided
        considering the `fps` and the `duration` provided.
        """
        def to_frame(
            progress: ProgressValue
        ) -> FrameIndex:
            from yta_video_frame_time.t_media import TMedia

            return FrameIndex(
                value = TMedia.from_progress_normalized(
                    progress_normalized = float(progress),
                    duration = float(duration),
                    fps = float(fps)
                ).frame_index
            )

        if (
            progress_specification.start_progress and
            progress_specification.end_progress
        ):
            start_frame = to_frame(progress_specification.start_progress)
            end_frame = to_frame(progress_specification.end_progress)
        elif (
            progress_specification.start_progress and
            progress_specification.duration
        ):
            start_frame = to_frame(progress_specification.start_progress)
            end_frame = to_frame(
                ProgressValue(progress_specification.start_progress.value + progress_specification.duration.value)
            )
        elif (
            progress_specification.end_progress and
            progress_specification.duration
        ):
            end_frame = to_frame(progress_specification.end_progress)
            start_frame = to_frame(
                ProgressValue(progress_specification.end_progress.value - progress_specification.duration.value)
            )
        else:
            raise ValueError

        return FrameIndexSpecificationResolved(
            start_frame = start_frame,
            end_frame = end_frame
        )
    
    