import click

from ffx.model.track import Track

from .track_type import TrackType

from .track_disposition import TrackDisposition

from .track_type import TrackType

from ffx.model.track_tag import TrackTag
from ffx.track_descriptor import TrackDescriptor


class TrackController():
    
    def __init__(self, context):
        
        self.context = context
        self.Session = self.context['database']['session'] # convenience

        self.__configurationData = self.context['config'].getData()

        metadataConfiguration = self.__configurationData['metadata'] if 'metadata' in self.__configurationData.keys() else {}

        self.__signatureTags = metadataConfiguration['signature'] if 'signature' in metadataConfiguration.keys() else {}
        self.__removeGlobalKeys = metadataConfiguration['remove'] if 'remove' in metadataConfiguration.keys() else []
        self.__ignoreGlobalKeys = metadataConfiguration['ignore'] if 'ignore' in metadataConfiguration.keys() else []
        self.__removeTrackKeys = (metadataConfiguration['streams']['remove']
                                    if 'streams' in metadataConfiguration.keys()
                                    and 'remove' in metadataConfiguration['streams'].keys() else [])
        self.__ignoreTrackKeys = (metadataConfiguration['streams']['ignore']
                                    if 'streams' in metadataConfiguration.keys()
                                    and 'ignore' in metadataConfiguration['streams'].keys() else [])


    def addTrack(self, trackDescriptor : TrackDescriptor, patternId = None):

        # option to override pattern id in case track descriptor has not set it
        patId = int(trackDescriptor.getPatternId() if patternId is None else patternId)

        try:
            s = self.Session()
            track = Track(pattern_id = patId,
                            track_type = int(trackDescriptor.getType().index()),
                            codec_name = str(trackDescriptor.getCodec().identifier()),
                            index = int(trackDescriptor.getIndex()),
                            source_index = int(trackDescriptor.getSourceIndex()),
                            disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())),
                            audio_layout = trackDescriptor.getAudioLayout().index())

            s.add(track)
            s.commit()

            for k,v in trackDescriptor.getTags().items():

                # Filter tags that make no sense to preserve
                if k not in self.__ignoreTrackKeys and k not in self.__removeTrackKeys:
                    tag = TrackTag(track_id = track.id,
                                    key = k,
                                    value = v)
                    s.add(tag)
            s.commit()

        except Exception as ex:
            raise click.ClickException(f"TrackController.addTrack(): {repr(ex)}")
        finally:
            s.close()


    def updateTrack(self, trackId, trackDescriptor : TrackDescriptor):

        if type(trackDescriptor) is not TrackDescriptor:
            raise TypeError('TrackController.updateTrack(): Argument trackDescriptor is required to be of type TrackDescriptor')

        try:
            s = self.Session()
            track = s.query(Track).filter(Track.id == int(trackId)).first()

            if track is not None:

                track.index = int(trackDescriptor.getIndex())

                track.track_type = int(trackDescriptor.getType().index())
                track.codec_name = str(trackDescriptor.getCodec().identifier())
                track.audio_layout = int(trackDescriptor.getAudioLayout().index())

                track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet()))

                descriptorTagKeys = trackDescriptor.getTags()
                tagKeysInDescriptor = set(descriptorTagKeys.keys())
                tagKeysInDb = {t.key for t in track.track_tags}

                for k in tagKeysInDescriptor & tagKeysInDb: # to update
                    tags = [t for t in track.track_tags if t.key == k]
                    tags[0].value = descriptorTagKeys[k]
                for k in tagKeysInDescriptor - tagKeysInDb: # to add
                    tag = TrackTag(track_id=track.id, key=k, value=descriptorTagKeys[k])
                    s.add(tag)
                for k in tagKeysInDb - tagKeysInDescriptor: # to remove
                    tags = [t for t in track.track_tags if t.key == k]
                    s.delete(tags[0])

                s.commit()
                return True

            else:
                return False

        except Exception as ex:
            raise click.ClickException(f"TrackController.updateTrack(): {repr(ex)}")
        finally:
            s.close()

    def findTracks(self, patternId):

        try:
            s = self.Session()

            q = s.query(Track).filter(Track.pattern_id == int(patternId))
            return sorted([t for t in q.all()], key=lambda d: d.getIndex())

        except Exception as ex:
            raise click.ClickException(f"TrackController.findTracks(): {repr(ex)}")
        finally:
            s.close()


    def findSiblingDescriptors(self, patternId):
        """Finds all stored tracks related to a pattern, packs them in descriptors
        and also setting sub indices and returns list of descriptors"""

        siblingTracks = self.findTracks(patternId)
        siblingDescriptors = []

        subIndexCounter = {}
        st: Track
        for st in siblingTracks:
            trackType = st.getType()

            if not trackType in subIndexCounter.keys():
                subIndexCounter[trackType] = 0
            siblingDescriptors.append(st.getDescriptor(subIndex=subIndexCounter[trackType]))
            subIndexCounter[trackType] += 1

        return siblingDescriptors


    #TODO: mit optionalem Parameter lösen ^
    def findVideoTracks(self, patternId):

        try:
            s = self.Session()

            q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.VIDEO.index())
            return [a for a in q.all()]

        except Exception as ex:
            raise click.ClickException(f"TrackController.findVideoTracks(): {repr(ex)}")
        finally:
            s.close()

    def findAudioTracks(self, patternId):

        try:
            s = self.Session()

            q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.AUDIO.index())
            return [a for a in q.all()]

        except Exception as ex:
            raise click.ClickException(f"TrackController.findAudioTracks(): {repr(ex)}")
        finally:
            s.close()

    def findSubtitleTracks(self, patternId):

        try:
            s = self.Session()
                
            q = s.query(Track).filter(Track.pattern_id == int(patternId), Track.track_type == TrackType.SUBTITLE.index())
            return [s for s in q.all()]

        except Exception as ex:
            raise click.ClickException(f"TrackController.findSubtitleTracks(): {repr(ex)}")
        finally:
            s.close()


    def getTrack(self, patternId : int, index: int) -> Track:
        
        try:
            s = self.Session()
            return s.query(Track).filter(
                Track.pattern_id == int(patternId),
                Track.index == int(index),
            ).first()

        except Exception as ex:
            raise click.ClickException(f"TrackController.getTrack(): {repr(ex)}")
        finally:
            s.close()

    def setDispositionState(self, patternId: int, index: int, disposition : TrackDisposition, state : bool):

        if type(patternId) is not int:
            raise TypeError('TrackController.setTrackDisposition(): Argument patternId is required to be of type int')
        if type(index) is not int:
            raise TypeError('TrackController.setTrackDisposition(): Argument index is required to be of type int')
        if type(disposition) is not TrackDisposition:
            raise TypeError('TrackController.setTrackDisposition(): Argument disposition is required to be of type TrackDisposition')
        if type(state) is not bool:
            raise TypeError('TrackController.setTrackDisposition(): Argument state is required to be of type bool')

        try:
            s = self.Session()
            track = s.query(Track).filter(Track.pattern_id == patternId, Track.index == index).first()

            if track is not None:

                if state:
                    track.setDisposition(disposition)
                else:
                    track.resetDisposition(disposition)

                s.commit()
                return True

            else:
                return False

        except Exception as ex:
            raise click.ClickException(f"TrackController.updateTrack(): {repr(ex)}")
        finally:
            s.close()

    def deleteTrack(self, trackId):
        try:
            s = self.Session()

            track = s.query(Track).filter(Track.id == int(trackId)).first()

            if track is not None:
                patternId = int(track.pattern_id)

                q_siblings = s.query(Track).filter(Track.pattern_id == patternId).order_by(Track.index)
                siblingTracks = q_siblings.all()

                if len(siblingTracks) <= 1:
                    raise click.ClickException(
                        f"Cannot delete the last track from pattern #{patternId}. Patterns must define at least one track."
                    )

                index = 0
                for track in siblingTracks:

                    if track.id == int(trackId):
                        s.delete(track)
                    else:
                        track.index = index
                        index += 1

                s.commit()
                return True

            return False

        except Exception as ex:
            raise click.ClickException(f"TrackController.deleteTrack(): {repr(ex)}")
        finally:
            s.close()


#     def setDefaultSubTrack(self, trackType, subIndex):
#             pass
# 
#     def setForcedSubTrack(self, trackType, subIndex):
#             pass
