import pygame

# Import the default text font
from settings import FONT

class Button:
    """
    Create a Button object.

    Attributes
    ----------
    text : str
        Text of the button
    text_size : int
        Size of the text
    text_font : str
        Font of the text
    text_color : tuple
        RGB value of text color
    x : int
        Position by x coordinate
    y : int
        Position by y coordinate
    width : int
        Width of the button
    height : int
        Height of the button
    background_color : tuple
        RGB value for background color
    background_surface : pygame.Surface()
        The surface object of the background
    background_rectangle : pygame.Surface().get_rect()
        The rectangle of the background, drawn on background_surface
    border : bool
        True/False, draw a border around the button
    border_color : tuple
        RGB value for border color
    border_thickness : int
        Thickness of the border
    already_clicked : bool
        True/False, prevents multiple reads of one click of the button
    click_reaction : func
        Calls func() when the button is clicked

    Methods
    -------
    set_text(new_text)
        Set the new text displayed on the button
    set_text_size(new_size)
        Set the size of the text
    set_text_font(new_font)
        Set the font of the text
    set_text_color(new_color)
        Set the color of the text
    set_position(new_x, new_y)
        Set the position of the button
    set_size(new_width, new_height)
        Set the size of the button
    set_background_color(new_color)
        Set the color of the background
    set_border_color(new_color)
        Set the color of the border
    set_border_thickness(new_thickness)
        Set the thickness of the border
    hover()
        Check if the cursor collides with the button
    clicked()
        Check if the button is clicked and call click_reaction()
    connect(func)
        Connect a function to be called when the button is clicked
    show(surface)
        Display the button on the surface
    """
    def __init__(self, text, pos_x, pos_y, width, height):
        """
        Parameters
        ----------
        text : str
            Text of the button
        pos_x : int
            The position of the button on the x-axis
        pos_y : int
            The position of the button on the y-axis
        width : int
            The width of the button
        height : int
            The height of the button
        """
        self.text = text
        self.text_size = 15
        self.text_font = FONT
        self.text_color = (0, 0, 0)

        self.x = pos_x
        self.y = pos_y

        self.width = width
        self.height = height

        self.background_color = (0, 255, 255)
        self.background_surface = None
        self.background_rectangle = None

        self.border = True
        self.border_color = (0, 255, 0)
        self.border_thickness = 5

        # mouse events
        self.already_clicked = False
        self.click_reaction = None

    def set_text(self, new_text: str) -> None:
        """
        Set the text of the button.

        Parameters
        ----------
        new_text : str
            New text of the button
        """
        self.text = new_text

    def set_text_size(self, new_size: int) -> None:
        """
        Set the size of the text.

        Parameters
        ----------
        new_size : int
            New size for text
        """
        self.text_size = new_size

    def set_text_font(self, new_font: str) -> None:
        """
        Set the font of the text.

        Parameters
        ----------
        new_font : str
            Path to a *.ttf file
        """
        self.text_font = new_font

    def set_text_color(self, new_color: tuple) -> None:
        """
        Set the color of the text.

        Parameters
        ----------
        new_color : tuple
            RGB value for color
        """
        self.text_color = new_color

    def set_position(self, new_x: int or None, new_y: int or None) -> None:
        """
        Set the position of the button.

        Parameters
        ----------
        new_x : int or None
            New x coordinate for the button, None leaves the value unchanged
        new_y : int or None
            New y coordinate for the button, None leaves the value unchanged
        """
        if new_x:
            self.x = new_x
        if new_y:
            self.y = new_y

    def set_size(self, new_width, new_height: int) -> None:
        """
        Set the size of the button.

        Parameters
        ----------
        new_width : int or None
            New width of the button, None leaves the value unchanged
        new_height : int or None
            New height of the button, None leaves the value unchanged
        """
        if new_width:
            self.width = new_width
        if new_height:
            self.height = new_height

    def set_background_color(self, new_color: tuple) -> None:
        """
        Set the color of the background.

        Parameters
        ----------
        new_color : tuple
            RGB value for color
        """
        self.background_color = new_color

    def set_border_color(self, new_color: tuple) -> None:
        """
        Set the color of the border.

        Parameters
        ----------
        new_color : tuple
            RGB value for color
        """
        self.border_color = new_color

    def set_border_thickness(self, new_thickness: int) -> None:
        """
        Set the thickness of the border.

        Parameters
        ----------
        new_thickness : int
            New border thickness
        """
        self.border_thickness = new_thickness

    def hover(self) -> bool:
        """
        Check if the cursor collides with the button.
        """
        if self.background_rectangle.collidepoint(pygame.mouse.get_pos()):
            return True
        else:
            return False

    def clicked(self) -> None:
        """
        Check if the button is clicked.

        Sets already_clicked to True until the mouse button is released,
        then it sets already_clicked to False.
        If click_reaction is not None, call it.
        """
        if self.hover() and pygame.mouse.get_pressed()[0] and not self.already_clicked:
            if self.click_reaction is not None:
                self.click_reaction()

            self.already_clicked = True

        elif not pygame.mouse.get_pressed()[0]:
            self.already_clicked = False

    def connect(self, func) -> None:
        """
        Connects a function to the button.

        Parameters
        ----------
        func : function
            A new function
        """
        self.click_reaction = func

    def show(self, surface) -> None:
        """
        Display the button on the surface.

        Parameters
        ----------
        surface : pygame.display.set_mode()
            The surface object created by pygame
        """

        # Create the surface of the text and get its rectangle to be displayed on the surface
        text_surface = pygame.font.Font(self.text_font, self.text_size).render(self.text, True, self.text_color)
        text_rectangle = text_surface.get_rect(center=(self.x, self.y))

        # Create the surface of the background depending on the values of width and height
        self.background_surface = pygame.Surface((self.width, self.height))
        # Fill the surface with the color of background_color
        self.background_surface.fill(self.background_color)
        # Get the rectangle of the surface so it can be drawn on the screen
        self.background_rectangle = self.background_surface.get_rect(center=(self.x, self.y))
        # Check if the mouse is clicked
        self.clicked()
        # Draw the background of the button on surface
        surface.blit(self.background_surface, self.background_rectangle)
        # Check if the cursor collides with the button
        if self.hover():
            # If so, draw the border
            pygame.draw.rect(surface,
                             self.border_color,
                             pygame.Rect(pygame.Surface((self.width, self.height)).get_rect(center=(self.x, self.y))),
                             self.border_thickness)
        # Draw the button on the surface
        surface.blit(text_surface, text_rectangle)


from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect, Qt)
from PySide6.QtGui import (QFont)
from PySide6.QtWidgets import (QFrame, QLabel, QWidget)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):

        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")

        MainWindow.resize(1321, 761)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")

        self.telabase = QFrame(self.centralwidget)
        self.telabase.setObjectName(u"telabase")
        self.telabase.setGeometry(QRect(3, 3, 1310, 750))
        self.telabase.setStyleSheet(u"background-color:#064A80;")
        self.telabase.setFrameShape(QFrame.StyledPanel)
        self.telabase.setFrameShadow(QFrame.Raised)

        self.retangulochave = QWidget(self.telabase)
        self.retangulochave.setObjectName(u"retangulochave")
        self.retangulochave.setGeometry(QRect(30, 20, 551, 711))
        self.retangulochave.setStyleSheet(u"background:#006DAD")

        self.titulochave = QLabel(self.retangulochave)
        self.titulochave.setObjectName(u"titulochave")
        self.titulochave.setGeometry(QRect(200, 22, 151, 51))
        font = QFont()
        self.titulochave.setFont(font)
        self.titulochave.setStyleSheet(u"font: 26pt \"Aldrich\"; color:#ffffff;\n")
        self.bolotaschave = QFrame(self.retangulochave)
        self.bolotaschave.setObjectName(u"bolotaschave")
        self.bolotaschave.setGeometry(QRect(20, 100, 511, 561))
        self.bolotaschave.setFrameShape(QFrame.StyledPanel)
        self.bolotaschave.setFrameShadow(QFrame.Raised)

        self.label_2 = QLabel(self.bolotaschave)
        self.label_2.setObjectName(u"label_2")
        self.label_2.setGeometry(QRect(5, 5, 48, 48))
        font1 = QFont()
        font1.setFamilies([u"Aldrich"])
        font1.setPointSize(12)
        self.label_2.setFont(font1)
        self.label_2.setStyleSheet(u"background:#ffffff; border-radius:24px;")
        self.label_2.setAlignment(Qt.AlignCenter)

        self.label_3 = QLabel(self.bolotaschave)
        self.label_3.setObjectName(u"label_3")
        self.label_3.setGeometry(QRect(75, 5, 48, 48))
        self.label_3.setStyleSheet(u"background:#ffffff; border-radius:24px;""font: 12pt \"Aldrich\";")
        self.label_3.setAlignment(Qt.AlignCenter)

        self.label_4 = QLabel(self.bolotaschave)
        self.label_4.setObjectName(u"label_4")
        self.label_4.setGeometry(QRect(140, 5, 48, 48))
        self.label_4.setStyleSheet(u"background:#ffffff; border-radius:24px;""font: 12pt \"Aldrich\";")
        self.label_4.setAlignment(Qt.AlignCenter)

        MainWindow.setCentralWidget(self.centralwidget)
        self.retranslateUi(MainWindow)
        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.titulochave.setText(QCoreApplication.translate("MainWindow", u"CHAVES", None))
        self.label_2.setText(QCoreApplication.translate("MainWindow", u"101", None))
        self.label_3.setText(QCoreApplication.translate("MainWindow", u"102", None))
        self.label_4.setText(QCoreApplication.translate("MainWindow", u"103", None))
    # retranslateUi


import customtkinter as ctk

class Configs(ctk.CTkFrame):
    def __init__(self, master, width=240, height=380, **kwargs):
        super().__init__(master, width=width, height=height, **kwargs)
        self.grid_propagate(False)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(2, weight=1)

        self.configs_label = ctk.CTkLabel(self, text='Configuration:', font=ctk.CTkFont(family="Arial", size=16))
        self.configs_label.grid(columnspan = 2, padx=20, pady=5, column=0, sticky='w')

        self.configs_container = ctk.CTkScrollableFrame(self)
        self.configs_container.grid(row=2, column=0, sticky='nswe')
        self.configs_container.grid_columnconfigure(0, weight=1)

        self.prescript_label = ctk.CTkLabel(self.configs_container, text='Prescript', font=ctk.CTkFont(family="Arial", size=14))
        self.prescript_label.grid(columnspan = 2, padx=24, pady=0, column=0, sticky='w', row=1)

        self.prescript_textbox = ctk.CTkTextbox(self.configs_container, font=ctk.CTkFont(family="Arial", size=14), height=80)
        self.prescript_textbox.grid(columnspan = 2, padx=20, pady=(0,10), column=0, sticky='we', row=2)

        self.postscript_label = ctk.CTkLabel(self.configs_container, text='Postscript', font=ctk.CTkFont(family="Arial", size=14))
        self.postscript_label.grid(columnspan = 2, padx=24, pady=0, column=0, sticky='w', row=3)

        self.postscript_textbox = ctk.CTkTextbox(self.configs_container, font=ctk.CTkFont(family="Arial", size=14), height=80)
        self.postscript_textbox.grid(columnspan = 2, padx=20, pady=(0,10), column=0, sticky='we', row=4)

        self.prompt_label = ctk.CTkLabel(self.configs_container, text='Prompt', font=ctk.CTkFont(family="Arial", size=14))
        self.prompt_label.grid(columnspan = 2, padx=24, pady=0, column=0, sticky='w', row=5)

        self.prompt_textbox = ctk.CTkTextbox(self.configs_container, font=ctk.CTkFont(family="Arial", size=14), height=80)
        self.prompt_textbox.grid(columnspan = 2, padx=20, pady=(0,10), column=0, sticky='we', row=6)

        self.prescript_textbox.bind("<KeyRelease>", self.update_prescript)
        self.postscript_textbox.bind("<KeyRelease>", self.update_postscript)
        self.prompt_textbox.bind("<KeyRelease>", self.update_prompt)

        self.load_prescript()
        self.load_postscript()
        self.load_prompt()

    def update_prescript(self, e):
        content = e.widget.get(1.0, "end-1c").encode('euc-kr')
        with open('prescript.txt', 'wb+') as f:
            f.write(content)

    def update_postscript(self, e):
        content = e.widget.get(1.0, "end-1c").encode('euc-kr')
        with open('postscript.txt', 'wb+') as f:
            f.write(content)

    def update_prompt(self, e):
        content = e.widget.get(1.0, "end-1c").encode('euc-kr')
        with open('prompt.txt', 'wb+') as f:
            f.write(content)

    def load_prescript(self):
        with open('prescript.txt', 'rb+') as f:
            self.prescript_textbox.insert('insert', f.read().decode('euc-kr'))

    def load_postscript(self):
        with open('postscript.txt', 'rb+') as f:
            self.postscript_textbox.insert('insert', f.read().decode('euc-kr'))

    def load_prompt(self):
        with open('prompt.txt', 'rb+') as f:
            self.prompt_textbox.insert('insert', f.read().decode('euc-kr'))


import pandas as pd
import numpy as np

from scipy.stats import norm, chi2, gamma

chat_id = 0 # Ваш chat ID, не меняйте название переменной

def solution(p: float, x: np.array) -> tuple:
    p = 1 - p
    n = len(x)
    x_s = np.square(x)
    R = np.sum(x_s)
    l_g = gamma.ppf(p/2, a = n, scale = 2/n)
    r_g = gamma.ppf(1 - p/2, a = n, scale = 2/n)
    sigma_left = np.sqrt( R / 14 / n / r_g)
    sigma_right = np.sqrt( R / 14 / n / l_g)
    return sigma_left, sigma_right


import tkinter
from tkinter import messagebox
from tkinter import simpledialog
import customtkinter
from CTkTable import *

import pandas as pd
import openpyxl as op
from openpyxl.styles import Border, Side

import tempfile
import pickle
import math
import os

from datetime import datetime

customtkinter.set_appearance_mode("System")
customtkinter.set_default_color_theme("green")

import ctypes
myappid = 'mycompany.myproduct.subproduct.version'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()

        self.hasLogin = False
        self.DatabaseWindowVar = None

        file = open("Data/Admin.txt", "rb")
        self.LoginData = pickle.load(file)
        print(self.LoginData)
        file.close()
        #! UI #############################################################################################################################
        if self.hasLogin == False:
            self.title("Login")
            self.iconbitmap("Logo.ico")

            self.nameLbl = customtkinter.CTkLabel(self, text="Nama")
            self.passLbl = customtkinter.CTkLabel(self, text="Password")
            self.nameEnt = customtkinter.CTkEntry(self)
            self.passEnt = customtkinter.CTkEntry(self)

            self.nameLbl.grid(row=0, column=0, padx=10, pady=(0,5))
            self.passLbl.grid(row=1, column=0, padx=10, pady=(0,5))
            self.nameEnt.grid(row=0, column=1, padx=10, pady=(0,5))
            self.passEnt.grid(row=1, column=1, padx=10, pady=(0,5))

            self.loginBtn = customtkinter.CTkButton(self, text="Login", command=self.Login)
            self.loginBtn.grid(row=2, column=1, pady=(0,5))

    #! Login Func #############################################################################################################################
    def Login(self):
        if self.nameEnt.get() == self.LoginData[0] and self.passEnt.get() == self.LoginData[1]:
            self.hasLogin = True
            print("Log in")

            self.nameLbl.destroy()
            self.passLbl.destroy()
            self.nameEnt.destroy()
            self.passEnt.destroy()
            self.loginBtn.destroy()

            # configure window
            width= self.winfo_screenwidth()
            height= self.winfo_screenheight()
            self.title("SIBANKSAM")
            self.geometry(f"{width}x{height}+{-10}+{-5}")
            self.iconbitmap("Logo.ico")
            # self.state("zoomed")
            # self.attributes('-fullscreen', True)

            # configure grid layout (4x4)
            self.grid_columnconfigure((1, 2), weight=1)
            self.grid_rowconfigure((3, 4), weight=1)

            # loginW = LoginWindow(self)

            # Tittle
            self.sidebar_frame = customtkinter.CTkFrame(self, width=140, corner_radius=0)
            self.sidebar_frame.grid(row=0, column=0, rowspan=6, sticky="nsew")
            self.sidebar_frame.grid_rowconfigure(5, weight=1)
            self.logo_label = customtkinter.CTkLabel(self.sidebar_frame, text="SIBANKSAM", font=customtkinter.CTkFont(size=20, weight="bold"))
            self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 10))

            # Menu Button
            # self.InputWindowBtn = customtkinter.CTkButton(self.sidebar_frame, text="Input Sampah", command=self.InputWindow)
            # self.InputWindowBtn.grid(row=1, column=0, padx=20, pady=10)
            # self.InputWindowBtn.configure(state="disabled")
            self.InputHistoryBtn = customtkinter.CTkButton(self.sidebar_frame, text="Riwayat Input", command=self.InputHistoryWindow)
            self.InputHistoryBtn.grid(row=1, column=0, padx=20, pady=10)
            self.O_DatabaseBtn = customtkinter.CTkButton(self.sidebar_frame, text="Rekap Data", command=self.DatabaseWindow)
            self.O_DatabaseBtn.grid(row=2, column=0, padx=20, pady=10)
            self.AdminBtn = customtkinter.CTkButton(self.sidebar_frame, text="Admin", command=self.renameAdmin)
            self.AdminBtn.grid(row=3, column=0, padx=20, pady=10)

            # Name Entry
            self.InputTittle = customtkinter.CTkLabel(self, text="INPUT SAMPAH", font=customtkinter.CTkFont(size=50, weight="bold"))
            self.InputTittle.grid(row=0, column=1, columnspan=2, padx=20, pady=20, sticky="nsew")
            self.nameEntry = customtkinter.CTkEntry(self, placeholder_text="Masukkan Nama...", font=customtkinter.CTkFont(size=30))
            self.nameEntry.grid(row=1, column=1, columnspan=2, padx=20, pady=20, sticky="new")

            file = open("Data/Tipe Sampah.txt", "rb")
            self.trashType = pickle.load(file)
            print(self.trashType)

            # Sampah Grid
            self.sf = customtkinter.CTkScrollableFrame(self)
            self.sf.grid(row=2, column=1, rowspan=2, columnspan=2, padx=20, sticky="nsew")
            self.sf.grid_columnconfigure(1, weight=1)

            self.gridFrame1 = customtkinter.CTkFrame(self.sf)
            self.gridFrame1.grid(row=0, column=0, sticky="nsew")
            self.gridFrame1.grid_columnconfigure(0, weight=1)

            self.gridFrame2 = customtkinter.CTkFrame(self.sf)
            self.gridFrame2.grid(row=0, column=1, sticky="nsew")
            self.gridFrame2.grid_columnconfigure(0, weight=1)

            self.UpdateTrashList()

            #! Money ##########################################################################################################################

            self.total = 0

            self.totalLabel = customtkinter.CTkLabel(self, text="Total : Rp. 0", font=customtkinter.CTkFont(size=30, weight="bold"))
            self.totalLabel.grid(row=4, column=1, padx=20, sticky="w")
            self.AddTrashBtn = customtkinter.CTkButton(self, text="Tambah Sampah", font=customtkinter.CTkFont(size=30, weight="bold"), command=self.AddTrash)
            self.AddTrashBtn.grid(row=5, column=1, padx=20, pady=(0,20), sticky="w")
            self.InputBtn = customtkinter.CTkButton(self, text="INPUT", font=customtkinter.CTkFont(size=30, weight="bold"), command=self.InputData)
            self.InputBtn.grid(row=5, column=2, padx=20, pady=(0,20), sticky="e")
        else:
            messagebox.showwarning("Warning", "Nama atau Password Salah!")

    def renameAdmin(self):
        self.nameLgn = simpledialog.askstring("Rename", "Masukkan nama Admin", parent=self)
        if self.nameLgn == None:
            return

        self.passLgn = simpledialog.askstring("Rename", "Masukkan password", parent=self)
        if self.passLgn == None:
            return

        self.LoginData = [self.nameLgn, self.passLgn]
        file = open("Data/Admin.txt", "wb")
        pickle.dump(self.LoginData, file)
        file.close()

    #! Add Trash Func #############################################################################################################################
    def AddTrash(self):
        nameTrash = simpledialog.askstring("Tambah Sampah",  "Nama  : ", parent=self)
        if nameTrash == None:
            return

        priceTrash = simpledialog.askstring("Tambah Sampah", "Harga : ", parent=self)
        if priceTrash == None:
            return

        file = open("Data/Data.txt", "rb")
        dataP = pickle.load(file)
        file.close()

        for key in dataP.keys():
            dataP[key] += [0]

        file = open("Data/Data.txt", "wb")
        pickle.dump(dataP, file)
        file.close()

        file = open("Data/Data Sampah.txt", "rb")
        dataB = pickle.load(file)
        file.close()

        for yKey in dataB.keys():
            # for key in dataB[yKey].keys():
            dataB[yKey][1] += [0]

        file = open("Data/Data Sampah.txt", "wb")
        pickle.dump(dataB, file)
        file.close()

        self.trashType[0] += [nameTrash]
        self.trashType[1] += [priceTrash]

        print(self.trashType)
        file = open("Data/Tipe Sampah.txt", "wb")
        pickle.dump(self.trashType, file)
        file.close()

        self.UpdateTrashList()

    #! Update Trash List Func #############################################################################################################################
    def UpdateTrashList(self):
        self.gridFrame1.destroy()
        self.gridFrame2.destroy()

        self.gridFrame1 = customtkinter.CTkFrame(self.sf)
        self.gridFrame1.grid(row=0, column=0, sticky="nsew")
        self.gridFrame1.grid_columnconfigure(0, weight=1)

        self.gridFrame2 = customtkinter.CTkFrame(self.sf)
        self.gridFrame2.grid(row=0, column=1, sticky="nsew")
        self.gridFrame2.grid_columnconfigure(0, weight=1)

        self.length = len(self.trashType[0])

        # Price for each trash
        self.trashPrice = [0] * self.length
        # Total for each trash
        self.trashTotal = [0] * self.length
        # Total money for each trash
        self.trashMoney = [0] * self.length

        self.varPList = [customtkinter.StringVar() for i in range(self.length)]
        self.varAList = [customtkinter.StringVar() for i in range(self.length)]
        self.varList = [customtkinter.StringVar() for i in range(self.length)]

        midPoint = math.ceil(len(self.trashType[0]))/2
        self.S_Label_list = [customtkinter.CTkLabel(self.gridFrame1 if i < midPoint else self.gridFrame2, font=customtkinter.CTkFont(size=15)) for i in range(self.length)]
        self.S_Label2_list = [customtkinter.CTkLabel(self.gridFrame1 if i < midPoint else self.gridFrame2, text="Rp. ", font=customtkinter.CTkFont(size=15)) for i in range(self.length)]
        self.S_Price_list = [customtkinter.CTkEntry(self.gridFrame1 if i < midPoint else self.gridFrame2, textvariable=self.varPList[i], width=100) for i in range(self.length)]
        self.S_Label3_list = [customtkinter.CTkLabel(self.gridFrame1 if i < midPoint else self.gridFrame2, text="/", font=customtkinter.CTkFont(size=15)) for i in range(self.length)]
        self.S_Amount_list = [customtkinter.CTkEntry(self.gridFrame1 if i < midPoint else self.gridFrame2, textvariable=self.varAList[i], width=30) for i in range(self.length)]
        self.S_Entry_list = [customtkinter.CTkEntry(self.gridFrame1 if i < midPoint else self.gridFrame2, textvariable=self.varList[i], width=50) for i in range(self.length)]

        for i in range(self.length):
            self.S_Label_list[i].configure(text=self.trashType[0][i])
            self.S_Label_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=0, padx=(20,0), pady=10, sticky="new")
            self.S_Label2_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=1, padx=(20,5), pady=10, sticky="new")
            self.S_Price_list[i].insert(0, self.trashType[1][i])
            self.S_Price_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=2, padx=(10,5), pady=10, sticky="new")
            self.S_Label3_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=3, padx=5, pady=10, sticky="new")
            self.S_Amount_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=4, padx=(5,5), pady=10, sticky="new")
            self.S_Amount_list[i].insert(0, "1" if i != 9 else "1.5")
            self.S_Entry_list[i].grid(row=i if i < midPoint else i-int(midPoint), column=5, padx=10, pady=10, sticky="new")

        for i in range(self.length):
            self.varPList[i].trace('w', self.changeTotal)
            self.varAList[i].trace('w', self.changeTotal)
            self.varList[i].trace('w', self.changeTotal)

    #! Input Data Func #############################################################################################################################

    def InputData(self):
        userName = self.nameEntry.get().upper()

        if userName != "":
            #! PERSON DATA #############################################################################################################################
            self.P_dataList = {}

            # Load
            if os.stat("Data/Data.txt").st_size != 0:
                self.file = open("Data/Data.txt", "rb")
                self.P_dataList = pickle.load(self.file)
                self.file.close()
                print(self.P_dataList)
            else:
                self.file = open("Data/Data.txt", "wb")
                pickle.dump(self.P_dataList, self.file)
                self.file.close()

            #! MONTH DATA ##############################################################################################################################

            # Load
            # laod year > month > data to mData
            self.file = open("Data/Data Sampah.txt", "rb")
            self.DataDict = pickle.load(self.file)
            self.file.close()

            now = datetime.now()
            yStr = int(now.strftime("%Y"))
            mStr = int(now.strftime("%m"))
            dStr = int(now.strftime("%d"))

            inputDataList = [self.total]
            inputDataList += [self.trashTotal[i] for i in range(self.length)]

            #! Person Recap Input #############################################################################################################################

            print("Person Total : ", self.total)
            if self.P_dataList.get(userName) == None:                   # if username not exist

                result = messagebox.askquestion("Nama Tidak Terdaftar", "Nama tidak terdaftar\nApakah anda ingin menambahkan {} Kedalam daftar?".format(userName))
                if result == "yes":
                    self.P_dataList[userName] = [0] * (self.length+1)                   # add new list to the P_dataList with username as key
                else:
                    messagebox.showinfo("Info", "Nama tidak terdaftar!")
                    return

            currDataList = [(self.P_dataList[userName][i] + inputDataList[i]) for i in range(self.length+1)]
            self.P_dataList[userName] = currDataList

            print("\nself.P_dataList :\n", self.P_dataList)

            # Save data
            self.file = open("Data/Data.txt", "wb")
            pickle.dump(self.P_dataList, self.file)
            self.file.close()
            currDataList.clear()

            #! Monthly Recap Input #############################################################################################################################

            # Add monthly data (currYear - 2022) times
            if not (yStr in self.DataDict):
                mData = {}
                for i in range(13):
                    mData[i+1] = [0] * (self.length+1)

                print()
                print(mData)
                self.DataDict[yStr] = mData

            currDataList = [(self.DataDict[yStr][mStr][i] + inputDataList[i]) for i in range(self.length+1)]
            self.DataDict[yStr][mStr] = currDataList

            currYearTotal = [(self.DataDict[yStr][13][i] + inputDataList[i]) for i in range(self.length+1)]
            self.DataDict[yStr][13] = currYearTotal

            print("self.DataDict[2023] :\n", self.DataDict[2024])
            print("self.DataDict[2024] :\n", self.DataDict[2024])

            self.file = open("Data/Data Sampah.txt", "wb")
            pickle.dump(self.DataDict, self.file)
            self.file.close()

            #! History Input #############################################################################################################################
            file = open("Data/Tipe Sampah.txt", "rb")
            trashType = pickle.load(file)
            file.close()

            self.trashTypeArr = []
            self.HistoryList = []

            if os.stat("Data/Data.txt").st_size != 0:
                self.file = open("Data/Riwayat.txt", "rb")
                self.HistoryList = pickle.load(self.file)
                self.file.close()

            currHistoryText = "- {}/{}/{} {} {} ".format(dStr, mStr, yStr, userName, self.total)

            for s, i in enumerate(trashType[0]):
                if self.trashTotal[s] == 0:
                    continue

                currHistoryText += ''.join(next(zip(*i.split()))) + ": {} ".format(self.trashTotal[s])

            print("currHistoryText :\n", currHistoryText)

            self.HistoryList.append(currHistoryText)
            print("HistoryList :\n", self.HistoryList)

            self.file = open("Data/Riwayat.txt", "wb")
            pickle.dump(self.HistoryList, self.file)
            self.file.close()

            # Updating table
            if self.DatabaseWindowVar != None:
                self.DatabaseWindowVar.updateTable()

            self.nameEntry.delete(0, customtkinter.END)
            for i in range(self.length):
                self.S_Entry_list[i].delete(0, customtkinter.END)
        else :
            messagebox.showwarning("Warning", "Tolong Masukkan Nama!")

    #! Change Total Label Text Func #############################################################################################################

    def changeTotal(self, *args):
        # Total money
        self.total = 0
        # StringVar to float
        # amount of each trash
        for i in range(self.length):
            self.trashPrice[i] = float(0 if self.varPList[i].get() == '' else self.varPList[i].get()) / float(0 if self.varAList[i].get() == '' else self.varAList[i].get())
            self.trashTotal[i] = float(0 if self.varList[i].get() == '' else self.varList[i].get())

            # The money of each trash
            self.trashMoney[i] = self.trashTotal[i] * self.trashPrice[i]

            # Total money'
            self.total += self.trashMoney[i]

        self.totalLabel.configure(text='')
        self.totalLabel.configure(text="Total : Rp. {:,.2f}".format(self.total))

    #! Button Function #############################################################################################################################

    def InputWindow(self):
        print("Input Window")

    def InputHistoryWindow(self):
        print("Input History Window")
        self.HistoryWindow = HistoryWindow(self)

    def DatabaseWindow(self):
        print("Orang Database Window")
        self.DatabaseWindow = OrangDatabaseWindow(self)

##########################################################################################################!
########################################! HISTORY WINDOW #################################################!
##########################################################################################################!

class HistoryWindow(customtkinter.CTkToplevel):
    def __init__(self, master = None):
        #! UI #############################################################################################################################
        super().__init__(master=master)

        # configure window
        self.title("SIBANKSAM")
        self.geometry(f"{1100}x{580}")
        self.iconbitmap("Logo.ico")
        self.after(250, lambda: self.iconbitmap('Logo.ico'))

        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)

        self.HistoryTittle = customtkinter.CTkLabel(self, text="RIWAYAT", font=customtkinter.CTkFont(size=50, weight="bold"))
        self.HistoryTittle.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="nsew")

        self.HistoryList = []

        if os.stat("Data/Data.txt").st_size != 0:
            self.file = open("Data/Riwayat.txt", "rb")
            self.HistoryList = pickle.load(self.file)
            self.file.close()

        print(self.HistoryList)

        self.HistoryFrame = customtkinter.CTkTextbox(self)
        for i in range(len(self.HistoryList)) :
            self.HistoryFrame.insert("0.0", text=self.HistoryList[i] + "\n")
        self.HistoryFrame.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew")

        self.lift()
        self.attributes('-topmost',True)
        self.after_idle(self.attributes,'-topmost',False)

    #! Function ####################################################################################################################################

    # def updateHistory(self):

    #! Button Function #############################################################################################################################

    def InputWindow(self):
        print("Input Window")

    def InputHistoryWindow(self):
        print("Input History Window")

    def O_DatabaseWindow(self):
        print("Orang Database Window")
        OrangDatabaseWindow(self)

##########################################################################################################!
####################################! ORANG DATABASE WINDOW ##############################################!
##########################################################################################################!

class OrangDatabaseWindow(customtkinter.CTkToplevel):
    def __init__(self, master=None):
        #! UI #############################################################################################################################
        super().__init__(master=master)

        # configure window
        self.title("SIBANKSAM")
        self.geometry(f"{1100}x{580}")
        self.iconbitmap("Logo.ico")
        self.after(250, lambda: self.iconbitmap('Logo.ico'))
        # width= self.winfo_screenwidth()
        # height= self.winfo_screenheight()
        # self.geometry(f"{width}x{height}+{0}+{0}")

        # configure grid layout (4x4)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=1)

        self.ODBTittle = customtkinter.CTkLabel(self, text="REKAP DATA", font=customtkinter.CTkFont(size=50, weight="bold"))
        self.ODBTittle.grid(row=0, column=0, columnspan=3, padx=20, pady=20, sticky="nsew")

        self.tabview = customtkinter.CTkTabview(self, width=250)
        self.tabview.grid(row=1, column=0, columnspan=3, padx=20, pady=20, sticky="nsew")
        self.tabview.add("Perorang")
        self.tabview.add("Bulanan")
        self.tabview.tab("Perorang").grid_columnconfigure(0, weight=1)
        self.tabview.tab("Perorang").grid_rowconfigure(0, weight=1)
        self.tabview.tab("Bulanan").grid_columnconfigure(0, weight=1)
        self.tabview.tab("Bulanan").grid_rowconfigure(0, weight=1)

        self.updateTable()

        self.RenameBtn = customtkinter.CTkButton(self, text="Rename", command=self.rename)
        self.RenameBtn.grid(row=2, column=2, padx=20, pady=20, sticky="e")
        self.PrintBBtn = customtkinter.CTkButton(self, text="Print Bulanan", command=self.print_B_Recap)
        self.PrintBBtn.grid(row=2, column=1, padx=20, pady=20, sticky="e")
        self.PrintPBtn = customtkinter.CTkButton(self, text="Print Perorang", command=self.print_P_Recap)
        self.PrintPBtn.grid(row=2, column=0, padx=20, pady=20, sticky="e")

        self.lift()
        self.attributes('-topmost', True)
        self.after_idle(self.attributes,'-topmost', False)

    #! Function ####################################################################################################################################

    def rename(self):
        if os.stat("Data/Data.txt").st_size != 0:
            self.file = open("Data/Data.txt", "rb")
            self.P_dataLoad = pickle.load(self.file)
            self.file.close()

        num = simpledialog.askinteger("Rename", "Nomor berapa yang ingin diganti?", parent=self)
        if num == None:
            return

        newName = simpledialog.askstring("Rename", "Masukkan nama baru : ", parent=self)
        if newName == None:
            return

        newName = newName.upper()
        for key in self.P_dataLoad.keys():
            i = list(self.P_dataLoad.keys()).index(key)
            if i == num-1:
                self.temp = self.P_dataLoad[key]
                self.P_dataLoad.pop(key)
                break

        if self.P_dataLoad.get(newName) == None:
            self.P_dataLoad[newName] = self.temp
        else:
            for i in range(11):
                self.P_dataLoad[newName][i] += self.temp[i]

        self.file = open("Data/Data.txt", "wb")
        pickle.dump(self.P_dataLoad, self.file)
        self.file.close()

        self.updateTable()

    #! Print Person Data in excel #############################################################################################################################
    def print_P_Recap(self):
        fileName = tempfile.mktemp(".xlsx")

        printData = pd.DataFrame(self.TableList_P)
        print(printData)

        printData.to_excel(fileName, index=False, header=False)

        excelFile = op.load_workbook(fileName)
        fileSheet = excelFile.active

        cellRange = "A1:{}{}".format(chr(len(self.TableList_P[0]) + 64), len(self.TableList_P))
        endColumn = chr(len(self.TableList_P[0]) + 64)
        print(cellRange)

        border = Side(border_style="thin", color="000000")
        for row in fileSheet[cellRange]:
            for cell in row:
                cell.border = Border(top=border, left=border, right=border, bottom=border)

        fileSheet.column_dimensions["A"].width = 3
        fileSheet.column_dimensions["B"].width = 11
        fileSheet.column_dimensions["C"].width = 10
        for i in self.range_char("D", endColumn):
            fileSheet.column_dimensions[i].width = 3
        excelFile.save(fileName)

        os.startfile(fileName, "print")

    #! Print Monthly Data in excel #############################################################################################################################
    def print_B_Recap(self):
        fileName = tempfile.mktemp(".xlsx")

        printData = {}

        data = []
        for y in list(self.TableList_B.keys()):
            data += [["", y]] + self.TableList_B[y]

        printData = pd.DataFrame(data)
        print(printData)

        printData.to_excel(fileName, index=False, header=False)

        excelFile = op.load_workbook(fileName)
        fileSheet = excelFile.active

        now = datetime.now()
        yStr = int(now.strftime("%Y"))

        cellRange = "A1:{}{}".format(chr(len(self.TableList_B[yStr][1]) + 64), (15 * len(self.TableList_B)))
        endColumn = chr(len(self.TableList_B[yStr][1]) + 64)
        print(cellRange)

        border = Side(border_style="thin", color="000000")
        for row in fileSheet[cellRange]:
            for cell in row:
                cell.border = Border(top=border, left=border, right=border, bottom=border)

        fileSheet.column_dimensions["A"].width = 3
        fileSheet.column_dimensions["B"].width = 11
        fileSheet.column_dimensions["C"].width = 10
        for i in self.range_char("D", endColumn):
            fileSheet.column_dimensions[i].width = 3
        excelFile.save(fileName)

        os.startfile(fileName, "print")

    def updateTable(self):
        #! Table Data #############################################################################################################################
        # Load
        # Load person Data
        file = open("Data/Tipe Sampah.txt", "rb")
        trashType = pickle.load(file)
        file.close()

        self.trashTypeArr = []

        for s in trashType[0]:
            self.trashTypeArr += [''.join(next(zip(*s.split())))]

        self.ListP = ["No.", "Nama", "Tabungan"]
        self.ListP += self.trashTypeArr
        self.ListM = ["No.", "Bulan", "Tabungan"]
        self.ListM += self.trashTypeArr

        if os.stat("Data/Data.txt").st_size != 0:
            self.file = open("Data/Data.txt", "rb")
            self.P_dataLoad = pickle.load(self.file)
            self.file.close()

        # Load month Data
        if os.stat("Data/Data Sampah.txt").st_size != 0:
            self.file = open("Data/Data Sampah.txt", "rb")
            self.B_dataLoad = pickle.load(self.file)
            print("Recap Monthly :\n", self.B_dataLoad)
            self.file.close()

        self.names = list(self.P_dataLoad.keys())

        #! 14 = no + nama + total + sampah[11]

        self.totalPerson = len(self.names)
        self.TableList_P = [self.ListP]
        self.TableList_P += [[0]*len(self.ListP)]*self.totalPerson
        print("self.TableList_P : \n", self.TableList_P)

        for key in self.P_dataLoad.keys():
            i = list(self.P_dataLoad.keys()).index(key)
            l = [i+1, self.names[i]]
            l += self.P_dataLoad[key]
            l[2] = "{:,.2f}".format(l[2])
            self.TableList_P[i+1] = l

        print("Person Table List : \n\n", self.TableList_P)

        # create table monthly data
        self.Month = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli",
                        "Agustus", "September", "Oktober", "November", "Desember", "Total"]

        self.TableList_B = {}

        print()
        print(self.B_dataLoad)

        for i in list(self.B_dataLoad.keys()): # i = 2023, 2024, ... Year
            # for j in list(self.B_dataLoad[i].keys()): j = 1, 2, ..., 12 Month
            self.TableList_B[i] = [self.ListM]
            self.TableList_B[i] += [(["" if j == 13 else j, self.Month[j-1]] + self.B_dataLoad[i][j]) for j in list(self.B_dataLoad[i].keys())]

        print("Month Table List :\n\n", self.TableList_B)

        #! create table UI #############################################################################################################################

        #! Tabel Perorangan
        self.tableFrameP = customtkinter.CTkScrollableFrame(self.tabview.tab("Perorang"))
        self.tableFrameP.grid(row=0, column=0, sticky="news")

        self.tableP = CTkTable(self.tableFrameP, row=self.totalPerson+1, column=len(self.ListP), values=self.TableList_P, corner_radius=0)
        self.tableP.grid(row=0, column=0)

        for i in range(len(self.ListP)):
            if i == 0:
                self.tableP.edit_column(0, width=20)
            elif i == 1 or i == 2:
                continue
            else:
                self.tableP.edit_column(i, width=30)

        #! Tabel Bulanan
        self.tableFrameB = customtkinter.CTkScrollableFrame(self.tabview.tab("Bulanan"))
        self.tableFrameB.grid(row=0, column=0, sticky="news")

        year = list(self.B_dataLoad.keys())
        self.yFrame = [customtkinter.CTkFrame(self.tableFrameB) for i in range(len(self.B_dataLoad))]
        self.yLabel = [customtkinter.CTkLabel(self.yFrame[i], text=year[i], font=customtkinter.CTkFont(size=30, weight="bold")) for i in range(len(self.B_dataLoad))]
        self.tableB = [CTkTable(self.yFrame[i], row=14, column=len(self.ListM), values=self.TableList_B[year[i]], corner_radius=0) for i in range(len(self.B_dataLoad))]

        for i in range(len(year)):
            self.yFrame[i].grid(row=i, column=0)
            self.yLabel[i].grid(row=0, column=0)
            self.tableB[i].grid(row=1, column=0)
            for j in range(len(self.ListM)):
                if j == 0:
                    self.tableB[i].edit_column(0, width=20)
                elif j == 1 or j == 2:
                    continue
                else:
                    self.tableB[i].edit_column(j, width=30)

    def range_char(self, start, stop):
        return (chr(n) for n in range(ord(start), ord(stop) + 1))

if __name__ == "__main__":
    app = App()
    app.mainloop()

# Build command for customTkinter
# pyinstaller -F -i "Logo.ico" BankSampahApp.py --collect-all customtkinter -w


import time
from datetime import datetime, timedelta
from random import randint
from urllib.parse import unquote, urljoin

import adal
import requests
from logzero import setup_logger

import settings

MAX_ATTEMPTS = 5

class ThrottlingException(Exception):
    def __init__(self, wait_sec):
        self.wait_sec = wait_sec

class TooManyThrottlingException(Exception):
    def __init__(self, message):
        self.message = message

def retry_on_throttling(req):
    """ Decorator for requests which may raise ThrottlingException.
        When a request raises ThrottlingException, the request will be retried unless exceeding MAX_ATTEMPTS.
    """
    def wrapper(*args, **kwargs):
        for _ in range(MAX_ATTEMPTS):
            try:
                return req(*args, **kwargs)
            except ThrottlingException as e:
                time.sleep(e.wait_sec)
                continue

        raise TooManyThrottlingException(
            'Exceeded maximum number of attempts.')

    return wrapper

class GraphClient():
    def __init__(self, tenant_name, client_id, client_secret, chasing_enable=True, logging_enable=False):
        # settings of logging
        self.logger = setup_logger()
        self.logger.disabled = not logging_enable

        # settings of adal and ms graph
        self.graph_base_url = 'https://graph.microsoft.com/'
        self.graph_version = 'beta'  # or 'v1.0'
        self.authority_url = f'https://login.microsoftonline.com/{tenant_name}'
        self.client_id = client_id
        self.client_secret = client_secret

        # chase next links
        self.chasing_enable = chasing_enable

        # refresh token 10 minutes before expiration
        self.time_to_refresh_token_sec = 600
        self.context = adal.AuthenticationContext(self.authority_url)
        self.token = self._get_token()

        # settings of retrying
        self.retry_enabled = True
        self.wait_random_min = 3
        self.wait_random_max = 5

    def _get_token(self):
        return self.context.acquire_token_with_client_credentials(
            self.graph_base_url, self.client_id, self.client_secret)

    def _token_expires_on(self):
        token_time_format = '%Y-%m-%d %H:%M:%S.%f'
        return datetime.strptime(self.token['expiresOn'], token_time_format)

    def _is_token_old(self):
        expires_in = self._token_expires_on() - datetime.now()
        return expires_in < timedelta(seconds=self.time_to_refresh_token_sec)

    def _refresh_token_if_needed(self):
        if self._is_token_old():
            self.token = self._get_token()

    def _get_chasing(self, api_path, params=None):
        """ get all data, chasing next links
        """
        res = self._request('GET', api_path, params=params).json()
        values = res['value']
        next_url = res['@odata.nextLink'] if '@odata.nextLink' in res else None
        while next_url:
            res = self._request('GET', next_url).json()
            values += res['value']
            next_url = res['@odata.nextLink'] if '@odata.nextLink' in res else None

        return values

    @retry_on_throttling
    def _request(self, method, api_path, params=None, data=None):
        self._refresh_token_if_needed()

        if api_path.startswith(self.graph_base_url):
            url = api_path
        else:
            url = urljoin(self.graph_base_url,
                          '/'.join([self.graph_version] + api_path.split('/')))
        headers = {'Authorization':  self.token['accessToken'],
                   'Content-Type': 'application/json'}
        res = requests.request(method, url, params=params, json=data,
                               headers=headers, stream=False)

        if res.ok:
            self.logger.info(
                f'{res.request.method} {res.status_code} {unquote(res.request.url)}')
        elif res.status_code == 429 and self.retry_enabled:
            self.logger.warn(
                f'{res.request.method} {res.status_code} {unquote(res.request.url)}')
            if 'Retry-After' in res.headers:
                wait_sec = int(res.headers['Retry-After'])
            else:
                wait_sec = randint(self.wait_random_min, self.wait_random_max)

            self.logger.warn(
                f'{res.request.method} {res.status_code} {unquote(res.request.url)}' +
                f'\nThe request will be retried in {wait_sec} secs')
            raise ThrottlingException(wait_sec)
        else:
            self.logger.error(
                f'{res.request.method} {res.status_code} {unquote(res.request.url)}\n{res.headers}\n{res.text}')
            # res.raise_for_status()

        return res

    def get(self, api_path, params=None):
        if self.chasing_enable:
            return self._get_chasing(api_path, params=params)
        else:
            return self._request('GET', api_path, params=params)

    def post(self, api_path, params=None, data=None):
        return self._request('POST', api_path, params=params, data=data)

    def patch(self, api_path, params=None, data=None):
        return self._request('PATCH', api_path, params=params, data=data)

    def put(self, api_path, params=None, data=None):
        return self._request('PUT', api_path, params=params, data=data)

    def delete(self, api_path, params=None, data=None):
        return self._request('DELETE', api_path, params=params, data=data)


#Case number 2:
from ast import Break
from CodeLibrary import Simulation
import scipy
from scipy import optimize
import time
import matplotlib.pyplot as plt
import numpy

"""Created on the 24.05.2022
@author: Richard ten Hagen
@author contact: Richardxtenxhagen@gmail.com

Case Example Nr 2: This code will make use of the brute force optimization algorithm to find the optimal profit for the given flowsheet by varying the following parameters:
- Stagenumber of Column 1
- Reboiler Duty of Column 1
- Stagenumber of Column 1
- Reboiler Duty of Column 1

For the optimization alorithm to be able to function we will need to rewrite our previous process into a function that is called with the variable values as inputs and which returns the profit for the given case.
This function is called: funcToMin and it will return the negative profit.

It consists of multiple substeps:
1. Unpacking the variables(the scipy library wants it that way)
2. Setting the variables into the flowsheet
3. Running the simulation
4. Retrieving the data needed for cost analysis
5. Cost analysis
6. Saving data into global arrays for later plotting and data analysis

The Stagenumber and Reboiler duties will be varies between given maximum and minimum boundries. Since there is a near infinite number of different combinations of these 4 values, which will each have a different expected profit, it will be necessary to find a mechanism by which we can find the maximum profit with minimal number of computations.
This problem is well known in computer science (they call it "non differentiable root finding") and they have developed different algorithms that can solve this.
In this case we will be using the Brute force method. It functions by taking the minimum and maximum bound of each variable and selecting a given number of values in between (aka Ns = 3) for each variable and then going through the different permutations of these combinations.
It is also possible that the process is repeated multiple times where each finished search reduces the bounds to a smaller section until no further optimization is possible.
This method is usually slow but very reliable in its convergence on the minimum. Unlike most other algorithms it does not make use of stochastic which is a major disadvantage.
"""

#lets first just initalize some stuff

sim = Simulation(AspenFileName= "CaseExample2_2.bkp", WorkingDirectoryPath= r"c:/Users/s2371014/Desktop/AspenPythonInterface" ,VISIBILITY=False)

plottingcounterList = []
BreakEvenYearsForInvestmentList = []
AnnualProfitWithInvestmentCostsList = []
AnnualRevenueList = []
TotalAnnualUtilityCostList = []
vList = []
AnnualInvestmentCostsList = []

#Lets do some optimizing depending on some variables. For this we will need to make a function which will be minimized

def funcToMin(v):
    """ This function changes some Variables in Aspen and returns the number of years until investment is paid off
    """
    funcToMin.plottingcounter +=1
    ProductStreamnameList = ["S3","S4","S5"]
    ColumnNameList = ["B1", "B2"]

    # Lets first unpack the the variable
    Blockname1 = ColumnNameList[0]
    Blockname2 = ColumnNameList[1]
    StagenumberColumn1, ReboilerDuty1, StagenumberColumn2, ReboilerDuty2 = v        #These are the variables which the optimizer is allowed to change
    print("The value of v is ", v)

        #Setting the first columns variabels, these are changed by the optimizer
    sim.BLK_RADFRAC_Set_ReboilerDuty(Blockname1 ,ReboilerDuty1)
    sim.BLK_RADFRAC_Set_NSTAGE(Blockname1 ,StagenumberColumn1)
    sim.BLK_RADFRAC_Set_FeedStage(Blockname1,round(StagenumberColumn1/2,0),"S1")
        #Setting the second column
    sim.BLK_RADFRAC_Set_ReboilerDuty(Blockname2 ,ReboilerDuty2)
    sim.BLK_RADFRAC_Set_NSTAGE(Blockname2 ,StagenumberColumn2)
    sim.BLK_RADFRAC_Set_FeedStage(Blockname2,round(StagenumberColumn2/2,0), "S2")

    # Now we run the simulation
    convergence = sim.Run()
    sim.DialogSuppression(TrueOrFalse= True)
    if convergence == False:
        input("Hey the Convergence failed... why?")

    # Get the outputs and print them:
    ##OutputDictColumn1 = sim.BLK_RADFRAC_GET_OUTPUTS(Blockname1)
    ##OutputDictColumn2 = sim.BLK_RADFRAC_GET_OUTPUTS(Blockname2)
    ##print('\n \n Output of first Column \n \n ')
    ##sim.print_dictionary(OutputDictColumn1)
    ##print('\n \n Output of second Column \n \n ')
    ##sim.print_dictionary(OutputDictColumn2)

    #Get inputs for Cost analysis
    Columnpressure1 = sim.BLK_Get_Pressure(Blockname1)
    n_stages1 =sim.BLK_Get_NStages(Blockname1)
    condenser_duty1 = sim.BLK_RADFRAC_Get_Condenser_HeatingDuty(Blockname1)
    reboiler_temperature1 = sim.BLK_RADFRAC_Get_Reboiler_Temperature(Blockname1)
    reboiler_duty1 = sim.BLK_RADFRAC_Get_Reboiler_HeatDuty(Blockname1)
    tops_temperature1 =sim.BLK_RADFRAC_Get_Condenser_Temperature(Blockname1)
    vapor_flows1 =sim.BLK_Get_Column_Stage_Vapor_Flows(Blockname1)
    stage_mw1 = sim.BLK_Get_Column_Stage_Molar_Weights(Blockname1)
    stage_temp1 = sim.BLK_Get_Column_Stage_Temperatures(Blockname1)

    Columnpressure2 =  sim.BLK_Get_Pressure(Blockname2)
    n_stages2=sim.BLK_Get_NStages(Blockname2)
    condenser_duty2= sim.BLK_RADFRAC_Get_Condenser_HeatingDuty(Blockname2)
    reboiler_temperature2 = sim.BLK_RADFRAC_Get_Reboiler_Temperature(Blockname2)
    reboiler_duty2=sim.BLK_RADFRAC_Get_Reboiler_HeatDuty(Blockname2)
    tops_temperature2= sim.BLK_RADFRAC_Get_Condenser_Temperature(Blockname2)
    vapor_flows2=sim.BLK_Get_Column_Stage_Vapor_Flows(Blockname2)
    stage_mw2=sim.BLK_Get_Column_Stage_Molar_Weights(Blockname2)
    stage_temp2=sim.BLK_Get_Column_Stage_Temperatures(Blockname2)

    # Do the Cost analysis
    InvestcostColumn1 = sim.CAL_InvestmentCost(Columnpressure1, n_stages1, condenser_duty1, reboiler_temperature1, reboiler_duty1, tops_temperature1, vapor_flows1, stage_mw1, stage_temp1)
    InvestcostColumn2 = sim.CAL_InvestmentCost(Columnpressure2, n_stages2, condenser_duty2, reboiler_temperature2, reboiler_duty2, tops_temperature2, vapor_flows2, stage_mw2, stage_temp2)
    TotalInvestmentCost = InvestcostColumn1 + InvestcostColumn2
    print("The investment cost of col 1 is: ", InvestcostColumn1, "  The investment cost of col 2 is: ", InvestcostColumn2)

    AnnualUtilityCostColumn1 = sim.CAL_Annual_OperatingCost(reboiler_duty1, condenser_duty1)
    AnnualUtilityCostColumn2 = sim.CAL_Annual_OperatingCost(reboiler_duty2, condenser_duty2)
    TotalAnnualUtilityCost =  AnnualUtilityCostColumn1 + AnnualUtilityCostColumn2
    print("Total annual cost in col 1 is: ", AnnualUtilityCostColumn1,"  Total annual cost in col 2 is: ", AnnualUtilityCostColumn2)

        #Check all streams to find the value of each stream
    AnnualRevenue = 0
    for ProductStreamname in ProductStreamnameList:
        StreamDictionary = sim.STRM_GET_OUTPUTS(ProductStreamname)
        MoleFlowList = StreamDictionary["MoleFlowList"]
        product_specification = 0.95
        total_stream_value, component_purities = sim.CAL_stream_value( MoleFlowList, product_specification)
        if total_stream_value != 0: print("For ", ProductStreamname, " the total stream value is: ", total_stream_value)
        AnnualRevenue = AnnualRevenue + total_stream_value

    AnnualProfit = AnnualRevenue - TotalAnnualUtilityCost
    if AnnualProfit >0: print("The total Annual Profit is: ", AnnualProfit)
    BreakEvenYearsForInvestment = TotalInvestmentCost/AnnualProfit
    if BreakEvenYearsForInvestment >0: print("The Return on investment is: ", BreakEvenYearsForInvestment)

    AnnualInvestmentCosts = TotalInvestmentCost/20
    AnnualProfitWithInvestmentCosts = AnnualRevenue - TotalAnnualUtilityCost - AnnualInvestmentCosts

#Tests to make sure the units are correct
    if TotalAnnualUtilityCost <0:
        print("Hold on Utility costs are negative... that should not be the case... lets punish with 30")
        return 30
    if AnnualRevenue <0:
        print("Hold on AnnualRevenue is negative... that should not be the case")
        return 30

    #Saving the internal function Data into the Global list variables
    BreakEvenYearsForInvestmentList.append(BreakEvenYearsForInvestment)
    AnnualRevenueList.append(AnnualRevenue)
    TotalAnnualUtilityCostList.append(TotalAnnualUtilityCost)
    AnnualInvestmentCostsList.append(AnnualInvestmentCosts)
    vList.append(v)

    AnnualProfitWithInvestmentCostsList.append(-AnnualProfitWithInvestmentCosts)
    plottingcounterList.append(funcToMin.plottingcounter)

    #plt.figure(1)
    #plt.clf()
    #plt.plot(plottingcounterList, AnnualProfitWithInvestmentCostsList,"*")
    #plt.title("Negative Profit(including investment costs)")
    #plt.draw()
    #plt.pause(0.001)

    return -AnnualProfitWithInvestmentCosts

funcToMin.plottingcounter = 0       #Instanciate the function attribute such that it zero the first time it is called.

"""

    plt.figure(2)
    plt.clf()
    RunningMean = sim.running_mean(BreakEvenYearsForInvestmentList,20)
    plt.plot(plottingcounterList, RunningMean,"*")
    plt.title("Running mean of ROI")
    plt.draw()
    plt.pause(0.1)
"""

## THIS FUNCTION DOES OPTIMIZATIONS.....

# define the bounds on the search of #StagenumberColumn1, ReboilerDuty1, StagenumberColumn2, ReboilerDuty2
bounds = [[2, 20], [100, 100000],[2,20],[100,100000]]

#If you want to use brute force
results = optimize.brute(funcToMin,bounds,Ns=3)
#If you want evolutionary strategy
#results = optimize.differential_evolution(funcToMin, bounds, args=(), strategy='best1bin', maxiter=10, popsize=10, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='immediate', workers=1, constraints=(), x0=None)
        #Maximum number of function calls is: (maxiter + 1) * popsize * len(x)

#print the results and values:
print("\n\n\n\n\nThe plottingcounterList is : ",plottingcounterList,"\n\n\n\n")
print("\n\n\n\n\nThe AnnualProfitWithInvestmentCostsList is : ",AnnualProfitWithInvestmentCostsList,"\n\n\n\n")
print("\n\n\n\n\nThe AnnualRevenueList is : ",AnnualRevenueList,"\n\n\n\n")
print("\n\n\n\n\nThe AnnualInvestmentCostsList is : ",AnnualInvestmentCostsList,"\n\n\n\n")
print("\n\n\n\n\nThe BreakEvenYearsForInvestmentList is : ",BreakEvenYearsForInvestmentList,"\n\n\n\n")
print("\n\n\n\n\nThe TotalAnnualUtilityCostList is : ",TotalAnnualUtilityCostList,"\n\n\n\n")
print("\n\n\n\n\nThe vList is : ",vList,"\n\n\n\n")

plt.figure(1)

plt.plot(plottingcounterList, AnnualProfitWithInvestmentCostsList,"*")
plt.title("Negative Profit(including investment costs)")
plt.show()
plt.savefig("NegativeProfitBrute")
plt.clf()
plt.plot(plottingcounterList, AnnualRevenueList,"*")
plt.title("Annual Revenue")
plt.show()
plt.savefig("AnnualRevenueBrute")
plt.clf()
plt.plot(plottingcounterList, AnnualInvestmentCostsList,"*")
plt.title("Annual Revenue")
plt.show()
plt.savefig("AnnualInvestmentCostsBrute")
plt.clf()
plt.plot(plottingcounterList, BreakEvenYearsForInvestmentList,"*")
plt.title("Return on Investment years")
plt.show()
plt.savefig("ROIyearsBrute")
plt.clf()
plt.plot(plottingcounterList, TotalAnnualUtilityCostList,"*")
plt.title("Annual utility costs")
plt.show()
plt.savefig("AnnualUtilityCostsBrute")
plt.clf()

print("Hey the results are:")
print(results)
input("Hey are you happy with life?")

#print("\n\n\nThe total runtime was: ", runtimetotal)

#print("Number of Stages in Column 1")
#print(results["StagenumberColumn1"])
#print("Number of Stages in Column 2")
#print(results["StagenumberColumn2"])
#print("ReboilerDuty1")
#print(results["ReboilerDuty1"])
#print("ReboilerDuty2")
#print(results["ReboilerDuty1"])

"""
# objective function
def objective(v):
	x, y = v
	return -20.0 * exp(-0.2 * sqrt(0.5 * (x**2 + y**2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20

# define range for input
r_min, r_max = -5.0, 5.0
# define the bounds on the search
bounds = [[r_min, r_max], [r_min, r_max]]
# perform the differential evolution search
result = differential_evolution(objective, bounds)
# summarize the result
print('Status : %s' % result['message'])
print('Total Evaluations: %d' % result['nfev'])
# evaluate solution
solution = result['x']
evaluation = objective(solution)
print('Solution: f(%s) = %.5f' % (solution, evaluation))

plt.ion()
for i in range(100):
    x = range(i)
    y = range(i)
    # plt.gca().cla() # optionally clear axes

plt.show(block=True)

"""

def print_dictionary(dct):
    """ Takes Dictionary input with two items inside and prints them"""
    print("Items held:")
    for item, amount in dct.items():  # dct.iteritems() in Python 2
        print("{} ({})".format(item, amount))

sim.CloseAspen()


import sys
import json
import argparse
from os import path
from pydub import AudioSegment
from pydub.playback import play, _play_with_ffplay
import speech_recognition as sr

VERSION = '0.0.2'

nato = [
    'alfa', 'bravo', 'charlie', 'delta',     #  0-3
    'echo', 'foxtrot', 'golf', 'hotel',      #  4-7
    'india', 'juliett', 'kilo', 'lima',      #  8-11
    'mike', 'november', 'oscar', 'papa',     # 12-15
    'quebec', 'romeo', 'sierra', 'tango',    # 16-19
    'uniform', 'victor', 'whiskey', 'xray',  # 20-23
    'yankee', 'zulu', 'one', 'two',          # 24-27
    'three', 'four', 'five', 'six',          # 28-31
    'seven', 'eight', 'nine', 'zero',        # 32-35
    ]

conv = {
 'even': {
    '0': nato[0],
    '1': nato[2],
    '2': nato[4],
    '3': nato[6],
    '4': nato[8],
    '5': nato[10],
    '6': nato[12],
    '7': nato[14],
    '8': nato[16],
    '9': nato[18],
    'a': nato[20],
    'b': nato[22],
    'c': nato[24],
    'd': nato[26],
    'e': nato[28],
    'f': nato[30],
 },
 'odd': {
    '0': nato[1],
    '1': nato[3],
    '2': nato[5],
    '3': nato[7],
    '4': nato[9],
    '5': nato[11],
    '6': nato[13],
    '7': nato[15],
    '8': nato[17],
    '9': nato[19],
    'a': nato[21],
    'b': nato[23],
    'c': nato[25],
    'd': nato[27],
    'e': nato[29],
    'f': nato[31],
 },
 'special': {
    'start': nato[35],
    'silence': 'silence',
 },
}

def main():
    parser = argparse.ArgumentParser(description='NATO alphabet encoder/decoder.')

    subparsers = parser.add_subparsers(dest='command')
    subparsers.required = True

    parser_encode = subparsers.add_parser('encode')
    parser_encode.add_argument('-f', '--filename', help='Audio output filename (.wav)', required=False)
    parser_encode.add_argument('-s', '--speaker', help='Audio output on speakers', action='store_true')
    parser_encode.add_argument('-A', '--ascii-message', help='Message', required=False)
    parser_encode.add_argument('-H', '--hex-message', help='Message', required=False)

    parser_decode = subparsers.add_parser('decode')
    parser_decode.add_argument('-f', '--filename', help='Audio input filename (.wav) - Hic sunt leones!', required=False)
    parser_decode.add_argument('-m', '--mic', help='Audio input from microphone - Hic sunt leones!', required=False)
    parser_decode.add_argument('-M', '--message', help='Message', required=False)

    parser_map = subparsers.add_parser('map')

    parser_version = subparsers.add_parser('version')

    args = parser.parse_args()

    if args.command == 'encode':

        if args.ascii_message is not None or args.hex_message is not None:

            if args.ascii_message is not None:
                fingerprint = args.ascii_message.encode('ascii').hex().lower().replace(' ', '')

            elif args.hex_message is not None:
                fingerprint = args.hex_message.lower().replace(' ', '')

            result = ''
            result_sound =  AudioSegment.from_wav(f"sounds/{conv['special']['silence']}.wav")

            result = f"{result}{conv['special']['start']} "
            result = f"{result}{conv['special']['start']} "
            result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['start']}.wav")
            result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['silence']}.wav")
            result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['start']}.wav")
            result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['silence']}.wav")

            for i in range(0, len(fingerprint)):
                if i % 2 == 0: # Even
                    result = f"{result}{conv['even'][fingerprint[i]]} "
                    result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['even'][fingerprint[i]]}.wav")
                    result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['silence']}.wav")
                else: # Odd
                    result = result + conv['odd'][fingerprint[i]] + ' '
                    result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['odd'][fingerprint[i]]}.wav")
                    result_sound = result_sound + AudioSegment.from_wav(f"sounds/{conv['special']['silence']}.wav")

            print(result)

            if args.filename is not None:
                result_sound.export(args.filename, format='wav')
            if args.speaker:
                _play_with_ffplay(result_sound)

        else:
            print('Missing ascii or hex message parameter')

    elif args.command == 'decode':

        if args.filename is not None or args.mic is not None or args.message is not None:

            if args.mic is not None:
                r = sr.Recognizer()
                with sr.Microphone() as source:
                    print('Say something!')
                    audio = r.listen(source)
                try:
                    # words = r.recognize_sphinx(audio).lower()
                    words = r.recognize_google(audio).lower() # better performace, less privacy
                except sr.UnknownValueError:
                    print('Could not understand audio')
                except sr.RequestError as e:
                    print('Error; {0}'.format(e))

            if args.filename is not None:
                r = sr.Recognizer()
                with sr.AudioFile(path.join(path.dirname(path.realpath(__file__)), args.filename)) as source:
                    audio = r.record(source)
                try:
                    # words = r.recognize_sphinx(audio).lower()
                    words = r.recognize_google(audio).lower() # better performace, less privacy
                except sr.UnknownValueError:
                    print('Could not understand audio')
                except sr.RequestError as e:
                    print('Error; {0}'.format(e))

            elif args.message is not None:
                words = args.message.lower()

            # correct well known mistakes
            words = words.replace('0', 'zero ')
            words = words.replace('1', 'one ')
            words = words.replace('2', 'two ')
            words = words.replace('3', 'three ')
            words = words.replace('4', 'four ')
            words = words.replace('5', 'five ')
            words = words.replace('6', 'six ')
            words = words.replace('8', 'seven ')
            words = words.replace('8', 'eight ')
            words = words.replace('9', 'nine ')
            words = words.replace('alpha', 'alfa')
            words = words.replace('to extract', 'foxtrot')
            words = words.replace('for', 'four')
            words = words.replace('to', 'two')
            words = words.replace('unifourm', 'uniform')
            words = words.replace('juliet', 'juliett')
            words = words.replace('victwor', 'victor')
            words = words.replace('x-ray', 'xray')
            words = words.replace('is', '')
            words = words.replace('dell ten', 'delta')
            words = words.replace('stocks tracts', 'foxtrot')
            words = words.replace('stwocks tracts', 'foxtrot')
            words = words.replace('indiana', 'india')
            words = words.replace('might be', 'mike')
            words = words.replace('x. ray', 'xray')
            words = words.replace('soon', 'zulu')
            words = words.replace('whkey', 'whiskey')
            words = words.replace('   ', ' ')
            words = words.replace('  ', ' ')
            words = ' '.join(words.split())
            print(words)

            words = words.split(' ')
            even = True
            result = ''
            for i in words:
                if not i == conv['special']['start']:
                    if even:
                        even = False
                        character = list(conv['even'].keys())[list(conv['even'].values()).index(i)]
                        result = f'{result}{character}'
                    else:
                        even = True
                        character = list(conv['odd'].keys())[list(conv['odd'].values()).index(i)]
                        result = f'{result}{character}'

            print(result)

        else:
            print('Missing filename or message parameter')

    elif args.command == 'map':
        print(json.dumps(conv, sort_keys=True, indent=4))

    elif args.command == 'version':
        print('NATO encoder - Version {}'.format(VERSION))
        print('GNU GENERAL PUBLIC LICENSE, Version 3')
        print('')
        print('')
        print('GREETINGS PROFESSOR FALKEN')
        print('')
        print('HELLO')
        print('')
        print('A STRANGE GAME.')
        print('THE ONLY WINNING MOVE IS')
        print('NOT TO PLAY.')

if __name__ == '__main__':
    main()


import numpy as np
import diskannpy as diks
import scipy.sparse as sp
import json
import os
import time
from xc.libs.utils import KshardedOva
from sklearn.preprocessing import normalize
from sklearn_extra.cluster import KMedoids
import shutil

# +
def LoL2Sparse(list_of_list, num_cols, padd=True, data=None):
    num_rows = len(list_of_list)
    rows = np.concatenate(list(map(lambda x: [x[0]]*len(x[1]),
                               enumerate(list_of_list))))
    cols = np.concatenate(list_of_list)
    sparse_mat = sp.lil_matrix((num_rows+padd, num_cols+padd), dtype=np.float32)
    if data is None:
        data = np.ones(rows.size, dtype=np.float32)
    data = np.ravel(data)
    sparse_mat[rows, cols] = data
    return sparse_mat.tocsr()

class DiskANN:
    def __init__(self, anns_args= {"L": 300, "R": 100,
                                   "C": 750, "alpha": 1.2,
                                   "saturate_graph": False,
                                   "num_threads": 128},
                 metric="l2", dtype=np.single, num_threads=32,
                 model_path="./"):
        self.model_path = model_path
        self.metric = metric
        self.dtype = dtype
        self.num_threads = num_threads
        self.args = {"ANNS": anns_args}
        self.args["META"] = {}
        self.set_params(self.args["ANNS"])
        self.vects = None
        self.index = None

    def set_params(self, args):
        self.params = {}
        for key in args.keys():
            self.params[key] = args[key]

    def optimize(self):
        pass

    def to_bin(self, vect, path="./"):
        path = os.path.join(path, "vect.bin")
        with open(path, 'wb') as f:
            f.write(vect.shape[0].to_bytes(4, "little"))
            f.write(vect.shape[1].to_bytes(4, "little"))
            f.write(vect.astype(np.float32).tobytes())
        return path

    def get_vect(self):
        path = os.path.join(self.model_path, "index.bin.data")
        with open(path, 'rb') as f:
            N = int.from_bytes(f.read(4), "little")
            D = int.from_bytes(f.read(4), "little")
            data = np.fromfile(f, dtype="float32")
        return data.reshape(N, D)

    def fit(self, vects, model_path=None):
        if model_path is None:
            model_path = self.model_path
        self.model_path = model_path
        if os.path.exists(model_path):
            shutil.rmtree(model_path)
        os.makedirs(self.model_path)
        self.vects = normalize(np.float32(vects))
        start = time.time()
        if os.path.exists(os.path.join(model_path, "vectors.bin")):
            print("Removing old vectors.bin")
            os.remove(os.path.join(model_path, "vectors.bin"))

        diks.build_memory_index(
            data=self.vects,
            distance_metric="l2",
            vector_dtype=np.single,
            index_directory=model_path,
            complexity=self.params["L"],
            graph_degree=self.params["R"],
            num_threads=self.num_threads,
            index_prefix="index.bin",
            alpha=1.2,
            use_pq_build=False,
            num_pq_bytes=8,
            use_opq=False,
        )
        self.index = diks.StaticMemoryIndex(
            distance_metric="l2",
            vector_dtype=np.single,
            index_directory=model_path,
            num_threads=self.num_threads,  # this can be different at search time if you would like
            initial_search_complexity=self.params["L"],
            index_prefix="index.bin"
        )
        end = time.time()
        self.args["META"]["num_nodes"] = vects.shape[0]
        print(f"build_time {end-start}")

    def save(self, out_path=None):
        if out_path is None:
            out_path = self.model_path
        self.model_path = out_path
        os.makedirs(out_path, exist_ok=True)
        print("Model is already saved")
        path = os.path.join(out_path, "args.json")
        with open(path, 'w', encoding='utf-8') as f:
            json.dump(self.args, f, ensure_ascii=False, indent=4)

    def load(self, in_path):
        if in_path is None:
            in_path = self.model_path
        self.model_path = in_path
        self.index = diks.StaticMemoryIndex(
            distance_metric="l2",
            vector_dtype=np.single,
            index_directory=in_path,
            num_threads=self.num_threads,  # this can be different at search time if you would like
            initial_search_complexity=self.params["L"],
            index_prefix="index.bin"
        )

        path = os.path.join(in_path, "args.json")
        with open(path, 'r', encoding='utf-8') as f:
            self.args = json.load(f)
        self.set_params(self.args["ANNS"])

    def get_index(self, offset=6, sparse=False, prefix="index.bin"):
        path = os.path.join(self.model_path, f"{prefix}")
        data = np.fromfile(path, dtype='uint32')   #1, 2, 2, 1, 2, 3, 1, 2, 3
        num_nodes = 0
        _index = []
        _meta = data[:offset]
        while(offset < data.shape[0]):
            num_nodes += 1
            start = offset + 1
            end = start + data[offset]
            _index.append(data[start:end])
            offset = end
        del data
        if sparse:
            _index = LoL2Sparse(_index, self.args["META"]["num_nodes"], False)
        self._meta = _meta
        return _meta, _index

    def update(self, LoL, _meta=None, vect=None, outfile = None):

        if _meta is None:
            print("Using preset meta")
            _meta = self._meta
        if outfile is not None:
            if os.path.exists(outfile):
                shutil.rmtree(outfile)
            os.makedirs(outfile, exist_ok=True)
            self.model_path = outfile
            if vect is None:
                vect = self.vects
            else:
                print("Normalizing the vectors")
                vect = normalize(vect)

        if vect is None:
            vect = os.path.join(self.model_path, "index.bin.data")

        path = os.path.join(self.model_path, "index.bin")
        kmedoids = KMedoids(n_clusters=1, random_state=22).fit(vect)
        _meta[3] = kmedoids.medoid_indices_[0]
        print("New metadata", _meta)

        if sp.issparse(LoL):
            LoL = LoL.tolil()
            LoL[_meta[3], :] = 1
            LoL = LoL.rows
        new_idx = []
        for u in range(len(LoL)):
            new_idx.extend([len(LoL[u])] + LoL[u])

        file_size = len(new_idx) * 4 + 24
        x = np.array(new_idx).astype(np.uint32)
        with open(path, 'wb') as f:
            f.write(file_size.to_bytes(8, "little"))
            f.write(_meta[2:].tobytes())
            f.write(x.tobytes())

        diks.update_memory_index(path, "l2", self.model_path,
                                 self.params["R"], self.params["R"],
                                 self.num_threads, 1.2,
                                 np.single, "index.bin", vect)
        self.save(self.model_path)
        self.load(self.model_path)
        return True

    def update_node_vect(self, node_vect):
        N, d = node_vect.shape
        assert N == self.args["META"]["num_nodes"]
        self.index.update_node_vect(node_vect, N, d)

    def _set_query_time_params(self, R):
        anns_args= {"L": 3*R, "R": R,
                    "C": 7*R, "alpha": 1.2,
                    "saturate_graph": False,
                    "num_threads": 128}
        self.args["ANNS"] = anns_args

    def query(self, q_data, R=None, L=None):
        q_data = normalize(q_data)
        if L is None:
            L = self.args["ANNS"]["L"]

        if R is None:
            R = self.args["ANNS"]["R"]
        idx, scr, = self.index.batch_search(q_data, R, L, self.num_threads)
        return idx, -scr # converting distance to similarity


import pyrealsense2.pyrealsense2 as rs
import numpy as np
import cv2
import logging

class RealSense:
    def __init__(self):
        # Set up pipeline
        self.__pipeline__ = rs.pipeline()
        self.__color_sensor__ = None
        self.__config__ = rs.config()
        self.__color_frame__ = None
        self.__depth_frame__ = None
        self.__color_image__ = None
        self.__depth_image__ = None
        self.__device__ = None
        self.__pipeline_profile__ = None

    def get_color_frame(self):
        return self.__color_frame__
    def get_depth_frame(self):
        return self.__depth_frame__
    def get_color_image(self):
        return self.__color_image__
    def get_depth_image(self):
        return self.__depth_image__

    def initialize_real_sense(self):
        # Get device product line for setting a supporting resolution
        pipeline_wrapper = rs.pipeline_wrapper(self.__pipeline__)
        self.__pipeline_profile__ = self.__config__.resolve(pipeline_wrapper)
        self.__device__ = self.__pipeline_profile__.get_device()
        self.__color_sensor__ = self.__device__.query_sensors()[1]
        self.__color_sensor__.set_option(rs.option.enable_auto_exposure, True)
        device_product_line = str(self.__device__.get_info(rs.camera_info.product_line))

        found_rgb = False
        for s in self.__device__.sensors:
            if s.get_info(rs.camera_info.name) == 'RGB Camera':
                found_rgb = True
                break
        if not found_rgb:
            print("The demo requires Depth camera with Color sensor")
            exit(0)
        # Configure depth and color streams
        self.__config__.enable_stream(rs.stream.depth, 424, 240, rs.format.z16, 30)

        if device_product_line == 'D435i':
            self.__config__.enable_stream(rs.stream.color, 424, 240, rs.format.bgr8, 30)
        else:
            self.__config__.enable_stream(rs.stream.color, 424, 240, rs.format.bgr8, 30)

        # Start streaming
        self.__pipeline__.start(self.__config__)

    def get_color_depth_image(self):

        frames = self.__pipeline__.wait_for_frames()

        # Get aligned frames
        # aligned_frames = self.__pipeline__.process(frames)

        self.__color_frame__ = frames.get_color_frame()
        self.__depth_frame__ = frames.get_depth_frame()

        #convert frames to images
        self.__color_image__ = np.asanyarray(self.__color_frame__.get_data(), dtype=np.uint8)
        self.__depth_image__ = np.asanyarray(self.__depth_frame__.get_data(), dtype=np.uint16)

# Creates align object for depth frame
# align_to = rs.stream.color
# align = rs.align(align_to)

    def set_all_bounding_box_depth_values(self, box_list):
        if box_list == None or len(box_list) == 0:
            return
        else:
            invalid_depth_value = False
            for i in range(len(box_list)):
                depth_value = self.get_depth_value_from_bounding_box(box_list[i])

                #check for invalid depth values
                if(np.isnan(depth_value)):

                    invalid_depth_value = True
                    return invalid_depth_value

                box_list[i].set_depth(depth_value)

#index into depth image using coordinates from bounding box
#helper function for set_all_bounding_box_depth_values()
    def get_depth_value_from_bounding_box(self, bounding_box):
        #return a float by indexing into the numpy array using the coordinates given by the bounding box
        x1, x2 = bounding_box.get_x_value()
        y1, y2 = bounding_box.get_y_value()

        color_frame = self.__color_frame__
        depth_frame = self.__depth_frame__
        profile = self.__pipeline_profile__

        color_intrinsics = color_frame.profile.as_video_stream_profile().intrinsics
        depth_intrinsics = depth_frame.profile.as_video_stream_profile().intrinsics
        depth_to_color_extrin = depth_frame.get_profile().as_video_stream_profile().get_extrinsics_to(color_frame.get_profile())
        color_to_depth_extrin = color_frame.get_profile().as_video_stream_profile().get_extrinsics_to(depth_frame.get_profile())
        depth_scale = profile.get_device().first_depth_sensor().get_depth_scale()
        # fov = rs.rs2_fov(color_intrinsics)

        corner_one = rs.rs2_project_color_pixel_to_depth_pixel(
        depth_frame.get_data(), depth_scale,
        0.1, 15,
        depth_intrinsics, color_intrinsics, depth_to_color_extrin, color_to_depth_extrin, (x1, y1))

        corner_two = rs.rs2_project_color_pixel_to_depth_pixel(
        depth_frame.get_data(), depth_scale,
        0.1, 15,
        depth_intrinsics, color_intrinsics, depth_to_color_extrin, color_to_depth_extrin, (x2, y2))

        x1 = int(corner_one[0])
        y1 = int(corner_one[1])

        x2 = int(corner_two[0])
        y2 = int(corner_two[1])
        #TODO
        #Why is this array sometimes empty?
        #Either ignore NaNs or catch edge cases for when NaNs occur
        depth_box = self.__depth_image__[y1:y2, x1:x2]
        # logging.debug("x1, y1, x2, y2", str(x1, y1, x2, y2))
        # logging.debug("Depth box: ", str(depth_box))
        #not using nanmean as nan is not returned for integer data types

        depth_value = np.mean(depth_box[np.nonzero(depth_box)])

        #To convert to meters, also casts depth value to np.float32
        depth_value = depth_value/np.float32(1000)
        # logging.debug("Depth value: ", str(depth_value))

        return depth_value

    def get_intrinsics(self):
        pipeline_wrapper = rs.pipeline_wrapper(self.__pipeline__)
        pipeline_profile = self.__config__.resolve(pipeline_wrapper)
        profile = pipeline_profile.get_stream(rs.stream.depth)
        intrinsics = profile.as_video_stream_profile().get_intrinsics()

        # profile = config.get_stream(rs.stream.depth)
        # intrinsics = profile.as_video_stream_profile().get_intrinsics()
        return intrinsics


import traceback
import numpy as np
import torch
from tqdm import tqdm

import utils.checkpoint as cu
import utils.logging as logging
import utils.misc as misc
from datasets import loader
from models import build_model
from config.defaults import assert_and_infer_cfg
from utils.misc import launch_job
from utils.parser import load_config, parse_args
from utils.compute_all_metrics import evaluate
from copy import copy
import os
import pycocotools.mask as m
import cv2

logger = logging.get_logger(__name__)

def postprocesses_n_save(preds_dict,cfg):
    print('Beggining noise postprocessing...')
    funct = lambda x: 2*(x[0]*x[1])/(x[1]+x[0])

    for video in preds_dict:
        preds_dict[video].sort(key=lambda x: x[3])
        cat_preds = np.array([u[0] for u in preds_dict[video]])
        scores = np.array([u[1] for u in preds_dict[video]])
        frames = ['{:0>9}'.format(u[3]*6) for u in preds_dict[video]]

        scores = np.array(scores)
        #Noise filtering by replacing frame neighbour with higher scores
        for _ in tqdm(range(250),desc='{} postprocessing ...'.format(video)):
            new_scores = copy(scores)
            new_preds = copy(cat_preds)
            for idx in range(1,len(scores)-1):
                ant = max(0,idx-1)
                nex = min(len(cat_preds)-1,idx+1)
                if (cat_preds[ant] != cat_preds[idx]) or (cat_preds[idx] != cat_preds[nex]):
                    if cat_preds[ant] == cat_preds[nex] and np.mean([scores[ant],scores[nex]])>scores[idx]:
                        new_preds[idx] = cat_preds[ant] if ant>0 else cat_preds[nex]
                        new_scores[idx] = scores[ant] if ant>0 else scores[nex]
                    elif cat_preds[ant] != cat_preds[nex] and (scores[ant]>scores[idx] or scores[nex]>scores[idx]):
                        if scores[ant] > scores[nex] :
                            new_preds[idx] = cat_preds[ant]
                            arr = [scores[ant],scores[idx]]
                            new_scores[idx] = float(funct(arr))
                        elif scores[ant] < scores[nex]:
                            new_preds[idx] = cat_preds[nex]
                            arr = [scores[nex],scores[idx]]
                            new_scores[idx] = float(funct(arr))
            scores = new_scores
            cat_preds = new_preds

        with open(os.path.join(cfg.OUTPUT_DIR,video,'action_discrete.txt'),'w') as f:
            lines = ['{},{}\n'.format(frames[i],cat_preds[i]) for i in range(len(cat_preds))]
            f.writelines(lines)
    print('Postprocessing finished.')

def floatbox(box):
    """
        Transform string key box to list of floats box
    """
    keys = box.split(' ')
    return list(map(float,keys))

def boxiou(bb1,bb2):
    """
        Get the IoU of two bboxes
        bb1: Bbox 1
        bb2: Bbox 2
    """

    #Calculate coordinates of the intersection box
    x1 = max(bb1[0],bb2[0])
    y1 = max(bb1[1],bb2[1])
    x2 = min(bb1[2],bb2[2])
    y2 = min(bb1[3],bb2[3])

    #Return 0 if intersection box is outside any box
    if x2<x1 or y2<y1:
        return 0.0

    #Calculate box IoU
    inter = (x2-x1)*(y2-y1)
    area1 = (bb1[2]-bb1[0])*(bb1[3]-bb1[1])
    area2 = (bb2[2]-bb2[0])*(bb2[3]-bb2[1])
    box_iou = inter/(area1+area2-inter)
    assert box_iou>=0 and box_iou<=1
    return box_iou

def getrealbox(boxes,box):
    """
        Get the real original box for a box with small decimal diferences.
    """
    maxiou = 0
    realbox = ''
    for box2 in boxes:
        iou = boxiou(floatbox(box2),box)
        if iou>maxiou:
            maxiou=iou
            realbox=box2

    assert maxiou>0.99 and maxiou<=1, 'Incorrect max IoU {}'.format(maxiou)
    return realbox

def saveSegments(preds,cfg):
    print('Beggining proposals to segmentation processing...')
    try:
        features = torch.load(os.path.join(cfg.OUTPUT_DIR,'stuff_MS','features.pth'))
        feat_dict = {}
        for feat in features:
            feat_dict[feat['file_name']] = feat

        for video in preds:
            for image in preds[video]:
                if image[3]%10==0:
                    im_name = image[2]
                    feat = feat_dict[im_name]
                    sem_im = np.zeros((1080,1920))
                    all_segmets = []
                    for bbox,box_pred in zip(image[0],image[1]):
                        if bbox != [0.0,0.0,0.0,0.0]:
                            category = np.argmax(box_pred)
                            score = box_pred[category]
                            real_box = getrealbox(feat['bboxes'].keys(),bbox)

                            if category<9:
                                #Box values are not exactly the same so the real box must be found with IoU
                                segment = feat['segments'][real_box]
                                all_segmets.append((category+1,segment,score))
                    all_segmets.sort(key=lambda x: x[2])

                    sem_im = np.zeros((1080,1920))
                    for segment in all_segmets:
                        mask = m.decode(segment[1]).astype('uint8')
                        sem_im[mask==1]=segment[0]

                    cv2.imwrite(os.path.join(cfg.OUTPUT_DIR,im_name),sem_im)
    except:
        traceback.print_exc()
        breakpoint()
    print('Proposals to segmentation finished.')

@torch.no_grad()
def eval_epoch(val_loader, model, cfg):
    """
    Evaluate the model on the test set.
    Args:
        val_loader (loader): data loader to provide validation data.
        model (model): model to evaluate the performance.
        cfg (CfgNode): configs. Details can be found in
            slowfast/config/defaults.py
    """

    # Evaluation mode enabled.
    preds_dict = {}
    box_preds_dict = {}
    model.eval()
    for inputs,image_names,sec,video_name,ori_boxes,features,idxs in tqdm(val_loader,desc='Action recognition and proposal classification predictions ...'):
        if cfg.NUM_GPUS:
            # Transferthe data to the current GPU device.
            if isinstance(inputs, (list,)):
                for i in range(len(inputs)):
                    inputs[i] = inputs[i].cuda(non_blocking=True)
            else:
                inputs = inputs.cuda(non_blocking=True)
            features = features.cuda(non_blocking=True)

        #Predictions for the "gestures" (action recognition) and "tools" (instrument segmentation) tasks
        all_preds = model(inputs,features,idxs)
        preds = all_preds['gestures'][0].cpu().tolist()
        box_preds = all_preds['tools'][0].cpu().numpy()
        assert len(preds)==len(image_names)==len(sec)==len(video_name)

        #Save predicitions for later postprocessing and final file saving
        for nid,name in enumerate(video_name):
            if name not in preds_dict:
                preds_dict[name] = []
                box_preds_dict[name] = []
            category = int(np.argmax(preds[nid]))
            preds_dict[name].append((category, preds[nid][category], image_names[nid], sec[nid], name))
            this_box_preds = box_preds[idxs==nid].tolist()
            this_ori_boxes = ori_boxes[idxs==nid].tolist()
            box_preds_dict[name].append((this_ori_boxes, this_box_preds, image_names[nid], sec[nid], name))

    print('Action recognition and sementation inference have finished. Now performing noise postprocessing and proposal processing...')

    postprocesses_n_save(preds_dict,cfg)
    print('Action recognition predictions at {}/video_*/action_discrete.txt'.format(cfg.OUTPUT_DIR))

    saveSegments(box_preds_dict,cfg)
    print('Instrument segmentation predictions at {}/video_*/segmentation'.format(cfg.OUTPUT_DIR))

def test(cfg):
    """
    Perform testing on the pretrained video model.
    Args:
        cfg (CfgNode): configs. Details can be found in
            config/defaults.py
    """
    # Setup logging format.
    logging.setup_logging(cfg.OUTPUT_DIR)

    # Build the video model and print model statistics.
    model = build_model(cfg)

    # Load checkpoints
    cu.load_test_checkpoint(cfg, model)

    # Create video testing loaders.
    test_loader = loader.construct_loader(cfg, "val")
    logger.info("Testing model for {} iterations".format(len(test_loader)))
    logger.info("Data taken from {}".format(os.getenv('TEST_DIR')))

    # # Perform test on the entire dataset.
    with torch.no_grad():
        eval_epoch(test_loader, model, cfg)

def main():
    """
    Main function to spawn the train and test process.
    """
    args = parse_args()
    cfg = load_config(args)
    cfg = assert_and_infer_cfg(cfg)

    # Perform multi-clip testing.
    if cfg.TEST.ENABLE:
        launch_job(cfg=cfg, init_method=args.init_method, func=test)

if __name__ == "__main__":
    main()