"""
util functions and classes
"""
import json
from pprint import pprint
from tempfile import _TemporaryFileWrapper
from typing import List

import gradio as gr
from gradio.components import Component

def parse(param: json) -> dict:
    with open(param) as file:
        return json.load(file)


data = parse("./data.json")
codecs = parse("./codecs.json")

"""Video"""
containers = [j.get("name") for i in data["containers"]
              for j in data["containers"][i]]
video_containers = [i.get("name") for i in data["containers"]["video"]]
video_codecs = [i.get("value") for i in data["codecs"]["video"]]
video_aspect_ratio = [i.get("name") for i in data["aspects"]]
video_scaling = [i.get("name") for i in data["scalings"]]
""" Audio """
audio_containers = [i.get("name") for i in data["containers"]["audio"]]
audio_codecs = [i.get("value") for i in data["codecs"]["audio"]]
audio_channels = [i.get("name") for i in data["audioChannels"]]
audio_quality = [i.get("name") for i in data["audioQualities"]]
audio_sample_rates = [i.get("name") for i in data["sampleRates"]]

""" Video & Audio Filters """
# deband=[i.get("name") for i in data["deband"]]
# deflicker=[i.get("name") for i in data["deflicker"]]
# deshake=[i.get("name") for i in data["deshake"]]
# dejudder=[i.get("name") for i in data["dejudder"]]
# denoise=[i.get("name") for i in data["denoise"]]
# deinterlace=[i.get("name") for i in data["deinterlace"]]
filters = ["deband", "deflicker", "deshake",
           "dejudder", "denoise", "deinterlace"]
vf = [{vFilter: names} for vFilter in filters for names in [
    [i for i in data[vFilter]]]]

presets = [i.get("name") for i in data["presets"]]
profiles = [i.get("name") for i in data["profiles"]]
speeds = [i.get("name") for i in data["speeds"]]


outputMap = parse("./mappings.json")
newoutputMap = parse("./new_mappings.json")
"""Output Mappings of commands to value
   audioQuality -b:a 128k 
"""


class CommandBuilder():
    """Takes a collection of gradio layout elements and attaches 
    a function to each component in the context
    to build an array of ffmpeg commands"""

    def __call__(self, *args, **kwds):
        return [i.value for i in self._component]

    def do(self, *inputs, **kwds):
        for comp in self._component:
            if comp.label is not None:
                self.changefunc(comp, "", comp.value)

    def reset(self):
        self.outputDict = {"vf": {}, "af": {}}
        self.commands=""
        self.vf, self.af, self.extra = ([] for _ in range(3))

    def __init__(self, *inputs: gr.Blocks) -> None:
        """
        Parameters:
            *inputs: A tuple of layout blocks containing components(Textbox,Button...).
        """

        self.outputDict = {"vf": {}, "af": {}}
        self.formatOutputDict = {"vf": {}, "af": {}}
        # state=gr.Variable()
        # state2=gr.Variable()

        self._component: List[Component] = []
        self.vf, self.af, self.extra = ([] for _ in range(3))
        self.commands = ""
        if inputs is None:
            return None
        for i in inputs:
            self._component += self._get_component_instance(i)
        for comp in self._component:
            state = gr.Variable()
            state2 = gr.Variable()
            if comp.label is not None:
                state.value = comp
                state2.value = comp.label
                comp.change(fn=self.changefunc, inputs=[
                            state, state2, comp], outputs=[])

    def changefunc(self, input: gr.components.IOComponent, c_label="", newValue=""):
        label, *_ = input.label.strip(": \n").lower().split(
        ) if type(input.label) != list else "".join(input.label).strip(": ").lower().split()
        label += "".join(_).title()
        key = newoutputMap.get(label)
        lst_extra, vf, af = ([] for _ in range(3))
        if newValue not in [None, "Source", "Auto", "", "None", "none", 0]:
            self.setVf(label, newValue)
            self.setAf(label, newValue)
            self.setF(label, newValue)
            for val in self.outputDict:
                if val == "vf":
                    vf = self.outputDict.get(val).values()
                    vf = ",".join(list(vf))
                elif val == "af":
                    af = self.outputDict.get(val).values()
                    af = ",".join(list(af))
                    pass
                else:
                    lst_extra.extend([val, self.outputDict.get(val)])

        else:
            self.outputDict.pop(key, "No Key Exists")
            self.outputDict["vf"].pop(label, "No Key Exists")
            self.outputDict["af"].pop(label, "No Key Exists")
        self.vf = f"-vf '{vf}'" if vf else ""
        self.af = f"-af '{af}'" if af else ""
        self.extra = " ".join(lst_extra)
        self.commands = f"{self.vf} {self.af} {self.extra}"

        print(self.vf, self.af, self.extra)

    def setVf(self, label:str, newValue:"str| int"):
        """Sets Video filters 

        Args:
            label : label of components
            newValue : value of component
        """
        if newoutputMap["vf"].get(label):
            key = newoutputMap["vf"].get(label)
            if label in ["deinterlace", "denoise"]:
                value = "_".join(newValue.lower().split())
                arg = key.get(value, None)
                self.outputDict["vf"].update({label: arg})
            else:
                self.outputDict["vf"].update({key: key})

    def setF(self, label, newValue):
        """ Sets Extra filters 
        Args:
            label : label of components
            newValue : value of component
        """
        if newoutputMap.get(label):
            key = newoutputMap.get(label)
            if label in ["video", "audio"]:
                value=codecs.get(label).get(newValue,newValue)
                print(value)
                self.outputDict.update({key:value})
            elif label in ["startTime", "stopTime"]:
                self.outputDict.update({key: newValue})
            else:
                value = "".join([i.get("value", "None") for i in data.get(
                    label) if i.get("name", None) == newValue])
                self.outputDict.update({key: value})

    def setAf(self, label:str, newValue:"str|int"):
        """ Sets Extra filters 
        Args:
            label : label of components
            newValue : value of component
        """
        if newoutputMap["af"].get(label):
            value = int(newValue)/100
            arg = f"{label}={value}"
            self.outputDict["af"].update({label: arg})

    def update(self, Component: Component):
        for comp in self._component:
            comp.change(lambda: gr.update(
                value=self.outputDict), [], [Component])

    def _get_component_instance(self, inputs: gr.Blocks) -> List[Component]:
        """
        returns components present in a layout block
        Parameters:
            inputs: layout block
        """
        res=[]
        for i in inputs.children:
            # print(i,hasattr(i,"children"))
            if not (hasattr(i,"children")):
                # res.append(gr.components.get_component_instance(i,render=True))
                res+=[gr.components.get_component_instance(i,render=True)]
                # print(res)
            elif hasattr(i,"children"):
                res+=self._get_component_instance(i)
        # print(res)
        return res
        # return [gr.components.get_component_instance(i, render=True) for i in inputs.children if not hasattr(i, "children")]

    def setVideoFilters(self, options):
        value = self.outputDict.get(options, "-")
        filters = newoutputMap.get(options, None)
        arg = ""
        if options in ["deinterlace", "denoise"]:
            value = "_".join(value.lower().split())
            arg = filters.get(value, None)
            # self.vf.append(arg)
            self.outputDict["vf"].update({options: arg})
            return True
        if options in ["deband", "deflicker", "deshake", "dejudder"]:
            arg = filters
            self.outputDict["vf"].update({options: arg})
            return True

        return

    def setAudioFilters(self, options):
        value = self.outputDict.get(options, "-")
        if options in ["acontrast"]:
            value = int(value)/100
            arg = f"{options}={value}"

            self.outputDict["af"].update({options: arg})
            return True
        return

    def setFormat(self, options):
        value = self.outputDict.get(options, "-")
        filters = newoutputMap.get(options, None)
        if options in ["video", "audio"]:
            value = "".join([i.get("value", "None") for i in data.get(
                "codecs").get(options) if i.get("name", None) == value])
            arg = f"{filters} {value}"
            self.outputDict.update({options: arg})
            return True
        elif data.get(options) == None:
            arg = f"{filters} {value}"
            self.outputDict.update({options: arg})
            return True
        elif options != "clip":
            value = "".join([i.get("value", "None") for i in data.get(
                options) if i.get("name", None) == value])
            arg = f"{filters} {value}"
            self.outputDict.update({options: arg})

    def build(self):
        for i in self.outputDict:
            if self.setVideoFilters(i):
                continue
            elif self.setAudioFilters(i):
                continue
            else:
                self.setFormat(i)
        lst_extra, vf, af = ([] for _ in range(3))
        for val in self.outputDict:
            if val == "vf":
                vf = self.outputDict.get(val).values()
                vf = ",".join(list(vf))
            elif val == "af":
                af = self.outputDict.get(val).values()
                af = ",".join(list(af))
            else:
                lst_extra.append(self.outputDict.get(val))
        # print(lst_extra, "temp x")
        # if vf:self.vf=f"-vf '{vf}'"
        # if af:self.af=f"-af '{af}'"
        self.vf = f"-vf '{vf}'" if vf else ""
        self.af = f"-af '{af}'" if af else ""
        self.extra = " ".join(lst_extra)
        self.commands = f"{self.vf} {self.af} {self.extra}"

    def startfunc(self, input: gr.components.IOComponent, c_label="", newValue=""):
        label, *_ = input.label.strip(": ").lower().split(
        ) if type(input.label) != list else "".join(input.label).strip(": ").lower().split()
        label += "".join(_).title()
        if newValue not in [None, "Source", "Auto", "", "None", 0]:
            self.outputDict["vf"].update({label: newValue})
            self.outputDict["af"].update({label: newValue})
            self.outputDict.update({label: newValue})
        else:
            self.outputDict.pop(label, "No Key Exists")
            self.outputDict["vf"].pop(label, "No Key Exists")
            self.outputDict["af"].pop(label, "No Key Exists")
            # self.formatOutputDict["vf"].pop(label, "Key is None or similar")
            # self.formatOutputDict["af"].pop(label, "Key is None or similar")
            # self.formatOutputDict.pop(label, "Key is None or similar")
        print(self.outputDict)
        self.build()


# def somefunc(input: gr.components.IOComponent, c_label=""):
#     label = ""
#     output = {}
#     print(input, c_label)
#     label, *_ = input.label.strip(": ").lower().split(
#     ) if type(input.label) != list else "".join(input.label).strip(": ").lower().split()
#     label += "".join(_).title()
#     print(newoutputMap.get(label), label, c_label)
#     if c_label not in [None, "Source", "Auto", ""]:
#         print(input.value)
#         output.update({label: c_label})
#     else:
#         output.pop(label, "No Key Exists")
#     pprint(output)

# def mediaChange(option):
#     no_=gr.update(visible=False)
#     if option in video_containers:
#         output=gr.update(visible=True)
#         return [no_,output]
#     elif option in audio_containers:
#         output=gr.update(visible=True)
#         return [output,no_]
#     else:
#         output=gr.update(visible=False)
#         return [no_,no_]


def mediaChange(option:str,state)-> List[Component]:
    """
        Allows playing the media in various options,
        Video, Audio or File

    Args:
        option : Clicked buttons value

    Returns:
        List[Component]: list of toggled output components to display
    """
    ops = {"Audio": gr.update(visible=True,value=state)}
    ops2 = {"Video": gr.update(visible=True,value=state)}
    ops3 = {"File": gr.update(visible=True,value=state, interactive=False)}

    def chosen(x): return x.get(option, gr.update(visible=False))
    return [chosen(ops), chosen(ops2), chosen(ops3)]


# def videoChange(value):
#     print(value.name)

    # if option in video_containers:
    #     output=gr.update(visible=True)
    #     return [no_,output]
    # elif option in audio_containers:
    #     output=gr.update(visible=True)
    #     return [output,no_]
    # else:
    #     output=gr.update(visible=False)
    #     return [no_,no_]




"""Helper Functions for Processing """


# def clear(*input):
#     print(input, " clear_func")
#     # for i in [inp for i in input for inp in i]:
#     #     print(i, hasattr(i,"cleared_value"),type(i))
#     # a=default_clear(input_components)
#     def clear_func(x): return [component.cleared_value if hasattr(
#         component, "cleared_value") else None for component in x]
#     print(clear_func(input))
#     return clear_func(input)

def customBitrate(choice:int)-> Component:
    """
        Toggle a component for custom Audio Quality 
        visible/none
    Args:
        choice : Custom audio quality

    Returns:
        Component: component toggle state
    """
    if choice == "Custom":
        return gr.update(visible=True)
    else:
        return gr.update(visible=False, value=0)


def supported_codecs(format: str)-> List[Component]:
    """
        Changes video and audio components with appropriate 
        options according to passed format 

    Args:
        format: passed media codec (x264,x265)

    Returns:
        List[Component]: list of components with updated choices
    """
    if format:
        format = format.lower()
    video_lst = [val.get("value") for val in data["codecs"]["video"]
                 if val.get("supported") == None or format in val["supported"]]
    audio_lst = [val.get("value") for val in data["codecs"]["audio"]
                 if val.get("supported") == None or format in val["supported"]]
    return [gr.update(choices=video_lst), gr.update(choices=audio_lst)]


def supported_presets(format: str)-> Component:
    """ 
        Changes presets component with appropriate 
        options according to passed format
    Args:
        format: passed media codec (x264,x265)

    Returns:
        Component: component with updated choice list (video codecs)
    """ 
    if format:
        format = format.lower()
    video_lst = [val.get("name") for val in data["presets"]
                 if val.get("supported") == None or format in val["supported"]]
    return gr.update(choices=video_lst)


def change_clipbox(choice:str)-> List[Component]:
    """
    Toggles the clipping Textbox 
    
    Args:
        choice: Enabled/None

    Returns:
        List[Component]: list of components with visible state of the clip components
    """
    if choice == "Enabled":
        return [gr.update(visible=True, value="00:00"), gr.update(visible=True, value="00:10")]
    else:
        return [gr.update(visible=False, value=""), gr.update(visible=False, value="")]


def updateOutput(file: _TemporaryFileWrapper)-> Component:
    if file:
        print(file.name)
        return gr.update(value=file.name)


def get_component_instance(inputs: gr.Blocks)-> List[Component]:
    """ returns only components

    Args:
        inputs: layout elements

    Returns:
        List[Component]: components
    """
    return [gr.components.get_component_instance(i, render=True) for i in inputs.children]


class Clear(CommandBuilder):
    """ Class for clearing components in layouts
    """

    def __call__(self, *args, **kwds):
        return self._component

    def __str__(self):
        return f"{self._component} __clear__ class"

    def __repr__(self):
        return self._component

    def __init__(self, *input_component: gr.Blocks()) -> None:
        """
        Parameters:
            *input_component: A tuple of layout blocks containing components
        """
        self._component = []
        if input_component is not None:
            for i in input_component:
                # self._component += super()._get_component_instance(i)
                self._component += self.__get_component_instance(i)

    def __get_component_instance(self, inputs: gr.Blocks) -> list:
        # print(inputs, " class instance")
        res=[]
        # print(*inputs.children)
        for i in inputs.children:
            # print(i,hasattr(i,"children"))
            if not (hasattr(i,"children")):
                # res.append(gr.components.get_component_instance(i,render=True))
                res+=[gr.components.get_component_instance(i,render=True)]
                # print(i)
            elif hasattr(i,"children"):
                # print(*i.children)
                res+=self.__get_component_instance(i)
                # res=[gr.components.get_component_instance(i, render=True) for i in inputs.children if not hasattr(i, "children")]
                # print(res,"__ result")
        # print(res)
        return res
        # return [gr.components.get_component_instance(i, render=True) for i in inputs.children if not hasattr(i, "children")]

    def add(self, *args):
        print(args, type(args))
        if args is not None:
            for i in args:
                self._component += super().__get_component_instance(i)
        return self._component

    def clear(self, *args):
        """
        Function to clear components from a Block in the class instance
        """
        def clear_func(x): return [component.cleared_value if hasattr(
            component, "cleared_value") else component.value for component in x]
        return clear_func(self._component)