import pandas as pd
import copy
import matplotlib.gridspec as gridspec
from fpdf import FPDF
import matplotlib.pyplot as plt
from io import BytesIO
import numpy as np
import datetime
import os


class Form():

    """
    submissions
    repeats
    variable
    time_variable
    survey_name
    form
    choices
    survey
    settings
    media
    attachments
    """

    def __init__(self, submissions, survey, choices, settings, repeats, survey_name, form, variable, time_variable, media, attachments) -> None:
        self.submissions = submissions
        self.repeats = repeats
        self.variable = variable
        self.time_variable = time_variable
        self.survey_name = survey_name
        self.form = form
        self.survey = survey
        self.choices = choices
        self.settings = settings
        self.media = media
        self.attachments = attachments

    @property
    def _constructor(self):
        return Form

    def get_media(self, subs, reps):
        names = list(self.survey['name'].loc[(
            self.survey['type'] == 'image') | (self.survey['type'] == 'audio') | (self.survey['type'] == 'video')])
        media = set()
        for j in subs.columns:
            if j in names:
                media = media.union(set(subs[j].loc[~pd.isna(subs[j])]))
        for k in reps.values():
            for j in k.columns:
                if j in names:
                    media = media.union(set(k[j].loc[~pd.isna(k[j])]))
        return media

    def save_media(self, path="./"):
        if len(self.media.items()) != 0:
            if 'media' not in os.listdir(path):
                os.mkdir(path+'media')
            for key, value in self.media.items():
                file = open(path+'./media/'+key, "wb")
                file.write(value)
                file.close()

    def filter_variable(self, x):
        submissions = copy.copy(
            self.submissions.loc[self.submissions[self.variable] == x])
        set_not_rejected = list(submissions["KEY"])
        reps = copy.copy(self.repeats)
        for j in reps.keys():
            reps[j] = reps[j].loc[[True if reps[j]["PARENT_KEY"].iloc[i].split(
                "/")[0] in set_not_rejected else False for i in range(len(reps[j]))]]
        media = copy.copy(self.media)
        media = {key: value for key, value in media.items(
        ) if key in self.get_media(submissions, reps)}
        return Form(submissions, repeats=reps, media=media, survey_name=self.survey_name, form=self.form, variable=self.variable, time_variable=self.time_variable, survey=self.survey, choices=self.choices, settings=self.settings, attachments=self.attachments)

    def date_time_filter(
            self,
            time_start=None,
            time_end=None,
            date_start=None,
            date_end=None,
            day=None):
        if date_start is not None:
            submissions = copy.copy(self.submissions.loc[self.submissions[self.time_variable].map(
                lambda x: x.date()) >= date_start])
        if date_end is not None:
            submissions = copy.copy(self.submissions.loc[self.submissions[self.time_variable].map(
                lambda x: x.date()) <= date_end])
        if (time_start is not None) & (time_end is not None):
            if time_start > time_end:
                submissions = copy.copy(self.submissions.loc[(self.submissions[self.time_variable].map(lambda x: x.time()) >= time_start)
                                                             | (self.submissions[self.time_variable].map(lambda x: x.time()) < time_end)])
            else:
                submissions = copy.copy(self.submissions.loc[(self.submissions[self.time_variable].map(lambda x: x.time()) >= time_start)
                                                             & (self.submissions[self.time_variable].map(lambda x: x.time()) < time_end)])
        if (time_start is not None) & (time_end is None):
            submissions = copy.copy(self.submissions.loc[self.submissions[self.time_variable].map(
                lambda x: x.time()) >= time_start])
        if (time_start is None) & (time_end is not None):
            submissions = copy.copy(self.submissions.loc[self.submissions[self.time_variable].map(
                lambda x: x.time()) <= time_end])

        if day is not None:
            submissions = copy.copy(self.submissions.loc[[a in day for a in [self.submissions[self.time_variable][i].map(lambda x: x.date().isoweekday())
                                                                             for i in range(len(self.submissions[self.time_variable]))]]])
        set_not_rejected = list(submissions["KEY"])
        reps = copy.copy(self.repeats)
        for j in reps.keys():
            reps[j] = reps[j].loc[[True if reps[j]["PARENT_KEY"].iloc[i].split(
                "/")[0] in set_not_rejected else False for i in range(len(reps[j]))]]

        media = copy.copy(self.media)
        media = {key: value for key, value in media.items(
        ) if key in self.get_media(submissions, reps)}

        return Form(submissions, repeats=reps, media=media, survey_name=self.survey_name, form=self.form, variable=self.variable, time_variable=self.time_variable, survey=self.survey, choices=self.choices, settings=self.settings, attachments=self.attachments)

    def pdf_summary(self, directory='',name=""):

        def filter_text(s, filter=["""<span style="color:red">""", "</span>", "**"]):
            for j in filter:
                s = s.replace(j, "")
            return s

        def insert_newline(s):
            if len(s) < 2:
                return s[0]
            else:
                b = range(1, len(s))
                for i in range(len(b)):
                    s.insert(b[i]+i, "\n")
                z = "".join(s)
                return z

        def group_text(s, no):
            a = filter_text(s).split()
            if len(a) < no:
                return s
            else:
                b = [" ".join(a[x:x+no]) for x in range(0, len(a), no)]
                return insert_newline(b)

        def question_type(var, survey=self.survey):
            return survey["type"].loc[survey["name"] == var].iloc[0].split()

        def reindex(input, var, choices=self.choices):
            if question_type(var)[0] == "select_one":
                selects = choices["label::English (en)"].loc[choices["list_name"].map(lambda x: x.strip())
                                                             == question_type(var)[1]]
                input = input.reindex(selects)
            if question_type(var)[0] == "integer":
                input = input.sort_index()
            if question_type(var)[0] == "decimal":
                input = input.sort_index()
            if question_type(var)[0] == "select_multiple":
                selects = choices["label::English (en)"].loc[choices["list_name"].map(lambda x: x.strip())
                                                             == question_type(var)[1]]
                input = input.reindex(selects)
            if question_type(var)[0] == "select_one_from_file":
                selects = choices["label::English (en)"].loc[choices["list_name"].map(lambda x: x.strip())
                                                             == question_type(var)[1]]
                input = input.reindex(selects)
            return input

        def multiprocess(series):
            out = []
            for j in series.index:
                if not pd.isna(series[j]):
                    step = series[j].split(" \n")
                    for i in step:
                        out.append(i)
            out = pd.Series(out)
            return (out)

        def getIndexLength(var, data):
            if question_type(var)[0] != "select_multiple":
                a = data[var].loc[~data[var].isna()].value_counts()
                a = reindex(a, var).fillna(0)
            else:
                a = multiprocess(
                    data[var].loc[~data[var].isna()]).value_counts()
                a = reindex(a, var).fillna(0)
            return len(a.index)

        def pie(ax, var, data, survey=self.survey, title_group=4, label_group=3):
            if question_type(var)[0] != "select_multiple":
                a = data[var].loc[~data[var].isna()].value_counts()
                a = reindex(a, var).fillna(0)
            else:
                a = multiprocess(
                    data[var].loc[~data[var].isna()]).value_counts()
                a = reindex(a, var).fillna(0)
            title = group_text(
                survey["label::English (en)"].loc[survey["name"] == var].iloc[0], title_group)
            labels = [group_text(str(i), label_group) for i in a.index]
            if max(a, default=0) == 0:
                ax.set_title(title)
                ax.set_xlabel("(total="+str(a.sum())+")", labelpad=1)
            else:
                ax.pie(x=a.values, labels=labels,
                       autopct=lambda x: '{:.0f}'.format(x*a.values.sum()/100))
                ax.set_title(title)
                ax.set_xlabel("(total="+str(a.sum())+")", labelpad=1)

        def bar(ax, var, data, survey=self.survey, title_group=4, label_group=3):
            if question_type(var)[0] != "select_multiple":
                a = data[var].loc[~data[var].isna()].value_counts()
                a = reindex(a, var).fillna(0)
            else:
                a = multiprocess(
                    data[var].loc[~data[var].isna()]).value_counts()
                a = reindex(a, var).fillna(0)
            title = group_text(
                survey["label::English (en)"].loc[survey["name"] == var].iloc[0], title_group)
            label = [group_text(str(i), label_group) for i in a.index]
            y = range(len(a))
            width = a.values
            if max(a, default=0) == 0:
                ax.set_title(title)
                ax.set_xlabel("Count (total="+str(a.sum())+")", labelpad=1)
            else:
                bars = ax.barh(y=y, width=width, tick_label=label)
                ax.bar_label(bars)
                ax.set_title(title)
                ax.set_xlabel("Count (total="+str(a.sum())+")", labelpad=1)

        def hist(ax, var, data, survey=self.survey, title_group=4, label_group=3):
            a = data[var].loc[~data[var].isna()]
            title = group_text(
                survey["label::English (en)"].loc[survey["name"] == var].iloc[0], title_group)
            if len(a) == 0:
                ax.set_title(title)
                ax.set_ylabel("Count", labelpad=1)
                ax.set_xlabel("Participant's reply", labelpad=1)
            else:
                align = "mid"
                ax.hist(x=a, align=align, bins=int(max(a)-min(a)+1),
                        range=(min(a)-0.5, max(a)+0.5))
                ax.set_title(title)
                ax.set_ylabel("Count (total="+str(len(a))+")", labelpad=1)
                ax.set_xlabel("Participant's reply", labelpad=1)

        def inches(cm):
            return cm/2.54

        def fig_wrap(var, data, typ="bar", title_group=4, label_group=3, width=20, height=12):

            indexLength = getIndexLength(var, data=data)
            if indexLength > 7:
                height = min(height + (height/7)*(indexLength-7), 27)

            gs = gridspec.GridSpec(nrows=1, ncols=1)
            fig = plt.figure(figsize=(inches(width)*gs.nrows,
                                      inches(height)*gs.ncols))

            for i in range(0, gs.nrows*gs.ncols):
                ax = plt.subplot(gs[i//gs.ncols, i % gs.ncols])
                if typ == "bar":
                    bar(ax, var, data=data, title_group=title_group,
                        label_group=label_group)
                elif typ == "pie":
                    pie(ax, var, data=data, title_group=title_group,
                        label_group=label_group)
                elif typ == "hist":
                    hist(ax, var, data=data, title_group=title_group,
                         label_group=label_group)
            gs.tight_layout(fig)
            b = BytesIO()
            plt.savefig(b, format="png")
            plt.close()
            return b, height

        pdf = FPDF()

        pdf.add_page()
        pdf.set_font('Arial', 'B', 20)
        pdf.cell(w=210, txt=self.survey_name, align="C")
        pdf.ln(h=20)
        pdf.set_font('Arial', 'B', 12)
        pdf.cell(w=210, txt='Total number of submissions = ' +
                 str(len(self.submissions)), align="L")

        pdf.add_page()
        pdf.set_font('Arial', 'B', 16)

        ty = ['select_one', 'select_multiple', 'integer']

        print_dic = {self.survey['name'].iloc[j]: 'bar' for j in range(
            len(self.survey)) if question_type(self.survey['name'].iloc[j])[0] in ty}

        for i in print_dic.keys():
            for value in self.repeats.values():
                if i in value.columns:
                    figu, figuHeight = fig_wrap(
                        i, data=value, typ=print_dic[i])
                    pdf.set_x(105-100)
                    pdf.image(figu, w=200, h=figuHeight*10)
                    pdf.ln(10)
                    break
            else:
                figu, figuHeight = fig_wrap(
                    i, data=self.submissions, typ=print_dic[i])
                pdf.set_x(105-100)
                pdf.image(figu, w=200, h=figuHeight*10)
                pdf.ln(10)
            continue

        pdf.output(f"{directory}{self.survey_name if name=="" else name}.pdf")

    def add_headers(self, questions=True, variable=None):
        df = self.submissions
        repeats = self.repeats
        survey = self.survey

        if questions == True:
            a = []
            for j in df.columns:
                if j in list(survey["name"]):
                    x = survey["label::English (en)"].loc[survey["name"]
                                                          == j].iloc[0]
                    a.append(x)
                else:
                    a.append(np.nan)
            df_out = copy.deepcopy(df)
            df_out.loc[-1] = a
            df_out.sort_index(inplace=True)

            reps = copy.copy(repeats)
            for k in reps.keys():
                a = []
                for j in reps[k].columns:
                    if j in list(survey["name"]):
                        x = survey["label::English (en)"].loc[survey["name"]
                                                              == j].iloc[0]
                        a.append(x)
                    else:
                        a.append(np.nan)
                rep_out = copy.deepcopy(repeats[k])
                rep_out.loc[-1] = a
                rep_out.sort_index(inplace=True)
                reps[k] = rep_out

        if variable != None:
            a = []
            for j in df.columns:
                if j in list(survey["name"]):
                    x = survey[variable].loc[survey["name"]
                                             == j].iloc[0]
                    a.append(x)
                else:
                    a.append(np.nan)
            if not questions:
                df_out = copy.deepcopy(df)
                df_out.loc[-1] = a
                df_out.sort_index(inplace=True)
            else:
                df_out.loc[-2] = a
                df_out.sort_index(inplace=True)

            if not questions:
                reps = copy.copy(repeats)
            for k in reps.keys():
                a = []
                for j in reps[k].columns:
                    if j in list(survey["name"]):
                        x = survey[variable].loc[survey["name"]
                                                 == j].iloc[0]
                        a.append(x)
                    else:
                        a.append(np.nan)
                if not questions:
                    rep_out = copy.deepcopy(reps[k])
                    rep_out.loc[-1] = a
                    rep_out.sort_index(inplace=True)
                else:
                    rep_out.loc[-2] = a
                    rep_out.sort_index(inplace=True)
                reps[k] = rep_out

        if variable == None:
            new_labels = pd.MultiIndex.from_arrays(
                [df_out.columns, df_out.iloc[0]], names=['code', 'question'])
            df_out = df_out.set_axis(new_labels, axis=1).iloc[1:]
            for k in reps.keys():
                new_labels = pd.MultiIndex.from_arrays(
                    [reps[k].columns, reps[k].iloc[0]], names=['code', 'question'])
                reps[k] = reps[k].set_axis(new_labels, axis=1).iloc[1:]
        elif not questions:
            new_labels = pd.MultiIndex.from_arrays(
                [df_out.columns, df_out.iloc[0]], names=['code', 'variable'])
            df_out = df_out.set_axis(new_labels, axis=1).iloc[1:]
            for k in reps.keys():
                new_labels = pd.MultiIndex.from_arrays(
                    [reps[k].columns, reps[k].iloc[0]], names=['code', 'variable'])
                reps[k] = reps[k].set_axis(new_labels, axis=1).iloc[1:]
        else:
            new_labels = pd.MultiIndex.from_arrays(
                [df_out.columns, df_out.iloc[0], df_out.iloc[1]], names=['code', 'variable', 'question'])
            df_out = df_out.set_axis(new_labels, axis=1).iloc[2:]
            for k in reps.keys():
                new_labels = pd.MultiIndex.from_arrays(
                    [reps[k].columns, reps[k].iloc[0], reps[k].iloc[1]], names=['code', 'variable', 'question'])
                reps[k] = reps[k].set_axis(new_labels, axis=1).iloc[2:]

        return Form(submissions=df_out, repeats=reps, survey=survey, survey_name=self.survey_name, form=self.form, variable=self.variable, time_variable=self.time_variable,  choices=self.choices, settings=self.settings, media=self.media, attachments=self.attachments)
