import os
import re
import pandas as pd
import numpy as np
import collections
from statistics import mean, pstdev
from scipy.stats import shapiro, ttest_rel
import matplotlib.pyplot as plt


class Person:
    def __init__(self, id):
        self.id = id
        self.age = None
        self.BMI = None
        self.measurements = {}
        self.mes_ok = None
        self.mes_stand = None
        self.mes_meal = None
        self.mes_activity = None
        self.mes_evening = None


class Measurement:
    def __init__(self, fat, water, _1khz, _5khz, _50khz, _250khz, _500khz, _1000khz, phase, weight):
        self.fat = fat
        self.water = water
        self._1khz = _1khz
        self._5khz = _5khz
        self._50khz = _50khz
        self._250khz = _250khz
        self._500khz = _500khz
        self._1000khz = _1000khz
        self.phase = phase
        self.weight = weight


class Inbody:
    def __init__(self, filepath, outpath):
        self.relativeError = None
        self.files = []
        self.names = []
        self.ppl = {}
        self.measurementList = ['mes_ok', 'mes_stand', 'mes_meal', 'mes_activity', 'mes_evening']
        self.quantities = ['fat', 'water', '_1khz', '_5khz', '_50khz', '_250khz', '_500khz', '_1000khz', 'phase', 'weight']
        self.filepath = filepath
        self.outpath = outpath
        self.precision = 5
        if os.path.exists(self.outpath + 'export_data/data.csv'):
            self.readCSV()
        else:
            self.readFiles()
            self.writeCSV()

    def writeCSV(self):
        data = []
        for i in self.quantities:
            for j in self.measurementList:
                arr = self.getDataFromAll(data=i, mes=j)
                data.append(arr)
        ages = []
        bmis = []
        for k in self.ppl.keys():
            ages.append(self.ppl[k].age)
            bmis.append(self.ppl[k].BMI)
        ages = np.array(ages)
        bmis = np.array(bmis)
        data.append(ages)
        data.append(bmis)
        df = pd.DataFrame(data)
        df.to_csv(self.outpath + 'export_data/data.csv', sep=';', index=False)

    def readCSV(self):
        df = pd.read_csv(self.outpath + 'export_data/data.csv', sep=';', dtype=float)
        for col in df.columns:
            self.ppl[col] = Person(col)
            person = np.array(df[col])
            self.ppl[col].age = person[-2]
            self.ppl[col].BMI = person[-1]
            person = person[:-2]
            for v, k in enumerate(self.measurementList):
                self.ppl[col].measurements[k] = Measurement(*person[v::len(self.measurementList)])

    def readFiles(self):
        self.files = os.listdir(self.filepath)
        for i in self.files:
            if i.startswith(".") or i.startswith("~$"):  # skip idiotic macOS temp files
                continue
            else:
                self.names.append(re.split("\\.", i)[0])

        for i in self.names:
            f = pd.ExcelFile(f"{self.filepath}{i}.xlsx", engine="openpyxl")
            self.ppl[i] = Person(i)
            df = pd.read_excel(f)
            df = df.iloc[::-1].dropna()
            df = df.stack().str.replace(',', '.').unstack()

            self.ppl[i].BMI = df['45. BMI (Body Mass Index)'][4]
            self.ppl[i].age = df['6. Age'][4]
            df_1khz = df[['157. 1kHz-RA Impedance', '158. 1kHz-LA Impedance', '159. 1kHz-TR Impedance', '160. 1kHz-RL Impedance', '161. 1kHz-LL Impedance']]
            df_5khz = df[['162. 5kHz-RA Impedance', '163. 5kHz-LA Impedance', '164. 5kHz-TR Impedance', '165. 5kHz-RL Impedance', '166. 5kHz-LL Impedance']]
            df_50khz = df[['167. 50kHz-RA Impedance', '168. 50kHz-LA Impedance', '169. 50kHz-TR Impedance', '170. 50kHz-RL Impedance', '171. 50kHz-LL Impedance']]
            df_250khz = df[['172. 250kHz-RA Impedance', '173. 250kHz-LA Impedance', '174. 250kHz-TR Impedance', '175. 250kHz-RL Impedance', '176. 250kHz-LL Impedance']]
            df_500khz = df[['177. 500kHz-RA Impedance', '178. 500kHz-LA Impedance', '179. 500kHz-TR Impedance', '180. 500kHz-RL Impedance', '181. 500kHz-LL Impedance']]
            df_1000khz = df[['182. 1000kHz-RA Impedance', '183. 1000kHz-LA Impedance', '184. 1000kHz-TR Impedance', '185. 1000kHz-RL Impedance', '186. 1000kHz-LL Impedance']]
            df_impedances = [df_1khz.astype(float), df_5khz.astype(float), df_50khz.astype(float), df_250khz.astype(float), df_500khz.astype(float), df_1000khz.astype(float)]
            df_phase_weight = df[['207. 50kHz-Whole Body Phase Angle', '15. Weight']]
            df_phase_weight = df_phase_weight.astype(float).T
            df_fat_water = df[['33. BFM (Body Fat Mass)', '18. TBW (Total Body Water)']]
            df_fat_water = df_fat_water.astype(float).T

            impedances = ['_1khz', '_5khz', '_50khz', '_250khz', '_500khz', '_1000khz']
            df_i = pd.DataFrame()

            for j, k in zip(df_impedances, impedances):
                df_i[k] = self.calculateBodyImpedance(j)

            df_i = df_i.T
            self.ppl[i].age, self.ppl[i].BMI = self.getAgeBMI(i)

            for v, k in enumerate(self.measurementList):
                self.ppl[i].measurements[k] = Measurement(*df_fat_water[v], *df_i[v], *df_phase_weight[v])

        # order alphabetically
        self.ppl = collections.OrderedDict(sorted(self.ppl.items()))

    def calculateBodyImpedance(self, df):
        ra, la, tr, rl, ll = df.values.T

        arms = np.divide(np.multiply(ra, la), np.add(ra, la))
        legs = np.divide(np.multiply(rl, ll), np.add(rl, ll))
        arms_legs = np.add(arms, legs)
        imp = np.add(arms_legs, tr)
        imp = np.around(imp, 1)
        return imp

    def getAgeBMI(self, name):
        df = pd.read_excel(f"{self.filepath}{name}.xlsx", engine="openpyxl")
        df = df.stack().str.replace(',', '.').unstack()  # replace all ',' for '.'
        BMI = df['45. BMI (Body Mass Index)'][4]
        age = df['6. Age'][4]
        return age, BMI

    def getDataFromAll(self, data='all', mes='mes_ok'):
        allData = []
        for x, y in self.ppl.items():
            y = self.ppl[x].measurements[mes]
            if data in self.quantities:
                allData.append(getattr(y, data))
        return np.array(allData).flatten()

    def exportData(self):
        if self.relativeError is None:
            self.calculateRelErr()
        data = []
        for key, value in self.relativeError.items():
            data.append([key])
            for i in value:
                for k, v in i.items():
                    temp = list(self.getDataFromAll(data=key, mes=k))
                    temp = [k] + temp + [v]
                    data.append(temp)
        df = pd.DataFrame(data)
        df.to_excel(self.outpath + 'export_data/data.xlsx')

    def runStatisticTests(self):
        print(10*"=" + "NULL HYPOTESIS TESTS" + 10*"=")
        for i in self.quantities:
            print(i)
            ok_data = self.getDataFromAll(data=i, mes='mes_ok')
            stand_data = self.getDataFromAll(data=i, mes='mes_stand')
            meal_data = self.getDataFromAll(data=i, mes='mes_meal')
            activity_data = self.getDataFromAll(data=i, mes='mes_activity')
            evening_data = self.getDataFromAll(data=i, mes='mes_evening')

            tt_stand = ttest_rel(ok_data, stand_data)
            tt_meal = ttest_rel(ok_data, meal_data)
            tt_activity = ttest_rel(ok_data, activity_data)
            tt_evening = ttest_rel(ok_data, evening_data)
            print('TTEST:')
            print(f'mes_stand:\t\tstat: {tt_stand[0].round(self.precision)}\tp: {tt_stand[1].round(self.precision)}')
            print(f'mes_meal:\t\tstat: {tt_meal[0].round(self.precision)}\tp: {tt_meal[1].round(self.precision)}')
            print(f'mes_activity:\tstat: {tt_activity[0].round(self.precision)}\tp: {tt_activity[1].round(self.precision)}')
            print(f'mes_evening:\tstat: {tt_evening[0].round(self.precision)}\tp: {tt_evening[1].round(self.precision)}')

            print(60*"-")
            plt.figure()
            plt.title(i)
            plt.boxplot([ok_data, stand_data, meal_data, activity_data, evening_data])
            plt.xticks([1, 2, 3, 4, 5], ["referencia", "v stoji", "po jedeni", "fyz. aktivita", "po 6-8 hod"])
            plt.savefig(self.outpath + 'boxplots/' + i)
            plt.close()

    def calculateRelErr(self):
        vals = {}
        for i in self.quantities:
            vals[i] = []
            data = []
            for a in self.measurementList:
                data.append(self.getDataFromAll(data=i, mes=a))

            temp = {}
            for j, k in zip(data, self.measurementList):
                deviation = np.subtract(data[0], j)
                div = np.divide(deviation, data[0])
                div = div * 100
                temp[k] = abs(np.around(np.mean(div), self.precision))
            vals[i].append(temp)
        self.relativeError = vals

    def calculateNormalDist(self):
        print(14*"=" + "SHAPIRO TESTS" + 14*"=")
        for i in self.quantities:
            print(f"Shapiro tests for {i}")
            ok_data = self.getDataFromAll(data=i)
            for j in self.measurementList:
                tabs = '\t'
                if j in ['mes_stand', 'mes_meal']:
                    tabs = tabs * 2
                if j == 'mes_ok':
                    continue
                mes_data = self.getDataFromAll(data=i, mes=j)
                data = np.subtract(mes_data, ok_data)
                plt.figure()
                plt.title(j + " " + i)
                plt.hist(data)
                stat, p = shapiro(data)
                print(f"{j}:{tabs}stat: {round(stat, self.precision)}\tp: {round(p, self.precision)}")

                plt.savefig(self.outpath + 'normal_distribution/'+i+"_"+j)
                plt.close()
            print('\n', end='')

    def getMeans(self):
        for i in self.quantities:
            print(20 * '=' + i + 20 * '=')
            for j in self.measurementList:
                data = self.getDataFromAll(data=i, mes=j)
                print('---' + j + '---')
                print(
                    f"AVG: {round(mean(data), 2)}\n"
                    f"STDEV: {round(pstdev(data), )}\n"
                    f"MIN: {min(data)}\n"
                    f"MAX: {max(data)}\n")

        ages = []
        bmis = []
        for k in self.ppl.keys():
            ages.append(self.ppl[k].age)
            bmis.append(self.ppl[k].BMI)

        print(
            f"---AGE---\n"
            f"AVG: {round(mean(ages), 2)}\n"
            f"STDEV: {round(pstdev(ages), )}\n"
            f"MIN: {min(ages)}\n"
            f"MAX: {max(ages)}\n")
        print(
            f"---BMI---\n"
            f"AVG: {round(mean(bmis), 2)}\n"
            f"STDEV: {round(pstdev(bmis), )}\n"
            f"MIN: {min(bmis)}\n"
            f"MAX: {max(bmis)}\n")
