"""
Program to automatically calculate all the required data and plot them on a line graph for data analysis
How to use it:
Run the program and enter the directory which contains the CSV Files
Then the program will create another CSV file with all the required data calcualated for each planter and
Write them to excel files and store them in seperate folders based on planter names
There is also the option to view the line graph for a arbitrary time range speified in datetime (YYYY-MM-DD HH:MM:SS)
Created on 06-04-2018 by Sanjeev Rajasekaran
Last Edited on 06-26-2018
"""

import datetime  # Python's datettime module
import os  # import the module which can read file size, get current working directory
import sys  # To exit program if needed
import csv  # import the python module to be able write our data to a CSV file
import xlsxwriter  # Module to write csv data to excel file
import pandas as pd  # Plotting module
import matplotlib.pyplot as plt  # Plotting module
import seaborn as sns  # Plotting Module
import glob  # Get files of specific type
import pathlib  # Module to Get current dir
from collections import defaultdict # Module for pythons defaultdict module

sns.set() # Set Seaborn attributes to default

# Global Constants

DEPTH_BELOW_INVERT_DAN = 0.48
DEPTH_BELOW_INVERT_SAM = 0.50
DEPTH_BELOW_INVERT_SARAH = 0.50
DEPTH_BELOW_INVERT_DYLAN = 0.44
DIAMETER_OUTFLOW_PIPE_DAN = 0.60
DIAMETER_OUTFLOW_PIPE_SAM = 0.60
DIAMETER_OUTFLOW_PIPE_SARAH = 0.60
DIAMETER_OUTFLOW_PIPE_DYLAN = 0.59
SQUARE_ROOT_2GD_PLANTER_DAN = 6.23
SQUARE_ROOT_2GD_PLANTER_SAM = 6.21
SQUARE_ROOT_2GD_PLANTER_SARAH = 6.23
SQUARE_ROOT_2GD_PLANTER_DYLAN = 6.15
WEIRD_EQUATION_CD = 0.53
MONITORING_CHAMBER_SURFACE = 3.14
CONVERSION_TO_CUBIC_FEET = 28.32  # ft^3 - L
DRAINAGE_AREA_PLANTER_DAN = 1530.7
DRAINAGE_AREA_PLANTER_SAM = 1172.1
DRAINAGE_AREA_PLANTER_SARAH = 585.5
DRAINAGE_AREA_PLANTER_DYLAN = 1059.3
PLANTER_SURFACE_AREA = 72

class CalcColumns:

    def __init__(self):

        # Define all the variables

        self.rain_inches_list = list()
        self.depth_above_invert_list = list()
        self.outflow_cfs_list = list()
        self.outflow_ls_list = list()
        self.cumulative_rain_list = list()
        self.final_values_list = list()
        self.cumulative_outflow_volume_list = list()
        self.cumulative_inflow_volume_roof_list = list()
        self.cumulative_inflow_ord_list = list()
        self.event_list = list()

        # Initialise the Count Variables

        self.cumulative_rain_count = 0
        self.outflow_cfs_count = 0
        self.cumulative_outflow_count = 0
        self.roof_method_inflow_cfs_count = 0
        self.cumulative_inflow_volume_roof_method_count = 0
        self.cumulative_inflow_ord_l_count = 0
        self.rain_event_counter_dan = 1
        self.rain_event_counter_sam = 1
        self.rain_event_counter_sarah = 1
        self.rain_event_counter_dylan = 1

        self.columns = ["         Date          ", "Depth outflow (cm)", "Depth outflow (feet)", "Depth above invert",
                        "Depth Diameter",
                        "Outflow CFS", "Outflow LS", "Cumulative outflow volume", "Rain (mm)", "Rain (inch)",
                        "Cumulative Rain", "   Roof Method Inflow   ", "   Roof Method Inflow LS   ",
                        "   Cumulative Inflow Volume Roof Method   ", "Max Depth of Outflow (inch)"]

    """
    All the functions below use formulas from the excel sheet. We add all the values to a list to do future calculations
    """

    def calc_depth_of_outflow_ft(self, depth_of_outflow_cm, planter):

        depth_of_outflow_ft = list()

        for item in depth_of_outflow_cm[planter]:
            depth_of_outflow_ft.append(round((float(item) / 30.48), 2))

        return depth_of_outflow_ft

    def calc_depth_above_invert_ft(self, depth_of_outflow_ft, PLANTER_NAME):

        if PLANTER_NAME == "DAN":
            global DEPTH_BELOW_INVERT_DAN
            DEPTH_BELOW_INVERT = DEPTH_BELOW_INVERT_DAN
        if PLANTER_NAME == "SAM":
            global DEPTH_BELOW_INVERT_SAM
            DEPTH_BELOW_INVERT = DEPTH_BELOW_INVERT_SAM
        if PLANTER_NAME == "SARAH":
            global DEPTH_BELOW_INVERT_SARAH
            DEPTH_BELOW_INVERT = DEPTH_BELOW_INVERT_SARAH
        if PLANTER_NAME == "DYLAN":
            global DEPTH_BELOW_INVERT_DYLAN
            DEPTH_BELOW_INVERT = DEPTH_BELOW_INVERT_DYLAN

        for item in depth_of_outflow_ft:
            self.depth_above_invert_list.append(round((item - DEPTH_BELOW_INVERT), 4))

        return self.depth_above_invert_list

    def calc_depth_diameter(self, depth_above_invert, PLANTER_NAME):

        if PLANTER_NAME == "DAN":
            global DIAMETER_OUTFLOW_PIPE_DAN
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_DAN

        if PLANTER_NAME == "SAM":
            global DIAMETER_OUTFLOW_PIPE_SAM
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_SAM

        if PLANTER_NAME == "SARAH":
            global DIAMETER_OUTFLOW_PIPE_SARAH
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_SARAH

        if PLANTER_NAME == "DYLAN":
            global DIAMETER_OUTFLOW_PIPE_DYLAN
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_DYLAN

        depth_diameter = list()

        for item in depth_above_invert:
            depth_diameter.append(round((item / DIAMETER_OUTFLOW_PIPE), 4))

        return depth_diameter

    def calculate_outflow_cfs(self, depth_above_invert, depth_diameter, depth_of_outflow, date, PLANTER_NAME):

        global WEIRD_EQUATION_CD
        global MONITORING_CHAMBER_SURFACE

        if PLANTER_NAME == "DAN":
            global SQUARE_ROOT_2GD_PLANTER_DAN
            global DIAMETER_OUTFLOW_PIPE_DAN

            SQUARE_ROOT_2GD = SQUARE_ROOT_2GD_PLANTER_DAN
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_DYLAN

        if PLANTER_NAME == "SAM":
            global SQUARE_ROOT_2GD_PLANTER_SAM
            global DIAMETER_OUTFLOW_PIPE_SAM
            SQUARE_ROOT_2GD = SQUARE_ROOT_2GD_PLANTER_SAM
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_SAM

        if PLANTER_NAME == "SARAH":
            global SQUARE_ROOT_2GD_PLANTER_SARAH
            global DIAMETER_OUTFLOW_PIPE_SARAH
            SQUARE_ROOT_2GD = SQUARE_ROOT_2GD_PLANTER_SARAH
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_SARAH

        if PLANTER_NAME == "DYLAN":
            global SQUARE_ROOT_2GD_PLANTER_DYLAN
            SQUARE_ROOT_2GD = SQUARE_ROOT_2GD_PLANTER_DYLAN
            DIAMETER_OUTFLOW_PIPE = DIAMETER_OUTFLOW_PIPE_DYLAN

        for item in depth_diameter:

            if self.outflow_cfs_count == 0:  # If it is the first element, then just add 0, otherwise do rest of logic

                self.outflow_cfs_list.append(0)
                self.outflow_cfs_count += 1
                continue

            if depth_above_invert[self.outflow_cfs_count] > 0:
                outflow_cfs = (2 / 3) * SQUARE_ROOT_2GD * pow(DIAMETER_OUTFLOW_PIPE,
                                                              2) * WEIRD_EQUATION_CD * pow(item, 1.87)
            else:

                try:
                    outflow_cfs = round((MONITORING_CHAMBER_SURFACE * abs(
                    float(depth_of_outflow[self.outflow_cfs_count]) - float(
                        depth_of_outflow[self.outflow_cfs_count - 1])) / c1.calculate_hours_minutes(
                    date[self.outflow_cfs_count], date[self.outflow_cfs_count - 1])), 4)
                except IndexError:
                    print(date)
                    print(self.outflow_cfs_count)

            self.outflow_cfs_list.append(outflow_cfs)
            self.outflow_cfs_count += 1

        return self.outflow_cfs_list

    def calculate_hours_minutes(self, date_current, date_prev):

        date_prev = str(date_prev).strip('"')
        date_current = str(date_current).strip('"')

        try:
            date_prev = datetime.datetime.strptime(date_prev, '%d-%m-%Y %H:%M')  # Use pythons timedelta module
            date_current = datetime.datetime.strptime(date_current,
                                                      '%d-%m-%Y %H:%M')  # to calculate difference between dates
        except ValueError:
            date_prev = datetime.datetime.strptime(date_prev, '%Y-%m-%d %H:%M:%S').strftime('%d-%m-%Y %H:%M')
            date_current = datetime.datetime.strptime(date_current, '%Y-%m-%d %H:%M:%S').strftime('%d-%m-%Y %H:%M')
            date_prev = datetime.datetime.strptime(date_prev, '%d-%m-%Y %H:%M')
            date_current = datetime.datetime.strptime(date_current, '%d-%m-%Y %H:%M')
        time_delta_value = ((date_current - date_prev).total_seconds())

        return time_delta_value

    def calculate_outflow_ls(self, outflow_cfs):

        for item in outflow_cfs:
            self.outflow_ls_list.append(round((item * CONVERSION_TO_CUBIC_FEET), 4))

        return self.outflow_ls_list

    def calculate_cumulative_outflow_volume(self, outflow_cfs, date):

        for item in outflow_cfs:

            if self.cumulative_outflow_count == 0:
                self.cumulative_outflow_volume_list.append(0)
                self.cumulative_outflow_count += 1
                continue
            try:
                self.cumulative_outflow_volume_list.append(self.cumulative_outflow_volume_list[
                                                               self.cumulative_outflow_count - 1] + item * c1.calculate_hours_minutes(
                    date[self.cumulative_outflow_count], date[self.cumulative_outflow_count - 1]))
                self.cumulative_outflow_count += 1
            except IndexError:
                print(date)

        return self.cumulative_outflow_volume_list

    def calculate_rain_inches(self, rain_mm):

        for item in rain_mm:
            self.rain_inches_list.append(round((float(item) / 25.4), 4))

        return self.rain_inches_list

    def calculate_cumulative_rain(self, rain_mm):

        for item in rain_mm:
            self.cumulative_rain_list.append(round((sum(self.rain_inches_list[:(self.cumulative_rain_count + 1)])), 2))
            self.cumulative_rain_count += 1

        return self.cumulative_rain_list

    def calculate_roof_method_inflow_cfs(self, rain_inch, date, PLANTER_NAME):

        if PLANTER_NAME == "DAN":
            global DRAINAGE_AREA_PLANTER_DAN
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_DAN

        if PLANTER_NAME == "SAM":
            global DRAINAGE_AREA_PLANTER_SAM
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_SAM

        if PLANTER_NAME == "SARAH":
            global DRAINAGE_AREA_PLANTER_SARAH
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_SARAH

        if PLANTER_NAME == "DYLAN":
            global DRAINAGE_AREA_PLANTER_DYLAN
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_DYLAN

        roof_method_inflow_cfs_list = list()

        for item in rain_inch:
            roof_method_inflow_cfs_list.append(round(
                ((float(item) / 12) * DRAINAGE_AREA_PLANTER) / c1.calculate_hours_minutes(
                    date[self.roof_method_inflow_cfs_count], date[self.roof_method_inflow_cfs_count - 1]), 4))
            self.roof_method_inflow_cfs_count += 1

        return roof_method_inflow_cfs_list

    def calculate_roof_method_inflow_ls(self, inflow_cfs):

        global CONVERSTION_TO_CUBIC_FEET

        roof_method_inflow_ls = list()

        for item in inflow_cfs:
            roof_method_inflow_ls.append(round((item * CONVERSION_TO_CUBIC_FEET), 4))

        return roof_method_inflow_ls

    def calculate_cumulative_inflow_volume_roof_method(self, roof_method_inflow_cfs, date):

        for item in roof_method_inflow_cfs:

            if self.cumulative_inflow_volume_roof_method_count == 0:
                self.cumulative_inflow_volume_roof_list.append(0)
                self.cumulative_inflow_volume_roof_method_count += 1
                continue

            self.cumulative_inflow_volume_roof_list.append(self.cumulative_inflow_volume_roof_list[
                                                               self.cumulative_inflow_volume_roof_method_count - 1] + item * c1.calculate_hours_minutes(
                date[self.cumulative_inflow_volume_roof_method_count],
                date[self.cumulative_inflow_volume_roof_method_count - 1]))
            self.cumulative_inflow_volume_roof_method_count += 1

        return self.cumulative_inflow_volume_roof_list

    def calculate_cumulative_inflow_ORD_L(self, ord_inflow, date):

        for item in ord_inflow:

            if self.cumulative_inflow_ord_l_count == 0:
                self.cumulative_inflow_ord_list.append(0)
                self.cumulative_inflow_ord_l_count += 1
                continue

            # if self.cumulative_inflow_ord_l_count == 2390:
            #   break

            self.cumulative_inflow_ord_list.append(round((self.cumulative_inflow_ord_list[
                                                              self.cumulative_inflow_ord_l_count - 1] + ((float(
                item) + float(ord_inflow[self.cumulative_inflow_ord_l_count - 1])) / 2) * c1.calculate_hours_minutes(
                date[self.cumulative_inflow_ord_l_count], date[self.cumulative_inflow_ord_l_count - 1])), 3))
            self.cumulative_inflow_ord_l_count += 1

        return self.cumulative_inflow_ord_list

    def calculate_max_depth_outflow(self):

        max_depth_outflow_feet = max(self.depth_above_invert_list)
        max_depth_outflow_inch = max_depth_outflow_feet * 12

        return max_depth_outflow_feet, max_depth_outflow_inch

    def calculate_peak_rainfall(self):

        peak_rainfall = max(self.rain_inches_list)

        return peak_rainfall

    def calculate_peak_flow_cfs(self):

        peak_flow_cfs = max(self.outflow_cfs_list)

        return peak_flow_cfs

    def calculate_peak_flow_ls(self):

        peak_flow_ls = max(self.outflow_ls_list)

        return peak_flow_ls

    def calculate_rainflow_inflow_volume(self):

        return max(self.cumulative_inflow_volume_roof_list)  # Return the highest number from the list

    def calculate_outflow_volume(self):

        return max(self.cumulative_outflow_volume_list)

    def calculate_total_rainfall(self):

        return max(self.cumulative_rain_list)

    def calculate_rainfall_volume_x_roof(self, total_rainfall, PLANTER_NAME):

        if PLANTER_NAME == "DAN":
            global DRAINAGE_AREA_PLANTER_DAN
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_DAN

        if PLANTER_NAME == "SAM":
            global DRAINAGE_AREA_PLANTER_SAM
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_SAM

        if PLANTER_NAME == "SARAH":
            global DRAINAGE_AREA_PLANTER_SARAH
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_SARAH

        if PLANTER_NAME == "DYLAN":
            global DRAINAGE_AREA_PLANTER_DYLAN
            DRAINAGE_AREA_PLANTER = DRAINAGE_AREA_PLANTER_DYLAN

        return total_rainfall / 12 * DRAINAGE_AREA_PLANTER

    def calculate_ord_inflow_volume(self, total_rainfall):

        global CONVERSTION_TO_CUBIC_FEET
        global PLANTER_SURFACE_AREA

        return max(self.cumulative_inflow_ord_list) / CONVERSTION_TO_CUBIC_FEET + (
                total_rainfall / 12) * PLANTER_SURFACE_AREA

    def create_csv(self, final_dict):

        """
        Takes 1 Dictionary as argument
        Read the values from the dictionary and pass them on to write to a csv file
        """

        date = list()
        depth_outflow_cm_planter_dan = list()
        depth_outflow_cm_planter_sam = list()
        depth_outflow_cm_planter_sarah = list()
        depth_outflow_cm_planter_dylan = list()
        rain_mm = list()
        final_dict_dates = sorted(final_dict)  # Sort the dates

        for key in final_dict_dates:
            date.append(key)
            depth_outflow_cm_planter_dan.append(final_dict[key]['p1'])
            depth_outflow_cm_planter_sam.append(final_dict[key]['p2'])
            depth_outflow_cm_planter_sarah.append(final_dict[key]['p3'])
            depth_outflow_cm_planter_dylan.append(final_dict[key]['p4'])
            rain_mm.append(final_dict[key]['rain'])

        c1.detect_rain_events(final_dict)  # pass the dictionary to detect rain events
        c1.write_to_csv(date, depth_outflow_cm_planter_dan, depth_outflow_cm_planter_sam,   # write values to 'data.csv'
                        depth_outflow_cm_planter_sarah, depth_outflow_cm_planter_dylan, rain_mm)

    def detect_rain_events(self, data, gap=12):

        """
        Takes a dictionary and a optional parameter as arguments
        Detects the occurences of rain based on the gap value
        Default value of Gap is 12
        """
        cre_date = list()  # cre stands for Current Rain Event
        cre_depth_outflow = defaultdict(float)
        cre_rain = list()  # cre stands for Current Rain Event
        current_dir = pathlib.Path(__file__).parent
        os.chdir(current_dir)
        dates = sorted(data.keys())
        min_delta = datetime.timedelta(hours=gap)  # min gap with no rain for rain event
        state = 'find_start'
        start_dt = None  # beginning of this rain event
        last_dt = None  # last date rain > 0

        cre_depth_outflow["DAN"] = []
        cre_depth_outflow["SAM"] = []
        cre_depth_outflow["SARAH"] = []
        cre_depth_outflow["DYLAN"] = []
        event_counter_rain = 1
        event_counter_dry = 1

        for dt in dates:

            if state == 'find_end':
                if dt - last_dt >= min_delta: # if the time between the last date of rain and current date is more than the gap value

                    if data[dt]['rain'] == 0:
                        # found the end of the event
                        for planter in ["DAN", "SAM", "SARAH", "DYLAN"]:
                            c1.calc_data(cre_date, cre_depth_outflow, cre_rain, planter)
                        state = 'find_start'
                        start_dt = None
                        last_dt = None
                        cre_date.clear()        # clear all lists
                        cre_rain.clear()
                        cre_depth_outflow["DAN"] = []
                        cre_depth_outflow["SAM"] = []
                        cre_depth_outflow["SARAH"] = []
                        cre_depth_outflow["DYLAN"] = []
                        event_counter_dry += 1  # increment the event counters
                        event_counter_rain += 1
                    else:
                        last_dt = dt
                        cre_date.append(str(dt))
                        cre_rain.append(data[dt]['rain'])
                        event = "Rain" + str(event_counter_dry)
                        self.event_list.append(event)
                        cre_depth_outflow["DAN"].append(data[dt]['p1'])
                        cre_depth_outflow["SAM"].append(data[dt]['p2'])
                        cre_depth_outflow["SARAH"].append(data[dt]['p3'])
                        cre_depth_outflow["DYLAN"].append(data[dt]['p4'])

                elif data[dt]['rain'] > 0:
                    # found another rain value in current rain event
                    last_dt = dt
                    cre_date.append(str(dt))
                    cre_rain.append(data[dt]['rain'])
                    event = "Rain" + str(event_counter_rain)
                    self.event_list.append(event)
                    cre_depth_outflow["DAN"].append(data[dt]['p1'])
                    cre_depth_outflow["SAM"].append(data[dt]['p2'])
                    cre_depth_outflow["SARAH"].append(data[dt]['p3'])
                    cre_depth_outflow["DYLAN"].append(data[dt]['p4'])

                else:
                    cre_date.append(str(dt))
                    cre_rain.append(data[dt]['rain'])
                    event = "Rain" + str(event_counter_dry)
                    self.event_list.append(event)
                    cre_depth_outflow["DAN"].append(data[dt]['p1'])
                    cre_depth_outflow["SAM"].append(data[dt]['p2'])
                    cre_depth_outflow["SARAH"].append(data[dt]['p3'])
                    cre_depth_outflow["DYLAN"].append(data[dt]['p4'])

            if state == 'find_start' and data[dt]['rain'] > 0:
                # found the beginning of a new rain event
                state = 'find_end'  # next, look for the end of the rain event
                start_dt = dt  # start of the current rain event
                last_dt = dt  # most recent time that we've seen rain
                cre_date.append(str(dt))
                cre_rain.append(data[dt]['rain'])
                cre_depth_outflow["DAN"].append(data[dt]['p1'])
                cre_depth_outflow["SAM"].append(data[dt]['p2'])
                cre_depth_outflow["SARAH"].append(data[dt]['p3'])
                cre_depth_outflow["DYLAN"].append(data[dt]['p4'])
                event = "Rain" + str(event_counter_rain)
                self.event_list.append(event)
            if state == 'find_start' and data[dt]['rain'] == 0:
                event = "Dry" + str(event_counter_dry)
                self.event_list.append(event)

        # ran out of data
        if state == 'find-end':
            # currently looking for the end of a rain event so yield what we have
            for planter in ["DAN", "SAM", "SARAH", "DYLAN"]:
                c1.calc_data(cre_date, cre_depth_outflow, cre_rain, planter)

    def calc_data(self, date, depth_outflow_cm, rain_mm, planter):
        """
        Call all funtions with the required arguments and when its done, call the function to write the
        calculated data to a excel file
        """

        depth_outflow_feet = c1.calc_depth_of_outflow_ft(depth_outflow_cm, planter)
        depth_above_invert = c1.calc_depth_above_invert_ft(depth_outflow_feet, planter)
        depth_diamter = c1.calc_depth_diameter(depth_above_invert, planter)
        outflow_cfs = c1.calculate_outflow_cfs(depth_above_invert, depth_diamter, depth_outflow_feet, date,
                                               planter)
        outflow_ls = c1.calculate_outflow_ls(outflow_cfs)
        cumulative_outflow_volume = c1.calculate_cumulative_outflow_volume(outflow_cfs, date)
        rain_inches = c1.calculate_rain_inches(rain_mm)
        cumulative_rain = c1.calculate_cumulative_rain(rain_mm)
        roof_method_inflow_cfs = c1.calculate_roof_method_inflow_cfs(rain_inches, date, planter)
        roof_method_inflow_ls = c1.calculate_roof_method_inflow_ls(roof_method_inflow_cfs)
        cumulative_inflow_volume_roof_method = c1.calculate_cumulative_inflow_volume_roof_method(roof_method_inflow_cfs,
                                                                                date)
        # cumulative_inflow = c1.calculate_cumulative_inflow_ORD_L(ord_inflow_ls, date)
        max_depth_outflow_feet, max_depth_outflow_inch = c1.calculate_max_depth_outflow()
        peak_rainfall = c1.calculate_peak_rainfall()
        peak_flow_cfs = c1.calculate_peak_flow_cfs()
        peak_flow_ls = c1.calculate_peak_flow_ls()
        rainfall_inflow_volume = c1.calculate_rainflow_inflow_volume()
        outflow_volume = c1.calculate_outflow_volume()
        total_rainfall = c1.calculate_total_rainfall()
        rainfall_volume_x_roof = c1.calculate_rainfall_volume_x_roof(total_rainfall, planter)
        # ord_inflow_volume = c1.calculate_ord_inflow_volume(total_rainfall)
        c1.write_to_excel(date, depth_outflow_cm, depth_outflow_feet, depth_above_invert, depth_diamter, outflow_cfs,
                          outflow_ls, cumulative_outflow_volume, rain_mm, rain_inches, cumulative_rain,
                          roof_method_inflow_cfs, roof_method_inflow_ls, cumulative_inflow_volume_roof_method,
                          max_depth_outflow_feet, max_depth_outflow_inch, peak_rainfall, peak_flow_cfs, peak_flow_ls,
                          rainfall_inflow_volume, outflow_volume, total_rainfall, rainfall_volume_x_roof, planter)
        c1.clear_all()

    def clear_all(self):

        """
        Clears all lists and counter variables
        """
        self.rain_inches_list.clear()
        self.depth_above_invert_list.clear()
        self.outflow_cfs_list.clear()
        self.outflow_ls_list.clear()
        self.cumulative_rain_list.clear()
        self.final_values_list.clear()
        self.cumulative_outflow_volume_list.clear()
        self.cumulative_inflow_volume_roof_list.clear()
        self.cumulative_inflow_ord_list.clear()
        self.cumulative_rain_count = 0
        self.outflow_cfs_count = 0
        self.cumulative_outflow_count = 0
        self.roof_method_inflow_cfs_count = 0
        self.cumulative_inflow_volume_roof_method_count = 0
        self.cumulative_inflow_ord_l_count = 0

    def write_to_csv(self, date, depth_outflow_cm_planter_dan, depth_outflow_cm_planter_sam,
                     depth_outflow_cm_planter_sarah, depth_outflow_cm_planter_dylan, rain_mm):

        """
        Write the values into data.csv file
        """

        event_list = self.event_list
        current_dir = pathlib.Path(__file__).parent
        os.chdir(current_dir)

        myfile = open('Data.csv', 'w', newline='')
        with myfile:
            writer = csv.writer(myfile)
            writer.writerow(["Date", "Rain (mm)", "Depth Outflow P0", "Depth Outflow P1", "Depth Outflow P2",
                             "Depth Outflow P3", "Event"])
            for date_cur, rain_mm_cur, depth_outflow_cm_cur_dan, depth_outflow_cm_cur_sam, \
                depth_outflow_cm_cur_sarah, depth_outflow_cm_cur_dylan, event in zip(date, rain_mm,
                                                                              depth_outflow_cm_planter_dan,
                                                                              depth_outflow_cm_planter_sam,
                                                                              depth_outflow_cm_planter_sarah,
                                                                              depth_outflow_cm_planter_dylan, event_list):

                writer.writerow([date_cur, rain_mm_cur, depth_outflow_cm_cur_dan,
                                 depth_outflow_cm_cur_sam, depth_outflow_cm_cur_sarah, depth_outflow_cm_cur_dylan,
                                 event])

    def write_to_excel(self, date, depth_outflow_cm, depth_outflow_feet, depth_above_invert, depth_diamter, outflow_cfs,
                       outflow_ls, cumulative_outflow_volume, rain_mm, rain_inches, cumulative_rain,
                       roof_method_inflow_cfs, roof_method_inflow_ls, cumulative_inflow_volume_roof_method,
                       max_depth_outflow_feet, max_depth_outflow_inch, peak_rainfall, peak_flow_cfs, peak_flow_ls,
                       rainfall_inflow_volume, outflow_volume, total_rainfall, rainfall_volume_x_roof, PLANTER_NAME):

        """
         Write the contents of our calculations to a Excel file row by row
        """
        csv_row_count = 0
        final_values_counter = 0
        row = 0
        col = 0

        self.final_values_list = ["Max Depth of Outflow (feet)", max_depth_outflow_feet,
                                  "Max Depth of Outflow (inch)",
                                  max_depth_outflow_inch, "Peak Rainfall", peak_rainfall, "Peak Flow CFS",
                                  peak_flow_cfs, "Peak Flow LS", peak_flow_ls, "Rainfall Inflow Volume",
                                  rainfall_inflow_volume, "Outflow Volume", outflow_volume, "Total Rainfall",
                                  total_rainfall, "Rainfall volume * Roof", rainfall_volume_x_roof]

        current_dir = pathlib.Path(__file__).parent

        if PLANTER_NAME == "DAN":
            directory = str(current_dir) + "/" + PLANTER_NAME  # check if directory exists, if not
            if not os.path.exists(directory):                  # create it and change current directory to
                os.makedirs(directory)                         # the created directory
            os.chdir(directory)
            current_file_name = "RainGraph" + str(self.rain_event_counter_dan) + "-" + PLANTER_NAME + ".xlsx"
            workbook = xlsxwriter.Workbook(current_file_name)
            self.rain_event_counter_dan += 1

        if PLANTER_NAME == "SAM":
            directory = str(current_dir) + "/" + PLANTER_NAME
            if not os.path.exists(directory):
                os.makedirs(directory)
            os.chdir(directory)
            current_file_name = "RainGraph" + str(self.rain_event_counter_sam) + "-" + PLANTER_NAME + ".xlsx"
            workbook = xlsxwriter.Workbook(current_file_name)
            self.rain_event_counter_sam += 1

        if PLANTER_NAME == "SARAH":
            directory = str(current_dir) + "/" + PLANTER_NAME
            if not os.path.exists(directory):
                os.makedirs(directory)
            os.chdir(directory)
            current_file_name = "RainGraph" + str(self.rain_event_counter_sarah) + "-" + PLANTER_NAME + ".xlsx"
            workbook = xlsxwriter.Workbook(current_file_name)
            self.rain_event_counter_sarah += 1

        if PLANTER_NAME == "DYLAN":
            directory = str(current_dir) + "/" + PLANTER_NAME
            if not os.path.exists(directory):
                os.makedirs(directory)
            os.chdir(directory)
            current_file_name = "RainGraph" + str(self.rain_event_counter_dylan) + "-" + PLANTER_NAME + ".xlsx"
            workbook = xlsxwriter.Workbook(current_file_name)
            self.rain_event_counter_dylan += 1

        worksheet = workbook.add_worksheet("Data")

        cell_format_center = workbook.add_format({'align': 'center'})
        # cell_format.set_align('center')

        c1.set_column_width(worksheet, self.columns)

        worksheet.write(row, col, "Date", cell_format_center)
        worksheet.write(row, col + 1, "Depth outflow (cm)", cell_format_center)
        worksheet.write(row, col + 2, "Depth outflow (feet)", cell_format_center)
        worksheet.write(row, col + 3, "Depth above invert", cell_format_center)
        worksheet.write(row, col + 4, "Depth Diameter", cell_format_center)
        worksheet.write(row, col + 5, "Outflow CFS", cell_format_center)
        worksheet.write(row, col + 6, "Outflow LS", cell_format_center)
        worksheet.write(row, col + 7, "Cumulative outflow volume", cell_format_center)
        worksheet.write(row, col + 8, "Rain (mm)", cell_format_center)
        worksheet.write(row, col + 9, "Rain (inch)")
        worksheet.write(row, col + 10, "Cumulative Rain", cell_format_center)
        # worksheet.write(row, col + 11, "ORD Inflow LS", cell_format_center)
        worksheet.write(row, col + 11, "Roof Method Inflow", cell_format_center)
        worksheet.write(row, col + 12, "Roof Method Inflow LS", cell_format_center)
        worksheet.write(row, col + 13, "Cumulative Inflow Volume Roof Method", cell_format_center)

        row += 1

        for item_date in date:

            if final_values_counter <= 17:

                worksheet.write(row, col, item_date, cell_format_center)
                worksheet.write(row, col + 1, depth_outflow_cm[csv_row_count], cell_format_center)
                worksheet.write(row, col + 2, depth_outflow_feet[csv_row_count], cell_format_center)
                worksheet.write(row, col + 3, depth_above_invert[csv_row_count], cell_format_center)
                worksheet.write(row, col + 4, depth_diamter[csv_row_count], cell_format_center)
                worksheet.write(row, col + 5, outflow_cfs[csv_row_count], cell_format_center)
                worksheet.write(row, col + 6, outflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 7, cumulative_outflow_volume[csv_row_count], cell_format_center)
                worksheet.write(row, col + 8, rain_mm[csv_row_count], cell_format_center)
                worksheet.write(row, col + 9, rain_inches[csv_row_count], cell_format_center)
                worksheet.write(row, col + 10, cumulative_rain[csv_row_count], cell_format_center)
                # worksheet.write(row, col + 11, ord_inflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 11, roof_method_inflow_cfs[csv_row_count], cell_format_center)
                worksheet.write(row, col + 12, roof_method_inflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 13, cumulative_inflow_volume_roof_method[csv_row_count], cell_format_center)
                # worksheet.write(row, col + 14, cumulative_inflow[csv_row_count], cell_format_center)
                worksheet.write(row, col + 14, self.final_values_list[final_values_counter], cell_format_center)
                worksheet.write(row, col + 15, self.final_values_list[final_values_counter + 1], cell_format_center)

                row += 1
                csv_row_count += 1
                final_values_counter += 2

            else:

                worksheet.write(row, col, item_date, cell_format_center)
                worksheet.write(row, col + 1, depth_outflow_cm[csv_row_count], cell_format_center)
                worksheet.write(row, col + 2, depth_outflow_feet[csv_row_count], cell_format_center)
                worksheet.write(row, col + 3, depth_above_invert[csv_row_count], cell_format_center)
                worksheet.write(row, col + 4, depth_diamter[csv_row_count], cell_format_center)
                worksheet.write(row, col + 5, outflow_cfs[csv_row_count], cell_format_center)
                worksheet.write(row, col + 6, outflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 7, cumulative_outflow_volume[csv_row_count], cell_format_center)
                worksheet.write(row, col + 8, rain_mm[csv_row_count], cell_format_center)
                worksheet.write(row, col + 9, rain_inches[csv_row_count], cell_format_center)
                worksheet.write(row, col + 10, cumulative_rain[csv_row_count], cell_format_center)
                # worksheet.write(row, col + 11, ord_inflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 11, roof_method_inflow_cfs[csv_row_count], cell_format_center)
                worksheet.write(row, col + 12, roof_method_inflow_ls[csv_row_count], cell_format_center)
                worksheet.write(row, col + 13, cumulative_inflow_volume_roof_method[csv_row_count], cell_format_center)
                # worksheet.write(row, col + 14, cumulative_inflow[csv_row_count], cell_format_center)

                row += 1
                csv_row_count += 1

        if len(date) <= 17:

            final_values_remaining = len(self.final_values_list) - row * 2
            start_value = len(self.final_values_list) - (final_values_remaining // 2 + 1)
            for final_list_value in self.final_values_list[start_value::]:

                worksheet.write(row, col + 14, self.final_values_list[final_values_counter], cell_format_center)
                try:
                    worksheet.write(row, col + 15, self.final_values_list[final_values_counter + 1], cell_format_center)
                except IndexError:
                    break
                row += 1
                final_values_counter += 2

        ws = workbook.add_worksheet("Graph")

        # ---OPTION TO ADD CHART AS IMAGE VIA SCRIPT RATHER THAN GENERATING IT IN EXCEL---
        # img = openpyxl.drawing.image.Image('april_storm_dan.png')
        # ws.add_image(img)
        # ---END---

        date_formula = '=Data!$A$2:$A$' + str(row)
        value_formula_outflow = '=Data!$G$2:$G$' + str(row)
        value_formula_roof = '=Data!$M$2:$M$' + str(row)
        chart1 = workbook.add_chart({'type': 'line'})
        chart1.add_series({'categories': date_formula, 'values': value_formula_outflow, 'line': {'width': 4}})
        chart1.add_series({'categories': date_formula, 'values': value_formula_roof, 'line': {'width': 4}})
        chart1.set_title({'name': 'Outflow Vs Roof Method Inflow'})
        chart1.set_x_axis({'name': 'Time', 'text_axis': True, 'date_axis': False, 'label_position': 'low'})
        chart1.set_y_axis({'name': 'Flow L/S'})
        # Set an Excel chart style. Colors with white outline and shadow.
        chart1.set_style(10)

        # Insert the chart into the worksheet (with an offset).
        ws.insert_chart('F3', chart1)
        chart1.set_size({'width': 750, 'height': 400})
        workbook.close()

    def line_chart_outflow(self, start_time, end_time):

        current_dir = pathlib.Path(__file__).parent
        os.chdir(current_dir)
        line_chart_date_list = list()
        line_chart_rain_mm_list = list()
        line_chart_depth_outflow_list_dan = list()
        line_chart_depth_outflow_list_sam = list()
        line_chart_depth_outflow_list_sarah = list()
        line_chart_depth_outflow_list_dylan = list()
        plot_data_running = 0

        try:
            with open("Data.csv", "r+") as fp:

                lines = fp.readlines()
                for line in lines[1:]:
                    line = line.strip()
                    split_words = line.split(',')
                    date = split_words[0]
                    rain_mm = split_words[1]
                    depth_outflow_dan = split_words[2]
                    depth_outflow_sam = split_words[3]
                    depth_outflow_sarah = split_words[4]
                    depth_outflow_dylan = split_words[5]

                    if date == start_time:
                        plot_data_running = 1
                    if plot_data_running == 1:
                        line_chart_date_list.append(date)
                        line_chart_rain_mm_list.append(rain_mm)
                        line_chart_depth_outflow_list_dan.append(depth_outflow_dan)
                        line_chart_depth_outflow_list_sam.append(depth_outflow_sam)
                        line_chart_depth_outflow_list_sarah.append(depth_outflow_sarah)
                        line_chart_depth_outflow_list_dylan.append(depth_outflow_dylan)
                    if date == end_time:
                        plot_data_running = 0
                        break

        except FileNotFoundError:
            print("Cannot open file")
            sys.exit()

        workbook = xlsxwriter.Workbook("Outflow_datum.xlsx")
        worksheet = workbook.add_worksheet("Data")
        row = 0
        col = 0
        # cell_format.set_align('center')
        cell_format_center = workbook.add_format({'align': 'center'})
        worksheet.write(row, col, "Date", cell_format_center)
        worksheet.write(row, col + 1, "Rain (mm)", cell_format_center)
        worksheet.write(row, col + 2, "Dan Outflow", cell_format_center)
        worksheet.write(row, col + 3, "Sam Outflow", cell_format_center)
        worksheet.write(row, col + 4, "Sarah Outflow", cell_format_center)
        worksheet.write(row, col + 5, "Dylan Outflow", cell_format_center)
        worksheet.write(row, col + 6, "Cumulative Inflow", cell_format_center)
        row += 1
        cumulative_inflow = c1.cumulative(line_chart_rain_mm_list)
        for date_iter, rain_iter, outflow_dan, outflow_sam, outflow_sarah, outflow_dylan, inflow in zip(line_chart_date_list,
                                                                                line_chart_rain_mm_list,
                                                                                line_chart_depth_outflow_list_dan,
                                                                                line_chart_depth_outflow_list_sam,
                                                                                line_chart_depth_outflow_list_sarah,
                                                                                line_chart_depth_outflow_list_dylan, cumulative_inflow):
            worksheet.write(row, col, date_iter, cell_format_center)
            worksheet.write(row, col+1, float(rain_iter), cell_format_center)
            worksheet.write(row, col + 2, float(outflow_dan), cell_format_center)
            worksheet.write(row, col + 3, float(outflow_sam), cell_format_center)
            worksheet.write(row, col + 4, float(outflow_sarah), cell_format_center)
            worksheet.write(row, col + 5, float(outflow_dylan), cell_format_center)
            worksheet.write(row, col + 6, float(inflow), cell_format_center)
            row += 1

        value_formula_rain = '=Data!$B$2:$B$' + str(row)
        value_formula_dan = '=Data!$C$2:$C$' + str(row)
        value_formula_sam = '=Data!$D$2:$D$' + str(row)
        value_formula_sarah = '=Data!$E$2:$E$' + str(row)
        value_formula_dylan = '=Data!$F$2:$F$' + str(row)
        value_formula_cumulative_rain = '=Data!$G$2:$G$' + str(row)
        min_list = list()
        max_list = list()
        for list_item in [line_chart_depth_outflow_list_dan, line_chart_depth_outflow_list_sam, line_chart_depth_outflow_list_sarah, line_chart_depth_outflow_list_dylan]:
            list_item = list(map(float, list_item))
            min_list.append(min(list_item))
            max_list.append(max(list_item))

        min_value = min(min_list)
        max_value = max(max_list)

        for sheet in ["Planter Outflow", "Cumulative Inflow"]:
            ws = workbook.add_worksheet(sheet)
            # cell_format.set_align('center')
            chart1 = workbook.add_chart({'type': 'line'})
            date_formula = '=Data!$A$2:$A$' + str(row)
            if sheet == "Planter Outflow":
                chart1.add_series({'categories': date_formula, 'values': value_formula_dan, 'line': {'width': 4}})
                chart1.add_series({'categories': date_formula, 'values': value_formula_sam, 'line': {'width': 4}})
                chart1.add_series({'categories': date_formula, 'values': value_formula_sarah, 'line': {'width': 4}})
                chart1.add_series({'categories': date_formula, 'values': value_formula_dylan, 'line': {'width': 4}})
                chart1.set_title({'name': 'Outflow Datum '})
                chart1.set_x_axis({'name': 'Time', 'text_axis': True, 'date_axis': False, 'label_position': 'low'})
                chart1.set_y_axis({'name': 'Inflow vs Outflow', 'min': min_value, 'max': max_value})
                # Set an Excel chart style. Colors with white outline and shadow.
                chart1.set_style(10)
                # Insert the chart into the worksheet (with an offset).
                ws.insert_chart('F3', chart1)
                chart1.set_size({'width': 750, 'height': 400})

            if sheet == "Cumulative Inflow":

                chart1.add_series({'categories': date_formula, 'values': value_formula_cumulative_rain, 'line': {'width': 4}})
                chart1.set_title({'name': 'Cumulative Inflow'})
                chart1.set_x_axis({'name': 'Time', 'text_axis': True, 'date_axis': False, 'label_position': 'low'})
                chart1.set_y_axis({'name': 'Inflow'})
                # Set an Excel chart style. Colors with white outline and shadow.
                chart1.set_style(10)
                # Insert the chart into the worksheet (with an offset).
                ws.insert_chart('F3', chart1)
                chart1.set_size({'width': 750, 'height': 400})
        workbook.close()

    def cumulative(self, seq):
        """ Given a sequence, return a list of values of the cumulative """
        total = 0
        result = list()
        for item in seq:
            total += float(item)
            result.append(total)
        return result

    def set_column_width(self, worksheet, columns):
        length_list = [len(x) for x in columns]
        for i, width in enumerate(length_list):
            worksheet.set_column(i, i, width)

c1 = CalcColumns()  # Object of our class

def main():

    data = defaultdict(lambda: defaultdict(float))

    input_path = input("Enter Directory of CSV Files:")  # Get the path from the user

    if len(os.listdir(input_path)) == 0:
        print("Directory is empty")
        sys.exit()

    else:

        for file in glob.glob(os.path.join(input_path, 'StevensA[0-9]*.csv')):

            try:
                fp = open(file, 'r')  # Try to open it in "read-only" mode
            except FileNotFoundError:  # If we run into any problems, Throw an exception and return the below message
                print("File {} cannot be opened".format(file))
                sys.exit(0)
            with fp:

                if os.stat(file).st_size != 0:  # check if file is empty
                    for line, row in enumerate(fp):
                        if line in [0, 1, 2, 3]:
                            continue  # skip the header row
                        cols = row.strip().split(',')
                        dt = datetime.datetime.strptime(cols[0].strip('"'), "%Y-%m-%d %H:%M:%S")
                        data[dt]['rain'] = float(cols[172])
                        data[dt]['p1'] = float(cols[6])
                        data[dt]['p2'] = float(cols[12])
                        data[dt]['p3'] = float(cols[18])
                        data[dt]['p4'] = float(cols[24])

                else:
                    continue
        c1.create_csv(data)

    line_chart = input("Do you want to see outflow line chart for a time period? y/n \n")

    if line_chart.lower() == "y":
        time_start = input("Enter start time period \n")
        time_end = input("Enter End time period \n")
        c1.line_chart_outflow(time_start, time_end)

if __name__ == '__main__':
    main()


'''A module for working with multiple lists'''

def add_remaining_unseen_values_to_combined(lst, combined, seen, idx):
    while idx < len(lst):
        val = lst[idx]
        if seen.get(val, False):
            idx += 1
            continue

        combined.append(val)
        seen[val] = True

        idx += 1

def uniq_combine(l_one, l_two):
    '''Combine two lists of arbitrary elements w/o duplicates'''
    seen = {}
    combined = []
    idx_one = 0
    idx_two = 0

    while idx_one < len(l_one) and idx_one < len(l_two):
        val1 = l_one[idx_one]
        val2 = l_two[idx_two]

        if seen.get(val1, False):
            idx_one += 1
            continue
        if seen.get(val2, False):
            idx_two += 1
            continue

        combined.append(val1)
        seen[val1] = True

        combined.append(val2)
        seen[val2] = True

        idx_one += 1
        idx_two += 1

    add_remaining_unseen_values_to_combined(l_one, combined, seen, idx_one)
    add_remaining_unseen_values_to_combined(l_two, combined, seen, idx_two)

    return combined


from numpy import random
from typing import Any, Text, Dict, List
from typing import Any, Text, Dict, List
import datetime
from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.events import FollowupAction
from rasa_sdk.events import SlotSet
from rasa_sdk.events import ReminderScheduled, ReminderCancelled
from rasa_sdk.executor import CollectingDispatcher

import mysql.connector

mydb = mysql.connector.connect(
        host = "localhost",
        user = "root",
        passwd="",
        database="questions"
    )

mycursor = mydb.cursor()
myresult = []
hintlist = []
finfo = []
randomnum = 0
hintmaxx = 0
hintcnt = 0
failcnt = 0
failmax = 2
count = 0
i=0

class ActionIncreaseScore(Action):

    def name(self) -> Text:
        return "action_answer"

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

        global randomnum, myresult, hintlist, hintmaxx, hintcnt, finfo, count, i
        ansentity=next(tracker.get_latest_entity_values("answer"), None)
        count = count + 1
        if i == 4:
            return [FollowupAction("action_send_problem_link")]

        # dispatcher.utter_message(text="Hello World!")

        return [FollowupAction("action_quiz")]

class ActionQuiz(Action):

    def name(self) -> Text:
        return "action_quiz"

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

        global randomnum, myresult, hintlist, hintmaxx, hintcnt, finfo,i,count
        quizentity=next(tracker.get_latest_entity_values("quiz"), None)
        queryy = 'select * from quizes'
        mycursor.execute(queryy)
        myresult = mycursor.fetchall()
        maxx = len(myresult)

        randomnum = random.randint(maxx)
        if i == 4:
            return [FollowupAction("action_send_problem_link")]

        msg = myresult[i][1]
        buttons = [{"title": myresult[i][2], "payload": myresult[i][2]}, {"title": myresult[i][3],  "payload":myresult[i][3]}, {"title": myresult[i][4], "payload": myresult[i][4]}, {"title": myresult[i][5], "payload": myresult[i][5]}]
        dispatcher.utter_button_message(msg+str(count), buttons)
        i = i+1

        return []

class ActionReactToReminder(Action):
    """Reminds the user to call someone."""

    def name(self) -> Text:
        return "action_react_to_reminder"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:

        #name = next(tracker.get_latest_entity_values("new"))
        dispatcher.utter_message("Opps! Unfortunately your time has ended! You final score is" + str(count) + "out of 5")

        return [FollowupAction("action_send_problem_link")]

class ActionSetReminder(Action):
    """Schedules a reminder, supplied with the last message's entities."""

    def name(self) -> Text:
        return "action_set_reminder"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:
        # dispatcher.utter_message("Here's a new question for you. Given two strings text1 and text2, return the length of their longest common subsequence.I am setting the timer to 5 seconds.")

        dispatcher.utter_message("The quiz has started and so has the timer. We challenge you to finish it withing 20 minutest. Best of luck!")
        date = datetime.datetime.now() + datetime.timedelta(seconds=7200)
        entities = tracker.latest_message.get("new")

        reminder = ReminderScheduled(
            "EXTERNAL_reminder",
            trigger_date_time=date,
            entities=entities,
            name="my_reminder",
            kill_on_user_message=False,
        )

        return [reminder]

class Actionsendmail(Action):

    def name(self) -> Text:
        return "action_send_problem_link"

    def run(self, dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:

        global randomnum, myresult, count

        import smtplib

        EMAIL_ADDRESS = "benjaminimp10@gmail.com"
        EMAIL_PASSWORD = ""
        recieverid = str(tracker.get_slot('mail'))

        with smtplib.SMTP('smtp.gmail.com', 587) as smtp:
            smtp.ehlo()
            smtp.starttls()
            smtp.ehlo()

            smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)

            subject = 'RASA QUIZ SCORE'
            body = "here's the score of your RASA quiz: " + " " + str(count)

            msg = f'Subject: {subject}\n\n{body}'

            smtp.sendmail(EMAIL_ADDRESS, recieverid, msg)

        msg = "The link has been sent to your email id. Thank you for playing the Quiz. Hope you liked it."

        dispatcher.utter_message(text = msg)

        return [FollowupAction("utter_goodbye")]


import sys
sys.stdin = open('input.txt','r')

def bfs(v):
    que=[v]
    while len(que)>0:
        v=que.pop(0)
        visited.append(v)
        for i in mp[v]:
            if i not in visited and i not in que:
                que.append(i)

inp=list(map(int,"1 2 1 3 2 4 2 5 4 6 5 6 6 7 3 7".split()))
mp=[[] for _ in range(8)]
for i in range(len(inp)//2):
    mp[inp[i*2]] += [inp[i*2+1]]
    mp[inp[i*2+1]] += [inp[i*2]]
visited = []
que=[]
bfs(1)
print(visited)


from bs4 import BeautifulSoup
import requests
import pandas as pd

def COVID_scrape():

    url = 'https://data.humdata.org/dataset/novel-coronavirus-2019-ncov-cases'

    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'lxml')
    scraped = soup.find_all('a', class_='resource-url-analytics')

    confirmed = scraped[0]['href']
    deaths = scraped[1]['href']
    recovered = scraped[2]['href']

    confirmed_df = pd.read_csv(confirmed)
    deaths_df = pd.read_csv(deaths)
    recovered_df = pd.read_csv(recovered)

    lastupdate = []
    for col in confirmed_df.columns:
        lastupdate.append(col)

    lastupdate = lastupdate[-1]

    return {"confirmed": confirmed_df, "deaths": deaths_df, "recovered": recovered_df, "lastupdate": lastupdate}


import sqlite3
from flask_restful import Resource, reqparse
from flask_jwt import jwt_required

class Item(Resource):
    parser = reqparse.RequestParser() #Item class parser
    parser.add_argument("price", #requests must be in format { "price": float}
        type=float,
        required=True,
        help="FORMAT ERROR: This request must have be string : float where string == price "
    )

    @jwt_required() # jwt authentification required
    def get(self, name): # get request, looking for item called "name",
        item = self.find_by_name(name)
        if item:
            return item
        return {"message": "Item not found"}, 404

    @classmethod
    def find_by_name(cls, name): # abstracted and redifined from get
        # Does NOT require jwt_auth, so it can be used by both get and post
        connection = sqlite3.connect('data.db')
        cursor = connection.cursor()

        query = "SELECT * FROM items WHERE name=?"
        result = cursor.execute(query, (name,))
        row = result.fetchone()
        connection.close()
        if row:
            return {"item": {"name": row[0], "price": row[1]}}

    def post(self, name):
        if self.find_by_name(name):
            return {'message': 'An item with name ' + name + 'already exists.'}, 400
        data = Item.parser.parse_args() # what the user will send to the post request (in good format)
        # In our case, the user sends the price as JSON, but the item name gets passed into the function
        item = {"name": name, "price": data["price"]}
        try:
            self.insert(item)
        except:
            return{"message": "An error occurred while inserting"}, 500 # internal server error

        return item, 201 #post was successful

    @classmethod
    def insert(cls, item):
        # write to database
        # abstracted just like find_by_name so that it can be used by both post and put
        connection = sqlite3.connect('data.db')
        cursor = connection.cursor()

        query = "INSERT INTO items VALUES (?, ?)"
        cursor.execute(query, (item['name'], item['price']))

        connection.commit()
        connection.close()

    def delete(self, name):
        connection = sqlite3.connect('data.db')
        cursor = connection.cursor()

        query = "DELETE FROM items WHERE name=?"
        cursor.execute(query, (name,))

        connection.commit()
        connection.close()

        return {"message": "Item Deleted"}

    def put(self, name):
        data = Item.parser.parse_args() # only add valid JSON requests into data

        item = self.find_by_name(name) # find item
        updated_item = {'name': name, 'price': data['price']}

        if item is None: # no item found, add item name with JSON price
            try:
                self.insert(updated_item)
            except:
                return {"message": "Error inserting item"}, 500
        else:
            try:
                self.update(updated_item) # item found, updated with JSON price
            except:
                return {"message": "Error inserting item"}, 500
        return item

    @classmethod
    def update(cls, item):
        connection = sqlite3.connect('data.db')
        cursor = connection.cursor()

        query = "UPDATE items SET price=? WHERE name=?"
        cursor.execute(query, (item['price'], item['name']))

        connection.commit()
        connection.close()

class ItemList(Resource):
    def get(self):
        connection = sqlite3.connect('data.db')
        cursor = connection.cursor()

        query = "SELECT * FROM items" #select all name matches
        result = cursor.execute(query)
        items = []
        for row in result:
            items.append({'name':row[0], 'price':row[1]})

        connection.close()

        return {'items': items}


from userge import Message, userge

@userge.on_cmd(
    "bots",
    about={
        "header": "Bots in group",
        "description": "Check how many bots are there in the group",
        "usage": "{tr}bots",
    },
)
async def botz(message: Message):
    """Check the bots present in group."""
    chat = message.chat.id
    admin_b = []
    member_b = []
    total = 0
    async for bot in userge.iter_chat_members(chat, filter="bots"):
        total += 1
        mention = bot.user.mention
        if bot.status == "administrator":
            admin_b.append(mention)
        else:
            member_b.append(mention)
    adm = len(admin_b)
    mem = len(member_b)
    out = f"BOTS in {message.chat.title}: [{total}]\n\n"
    out += f"Admin bot(s): [{adm}]\n"
    out += "" if admin_b else ""
    out += "\n".join(admin_b)
    out += "\n\n" if admin_b else "\n"
    out += f"Member bot(s): [{mem}]\n"
    out += "" if member_b else ""
    out += "\n".join(member_b)
    await message.edit(out)


from odoo import fields
from odoo.tests import Form, SavepointCase

class TestSaleInvoicingDateSelection(SavepointCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.partner = cls.env["res.partner"].create({"name": "partner - test"})
        cls.partner_1 = cls.env["res.partner"].create({"name": "partner1 - test"})
        cls.product = cls.env["product.product"].create(
            {"name": "test invoicing", "type": "service", "invoice_policy": "order"}
        )

    def _create_sale_order(self, partner):
        with Form(self.env["sale.order"]) as sale_form:
            sale_form.partner_id = partner
            with sale_form.order_line.new() as so_line_form:
                so_line_form.product_id = self.product
                so_line_form.price_unit = 100.00
        return sale_form.save()

    def test_andvance_invoice(self):
        self.sale_order_1 = self._create_sale_order(self.partner)
        self.sale_order_2 = self._create_sale_order(self.partner_1)
        self.sale_order_1.action_confirm()
        self.sale_order_2.action_confirm()
        orders = self.sale_order_1 + self.sale_order_2
        with Form(
            self.env["sale.advance.payment.inv"].with_context(
                active_model="sale.order", active_ids=orders.ids, open_invoices=True
            )
        ) as wiz_invoice_form:
            wiz_invoice_form.invoice_date = "2022-11-01"
        wiz = wiz_invoice_form.save()
        action = wiz.create_invoices()
        invoices = self.env["account.move"].search(action["domain"])
        self.assertEqual(fields.Date.to_string(invoices[0].invoice_date), "2022-11-01")
        self.assertEqual(fields.Date.to_string(invoices[1].invoice_date), "2022-11-01")


"""
Code to run and test all search algorithms.
"""

from a_star import a_star_search
from bfs import breadth_first_search
from dfs import depth_first_search
from tests import dis_map2, dis_mapM, time_map1, time_map2, time_mapM, time_mapT

if __name__ == "__main__":
    path = breadth_first_search(time_map1, "John_Stevens", "Mariana_Cardoso")
    print(f"BFS Path: {path}")
    assert path == ["John_Stevens", "John_Doe", "Raj_Gupta", "Mariana_Cardoso"]
    path = depth_first_search(time_mapT, "Alex_Robbinson", "Aaron_Stone")
    print(f"DFS Path: {path}")
    assert path == ["Alex_Robbinson", "Walter_Walker", "Sarah_Parker", "Aaron_Stone"]
    path = a_star_search(dis_map2, time_map2, "John_Doe", "Alex_Robbinson")
    print(f"A* Path: {path}")
    assert path == ["John_Doe", "John_Stevens", "Walter_Walker", "Alex_Robbinson"]
    path = a_star_search(dis_mapM, time_mapM, "Hannah_Mullard", "Alex_Robbinson")
    print(f"A* Path: {path}")
    assert path == [
        "Hannah_Mullard",
        "David_Stone",
        "Catherine_Stevens",
        "Benjamin_Walker",
        "Alex_Robbinson",
    ]
    print("All passed.")


import pygame
import random
from pygame import Rect, mixer
pygame.init()

screen_wight = 600
screen_hight = 600

screen = pygame.display.set_mode((screen_wight, screen_hight))
pygame.display.set_caption("Snake")

mixer.music.load('background.wav')
mixer.music.play(-1)

# Collor
bg = (100,123,150)
body_inner = (50,175,25)
body_outer = (100,100,200)
red = (255,0,0)
foodc = (255,255,255)
blue = (0, 0, 255)
white = (255,255,255)

cell_size= 20
direction = 1
update_snake = 0

# Food
food =[0,0]
new_food = True
food_place = [0,0]
score = 0
game_over = False
clicked = False

font = pygame.font.SysFont(None,40)

def draw_screen():
    screen.fill(bg)

def draw_score():
    score_text = "Score : "+str(score)
    score_img = font.render(score_text,True,blue)
    screen.blit(score_img,(5,5))

def check_game_over(game_over):

    # Snakes eat itself
    head_count =0
    for segment in snake_pos:
        if snake_pos[0] ==segment and head_count>0:
            game_over = True
        head_count +=1

    #Chek Touch Boundary
    if snake_pos[0][0]<0 or snake_pos[0][0] > screen_wight or snake_pos[0][1]<0 or snake_pos[0][1] > screen_hight:
        game_over = True

    return game_over

# again_reac = Rect(screen_wight//2-80, screen_wight//2+5, 154,40)
again_rect = Rect(screen_wight // 2 - 80, screen_wight // 2, 160, 50)
def draw_game_over():
    ove_text = "Game Over !!!"
    over_img = font.render(ove_text,True,white)
    pygame.draw.rect(screen,blue,(screen_wight//2 -80, screen_hight//2 - 70,194,60),0,10)
    screen.blit(over_img,(screen_wight//2-80,screen_hight//2-50))

    again_txt = "Play Again"
    again_img = font.render(again_txt,True,white)
    pygame.draw.rect(screen, blue, again_rect,0,10)
    screen.blit(again_img, (screen_wight // 2 - 80, screen_hight // 2 + 10))

# Snakes Creat and  Set Positions
snake_pos = [[int(screen_wight/2),int(screen_hight/2)]]
snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size])
snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size * 2])
snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size * 3 ])

run = True
while run:

    draw_screen()
    draw_score()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

        elif event.type ==pygame.KEYDOWN:
            if event.key == pygame.K_UP and direction != 3:
                direction = 1
            if  event.key == pygame.K_DOWN and direction != 1:
                direction = 3
            if event.key == pygame.K_RIGHT and direction != 4:
                direction = 2
            if  event.key == pygame.K_LEFT and direction != 2:
                direction = 4
    # Food
    if new_food ==True:
        new_food =False
        food[0] = cell_size * random.randint(0,(screen_wight/cell_size)-3)
        food[1] = cell_size * random.randint(0,(screen_hight/cell_size)-3)

    # Draw Food
    pygame.draw.rect(screen,foodc,(food[0],food[1],cell_size,cell_size),0,5)

    if snake_pos[0] == food:
        new_food = True
        new_peice = list(snake_pos[-1])

        if direction ==1:
            new_peice[1] += cell_size

            explaration_sound = mixer.Sound('laser.wav')
            explaration_sound.play()

        if direction ==2:
             new_peice[0] -= cell_size
             explaration_sound = mixer.Sound('laser.wav')
             explaration_sound.play()

        if direction ==3:
            new_peice[1]-= cell_size
            explaration_sound = mixer.Sound('laser.wav')
            explaration_sound.play()
        if direction ==4:
            new_peice[0]+= cell_size
            explaration_sound = mixer.Sound('laser.wav')
            explaration_sound.play()
        snake_pos.append(new_peice)
        score +=1
        # print(score)

    if game_over == False:
        if update_snake > 80:
            update_snake = 0
            snake_pos = snake_pos[-1:] + snake_pos[:-1]
            if direction == 1:
                snake_pos[0][0] = snake_pos[1][0]
                snake_pos[0][1] = snake_pos[1][1] - cell_size
                #heading down
            if direction == 3:
                snake_pos[0][0] = snake_pos[1][0]
                snake_pos[0][1] = snake_pos[1][1] + cell_size
                #heading right
            if direction == 2:
                snake_pos[0][1] = snake_pos[1][1]
                snake_pos[0][0] = snake_pos[1][0] + cell_size
                #heading left
            if direction == 4:
                snake_pos[0][1] = snake_pos[1][1]
                snake_pos[0][0] = snake_pos[1][0] - cell_size

            game_over= check_game_over(game_over)

    if game_over == True:
            draw_game_over()
            if event.type == pygame.MOUSEBUTTONDOWN and clicked == False:
                clicked = True
            if event.type == pygame.MOUSEBUTTONUP and clicked == True:
                clicked = False
                pos = pygame.mouse.get_pos()
                if again_rect.collidedict(pos):

                #reset variables
                    direction =1
                    update_snake = 0
                    food =[0,0]
                    new_food = True
                    new_piece = [0, 0]

                    score = 0
                    game_over = False

                    # Creat Snake
                    snake_pos = [[int(screen_wight/2),int(screen_hight/2)]]
                    snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size])
                    snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size * 2])
                    snake_pos.append([int(screen_wight/2),int(screen_hight/2)+cell_size * 3 ])

    head = 1
    for x in snake_pos:
        if head ==0:
            pygame.draw.rect(screen,body_outer,(x[0],x[1],cell_size,cell_size))
            pygame.draw.rect(screen,body_inner,(x[0]+1,x[1]+1,cell_size-2,cell_size-2))

        if head ==1:
            pygame.draw.rect(screen,body_outer,(x[0],x[1],cell_size,cell_size))
            pygame.draw.rect(screen,red,(x[0]+1,x[1]+1,cell_size-2,cell_size-2))
            head =0

    pygame.display.update()
    update_snake += 1
pygame.quit()


import numpy as np
import gym
import scipy
import scipy.signal
import matplotlib.pyplot as plt
from functools import partial

def discretize_val(val, min_val, max_val, num_states):
    """
	Discretizes a single float
	if val < min_val, it gets a discrete value of 0
	if val >= max_val, it gets a discrete value of num_states-1

	Args:
	    val (float): value to discretize
	    min_val (float): lower bound of discretization
	    max_val (float): upper bound of discretization
	    num_states (int): number of discrete states

	Returns:
	    float: discrete value
	"""
    state = int(num_states * (val - min_val) / (max_val - min_val))
    if state >= num_states:
        state = num_states - 1
    if state < 0:
        state = 0
    return state

def observationToState(num_states, lower_bounds, upper_bounds, obs):
    """
	Turns an observation into a discrete state

	Args:
	    num_states (list): list of number of states for each dimension of observation
	    lower_bounds (list): list of lowerbounds for discretization
	    upper_bounds (list): list of upperbounds for discretization
	    obs (list): observation in R^N to discretize

	Returns:
	    int: discrete state
	"""
    state_idx = []
    for ob, lower, upper, num in zip(obs, lower_bounds, upper_bounds, num_states):
        state_idx.append(discretize_val(ob, lower, upper, num))

    return np.ravel_multi_index(state_idx, num_states)

def EpsilonGreedyPolicy(Q, eps, state):
    sample = np.random.random_sample()

    num_actions = Q.shape[1]

    if sample > eps:
        max_val = Q[state, :].max()
        max_indices = np.where(np.abs(Q[state, :] - max_val) < 1e-5)[0]
        rand_idx = np.random.randint(len(max_indices))
        max_action = max_indices[rand_idx]

        return max_action
    else:
        return np.random.randint(num_actions)

# Initialize policy
def initPi(Q, eps):
    pi = partial(EpsilonGreedyPolicy, Q, eps)
    return pi

def UpdatePi(Q, eps):
    pi = partial(EpsilonGreedyPolicy, Q, eps)
    return pi

def initQ(stateLength, numberOfActions):
    Q = np.zeros((stateLength, numberOfActions))
    return Q

def initReturns(stateLength, numberOfActions):
    returns = {}
    for i, value in enumerate(Q):
        returns[i,0] = []
        returns[i,1] = []
    return returns

def play(env, policy,stateMapping, display):
    obs = env.reset()
    # prev_screen = env.render(mode='rgb_array')
    # plt.imshow(prev_screen)
    rewards = []
    actions = []
    states = []
    totalRewards = 0
    while True:
        state = stateMapping(obs)
        action = policy(state)
        actions.append(action)
        states.append(state)

        obs, reward, done, info = env.step(action)
        if display == True:
            screen = env.render(mode='rgb_array')
            plt.imshow(screen)
        totalRewards += reward
        rewards.append(reward)
        if done == True:
            #print(obs)
            break

        #print(totalRewards)
    return states, actions, rewards

env = gym.make('CartPole-v0')

eps = 0.05
gamma = 1
numberOfActions = env.action_space.n
numberOfStates = [10, 8, 10, 8]
lowerBounds = [-2.4, -9999, -0.21, -9999]
upperBounds = [2.4, 9999, 0.21, 9999]
stateMapping = partial(observationToState, numberOfStates, lowerBounds, upperBounds)
stateLength = np.prod(np.array(numberOfStates))
Q = initQ(stateLength, numberOfActions)
returns = initReturns(stateLength, numberOfActions)
policy = initPi(Q, eps)
rewardList = []
avg = 0
for i in range(20000):
    if i%100 == 0 and i != 0:
        print("i = " + str(i) + ", and average reward over past 100 episode " + str(avg/ 100))
        avg = 0
        states, actions, rewards = play(env, policy, stateMapping, False)
    else:
        states, actions, rewards = play(env, policy,stateMapping,False)

    seen = set()
    G = 0
    for i, (state, action, reward) in enumerate(zip(reversed(states), reversed(actions), reversed(rewards))):
        if i == 0:
            G = G * gamma
        else:
            G = G * gamma + rewards[i - 1]
        if state in seen:
            continue

        seen.add(state)
        returns[state,action].append(G)

        Q[state, action] = sum(returns[state,action])/ len(returns[state,action])
        policy = UpdatePi(Q,eps)
    avg += sum(rewards)
    rewardList.append(sum(rewards))
plt.plot(rewardList)
plt.show()
eps = 0
policy = UpdatePi(Q, eps)
avg = 0
for i in range(100):
    states, actions, rewards = play(env, policy, stateMapping, False)
    avg += sum(rewards)
print(avg)
play(env, policy,stateMapping, True)


#Crie um programa que leia dois valores e mostre um menu na tela: [1]somar, [2] multiplicar, [3] maior, [4]Novos números, [5] sair.
from time import sleep
valor1 = int(input('Informe o primeiro valor: '))
valor2 = int(input('Informe o segundo valor: '))
opção = 0
resultado = 0
while True:
    sleep(2)
    print('''    [1] Somar
    [2] Multiplicar
    [3] Maior
    [4] Novos números
    [5] Sair''')
    opção = int(input('>>>>>Qual a sua opção? '))
    if opção == 5:
        print('Finalizando... ')
        sleep(1)
        break
    elif opção == 1:
        sleep(1)
        resultado = valor1 + valor2
        print(f'A soma entre {valor1} e {valor2} é {resultado}')
        print('=-='*10)
    elif opção == 2:
        sleep(1)
        resultado = valor1 * valor2
        print(f'A multiplicação entre {valor1} e {valor2} é {resultado}')
        print('=-='*10)
    elif opção == 3:
        sleep(1)
        if valor1 > valor2:
            print(f'{valor1} é o maior!')
            print('=-='*10)
        elif valor1 == valor2:
            print('Os dois valores são iguais')
            print('=-='*10)
        else:
            print(f'{valor2} é o maior!')
            print('=-='*10)
    elif opção == 4:
        sleep(2)
        valor1 = int(input('Informe o primeiro valor: '))
        valor2 = int(input('informe o segundo valor: '))
    else:
        print('Opção inválida!')
        print('=-='*10)


class Solution:
    def canPartitionKSubsets(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: bool
        """
        def helper(k, target, current, start):
            if k == 1:
                    return True
            if current == target:
                return helper(k-1, target, 0, 0)

            for i in range(start, len(nums)):
                if not visited[i] and nums[i] + current <= target:
                    visited[i] = True
                    if helper(k, target, current+nums[i], i+1):
                        return True
                    visited[i] = False
            return False

        if k == 1:
            return True
        if len(nums) < k:
            return False
        tot = sum(nums)
        if tot % k != 0:
            return False
        nums.sort(reverse=True)
        visited = [False] * len(nums)
        return helper(k, tot // k, 0, 0)


import torch
import numpy as np
import utils.data_format_utils as df_utils
from data_processing.camera_pipeline import apply_gains, apply_ccm, apply_smoothstep, gamma_compression

class SimplePostProcess:
    def __init__(self, gains=True, ccm=True, gamma=True, smoothstep=True, return_np=False):
        self.gains = gains
        self.ccm = ccm
        self.gamma = gamma
        self.smoothstep = smoothstep
        self.return_np = return_np

    def process(self, image, meta_info):
        return process_linear_image_rgb(image, meta_info, self.gains, self.ccm, self.gamma,
                                        self.smoothstep, self.return_np)

def process_linear_image_rgb(image, meta_info, gains=True, ccm=True, gamma=True, smoothstep=True, return_np=False):
    if gains:
        image = apply_gains(image, meta_info['rgb_gain'], meta_info['red_gain'], meta_info['blue_gain'])

    if ccm:
        image = apply_ccm(image, meta_info['cam2rgb'])

    if meta_info['gamma'] and gamma:
        image = gamma_compression(image)

    if meta_info['smoothstep'] and smoothstep:
        image = apply_smoothstep(image)

    image = image.clamp(0.0, 1.0)

    if return_np:
        image = df_utils.torch_to_npimage(image)
    return image

class BurstSRPostProcess:
    def __init__(self, no_white_balance=False, gamma=True, smoothstep=True, return_np=False):
        self.no_white_balance = no_white_balance
        self.gamma = gamma
        self.smoothstep = smoothstep
        self.return_np = return_np

    def process(self, image, meta_info, external_norm_factor=None):
        return process_burstsr_image_rgb(image, meta_info, external_norm_factor=external_norm_factor,
                                         no_white_balance=self.no_white_balance, gamma=self.gamma,
                                         smoothstep=self.smoothstep, return_np=self.return_np)

def process_burstsr_image_rgb(im, meta_info, return_np=False, external_norm_factor=None, gamma=True, smoothstep=True,
                              no_white_balance=False):
    im = im * meta_info.get('norm_factor', 1.0)

    if not meta_info.get('black_level_subtracted', False):
        im = (im - torch.tensor(meta_info['black_level'])[[0, 1, -1]].view(3, 1, 1).to(im.device))

    if not meta_info.get('while_balance_applied', False) and not no_white_balance:
        im = im * (meta_info['cam_wb'][[0, 1, -1]].view(3, 1, 1) / meta_info['cam_wb'][1]).to(im.device)

    im_out = im

    if external_norm_factor is None:
        im_out = im_out / im_out.max()
    else:
        im_out = im_out / external_norm_factor

    im_out = im_out.clamp(0.0, 1.0)

    if gamma:
        im_out = im_out ** (1.0 / 2.2)

    if smoothstep:
        # Smooth curve
        im_out = 3 * im_out ** 2 - 2 * im_out ** 3

    if return_np:
        im_out = im_out.permute(1, 2, 0).cpu().numpy() * 255.0
        im_out = im_out.astype(np.uint8)

    return im_out


import cv2
import numpy as np
import time
from tttAI import IA
from djitellopy import Tello
import time

'''
Class tttDetection
--------------------------------------------------------------------------------------
__init__()            # Inicia a captura de tela e o contorno dos players
tello_startup()       # Se conecta com o tello e abre câmera
filter_small()        # Filtra contornos pequenos que possam atrapalhar a detecção
match_shape ()        # Detecta a jogada em um quadrado específico do tabuleiro
most_frequent()       # Dado uma lista, retorna a string mais recorrente
get_common()          # Dado um conjunto de tabuleiros, seleciona as jogadas mais recorrentes
get_mask()            # Gera a máscara de cores no range informado
show_drone_play()     # Movimenta o drone de acordo com a jogada escolhida pelo algoritmo
get_squares()         # Identifica uma região com área mínima na cor desejada (azul)
detect_board()        # Identifica o tabuleiro na imagem e retorna suas dimensões e coordenadas
read_board()          # Lê o tabuleiro e retorna seu estado atual de jogadas
print_board()         # Imprime o estado do tabuleiro no terminal
play_ttt()            # Conduz o jogo, esperando e lendo as jogadas
--------------------------------------------------------------------------------------

'''

class tttDetection:

    def __init__(self):
        self.tello = Tello()
        self.ia = IA()

        # Contornos do player1 (X)
        player1 = cv2.imread("./player1.png")
        player1_gray = 255 - cv2.cvtColor(player1, cv2.COLOR_BGR2GRAY)

        _, thresh1 = cv2.threshold(player1_gray, 127, 255, 0)
        contours, _ = cv2.findContours(thresh1, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        self.player1_cnt = contours[0]

        # Contornos do player2 (Check mark)
        player2 = cv2.imread("./player2.png")
        player2_gray = 255 - cv2.cvtColor(player2, cv2.COLOR_BGR2GRAY)

        _, thresh2 = cv2.threshold(player2_gray, 127, 255, 0)
        contours, _ = cv2.findContours(thresh2, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
        self.player2_cnt = contours[0]

    def tello_startup(self):
        self.tello.connect()
        self.tello.streamon()

    def filter_small(self, contours, min_area):

        filtered_contours = []

        for contour in contours:
            if cv2.contourArea(contour) >= min_area:
                filtered_contours.append(contour)

        return filtered_contours

    def match_shape(self, square_cnts, max_tolerance):
        for contour in square_cnts:

            if cv2.matchShapes(contour, self.player1_cnt, 1, 0.0) < max_tolerance:
                return 1

            elif cv2.matchShapes(contour, self.player2_cnt, 1, 0.0) < max_tolerance:
                return -1

        return 0

    def most_frequent(self, list):

        most_frequent = 0
        result = list[0]

        for i in list:
            frequency = list.count(i)
            if(frequency > most_frequent):
                most_frequent = frequency
                result = i

        return result

    def get_common(self, boards):

        board_common = [[], [], []]
        for i in range(3):
            for j in range(3):
                play = []
                for board in boards:
                    play.append(board[i][j])

                board_common[i].append(self.most_frequent(play))

        return board_common

    def get_mask(self, hsv , lower_color , upper_color):
        lower = np.array(lower_color)
        upper = np.array(upper_color)
        mask = cv2.inRange(hsv , lower, upper)
        return mask

    def show_drone_play(self,drone_play):
        dist = 40
        wait = 1
        wait_after = 2
        if(drone_play == 1):
            self.tello.move_left(dist)
            time.sleep(wait)
            self.tello.move_up(dist)
            time.sleep(wait_after)

            self.tello.move_down(dist)
            time.sleep(wait)
            self.tello.move_right(dist)

        elif(drone_play == 2):

            self.tello.move_up(dist)
            time.sleep(wait_after)
            self.tello.move_down

        elif(drone_play == 3):
            self.tello.move_right(dist)
            time.sleep(wait)
            self.tello.move_up(dist)
            time.sleep(wait_after)

            self.tello.move_down(dist)
            time.sleep(wait)
            self.tello.move_left(dist)

        elif(drone_play == 4):
            self.tello.move_left(dist)
            time.sleep(wait_after)
            self.tello.move_right(dist)

        elif(drone_play == 5):
            self.tello.move_forward(dist)
            time.sleep(wait_after)
            self.tello.move_back(dist)

        elif(drone_play == 6):
            self.tello.move_right(dist)
            time.sleep(wait_after)
            self.tello.move_left(dist)

        elif(drone_play == 7):
            self.tello.move_left(dist)
            time.sleep(wait)
            self.tello.move_down(dist)
            time.sleep(wait_after)

            self.tello.move_up(dist)
            time.sleep(dist)
            self.tello.move_right(dist)

        elif(drone_play == 8):
            self.tello.move_down(dist)
            time.sleep(wait_after)
            self.tello.move_up(dist)

        elif(drone_play == 9):
            self.tello.move_right(dist)
            time.sleep(wait)
            self.tello.move_down(dist)
            time.sleep(wait_after)

            self.tello.move_up(dist)
            time.sleep(wait)
            self.tello.move_left(dist)

    def get_squares (self, image):

        # Mascara azul
        # Calibrar antes de usar
        lower_blue = [ 99, 89, 53]
        upper_blue = [ 119, 255, 255]

        # Criando as máscaras
        hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
        blue_mask = self.get_mask(hsv, lower_blue, upper_blue)

        # Os passos abaixo servem só para melhorar a visualizacao
        # da mascara para o usuario, nao interferem na deteccao
        blue_result = cv2.bitwise_and(image , image , mask= blue_mask)

        erode_size = 5
        dilate_size = 5

        erode_kernel = np.ones((erode_size, erode_size), np.float32)
        dilate_kernel = np.ones((dilate_size, dilate_size), np.float32)

        blue_result = cv2.dilate(blue_result, dilate_kernel)
        blue_result = cv2.erode(blue_result, erode_kernel)

        # Retorna uma lista com os quadrados que tem area maior ou igual a min_area
        square_detected = 0

        min_area = 6000 # Area minima para um quadrado ser contabilizado

        contours, _ = cv2.findContours(blue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

        for contour in contours:
            area = cv2.contourArea(contour)
            if area >= min_area:

                x,y,w,h=cv2.boundingRect(contour)
                square_detected = [x,y,w,h]

        return square_detected, blue_result

    def detect_board(self, tries, wait_time = 0.05, max_tolerance = .6):
        boards = []
        print("\nQuando encontrar o tabuleiro, aperte espaço com a janela selecionada.")
        while(True):

            self.tello.send_control_command("command")
            # _, self.frame = self.capture.read(0)
            self.frame = self.tello.get_frame_read().frame
            board, self.blue_img = self.get_squares(self.frame)
            frame_gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
            _, frame_thresh = cv2.threshold(frame_gray, 127, 255,0)
            cv2.imshow("frame threshold", frame_thresh)
            cv2.imshow("blue", self.blue_img)

            if(cv2.waitKey(1) == ord(" ")):
                break

        for _ in range(tries):
            #_, self.frame = self.capture.read(0)
            self.frame = self.tello.get_frame_read().frame

            board, self.blue_img = self.get_squares(self.frame)
            if board != 0:

                boards.append(self.read_board(board, max_tolerance))

            time.sleep(wait_time)

        return self.get_common(boards)

    def read_board(self, board, max_tolerance = 1):

        x,y,w,h = board
        board_simple = [[], [], []]

        frame_gray = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
        _, frame_thresh = cv2.threshold(frame_gray, 127, 255, 0)

        cv2.imshow("frame threshold", frame_thresh)
        cv2.imshow("blue", self.blue_img)

        for i in range(3):
            for j in range(3):

                square = frame_thresh[(y + h * (j)//3) : (y + h * (j + 1)//3), (x + w * (i)//3) : (x + w * (i + 1)//3)]
                square_cnts, _ = cv2.findContours(square, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
                square_cnts = self.filter_small(square_cnts, 15)

                board_simple[j].append(self.match_shape(square_cnts, max_tolerance))

        return board_simple

    def print_board(self, board):
        print(f" {board[0][0]} | {board[0][1]} | {board[0][2]}")
        print("-----------")
        print(f" {board[1][0]} | {board[1][1]} | {board[1][2]}")
        print("-----------")
        print(f" {board[2][0]} | {board[2][1]} | {board[2][2]}")
        print()
        print()
        print("###########################################")

    def play_ttt(self):
        self.tello_startup()
        self.tello.takeoff()
        board_state = []
        start_detetection = False

        c_choice = "X"
        h_choice = "V"

        print("Começando o jogo da velha!!")
        print("-------------------------------")

        first = input("Você que começar? [S/N] ")

        # Rodando o loop enquanto o tabuleiro não estiver preenchido nem ganho
        while len(self.ia.empty_cells(self.ia.board)) > 0 and not self.ia.game_over(self.ia.board):
            self.tello.send_control_command("command")

            if first in'Nn':
                jogada_ia = self.ia.ai_turn(c_choice, h_choice)
                self.show_drone_play(jogada_ia)
                print(f"Jogada do drone : {jogada_ia}")
                first = 'nothing'

            start_detetection = input("\nDigite qualquer coisa para iniciar uma detecção (se quiser sair do jogo, aperte q)  \n")

            if start_detetection in "Qq":
                break

            board_state = self.detect_board(tries = 25)
            print("Tabuleiro encontrado!\n")
            print("\nSituação atual do jogo:")
            self.ia.board = board_state
            self.ia.render(board_state, c_choice, h_choice)

            jogada_ia = self.ia.ai_turn(c_choice, h_choice)
            self.show_drone_play(jogada_ia)

            print(f"Jogada do drone : {jogada_ia}")
            # drone indica a jogada
        self.tello.land()

if __name__ == "__main__":

    detecting = tttDetection()
    detecting.play_ttt()
    detecting.capture.release()
    cv2.destroyAllWindows()