import click

from ffx.iso_language import IsoLanguage
from ffx.media_descriptor import MediaDescriptor
from ffx.track_descriptor import TrackDescriptor

from ffx.helper import dictDiff, setDiff, DIFF_ADDED_KEY, DIFF_CHANGED_KEY, DIFF_REMOVED_KEY, DIFF_UNCHANGED_KEY

from ffx.track_codec import TrackCodec
from ffx.track_disposition import TrackDisposition


class MediaDescriptorChangeSet():

    TAGS_KEY = "tags"
    TRACKS_KEY = "tracks"
    DISPOSITION_SET_KEY = "disposition_set"

    TRACK_DESCRIPTOR_KEY = "track_descriptor"


    def __init__(self, context,
                    targetMediaDescriptor: MediaDescriptor = None,
                    sourceMediaDescriptor: MediaDescriptor = None):

        self.__context = context
        self.__logger = context['logger']

        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 [])


        self.__targetTrackDescriptors = targetMediaDescriptor.getTrackDescriptors() if targetMediaDescriptor is not None else []
        self.__sourceTrackDescriptors = sourceMediaDescriptor.getTrackDescriptors() if sourceMediaDescriptor is not None else []
        self.__targetTrackDescriptorsByIndex = {
            trackDescriptor.getIndex(): trackDescriptor
            for trackDescriptor in self.__targetTrackDescriptors
        }
        self.__sourceTrackDescriptorsByIndex = {
            trackDescriptor.getIndex(): trackDescriptor
            for trackDescriptor in self.__sourceTrackDescriptors
        }

        targetMediaTags = targetMediaDescriptor.getTags() if targetMediaDescriptor is not None else {}
        sourceMediaTags = sourceMediaDescriptor.getTags() if sourceMediaDescriptor is not None else {}


        self.__changeSetObj = {}

        #if targetMediaDescriptor is not None:



        #!!#
        tagsDiff = dictDiff(sourceMediaTags,
                            targetMediaTags,
                            ignoreKeys=self.__ignoreGlobalKeys,
                            removeKeys=self.__removeGlobalKeys)

        if tagsDiff:
            self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY] = tagsDiff


        
        self.__numTargetTracks = len(self.__targetTrackDescriptors)

        # Current track configuration (of file)
        
        self.__numSourceTracks = len(self.__sourceTrackDescriptors)

        trackCompareResult = {}

        for targetTrackDescriptor in self.__targetTrackDescriptors:
            sourceTrackDescriptor = self.__sourceTrackDescriptorsByIndex.get(
                targetTrackDescriptor.getSourceIndex()
            )

            if sourceTrackDescriptor is None:
                if DIFF_ADDED_KEY not in trackCompareResult.keys():
                    trackCompareResult[DIFF_ADDED_KEY] = {}
                trackCompareResult[DIFF_ADDED_KEY][targetTrackDescriptor.getIndex()] = targetTrackDescriptor
                continue

            trackDiff = self.compareTracks(targetTrackDescriptor, sourceTrackDescriptor)
            if trackDiff:
                if DIFF_CHANGED_KEY not in trackCompareResult.keys():
                    trackCompareResult[DIFF_CHANGED_KEY] = {}
                trackCompareResult[DIFF_CHANGED_KEY][targetTrackDescriptor.getIndex()] = trackDiff

        targetSourceIndices = {
            targetTrackDescriptor.getSourceIndex()
            for targetTrackDescriptor in self.__targetTrackDescriptors
        }
        for sourceTrackDescriptor in self.__sourceTrackDescriptors:
            if sourceTrackDescriptor.getIndex() not in targetSourceIndices:
                if DIFF_REMOVED_KEY not in trackCompareResult.keys():
                    trackCompareResult[DIFF_REMOVED_KEY] = {}
                trackCompareResult[DIFF_REMOVED_KEY][sourceTrackDescriptor.getIndex()] = sourceTrackDescriptor


        if trackCompareResult:
            self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY] = trackCompareResult


    def compareTracks(self,
                targetTrackDescriptor: TrackDescriptor = None,
                sourceTrackDescriptor: TrackDescriptor = None):

        sourceTrackTags = sourceTrackDescriptor.getTags() if sourceTrackDescriptor is not None else {}
        targetTrackTags = (
            self.normalizeTrackTags(targetTrackDescriptor.getTags())
            if targetTrackDescriptor is not None
            else {}
        )

        trackCompareResult = {}

        tagsDiffResult = dictDiff(sourceTrackTags,
                                    targetTrackTags,
                                    ignoreKeys=self.__ignoreTrackKeys,
                                    removeKeys=self.__removeTrackKeys)

        if tagsDiffResult:
            trackCompareResult[MediaDescriptorChangeSet.TAGS_KEY] = tagsDiffResult

        sourceDispositionSet = sourceTrackDescriptor.getDispositionSet() if sourceTrackDescriptor is not None else set()
        targetDispositionSet = targetTrackDescriptor.getDispositionSet() if targetTrackDescriptor is not None else set()

        # if targetTrackDescriptor.getIndex() == 3:
        #     raise click.ClickException(f"{sourceDispositionSet} {targetDispositionSet}")

        dispositionDiffResult = setDiff(sourceDispositionSet, targetDispositionSet)

        if dispositionDiffResult:
            trackCompareResult[MediaDescriptorChangeSet.DISPOSITION_SET_KEY] = dispositionDiffResult

        return trackCompareResult

    def normalizeTrackTagValue(self, tagKey, tagValue):
        if tagKey != "language":
            return tagValue

        if isinstance(tagValue, IsoLanguage):
            return tagValue.threeLetter()

        trackLanguage = IsoLanguage.findThreeLetter(str(tagValue))
        if trackLanguage != IsoLanguage.UNDEFINED:
            return trackLanguage.threeLetter()

        return tagValue

    def normalizeTrackTags(self, trackTags: dict):
        return {
            tagKey: self.normalizeTrackTagValue(tagKey, tagValue)
            for tagKey, tagValue in trackTags.items()
        }


    def generateDispositionTokens(self):
        """
        #Example: -disposition:s:0 default -disposition:s:1 0
        """
        dispositionTokens = []

#         if MediaDescriptorChangeSet.TRACKS_KEY in self.__changeSetObj.keys():
# 
#             if DIFF_ADDED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY].keys():
#                 addedTracks: dict = self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY][DIFF_ADDED_KEY]
#                 trackDescriptor: TrackDescriptor
#                 for trackDescriptor in addedTracks.values():
#                     
#                     dispositionSet = trackDescriptor.getDispositionSet()
# 
#                     if dispositionSet:
#                         dispositionTokens += [f"-disposition:{trackDescriptor.getType().indicator()}:{trackDescriptor.getSubIndex()}",
#                                                 '+'.join([d.label() for d in dispositionSet])]
# 
#             if DIFF_CHANGED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY].keys():
#                 changedTracks: dict = self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY][DIFF_CHANGED_KEY]
#                 trackDiffObj: dict
# 
# 
#                 for trackIndex, trackDiffObj in changedTracks.items():
# 
#                     if MediaDescriptorChangeSet.DISPOSITION_SET_KEY in trackDiffObj.keys():
# 
#                         dispositionDiffObj: dict = trackDiffObj[MediaDescriptorChangeSet.DISPOSITION_SET_KEY]
# 
#                         addedDispositions = dispositionDiffObj[DIFF_ADDED_KEY] if DIFF_ADDED_KEY in dispositionDiffObj.keys() else set()
#                         removedDispositions = dispositionDiffObj[DIFF_REMOVED_KEY] if DIFF_REMOVED_KEY in dispositionDiffObj.keys() else set()
#                         unchangedDispositions = dispositionDiffObj[DIFF_UNCHANGED_KEY] if DIFF_UNCHANGED_KEY in dispositionDiffObj.keys() else set()
# 
#                         targetDispositions = addedDispositions | unchangedDispositions
# 
#                         trackDescriptor = self.__targetTrackDescriptors[trackIndex]
#                         streamIndicator = trackDescriptor.getType().indicator()
#                         subIndex = trackDescriptor.getSubIndex()
# 
#                         if targetDispositions:
#                             dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '+'.join([d.label() for d in targetDispositions])]
#                         # if not targetDispositions and removedDispositions:
#                         else:
#                             dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '0']
        for ttd in self.__targetTrackDescriptors:

            targetDispositions = ttd.getDispositionSet()
            streamIndicator = ttd.getType().indicator()
            subIndex = ttd.getSubIndex()
            
            if targetDispositions:
                dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '+'.join([d.label() for d in targetDispositions])]
            # if not targetDispositions and removedDispositions:
            else:
                dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '0']

        return dispositionTokens


    def generateMetadataTokens(self):

        metadataTokens = []

        if MediaDescriptorChangeSet.TAGS_KEY in self.__changeSetObj.keys():

            addedMediaTags = (self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY][DIFF_ADDED_KEY]
                                    if DIFF_ADDED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY].keys() else {})
            removedMediaTags = (self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY][DIFF_REMOVED_KEY]
                                    if DIFF_REMOVED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY].keys() else {})
            changedMediaTags = (self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY][DIFF_CHANGED_KEY]
                                    if DIFF_CHANGED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TAGS_KEY].keys() else {})

            outputMediaTags = addedMediaTags | changedMediaTags

            if (not 'no_signature' in self.__context.keys()
                    or not self.__context['no_signature']):
                outputMediaTags = outputMediaTags | self.__signatureTags

            # outputMediaTags = {k:v for k,v in outputMediaTags.items() if k not in self.__removeGlobalKeys}

            for tagKey, tagValue in outputMediaTags.items():
                metadataTokens += [f"-metadata:g",
                                    f"{tagKey}={tagValue}"]

            for tagKey, tagValue in changedMediaTags.items():
                metadataTokens += [f"-metadata:g",
                                    f"{tagKey}={tagValue}"]

            for removeKey in removedMediaTags.keys():
                metadataTokens += [f"-metadata:g",
                                    f"{removeKey}="]


        if MediaDescriptorChangeSet.TRACKS_KEY in self.__changeSetObj.keys():

            if DIFF_ADDED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY].keys():
                addedTracks: dict = self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY][DIFF_ADDED_KEY]
                trackDescriptor: TrackDescriptor
                for trackDescriptor in addedTracks.values():
                    for tagKey, tagValue in self.normalizeTrackTags(trackDescriptor.getTags()).items():
                        if not tagKey in self.__removeTrackKeys:
                            metadataTokens += [f"-metadata:s:{trackDescriptor.getType().indicator()}"
                                                + f":{trackDescriptor.getSubIndex()}",
                                                f"{tagKey}={tagValue}"]

            if DIFF_CHANGED_KEY in self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY].keys():
                changedTracks: dict = self.__changeSetObj[MediaDescriptorChangeSet.TRACKS_KEY][DIFF_CHANGED_KEY]
                trackDiffObj: dict
                for trackIndex, trackDiffObj in changedTracks.items():

                    if MediaDescriptorChangeSet.TAGS_KEY in trackDiffObj.keys():

                        tagsDiffObj = trackDiffObj[MediaDescriptorChangeSet.TAGS_KEY]

                        addedTrackTags = tagsDiffObj[DIFF_ADDED_KEY] if DIFF_ADDED_KEY in tagsDiffObj.keys() else {}
                        changedTrackTags = tagsDiffObj[DIFF_CHANGED_KEY] if DIFF_CHANGED_KEY in tagsDiffObj.keys() else {}
                        unchangedTrackTags = tagsDiffObj[DIFF_UNCHANGED_KEY] if DIFF_UNCHANGED_KEY in tagsDiffObj.keys() else {}
                        removedTrackTags = tagsDiffObj[DIFF_REMOVED_KEY] if DIFF_REMOVED_KEY in tagsDiffObj.keys() else {}

                        outputTrackTags = addedTrackTags | changedTrackTags

                        trackDescriptor = self.__targetTrackDescriptorsByIndex[trackIndex]

                        for tagKey, tagValue in self.normalizeTrackTags(outputTrackTags).items():
                            metadataTokens += [f"-metadata:s:{trackDescriptor.getType().indicator()}"
                                                + f":{trackDescriptor.getSubIndex()}",
                                                f"{tagKey}={tagValue}"]

                        if trackDescriptor.getExternalSourceFilePath():
                            # When a single-track external file substitutes the
                            # media payload, keep metadata from the regular
                            # source track unless the external/target side
                            # overrides it explicitly.
                            preservedTrackTags = (
                                {
                                    tagKey: tagValue
                                    for tagKey, tagValue in removedTrackTags.items()
                                    if tagKey not in self.__removeTrackKeys
                                }
                                | unchangedTrackTags
                            )
                            for tagKey, tagValue in self.normalizeTrackTags(preservedTrackTags).items():
                                metadataTokens += [f"-metadata:s:{trackDescriptor.getType().indicator()}"
                                                + f":{trackDescriptor.getSubIndex()}",
                                                f"{tagKey}={tagValue}"]
                        else:
                            for removeKey in removedTrackTags.keys():
                                metadataTokens += [f"-metadata:s:{trackDescriptor.getType().indicator()}"
                                                    + f":{trackDescriptor.getSubIndex()}",
                                                    f"{removeKey}="]

        for tagKey, tagValue in self.__context.get('encoding_metadata_tags', {}).items():
            metadataTokens += [f"-metadata:g", f"{tagKey}={tagValue}"]

        metadataTokens += self.generateConfiguredRemovalMetadataTokens()

        return metadataTokens


    def getChangeSetObj(self):
        return self.__changeSetObj

    def generateConfiguredRemovalMetadataTokens(self):
        metadataTokens = []

        for removeKey in self.__removeGlobalKeys:
            metadataTokens += ["-metadata:g", f"{removeKey}="]

        for trackDescriptor in self.__targetTrackDescriptors:
            for removeKey in self.__removeTrackKeys:
                metadataTokens += [
                    f"-metadata:s:{trackDescriptor.getType().indicator()}:{trackDescriptor.getSubIndex()}",
                    f"{removeKey}=",
                ]

        return metadataTokens
