""" compatibility OpenTimelineIO 0.12.0 and older
"""

import os
import re
import sys
import json
import opentimelineio as otio
from . import utils
import clique

self = sys.modules[__name__]
self.track_types = {
    "video": otio.schema.TrackKind.Video,
    "audio": otio.schema.TrackKind.Audio
}
self.project_fps = None


def create_otio_rational_time(frame, fps):
    return otio.opentime.RationalTime(
        float(frame),
        float(fps)
    )


def create_otio_time_range(start_frame, frame_duration, fps):
    return otio.opentime.TimeRange(
        start_time=create_otio_rational_time(start_frame, fps),
        duration=create_otio_rational_time(frame_duration, fps)
    )


def create_otio_reference(media_pool_item):
    metadata = _get_metadata_media_pool_item(media_pool_item)
    mp_clip_property = media_pool_item.GetClipProperty()
    path = mp_clip_property["File Path"]
    reformat_path = utils.get_reformated_path(path, padded=True)
    padding = utils.get_padding_from_path(path)

    if padding:
        metadata.update({
            "isSequence": True,
            "padding": padding
        })

    # get clip property regarding to type
    mp_clip_property = media_pool_item.GetClipProperty()
    fps = float(mp_clip_property["FPS"])
    if mp_clip_property["Type"] == "Video":
        frame_start = int(mp_clip_property["Start"])
        frame_duration = int(mp_clip_property["Frames"])
    else:
        audio_duration = str(mp_clip_property["Duration"])
        frame_start = 0
        frame_duration = int(utils.timecode_to_frames(
            audio_duration, float(fps)))

    otio_ex_ref_item = None

    if padding:
        # if it is file sequence try to create `ImageSequenceReference`
        # the OTIO might not be compatible so return nothing and do it old way
        try:
            dirname, filename = os.path.split(path)
            collection = clique.parse(filename, '{head}[{ranges}]{tail}')
            padding_num = len(re.findall("(\\d+)(?=-)", filename).pop())
            otio_ex_ref_item = otio.schema.ImageSequenceReference(
                target_url_base=dirname + os.sep,
                name_prefix=collection.format("{head}"),
                name_suffix=collection.format("{tail}"),
                start_frame=frame_start,
                frame_zero_padding=padding_num,
                rate=fps,
                available_range=create_otio_time_range(
                    frame_start,
                    frame_duration,
                    fps
                )
            )
        except AttributeError:
            pass

    if not otio_ex_ref_item:
        # in case old OTIO or video file create `ExternalReference`
        otio_ex_ref_item = otio.schema.ExternalReference(
            target_url=reformat_path,
            available_range=create_otio_time_range(
                frame_start,
                frame_duration,
                fps
            )
        )

    # add metadata to otio item
    add_otio_metadata(otio_ex_ref_item, media_pool_item, **metadata)

    return otio_ex_ref_item


def create_otio_markers(track_item, fps):
    track_item_markers = track_item.GetMarkers()
    markers = []
    for marker_frame in track_item_markers:
        note = track_item_markers[marker_frame]["note"]
        if "{" in note and "}" in note:
            metadata = json.loads(note)
        else:
            metadata = {"note": note}
        markers.append(
            otio.schema.Marker(
                name=track_item_markers[marker_frame]["name"],
                marked_range=create_otio_time_range(
                    marker_frame,
                    track_item_markers[marker_frame]["duration"],
                    fps
                ),
                color=track_item_markers[marker_frame]["color"].upper(),
                metadata=metadata
            )
        )
    return markers


def create_otio_clip(track_item):
    media_pool_item = track_item.GetMediaPoolItem()
    mp_clip_property = media_pool_item.GetClipProperty()

    if not self.project_fps:
        fps = mp_clip_property["FPS"]
    else:
        fps = self.project_fps

    name = track_item.GetName()

    media_reference = create_otio_reference(media_pool_item)
    source_range = create_otio_time_range(
        int(track_item.GetLeftOffset()),
        int(track_item.GetDuration()),
        fps
    )

    if mp_clip_property["Type"] == "Audio":
        return_clips = list()
        audio_chanels = mp_clip_property["Audio Ch"]
        for channel in range(0, int(audio_chanels)):
            clip = otio.schema.Clip(
                name=f"{name}_{channel}",
                source_range=source_range,
                media_reference=media_reference
            )
            for marker in create_otio_markers(track_item, fps):
                clip.markers.append(marker)
            return_clips.append(clip)
        return return_clips
    else:
        clip = otio.schema.Clip(
            name=name,
            source_range=source_range,
            media_reference=media_reference
        )
        for marker in create_otio_markers(track_item, fps):
            clip.markers.append(marker)

        return clip


def create_otio_gap(gap_start, clip_start, tl_start_frame, fps):
    return otio.schema.Gap(
        source_range=create_otio_time_range(
            gap_start,
            (clip_start - tl_start_frame) - gap_start,
            fps
        )
    )


def _create_otio_timeline(project, timeline, fps):
    metadata = _get_timeline_metadata(project, timeline)
    start_time = create_otio_rational_time(
        timeline.GetStartFrame(), fps)
    otio_timeline = otio.schema.Timeline(
        name=timeline.GetName(),
        global_start_time=start_time,
        metadata=metadata
    )
    return otio_timeline


def _get_timeline_metadata(project, timeline):
    media_pool = project.GetMediaPool()
    root_folder = media_pool.GetRootFolder()
    ls_folder = root_folder.GetClipList()
    timeline = project.GetCurrentTimeline()
    timeline_name = timeline.GetName()
    for tl in ls_folder:
        if tl.GetName() not in timeline_name:
            continue
        return _get_metadata_media_pool_item(tl)


def _get_metadata_media_pool_item(media_pool_item):
    data = dict()
    data.update({k: v for k, v in media_pool_item.GetMetadata().items()})
    property = media_pool_item.GetClipProperty() or {}
    for name, value in property.items():
        if "Resolution" in name and "" != value:
            width, height = value.split("x")
            data.update({
                "width": int(width),
                "height": int(height)
            })
        if "PAR" in name and "" != value:
            try:
                data.update({"pixelAspect": float(value)})
            except ValueError:
                if "Square" in value:
                    data.update({"pixelAspect": float(1)})
                else:
                    data.update({"pixelAspect": float(1)})

    return data


def create_otio_track(track_type, track_name):
    return otio.schema.Track(
        name=track_name,
        kind=self.track_types[track_type]
    )


def add_otio_gap(clip_start, otio_track, track_item, timeline):
    # if gap between track start and clip start
    if clip_start > otio_track.available_range().duration.value:
        # create gap and add it to track
        otio_track.append(
            create_otio_gap(
                otio_track.available_range().duration.value,
                track_item.GetStart(),
                timeline.GetStartFrame(),
                self.project_fps
            )
        )


def add_otio_metadata(otio_item, media_pool_item, **kwargs):
    mp_metadata = media_pool_item.GetMetadata()
    # add additional metadata from kwargs
    if kwargs:
        mp_metadata.update(kwargs)

    # add metadata to otio item metadata
    for key, value in mp_metadata.items():
        otio_item.metadata.update({key: value})


def create_otio_timeline(resolve_project):

    # get current timeline
    self.project_fps = resolve_project.GetSetting("timelineFrameRate")
    timeline = resolve_project.GetCurrentTimeline()

    # convert timeline to otio
    otio_timeline = _create_otio_timeline(
        resolve_project, timeline, self.project_fps)

    # loop all defined track types
    for track_type in list(self.track_types.keys()):
        # get total track count
        track_count = timeline.GetTrackCount(track_type)

        # loop all tracks by track indexes
        for track_index in range(1, int(track_count) + 1):
            # get current track name
            track_name = timeline.GetTrackName(track_type, track_index)

            # convert track to otio
            otio_track = create_otio_track(
                track_type, track_name)

            # get all track items in current track
            current_track_items = timeline.GetItemListInTrack(
                track_type, track_index)

            # loop available track items in current track items
            for track_item in current_track_items:
                # skip offline track items
                if track_item.GetMediaPoolItem() is None:
                    continue

                # calculate real clip start
                clip_start = track_item.GetStart() - timeline.GetStartFrame()

                add_otio_gap(
                    clip_start, otio_track, track_item, timeline)

                # create otio clip and add it to track
                otio_clip = create_otio_clip(track_item)

                if not isinstance(otio_clip, list):
                    otio_track.append(otio_clip)
                else:
                    for index, clip in enumerate(otio_clip):
                        if index == 0:
                            otio_track.append(clip)
                        else:
                            # add previouse otio track to timeline
                            otio_timeline.tracks.append(otio_track)
                            # convert track to otio
                            otio_track = create_otio_track(
                                track_type, track_name)
                            add_otio_gap(
                                clip_start, otio_track,
                                track_item, timeline)
                            otio_track.append(clip)

            # add track to otio timeline
            otio_timeline.tracks.append(otio_track)

    return otio_timeline


def write_to_file(otio_timeline, path):
    otio.adapters.write_to_file(otio_timeline, path)
