"""Utility functions and constants of the app."""
import json
import os

import dash_bootstrap_components as dbc
import pandas as pd
import plotly.express as px
from dash import html

COLORS = [
    px.colors.qualitative.G10,
    px.colors.sequential.Teal,
    px.colors.sequential.Brwnyl,
    px.colors.sequential.Burg,
    px.colors.sequential.Purp,
]


def pull_files(path):
    """
    Creates a list of paths for .json files in the input path.

    Parameters
    ----------
    path : str
        Path of the directory containing .json files to be visualized.

    Returns
    -------
    list
        List of paths.
    """
    files = []
    for i in os.listdir(path):
        if i[-5:] == ".json":
            files.append((os.path.join(path, i), i[:-5]))
    return files


def process_file(file, color):
    """
    Creates a dataframe from input file's data.
    Loads the data from the file into a dataframe and adds
    Result, Metadata, and Color (using a single color sequence
    from COLORS per file) columns to the dataframe using
    the data.

    Parameters
    ----------
    file : tuple
        (dataset-pipeline file path, dataset-pipeline name).
    color : int
        Index number for selecting a color sequence from COLORS.

    Returns
    -------
    pandas.core.frame.DataFrame
        A dataframe containing Subject, Dataset-Pipeline, Result
        Metadata, and Color columns.
    """
    data = None
    with open(file[0], "r") as dataset:
        data = json.load(dataset)
    x = []
    for k in data.keys():
        z = len(COLORS[color]) - 1
        for v in data[k].keys():
            x.append(
                (k, v, data[k][v]["Result"]["result"], data[k][v], COLORS[color][z])
            )
            z -= 1
    x = [
        (i[0], i[1], -1, i[3], i[4])
        if i[2] is None
        else (i[0], i[1], float(i[2]), i[3], i[4])
        for i in x
    ]
    df = pd.DataFrame(
        {
            "Subject": [i[0] for i in x],
            "Dataset-Pipeline": [file[1] + "-" + i[1] for i in x],
            "Result": [i[2] for i in x],
            "Metadata": [i[3] for i in x],
            "Color": [i[4] for i in x],
        }
    )
    return df


def process_files(path):
    """
    Creates a dataframe from the data in files located in the
    input path.
    Utilizes pull_files function to apply process_file function
    to multiple files.

    Parameters
    ----------
    path : str
        Path of the directory containing .json files to be visualized.


    Returns
    ----------
    pandas.core.frame.DataFrame
        A dataframe generated through conconcatenating dataframes
        generated by pull_files function for each file.
    """
    files = pull_files(path)
    dfs = []
    for i, j in enumerate(files):
        dfs.append(process_file(j, i))
    return pd.concat(dfs)


def pull_directories(path):
    """
    Generates a list of tuples for each directory located in the input path.
    Tuples are in (directory name, dataframe generated from the files
    in directory) form. Dataframes are generated using pull_files function.

    Parameters
    ----------
    path : str
        Path of the directory containing direcotries that all contain .json files
        to be visualized.

    Returns
    ----------
    list
        A list of tuples in (directory name, directory dataframe) form.
    """
    files = []
    for directory in os.listdir(path):
        files.append((directory, process_files(os.path.join(path, directory))))
    return files


def generate_summary(df):
    """
    Generates a summary for the input dataframe's data.
    The summary includes total number of datapoints, total number
    of missing datapoints, and number of missing datapoints in
    each dataset-pipeline.

    Parameters
    ----------
    df : pandas.core.frame.DataFrame
        Dataframe containing the data.

    Returns
    -------
    dash_bootstrap_components._components.Card.Card
        A structured and styled summary of the dataframe's data.
    """
    total = str(df.shape[0])
    missing = str(df[df["Result"] == -1].shape[0])
    header = html.H4("Summary", className="card-title")
    summary = [
        header,
        "Total number of datapoints: " + total,
        html.Br(),
        "Total number of missing datapoints: " + missing,
        html.Br(),
    ]
    pipelines = df["Dataset-Pipeline"].unique().tolist()
    for p in pipelines:
        s = (
            p
            + ": "
            + str(df[(df["Dataset-Pipeline"] == p) & (df["Result"] == -1)].shape[0])
        )
        summary.append(s)
        summary.append(html.Br())

    return dbc.Card(dbc.CardBody(summary, className="card-text"))


def extract_metadata(clickData, x=None, y=None, df=None):
    """
    Extracts and structures metadata from graphs' click event.
    The extracted metadata is used to process data and create UI elements to
    display the metadata. If x or y is None, it assumes the clickData is coming
    from histogram graph otherwise it assumes its coming from scatter plot graph.

    Parameters
    ----------
    clickData : dict
        Data from latest histogram graph click event.
    x : str, optional
        Name of the x-axis dataset-pipeline.
    y : str, optional
        Name of the y-axis dataset-pipeline.
    df : pandas.core.frame.DataFrame, optional
        Dataframe containing the data.

    Returns
    ----------
    dict
        Structured metadata.
    """
    metadata = {}
    if x is None or y is None:
        metadata["subject"] = clickData["points"][0]["customdata"][0]
        metadata["dataset-pipeline"] = clickData["points"][0]["y"]
        metadata["result"] = (
            "N/A"
            if clickData["points"][0]["x"] == -1
            else str(clickData["points"][0]["x"])
        )
        for k, v in list(clickData["points"][0]["customdata"][2].items())[:-1]:
            metadata[k] = {
                "status": "Incomplete" if v["status"] is None else v["status"],
                "inputID": "N/A" if v["inputID"] is None else str(v["inputID"]),
                "outputID": "N/A" if v["outputID"] is None else str(v["outputID"]),
                "taskID": "N/A" if v["taskID"] is None else str(v["taskID"]),
                "toolConfigID": "N/A"
                if v["toolConfigID"] is None
                else str(v["toolConfigID"]),
            }

    else:
        metadata["x"] = {
            "subject": df[
                (df["Dataset-Pipeline"] == x)
                & (df["Result"] == clickData["points"][0]["x"])
            ]["Subject"].iloc[0],
            "dataset-pipeline": x,
            "result": "N/A"
            if clickData["points"][0]["x"] == -1
            else str(clickData["points"][0]["x"]),
        }
        x_dic = df[
            (df["Dataset-Pipeline"] == x)
            & (df["Result"] == clickData["points"][0]["x"])
        ]["Metadata"].iloc[0]
        for k, v in list(x_dic.items())[:-1]:
            metadata["x"][k] = {
                "status": "Incomplete" if v["status"] is None else v["status"],
                "inputID": "N/A" if v["inputID"] is None else str(v["inputID"]),
                "outputID": "N/A" if v["outputID"] is None else str(v["outputID"]),
                "taskID": "N/A" if v["taskID"] is None else str(v["taskID"]),
                "toolConfigID": "N/A"
                if v["toolConfigID"] is None
                else str(v["toolConfigID"]),
            }
        metadata["y"] = {
            "subject": df[
                (df["Dataset-Pipeline"] == y)
                & (df["Result"] == clickData["points"][0]["y"])
            ]["Subject"].iloc[0],
            "dataset-pipeline": y,
            "result": "N/A"
            if clickData["points"][0]["y"] == -1
            else "Result: " + str(clickData["points"][0]["y"]),
        }
        y_dic = df[
            (df["Dataset-Pipeline"] == y)
            & (df["Result"] == clickData["points"][0]["y"])
        ]["Metadata"].iloc[0]
        for k, v in list(y_dic.items())[:-1]:
            metadata["y"][k] = {
                "status": "Incomplete" if v["status"] is None else v["status"],
                "inputID": "N/A" if v["inputID"] is None else str(v["inputID"]),
                "outputID": "N/A" if v["outputID"] is None else str(v["outputID"]),
                "taskID": "N/A" if v["taskID"] is None else str(v["taskID"]),
                "toolConfigID": "N/A"
                if v["toolConfigID"] is None
                else str(v["toolConfigID"]),
            }

    return metadata
