#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from .KITConfig import KITConfig
from .kitdata import KITData
from .kitlodger import KITLodger
from collections import OrderedDict
from .Utils import kitutils
import itertools
import logging

class KITMatplotlib(object):

    def __init__(self, cfg=None,is_cfg_new=None):

        self.__graphs = []
        self.__lodgers = []

        self.logger = logging.getLogger(__name__)
        self.logger.setLevel(logging.DEBUG)

        # load style parameters from cfg file
        self.__initStyle(cfg)
        self.__is_cfg_new = is_cfg_new

    def __initStyle(self, cfg):
        """ Loads and sets various parameters from cfg file which are then used
            to create the desired plot.

        """
        self.cfg = cfg
        # Canvas Options
        self.canvasSize = kitutils.extractList(cfg['Canvas','CanvasSize'], 'float')

        # Pad Options
        self.grid = True
        self.gridOptions = ('w', '-', '0.5')
        self.padSize = kitutils.extractList(cfg['Canvas','PadSize'], 'float')

        # Title options
        self.title = cfg['Title','Title']
        self.titleFont = cfg['Title','Font']
        self.titleFontSize = cfg['Title','FontSize']
        self.titleFontStyle = cfg['Title','FontStyle']
        self.titleOffset = 1 + cfg['Title','Offset']/100.

        # Axis Options
        self.labelX = cfg['XAxis','Title']
        self.labelY = cfg['YAxis','Title']
        self.rangeX = kitutils.extractList(cfg['XAxis','Range'], "float")
        self.rangeY = kitutils.extractList(cfg['YAxis','Range'], "float")
        self.fontX = cfg['XAxis','Font']
        self.fontY = cfg['YAxis','Font']
        self.fontSizeX = cfg['XAxis','FontSize']
        self.fontSizeY = cfg['YAxis','FontSize']
        self.fontStyleX = cfg['XAxis','FontStyle']
        self.fontStyleY = cfg['YAxis','FontStyle']
        self.absX = cfg['XAxis','Abs']
        self.absY = cfg['YAxis','Abs']
        self.logX = cfg['XAxis','Log']
        self.logY = cfg['YAxis','Log']
        self.tickX = cfg['XAxis','SciTick']
        self.tickY = cfg['YAxis','SciTick']

        # Marker Options
        self.markerSize = cfg['Marker','Size']
        self.markerSet = kitutils.extractList(cfg['Marker','Set'])
        self.hollowMarker = kitutils.extractList(cfg['Marker','HollowMarker'])

        #Line options
        self.colorPalette = cfg['Line','ColorPalette']
        self.colorSet = kitutils.extractList(cfg['Line','Color'])
        self.lineWidth = cfg['Line','Width']
        self.lineStyle = kitutils.extractList(cfg['Line','Style'])
        self.err = cfg['Line','ErrorBars']

        # KITPlot specific options
        self.norm = kitutils.extractList(cfg['Misc','Normalization'])
        self.splitGraph = cfg['Misc','SplitGraph']

        # legend options
        self.__entryDict = cfg['Legend','EntryList']
        self.legPosition = cfg['Legend','Position']

        # sets
        self.markers = {'s': 'square', 'v': 'triangle_down', '^': 'triangle_up',
                        '<': 'triangle_left', '>': 'triangle_right',
                        '8': 'octagon', 'p': 'pentagon', '*': 'star',
                        'h': 'hexagon1', 'H': 'hexagon2',
                        'D': 'diamond', 'd': 'thin_diamond', 'P': 'plus_filled',
                        'X': 'x_filled'}
        self.lines = ['None', '-', '--', '-.', ':']
        self.colors = self.__initColor()

        return True

    def __initColor(self):

        # standard mpl colorSet
        mpl_std = ['r', 'g', 'b', 'c', 'm', 'y', 'k']
        # KITcolor dictionary
        self.KITcolor = kitutils.get_KITcolor()

        if self.colorPalette == "std":
            mpl_std_sorted = [item for (i,item) in sorted(zip(self.colorSet, mpl_std))]
            return mpl_std_sorted
        elif self.colorPalette == "KIT":
            return list(self.KITcolor.keys())
        else:
            print("Warning:::Invalid 'ColorPalette' value. Using KITcolor as default")
            return list(self.KITcolor.keys())

    def addGraph(self, arg):
        """ Converts data of KITData objects or lists into a respective formate
        and writes them into .__graphs. Lodgers are seperated and written into
        .__lodgers.

        Args: x, y or KITData

        """

        x = []
        y = []
        dx = []
        dy = []
        if isinstance(arg, KITData):
            if KITData().getRPunchDict() == None:
                # self.__files.append(arg)
                # toggle absolute mode
                if self.absX:
                    x = list(np.absolute(arg.getX()))
                else:
                    x = arg.getX()
                if self.absY:
                    y = list(np.absolute(arg.getY()))
                else:
                    y = arg.getY()
                # get error bars if present
                if arg.getdX() != [] and arg.getdY() != []:
                    dx = arg.getdX()
                    dy = arg.getdY()
                elif arg.getdX() == [] and arg.getdY() == []:
                    pass
                else:
                    raise ValueError("Check data table. Only 2 (x,y) or "
                                     "4 (x,y,dx,dy) coordinates are allowed.")
                # create graph list
                if dx == [] and dy == []:
                    self.__graphs.append([x, y])
                elif dx != [] and dy != []:
                    self.__graphs.append([x, y, dx, dy])
                else:
                    raise ValueError("z-error not implemented yet")
            # Rpunch
            else:
                raise ValueError("Dictionary error")

        elif isinstance(arg, list) and len(arg) in [2,4]:

            if self.absX:
                x = list(np.absolute(arg[0]))
            else:
                x = arg
            if self.absY:
                y = list(np.absolute(arg[1]))
            else:
                y = arg[1]
            if len(args) == 4:
                dx = arg[2]
                dy = arg[3]

            # create graph list
            if dx == [] and dy == []:
                self.__graphs.append([x, y])
            elif dx != [] and dy != []:
                self.__graphs.append([x, y, dx, dy])
            else:
                raise ValueError("z-error not implemented yet")

        # add lodger
        elif isinstance(arg, KITLodger):
            self.__lodgers.append(arg)

        else:
            raise ValueError("Cant add following graph: " + str(arg))

        return True

    def draw(self, fileList):
        """
        doc

        """

        # create self.__graphs list
        for i, dset in enumerate(fileList):
            self.addGraph(dset)

        # read and adjsut .__entryDict before drawing
        self.readEntryDict(len(self.__graphs),self.getDefaultEntryDict(fileList))

        # interpret all entries in single file as graphs instead of a singel graph
        if self.splitGraph is True and len(self.__graphs) == 1:
            self.__graphs = [list(item) for item in zip(*self.__graphs[0])]

            # adjust entryDict
            newLength = len(self.__graphs)
            if len(self.__entryDict) != newLength:
                self.__entryDict = OrderedDict([])
                for i in range(0,newLength):
                    self.__entryDict.update({str(i) : "Data"+str(i)})
                self.cfg["Legend","EntryList"] = self.__entryDict

        elif self.splitGraph is True and len(self.__graphs) != 1:
            print("Warning::Can only split single graph. Request rejected")

        # apply user defined normalization or manipulation of y values of each graph
        kitutils.manipulate(self.__graphs, self.norm)

        # create an empty canvas with canvas size in [inch]: 1 inch = 2.54 cm
        fig = plt.figure(figsize=list(map(lambda x: x/2.54, self.canvasSize)))

        # specify (nrows, ncols, axnum)
        ax = fig.add_subplot(1, 1, 1)

        # adjust pad size: [left, bottom, width, height]
        ax.set_position(self.padSize)

        # adjust axis tick
        if self.tickX:
            plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))
        if self.tickY:
            plt.ticklabel_format(style='sci', axis='y', scilimits=(0,0))

        for i, table in enumerate(self.__graphs):
            if isinstance(self.hollowMarker, list) and i in self.hollowMarker:
                markerface = 'white'
            else:
                markerface = self.getColor(i)
            ax.plot(table[0],                           # x-axis
                    table[1],                           # y-axis
                    color=self.getColor(i),             # line color
                    marker=self.getMarker(i),           # marker style
                    markersize=self.markerSize,
                    markerfacecolor=markerface,
                    linewidth=self.lineWidth,
                    linestyle=self.getLineStyle(i),
                    label=self.getLabel(i))
        # set error bars
        for i, table in enumerate(self.__graphs):
            if len(table) == 4 and self.err == True:
                ax.errorbar(table[0],table[1],xerr=table[2],yerr=table[3],
                            color=self.getColor(i),
                            elinewidth=1)
            elif len(table) != 4 and self.err == True:
                print("Warning::Can't find x- and y-errors in file. Request "
                      "rejected.")

        # set titles
        # weights = ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black']
        ax.set_title(self.title,
                     fontsize=self.titleFontSize,
                     y=self.titleOffset,
                     fontweight=self.titleFontStyle)
        ax.set_xlabel(self.labelX,
                      fontsize=self.fontSizeX,
                      fontweight=self.fontStyleX)
        ax.set_ylabel(self.labelY,
                      fontsize=self.fontSizeY,
                      fontweight=self.fontStyleY)

        # set log styles
        if self.logX:
            ax.semilogx()
        if self.logY:
            ax.semilogy()

        # set grid
        if self.grid == True:
            # *args = [color,linstyle,linewidth]
            ax.grid()

        # set axis range manually
        if self.rangeX != 'auto':
            ax.set_xlim(self.rangeX)
        if self.rangeY != 'auto':
            ax.set_ylim(self.rangeY)

        self.setLegend(ax)

        # x = [graph[0][0] for graph in self.__graphs]
        # y = [graph[1][0] for graph in self.__graphs]
        # x = self.__graphs[0][0]
        # y = self.__graphs[0][1]
        # y = [y for y in self.__graphs[0][1]]
        # y2 = [y for y in self.__graphs[1][1] if y > 0.3e-12]
        # y3 = [y for y in self.__graphs[1][1] if y > 0.3e-12]
        # y = y1+y2+y3
        # print(y)
        # print(np.mean(y), np.std(y))
        # m,b = np.polyfit(x,y,1)
        # print(1/m,b)
        # t = np.arange(0,0.018,0.001)
        # f = m*t+b
        # ax.plot(t, f, color='black')
        # # ax.xaxis.get_children()[1].set_size(14)
        # ax.xaxis.get_children()[1].set_weight("bold")
        # ax.set_xticklabels
        # ax.axhline(y=12000,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        # ax.axhline(y=8400,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        # ax.axhline(y=2100,color=self.KITcolor['KITred'][3][1],linewidth=10,linestyle='-',zorder=0)
        return fig

    def setLegend(self, obj):

        # get names from cfg and lodger labels
        graphEntries = [items[1] for items in list(self.__entryDict.items())]

        total_len = len(self.__graphs)

        # reorder legend items according to 'EntryList'
        handles,labels = obj.get_legend_handles_labels()
        # handles = self.adjustOrder(handles)
        # labels = self.adjustOrder(labels)
        handles = kitutils.adjustOrder(handles, self.__entryDict, total_len)
        labels = kitutils.adjustOrder(labels, self.__entryDict, total_len)

        if self.legPosition == "auto":
            obj.legend(handles,labels)
        elif self.legPosition == "TL":
            obj.legend(handles,labels,loc='upper left')
        elif self.legPosition == "BL":
            obj.legend(handles,labels,loc='lower left')
        elif self.legPosition == "TR":
            obj.legend(handles,labels,loc='upper right')
        elif self.legPosition == "BR":
            obj.legend(handles,labels,loc='lower right')
        elif self.legPosition == "test2":
            obj.legend(handles,labels,bbox_to_anchor=(0., 1.17, 1., .102),
                       loc='upper right',ncol=3, mode="expand", borderaxespad=0.)
        elif self.legPosition == "test":
            obj.legend(handles,labels,bbox_to_anchor=(0., 0.,1.,1.),
                       loc='lower left',ncol=3, mode="expand", borderaxespad=0.)
        elif self.legPosition == "below":
            obj.legend(handles,labels,bbox_to_anchor=(0., -0.32, 1., .102),
                       loc='lower center',ncol=3, mode="expand", borderaxespad=0.)
        return True

    def getLabel(self, index):

        label = [items[1] for items in list(self.__entryDict.items())]
        return label[index]

    def getMarker(self, index):
        """ Returns a valid marker value for matplotlib's plot() function. If
        'MarkerSet' is a list, the method will cycle the list's items until all
        graphs are taken care of.

            Args:
                index (int): represents an iterator marking a certain graph in
                             .__graphs
        """

        try:
            # assign same marker to all graphs
            if isinstance(self.markerSet, int):
                return list(self.markers.keys())[self.markerSet]
            # cycle list of strings
            elif all(isinstance(item, str) for item in self.markerSet):
                for i, item in enumerate(itertools.cycle(self.markerSet)):
                    if index == i:
                        return item
            # cycle list of integers
            elif all(isinstance(item, int) for item in self.markerSet):
                for i, item in enumerate(itertools.cycle(self.markerSet)):
                    if index == i:
                        return list(self.markers.keys())[item]
        except:
            print("Warning:::Invalid value in 'MarkerSet'. Using default instead.")
            return list(self.markers.keys())[index]

    def getColor(self, index):

        try:
            # self.colors represents color_keys in KITcolor
            if all(isinstance(item, int) for item in self.colorSet) \
                        and isinstance(self.colorSet, list):
                for i, item in enumerate(itertools.cycle(self.colorSet)):
                    if i == index:
                        color = self.KITcolor[self.colors[item]][0][1]
                        return color

            # if colors in 'ColorSet' are strings and correspond to entries
            # in KITcolor dict
            elif all(isinstance(item, str) for item in self.colorSet) \
                        and isinstance(self.colorSet, list):
                # in case there are less entries in colorSet than needed we n
                # eed to cycle that list
                for i, cycled in enumerate(itertools.cycle(self.colorSet)):
                    if i == index:
                        color = cycled
                        break
                # search for RGB values in KITcolor dict for given color key
                for colorDict in list(self.KITcolor.values()):
                    try:
                        return colorDict[color]
                    except:
                        pass
                raise Exception
                color
        except:
            print("Warning:::Invalid input in 'ColorSet'. Using default instead.")
            for i, color in enumerate(itertools.cycle(self.colors)):
                if i == index:
                    return list(self.KITcolor[color].values())[0]

    def getLineStyle(self, index):

        try:
            if isinstance(self.lineStyle, int):
                return self.lines[self.lineStyle]
            elif all(isinstance(item, str) for item in self.lineStyle) \
                    and isinstance(self.lineStyle, list):
                for i, item in enumerate(itertools.cycle(self.lineStyle)):
                    if item not in self.lines:
                        raise ValueError
                    if index == i:
                        return item
            elif all(isinstance(item, int) for item in self.lineStyle) \
                    and isinstance(self.lineStyle, list):
                for i, item in enumerate(itertools.cycle(self.lineStyle)):
                    if index == i:
                        return self.lines[item]
        except:
            print("Warning:::Invalid value in 'LineStyle'. Using default instead.")
            return self.lines[1]

    def getGraphList(self):
        return self.__graphs

    def readEntryDict(self, exp_len, def_list):
        """'EntryList' makes the names and order of all graphs accessible. This
        subsection is read every time KITPlot is executed. An empty value ("")
        can be used to reset the entry to its default value (the original order
        and names given by .__files).
        """
        # writes entry dict to cfg and sets it back to default if value is ""
        if self.cfg['Legend','EntryList'] == "":
            self.cfg['Legend','EntryList'] = def_list
            self.__entryDict = def_list
            if self.__is_cfg_new == False:
                print("EntryDict was set back to default!")

        # calculate expected number of entries in 'EntryList'
        if len(self.__entryDict) != exp_len and self.splitGraph == False:
            raise KeyError("Unexpected 'EntryList' value! Number of graphs and "
                           "entries does not match or a key is used more than"
                           "once. Adjust or reset 'EntryList'.")
        return True

    def fixEntryDict(self):

        # get key list from 'EntryList'
        keys = [int(key) for key in self.__entryDict.keys()]

        # key list should start at 0 and should have a length of len(keys)
        straight_list = list(range(len(keys)))
        # print("fix", straight_list)

        # get reference list in respect to the original order of key list
        ref_list = [y for (x,y) in sorted(zip(keys, straight_list))]

        # reorder reference list so that values stay in the same order as before
        fixed_order = [y for (x,y) in sorted(zip(ref_list, straight_list))]

        values = list(self.__entryDict.values())
        new = OrderedDict(zip(fixed_order, values))
        self.__cfg['Legend','EntryList'] = new

    def getDefaultEntryDict(self,List):
        """ Loads default names and order in respect to the KITData objects
        in 'self.__files' list. Both keys and values of the dictionary must be
        strings.

        """
        entryDict = OrderedDict()
        # write legend entries in a dict
        for i, graph in enumerate(List):
            entryDict[i] = str(graph.getName())

        return entryDict


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Mar 12 00:20:31 2017

@author: loljkpro
"""
import os, sys, re, shutil

# load directory name
dir = os.getcwd()
if len(sys.argv) >= 2:
    dir = (sys.argv[1])
for F in os.listdir(dir):
    s = re.search(r'.wav', F)
    if s:
        subdir = re.split(r'_(fe|)male_', F)[0]
        # mkdir has no exist_ok and throw exception
        os.makedirs(subdir, exist_ok=True)
        shutil.move(F, subdir)


import os

from pyspark.sql import DataFrame
from pyspark.sql import types as t, functions as f
from pyspark.sql import SparkSession

from consts import COVID_DATE_FORMAT

def get_dataframe(name: str, session: SparkSession,
                  cols: list[str], type_mapping: dict,
                  date_format: str = COVID_DATE_FORMAT) -> DataFrame:
    return session.read.csv(
        os.path.join(os.getcwd(), 'input_data', name),
        schema=t.StructType([
            t.StructField(col, type_mapping[col])
            for col in cols
        ]),
        header=True,
        dateFormat=date_format,

    )

def fill_numeric_manual(df: DataFrame,
                        target_col: str,
                        median_cols: list[str],
                        mean_cols: list[str]) -> DataFrame:
    for median_column in median_cols:
        best_median_df = (df
                          .filter(f.col(median_column).isNotNull())
                          .groupBy(target_col)
                          .agg(f.percentile_approx(median_column, 0.5)
                               .alias(f"{median_column}_median")))

        joined_df = df.join(best_median_df, on=target_col, how='left')

        df = (joined_df
              .withColumn(median_column,
                          f.when(joined_df[median_column].isNull(),
                                 joined_df[f'{median_column}_median'])
                          .otherwise(joined_df[median_column]))
              .drop(f'{median_column}_median')
              )

    for mean_column in mean_cols:
        best_mean_df = (df
                        .filter(f.col(mean_column).isNotNull())
                        .groupBy(target_col)
                        .agg(f.percentile_approx(mean_column, 0.5)
                             .alias(f"{mean_column}_mean")))

        joined_df = df.join(best_mean_df, on=target_col, how='left')

        df = (joined_df
              .withColumn(mean_column,
                          f.when(joined_df[mean_column].isNull(),
                                 joined_df[f'{mean_column}_mean'])
                          .otherwise(joined_df[mean_column]))
              .drop(f'{mean_column}_mean')
              )

    return df

def split_dataframe(dataframe: DataFrame, thresholds: list) -> list[DataFrame]:
    dataframe = dataframe.orderBy(f.rand())
    return dataframe.randomSplit(thresholds, seed=42)


# -*- coding: utf-8 -*-

# Import dependencies
import uuid
import logging
import bcrypt  # https://github.com/pyca/bcrypt/, https://pypi.python.org/pypi/bcrypt/2.0.0
#from Crypto.Hash import SHA512
#from Crypto.Random.random import StrongRandom
from random import randint

# Import flask dependencies
from flask import Blueprint, render_template, make_response, flash, session
from flask.ext.login import login_user, login_required
from flask_restful import Resource, Api, reqparse

# Import the database object from the main app module
from app import db, api, login_manager, app

# Import services
from app.helpers import get_custom_logger
from app.mod_database.helpers import get_db_cursor
from app.mod_account.services import get_contacts_by_account, get_emails_by_account, get_telephones_by_account, \
    get_service_link_record_count_by_account, get_consent_record_count_by_account

# create logger with 'spam_application'
logger = get_custom_logger('mod_account_controllers')

def check_account_id(account_id=None):
    # TODO: check that session[account_id] and account_id from path are matching
    if account_id is None:
        logger.debug('Account ID must be provided as call parameter.')
        raise AttributeError('Account ID must be provided as call parameter.')
    else:
        return True

def get_potential_services_count(cursor=None, account_id=None):
    data = randint(10, 100)
    return cursor, data

def get_potential_consents_count(cursor=None, account_id=None):
    data = randint(10, 100)
    return cursor, data

def get_passive_services_count(cursor=None, account_id=None):
    data = randint(10, 100)
    return cursor, data

def get_passive_consents_count(cursor=None, account_id=None):
    data = randint(10, 100)
    return cursor, data

def get_service_link_record_count(cursor=None, account_id=None):

    check_account_id(account_id=account_id)

    if cursor is None:
        cursor = get_db_cursor()
        logger.debug('No DB cursor provided as call parameter. Getting new one.')

    cursor, data = get_service_link_record_count_by_account(cursor=cursor, account_id=account_id)

    return cursor, data

def get_consent_record_count(cursor=None, account_id=None):

    check_account_id(account_id=account_id)

    if cursor is None:
        cursor = get_db_cursor()
        logger.debug('No DB cursor provided as call parameter. Getting new one.')

    cursor, data = get_consent_record_count_by_account(cursor=cursor, account_id=account_id)

    return cursor, data

def get_contacts(cursor=None, account_id=None):

    check_account_id(account_id=account_id)

    if cursor is None:
        cursor = get_db_cursor()
        logger.debug('No DB cursor provided as call parameter. Getting new one.')

    cursor, data = get_contacts_by_account(cursor=cursor, account_id=account_id)

    return cursor, data

def get_emails(cursor=None, account_id=None):

    check_account_id(account_id=account_id)

    if cursor is None:
        cursor = get_db_cursor()
        logger.debug('No DB cursor provided as call parameter. Getting new one.')

    cursor, data = get_emails_by_account(cursor=cursor, account_id=account_id)

    return cursor, data

def get_telephones(cursor=None, account_id=None):

    check_account_id(account_id=account_id)

    if cursor is None:
        cursor = get_db_cursor()
        logger.debug('No DB cursor provided as call parameter. Getting new one.')

    cursor, data = get_telephones_by_account(cursor=cursor, account_id=account_id)

    return cursor, data


import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family'] = 'cmu serif'
# use latex for font rendering
matplotlib.rcParams['text.usetex'] = True

SMALL_SIZE = 12
MEDIUM_SIZE = 14
BIGGER_SIZE = 16

plt.rc('font', size=SMALL_SIZE)          # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE)     # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE)    # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE)    # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE)    # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE)  # fontsize of the figure title

from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.core.problem import Problem
from pymoo.operators.crossover.sbx import SBX
from pymoo.operators.mutation.pm import PM
from pymoo.operators.repair.rounding import RoundingRepair
from pymoo.operators.sampling.rnd import IntegerRandomSampling
from pymoo.optimize import minimize

import os
import sys
import comtypes.client

import time

from threading import Thread
import subprocess

def split(a, n):
    k, m = divmod(len(a), n)
    return (a[i*k+min(i, m):(i+1)*k+min(i+1, m)] for i in range(n))

class MyProblem(Problem):

    def __init__(self,SapModel, SapObject, LIST_HE, LIST_IPE, ModelPath , Main_File_name , File_ext):

        self.SapModel = SapModel
        self.SapObject = SapObject
        self.LIST_HE = LIST_HE
        self.LIST_IPE = LIST_IPE
        self.ModelPath = ModelPath
        self.Main_File_name = Main_File_name
        self.File_ext = File_ext
        self.gen = 0

        n_var = 6

        xl = np.zeros((n_var,))
        xu = np.array([len(LIST_HE)-1,len(LIST_HE)-1,len(LIST_HE)-1,
                       len(LIST_IPE)-1,len(LIST_IPE)-1,len(LIST_IPE)-1])

        super().__init__(n_var=n_var, n_obj=1, n_ieq_constr=4, xl=xl, xu=xu, vtype=int)

    def _evaluate(self, x, out, *args, **kwargs):
        List_file_names =[]
        for ii in range(x.shape[0]):
            List_file_names.append(f'{self.ModelPath}{self.Main_File_name}-{ii}{self.File_ext}')

        List_file_names = list(split(List_file_names, 3))
        X = np.array_split(x, 3)

        global outF1
        global outG1
        global outF2
        global outG2
        global outF3
        global outG3

        outF1 = np.zeros(len(List_file_names[0]))
        outG1 = np.zeros((len(List_file_names[0]),4))
        outF2 = np.zeros(len(List_file_names[1]))
        outG2 = np.zeros((len(List_file_names[1]),4))
        outF3 = np.zeros(len(List_file_names[2]))
        outG3 = np.zeros((len(List_file_names[2]),4))

        def run1(SapModel, Files, X, OBJ):
            global outF1
            global outG1
            for jj,file_name in enumerate(Files):
                SapModel.File.OpenFile(OBJ.ModelPath + OBJ.Main_File_name + OBJ.File_ext)
                ret = SapModel.SetModelIsLocked(False)
                for kk in range(1,16):
                    if kk in [1,4,7]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,0]])
                        # print(ret, self.LIST_HE[x[ii,0]])
                    elif kk in [2,5,8]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,1]])
                    elif kk in [3,6,9]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,2]])
                    elif kk in [10,11]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,3]])
                    elif kk in [12,13]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,4]])
                    elif kk in [14,15]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,5]])
                        # print(ret, self.LIST_IPE[x[ii,0]])
                ret = SapModel.File.Save(file_name)
                ret = SapModel.File.OpenFile(file_name)
                ret = SapModel.Analyze.RunAnalysis()
                ret = SapModel.DesignSteel.StartDesign()
                ret = SapModel.Results.Setup.SetCaseSelectedForOutput("DEAD")
                [NumberResults, LoadCase, StepType, StepNum, Fx, Fy, Fz, Mx, My, Mz, gx, gy, gz, ret] = SapModel.Results.BaseReact()
                outF1[jj] = Fz[0]
                g_summary = np.zeros(16)
                g_NMM = np.zeros(16)
                # g_V = np.zeros(16)
                for kk in range(1,16):
                    [NumberItems, FrameName, Ratio, RatioType, Location, ComboName, ErrorSummary, WarningSummary, ret] = SapModel.DesignSteel.GetSummaryResults(f'{kk}')
                    g_summary[kk-1] = Ratio[0]
                    [NumberItems, FrameName, Value, ret] = SapModel.DesignSteel.GetDetailResultsValue(f"{kk}", 0, 2, "TotalRatio")
                    g_NMM[kk-1] = Value[0]
                    # [NumberItems, FrameName, Value, ret] = self.SapModel.DesignSteel.GetDetailResultsValue(f"{jj}", 0, 3, "V2Ratio")
                    # g_V[jj-1] = Value[0]
                outG1[jj,0] = sum(np.max((g_NMM - 1, np.zeros(16)), axis=0))
                outG1[jj,1] = sum(np.max((g_summary - 1, np.zeros(16)), axis=0))
                # Vincoli geometrici
                outG1[jj,2] = max( X[jj,1] - X[jj,0] , 0)
                outG1[jj,3] = max( X[jj,2] - X[jj,1] , 0)

        def run2(SapModel, Files, X, OBJ):
            global outF2
            global outG2
            for jj,file_name in enumerate(Files):
                SapModel.File.OpenFile(OBJ.ModelPath + OBJ.Main_File_name + OBJ.File_ext)
                ret = SapModel.SetModelIsLocked(False)
                for kk in range(1,16):
                    if kk in [1,4,7]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,0]])
                        # print(ret, self.LIST_HE[x[ii,0]])
                    elif kk in [2,5,8]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,1]])
                    elif kk in [3,6,9]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,2]])
                    elif kk in [10,11]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,3]])
                    elif kk in [12,13]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,4]])
                    elif kk in [14,15]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,5]])
                        # print(ret, self.LIST_IPE[x[ii,0]])
                ret = SapModel.File.Save(file_name)
                ret = SapModel.File.OpenFile(file_name)
                ret = SapModel.Analyze.RunAnalysis()
                ret = SapModel.DesignSteel.StartDesign()
                ret = SapModel.Results.Setup.SetCaseSelectedForOutput("DEAD")
                [NumberResults, LoadCase, StepType, StepNum, Fx, Fy, Fz, Mx, My, Mz, gx, gy, gz, ret] = SapModel.Results.BaseReact()
                outF2[jj] = Fz[0]
                g_summary = np.zeros(16)
                g_NMM = np.zeros(16)
                # g_V = np.zeros(16)
                for kk in range(1,16):
                    [NumberItems, FrameName, Ratio, RatioType, Location, ComboName, ErrorSummary, WarningSummary, ret] = SapModel.DesignSteel.GetSummaryResults(f'{kk}')
                    g_summary[kk-1] = Ratio[0]
                    [NumberItems, FrameName, Value, ret] = SapModel.DesignSteel.GetDetailResultsValue(f"{kk}", 0, 2, "TotalRatio")
                    g_NMM[kk-1] = Value[0]
                    # [NumberItems, FrameName, Value, ret] = self.SapModel.DesignSteel.GetDetailResultsValue(f"{jj}", 0, 3, "V2Ratio")
                    # g_V[jj-1] = Value[0]
                outG2[jj,0] = sum(np.max((g_NMM - 1, np.zeros(16)), axis=0))
                outG2[jj,1] = sum(np.max((g_summary - 1, np.zeros(16)), axis=0))
                # Vincoli geometrici
                outG2[jj,2] = max( X[jj,1] - X[jj,0] , 0)
                outG2[jj,3] = max( X[jj,2] - X[jj,1] , 0)

        def run3(SapModel, Files, X, OBJ):
            global outF3
            global outG3
            for jj,file_name in enumerate(Files):
                SapModel.File.OpenFile(OBJ.ModelPath + OBJ.Main_File_name + OBJ.File_ext)
                ret = SapModel.SetModelIsLocked(False)
                for kk in range(1,16):
                    if kk in [1,4,7]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,0]])
                        # print(ret, self.LIST_HE[x[ii,0]])
                    elif kk in [2,5,8]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,1]])
                    elif kk in [3,6,9]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_HE[X[jj,2]])
                    elif kk in [10,11]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,3]])
                    elif kk in [12,13]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,4]])
                    elif kk in [14,15]:
                        ret = SapModel.FrameObj.SetSection(f"{kk}", OBJ.LIST_IPE[X[jj,5]])
                        # print(ret, self.LIST_IPE[x[ii,0]])
                ret = SapModel.File.Save(file_name)
                ret = SapModel.File.OpenFile(file_name)
                ret = SapModel.Analyze.RunAnalysis()
                ret = SapModel.DesignSteel.StartDesign()
                ret = SapModel.Results.Setup.SetCaseSelectedForOutput("DEAD")
                [NumberResults, LoadCase, StepType, StepNum, Fx, Fy, Fz, Mx, My, Mz, gx, gy, gz, ret] = SapModel.Results.BaseReact()
                outF3[jj] = Fz[0]
                g_summary = np.zeros(16)
                g_NMM = np.zeros(16)
                # g_V = np.zeros(16)
                for kk in range(1,16):
                    [NumberItems, FrameName, Ratio, RatioType, Location, ComboName, ErrorSummary, WarningSummary, ret] = SapModel.DesignSteel.GetSummaryResults(f'{kk}')
                    g_summary[kk-1] = Ratio[0]
                    [NumberItems, FrameName, Value, ret] = SapModel.DesignSteel.GetDetailResultsValue(f"{kk}", 0, 2, "TotalRatio")
                    g_NMM[kk-1] = Value[0]
                    # [NumberItems, FrameName, Value, ret] = self.SapModel.DesignSteel.GetDetailResultsValue(f"{jj}", 0, 3, "V2Ratio")
                    # g_V[jj-1] = Value[0]
                outG3[jj,0] = sum(np.max((g_NMM - 1, np.zeros(16)), axis=0))
                outG3[jj,1] = sum(np.max((g_summary - 1, np.zeros(16)), axis=0))
                # Vincoli geometrici
                outG3[jj,2] = max( X[jj,1] - X[jj,0] , 0)
                outG3[jj,3] = max( X[jj,2] - X[jj,1] , 0)

        threads = []

        t = Thread(target=run1, args=(SapModel[0], List_file_names[0], X[0], self))
        threads.append(t)
        t = Thread(target=run2, args=(SapModel[1], List_file_names[1], X[1], self))
        threads.append(t)
        t = Thread(target=run3, args=(SapModel[2], List_file_names[2], X[2], self))
        threads.append(t)

        # Start to evaluate time of analysis in parallel
        start = time.time()

        # Start all threads
        for tt in threads:
            tt.start()

        # Wait for all of them to finish
        for tt in threads:
            tt.join()

        # Evaluate time of analysis in parallel
        end = time.time()
        print('Elapsed time :',end - start,'s')

        # some code after execution of all bat files

        out["F"] = np.hstack( (outF1, outF2, outF3) )
        out["G"] = np.vstack( (outG1, outG2, outG3) )

        self.gen += 1
        print(f'gen {self.gen}\n','F=', out["F"], '\n', 'G=', out["G"], '\n\n')

Num_SAP2000_istances = 3

SapObject = []
SapModel = []

for jj in range(Num_SAP2000_istances):
    AttachToInstance = False
    SpecifyPath = False
    APIPath = 'C:\CSiAPIexample'
    Main_File_name = 'modelFixed'
    File_ext = '.sdb'
    ModelPath = APIPath + os.sep + 'IWSS23_TEST_parallel' + os.sep  #'API_1-001.sdb'
    helper = comtypes.client.CreateObject('SAP2000v1.Helper')
    helper = helper.QueryInterface(comtypes.gen.SAP2000v1.cHelper)
    if AttachToInstance:
        #attach to a running instance of SAP2000
        try:
            #get the active SapObject
                mySapObject = helper.GetObject("CSI.SAP2000.API.SapObject")

        except (OSError, comtypes.COMError):
            print("No running instance of the program found or failed to attach.")
            sys.exit(-1)
    else:
        if SpecifyPath:
            try:
                #'create an instance of the SAPObject from the specified path
                mySapObject = helper.CreateObject(ProgramPath)

            except (OSError, comtypes.COMError):
                print("Cannot start a new instance of the program from " + ProgramPath)
                sys.exit(-1)
        else:
            try:
                #create an instance of the SAPObject from the latest installed SAP2000
                mySapObject = helper.CreateObjectProgID("CSI.SAP2000.API.SapObject")

            except (OSError, comtypes.COMError):
                print("Cannot start a new instance of the program.")
                sys.exit(-1)
        #start SAP2000 application
        mySapObject.ApplicationStart(Units=6)
        SapObject.append(mySapObject)
        SapModel.append(mySapObject.SapModel)
        ret = mySapObject.Hide()

# ret = mySapObject.Hide()
# ret = mySapObject.ApplicationStart(Units=6, FileName=ModelPath + Main_File_name + File_ext) #model.sdb
# SapModel = mySapObject.SapModel

LIST_HE = np.load('LIST_HE.npy')
LIST_IPE = np.load('LIST_IPE.npy')

problem = MyProblem(SapModel, SapObject, LIST_HE, LIST_IPE, ModelPath , Main_File_name , File_ext)

method = GA(pop_size=48,
            sampling=IntegerRandomSampling(),
            crossover=SBX(prob=1.0, eta=3.0, vtype=float, repair=RoundingRepair()),
            mutation=PM(prob=1.0, eta=3.0, vtype=float, repair=RoundingRepair()),
            eliminate_duplicates=True,
            )

res = minimize(problem,
               method,
               termination=('n_gen', 50),
            #    seed=1,
               save_history=True
               )

print("Best solution found: %s" % res.X)
print("Function value: %s" % res.F)
print("Constraint violation: %s" % res.CV)

n_evals = np.array([e.evaluator.n_eval for e in res.history])
opt = np.array([e.opt[0].F for e in res.history])

# plt.title("Convergence")
plt.plot(n_evals, opt, "--", color='C1')
plt.yscale("log")
plt.xlabel('OF evaluations [-]')
plt.ylabel('OF: Dead Load [kN]')
plt.tight_layout()
# plt.show()
plt.savefig('convergence.pdf')
# plt.close()

with open('n_evals.npy', 'wb') as f:
    np.save(f, n_evals)
with open('opt.npy', 'wb') as f:
    np.save(f, opt)
with open('resX.npy', 'wb') as f:
    np.save(f, res.X)
with open('resF.npy', 'wb') as f:
    np.save(f, res.F)

_X = np.row_stack([a.pop.get("X") for a in res.history])
feasible = np.row_stack([a.pop.get("feasible") for a in res.history])[:, 0]

with open('_X.npy', 'wb') as f:
    np.save(f, _X)
with open('feasible.npy', 'wb') as f:
    np.save(f, feasible)

print('finito')

for ii in range(Num_SAP2000_istances):
    ret = SapObject[ii].ApplicationExit(False)
    SapModel[ii] = None
    SapObject[ii] = None


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from utils.models import BaseModel, models
from utils import constants as Constants, get_choices, get_kyc_upload_path

from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save
from django.utils.timezone import now
from django.dispatch import receiver
from django.core.cache import cache

class Lead(BaseModel):
    user = models.ForeignKey('users.user', on_delete=models.CASCADE)
    contact = models.ForeignKey(
        'crm.Contact', null=True, blank=True, on_delete=models.CASCADE)
    campaign = models.ForeignKey(
        'users.Campaign', null=True, blank=True, on_delete=models.CASCADE)
    pincode = models.CharField(max_length=6, null=True)
    bookmark = models.BooleanField(default=False)
    is_client = models.BooleanField(default=False)
    ignore = models.BooleanField(default=False)

    class Meta:
        ordering = ('-bookmark',)

    def create_opportunity(self, validated_data):
        instance = Opportunity.objects.create(
            lead_id=self.id, category_id=validated_data['category_id'])
        instance.update_category_opportunity(validated_data)
        return instance

    def get_quotes(self):
        from sales.models import Quote
        return Quote.objects.filter(
            opportunity__lead_id=self.id,
            ignore=False).exclude(status='rejected')

    def __str__(self):
        return "%s - Contact: %s" % (
            self.user.get_full_name(),
            self.contact.first_name if self.contact else 'Pending')

class Opportunity(BaseModel):
    lead = models.ForeignKey('crm.Lead', on_delete=models.CASCADE)
    category = models.ForeignKey('product.Category', on_delete=models.CASCADE)
    details = None

    def __str__(self):
        return '%s: %s' % (self.category.name, self.lead.__str__())

    def __init__(self, *args, **kwargs):
        super(self.__class__, self).__init__(*args, **kwargs)
        if hasattr(self, 'category'):
            self.category_name = self.category.name.replace(' ', '').lower()
            if hasattr(self, self.category_name):
                self.category_opportunity = getattr(self, self.category_name)

    def save(self, *args, **kw):
        cache.delete('USER_CONTACTS:%s' % self.lead.user_id)
        super(self.__class__, self).save(*args, **kw)

    def create_category_opportunity(self):
        ContentType.objects.get(
            model=self.category_name, app_label='crm'
        ).model_class().objects.create(opportunity_id=self.id)

    def calculate_suminsured(self):
        self.category_opportunity.calculate_suminsured()

    def get_premiums(self, **kw):
        return self.category_opportunity.get_premiums()

    def refresh_quote_data(self, **kw):
        return self.category_opportunity.refresh_quote_data(**kw)

    def get_quotes(self):
        return self.quote_set.filter(ignore=False).order_by(
            '%s__base_premium' % self.category_name)

    def get_recommendated_quotes(self):
        return self.get_quotes()[:5]

    def update_fields(self, **kw):
        for field in kw.keys():
            setattr(self, field, kw[field])
        self.save()

    def update_category_opportunity(self, validated_data):
        self.refresh_from_db()
        category_opportunity = getattr(self, self.category_name)
        fields = dict.fromkeys(Constants.CATEGORY_OPPORTUNITY_FIELDS_MAPPER[
            self.category_name], None)
        for field in fields.keys():
            fields[field] = validated_data.get(field, getattr(
                category_opportunity, field))
            if isinstance(fields[field], str):
                fields[field] = fields[field].lower()
        category_opportunity.update_fields(**fields)
        return category_opportunity

    @property
    def city(self):
        from users.models import Pincode
        pincodes = Pincode.objects.filter(pincode=self.lead.pincode)
        if pincodes.exists():
            return pincodes.get().city

    @property
    def citytier(self):
        if self.lead.pincode in Constants.NCR_PINCODES or self.city in Constants.MUMBAI_AREA_TIER: # noqa
            return Constants.MUMBAI_NCR_TIER
        return Constants.ALL_INDIA_TIER

    @property
    def companies_id(self):
        return self.lead.user.get_companies().values_list('id', flat=True)

class Contact(BaseModel):
    user = models.ForeignKey(
        'users.User', on_delete=models.CASCADE, null=True, blank=True)
    address = models.ForeignKey(
        'users.Address', null=True, blank=True, on_delete=models.CASCADE)
    gender = models.CharField(
        max_length=16, choices=get_choices(Constants.GENDER),
        null=True, blank=True)
    phone_no = models.CharField(max_length=40, null=True, blank=True)
    first_name = models.CharField(max_length=32, blank=True)
    middle_name = models.CharField(max_length=32, blank=True)
    last_name = models.CharField(max_length=32, blank=True)
    email = models.EmailField(max_length=64, null=True, blank=True)
    dob = models.DateField(null=True, blank=True)
    occupation = models.CharField(
        choices=get_choices(Constants.OCCUPATION_CHOICES), null=True,
        default=Constants.OCCUPATION_DEFAULT_CHOICE, blank=True, max_length=32)
    marital_status = models.CharField(
        choices=get_choices(
            Constants.MARITAL_STATUS), max_length=32, null=True, blank=True)
    annual_income = models.CharField(max_length=48, null=True, blank=True)

    def save(self, *args, **kwargs):
        self.first_name = self.first_name.lower()
        self.last_name = self.last_name.lower()
        super(self.__class__, self).save(*args, **kwargs)

    def __str__(self):
        full_name = self.get_full_name()
        return '%s - %s' % ((
            full_name if full_name else 'Parent'
        ), self.phone_no)

    def update_fields(self, **kw):
        for field in kw.keys():
            setattr(self, field, kw[field])
        self.save()

    def get_full_name(self):
        full_name = '%s %s %s' % (
            self.first_name, self.middle_name, self.last_name)
        return full_name.strip().title()

    def upload_docs(self, validated_data, fields):
        from sales.models import ProposerDocument
        for field in fields:
            name = validated_data[field].name.split('.')
            file_name = '%s_%s_%s.%s' % (
                self.id, name[0], now().date().isoformat(), name[1])
            doc, created = ProposerDocument.objects.get_or_create(
                document_type=field, contact_id=self.id, ignore=False)
            doc.file.save(file_name, validated_data[field])
            validated_data[field] = doc.file.url
        return validated_data

    @property
    def calling_no(self):
        if len(self.phone_no) == 10:
            return '+91%s' % self.phone_no
        if '+91' in self.phone_no and len(self.phone_no) == 13:
            return self.phone_no
        if '91' in self.phone_no[:2] and len(self.phone_no) > 10:
            return '+%s' % self.phone_no
        return self.phone_no

    @property
    def whatsapp_no(self):
        if len(self.phone_no) == 10:
            return '+91%s' % self.phone_no
        if '+91' in self.phone_no and len(self.phone_no) == 13:
            return self.phone_no
        if '91' in self.phone_no[:2] and len(self.phone_no) > 10:
            return '+' % self.phone_no
        return self.phone_no

class KYCDocument(BaseModel):
    contact = models.ForeignKey(
        'crm.Contact', on_delete=models.CASCADE, null=True, blank=True)
    document_number = models.CharField(max_length=64)
    document_type = models.CharField(
        choices=get_choices(Constants.KYC_DOC_TYPES), max_length=16)
    file = models.FileField(
        upload_to=get_kyc_upload_path, null=True, blank=True)

@receiver(post_save, sender=Opportunity, dispatch_uid="action%s" % str(now()))
def opportunity_post_save(sender, instance, created, **kwargs):
    if created:
        instance.create_category_opportunity()


class RouterList:
  {...}
  def refresh_routes(self):
    deletion_list = []
    for ipaddr, route in self.__routes.items():
      if ipaddr not in self.__links:
        if route.updated_since > self.__max_life:
          deletion_list.append(ipaddr)

    for ipaddr in deletion_list:
      del self.__routes[ipaddr]


import json
import threading

import cv2
import PySimpleGUI as sg
import trt_pose.coco
import trt_pose.models
from flask import Flask
from flask_restful import Api, Resource
from trt_pose.parse_objects import ParseObjects

from camera import Camera
from exercise import LeftBicepCurl, RightBicepCurl, ShoulderPress, Squat
from helper import HEIGHT, WIDTH, preprocess
from model import Model

executing = False  # global flag for session start
exercise = None  # global exercise object required for model inference and drawing
stopExercise = False  # global flag for stopping exercise after loop ends
drawn = None  # global for our image

class LeftCurlAPI(Resource):
    def get(self):
        global exercise, executing
        exercise = LeftBicepCurl()
        executing = True
        return {"leftCurl": f"{id}"}

class RightCurlAPI(Resource):
    def get(self):
        global exercise, executing
        exercise = RightBicepCurl()
        executing = True
        return {"rightCurl": f"{id}"}

class ShoulderPressAPI(Resource):
    def get(self):
        global exercise, executing
        exercise = ShoulderPress()
        executing = True
        return {"shoulderPress": f"{id}"}

class SquatAPI(Resource):
    def get(self):
        global exercise, executing
        exercise = Squat()
        executing = True
        return {"squat": f"{id}"}

class RepCountAPI(Resource):
    def get(self):
        global exercise
        reps = exercise.rep_count if exercise else 0
        return {"repCount": f"{reps}"}

class EndExerciseAPI(Resource):
    def get(self):
        global stopExercise
        stopExercise = True
        return {"endExercise": f"{id}"}

class StartSessionAPI(Resource):
    def get(self):
        return {"startSession": f"{id}"}

class DebugAPI(Resource):
    def get(self):
        return {"debug": f"{id}"}

# ------ Begin GUI layout ------

video_viewer_column = [
    # image will be flab2ab image
    [sg.Image(filename="", key="image")],
]

repcount_list_column = [
    [
        # current rep count
        sg.Text("Rep Count"),
        # change folder to pull actual rep count
        sg.In(size=(25, 1), enable_events=True, key="repCount"),
    ],
    [
        # previous exercise list
        sg.Listbox(values=[], enable_events=True, size=(40, 20), key="exerciseList")
    ],
]

# finally builds layout of gui
layout = [
    [
        sg.Column(video_viewer_column),
        sg.VSeperator(),
        sg.Column(repcount_list_column),
    ]
]

# ------ End GUI layout ------

def main():

    global exercise, stopExercise, drawn

    print("Beginning script")

    # Load the annotation file and create a topology tensor
    with open("human_pose.json", "r") as f:
        human_pose = json.load(f)

    # Create a topology tensor (intermediate DS that describes part linkages)
    topology = trt_pose.coco.coco_category_to_topology(human_pose)

    # Construct and load the model
    model = Model(pose_annotations=human_pose)
    model.load_model("resnet")

    model.get_optimized()
    model.log_fps()
    print("Set up model")

    # Set up the camera
    camera = Camera(width=640, height=480)
    camera.capture_video("mp4v", "/tmp/output.mp4")
    assert camera.cap is not None, "Camera Open Error"
    print("Set up camera")

    # Set up callable class used to parse the objects from the neural network
    parse_objects = ParseObjects(topology)

    app = Flask(__name__)
    api = Api(app)

    # add endpoints
    api.add_resource(LeftCurlAPI, "/leftCurl")
    api.add_resource(RightCurlAPI, "/rightCurl")
    api.add_resource(ShoulderPressAPI, "/shoulderPress")
    api.add_resource(SquatAPI, "/squat")
    api.add_resource(RepCountAPI, "/repCount")
    api.add_resource(EndExerciseAPI, "/endExercise")
    api.add_resource(StartSessionAPI, "/startSession")
    api.add_resource(DebugAPI, "/debug")

    t = threading.Thread(target=app.run, kwargs={"host": "0.0.0.0"})
    t.start()

    print("After networking")
    while not executing:
        pass

    window = sg.Window("Flab2Ab", location=(800, 400))
    window.Layout(layout).Finalize()

    print("Executing...")
    while True:

        while camera.cap.isOpened() and exercise:
            succeeded, image = camera.cap.read()

            if not succeeded:
                print("Camera read Error")
                break

            resized_img = cv2.resize(
                image, dsize=(WIDTH, HEIGHT), interpolation=cv2.INTER_AREA
            )
            preprocessed = preprocess(resized_img)
            counts, objects, peaks = model.execute_neural_net(
                data=preprocessed, parser=parse_objects
            )

            drawn = exercise.draw(image, counts, objects, peaks, t)

            encoded_img = cv2.imencode(".png", image)[1].tobytes()
            window.FindElement("image").update(data=encoded_img)

            if camera.out:
                camera.out.write(drawn)

            cv2.waitKey(1)

            if stopExercise:
                exercise = None
                stopExercise = False
                print("exercise ended successfully")

    # Clean up resources
    print("Cleaning up")
    cv2.destroyAllWindows()
    camera.out.release()
    camera.cap.release()

if __name__ == "__main__":
    main()


# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

bl_info = {
    "name" : "SurfaceAdaptiveRTI",
    "author" : "Ramamoorthy Luxman",
    "description" : "Virtual surface adaptive RTI (NBLP)",
    "blender" : (3, 1, 2),
    "version" : (0, 0, 1),
    "location" : "3D View > Tools > SurfaceAdaptiveRTI",
    "warning" : "",
    "category" : "3D View"
}

import os
from random import sample
import bpy
import numpy as np
import math
import time
import cv2
import numpy as np
from mathutils import Vector
import matplotlib.pyplot as plot
import yaml
from itertools import islice
from bpy.props import (StringProperty,
                       BoolProperty,
                       IntProperty,
                       FloatProperty,
                       FloatVectorProperty,
                       EnumProperty,
                       PointerProperty,
                       )
from bpy.types import (Panel,
                       Operator,
                       PropertyGroup,
                       )

#################################### Global vars { ################################
updated_lps = []
#################################### Global vars } ################################

#################################### Helpers { ####################################

# def cart2sph(x, y, z):
def cart2sph(*args):
    x = args[0]
    y = args[1]
    z = 1.0
    if len(args)>2:
        z = args[2]
    print(x,",",y,",",z)
    hxy = np.hypot(x, y)
    r = np.hypot(hxy, z)
    el = np.arctan2(z, hxy)
    az = np.arctan2(y, x)
    return r, az, el

def sph2cart(az, el, r):
    rcos_theta = r * np.cos(el)
    x = rcos_theta * np.cos(az)
    y = rcos_theta * np.sin(az)
    z = r * np.sin(el)
    return x, y, z

# Reads the standard .lp file to extract the light positions.
# This function calculates just the directions - thetas and phis. The radius of the dome in the actual lp file is ignored and considers the radius passed to this function only to recreate new light positions.
def read_lp_file(file_path, dome_radius):
    light_positions = []
    # Read in .lp data

    if not os.path.exists(file_path):
        return light_positions

    try:
        file = open(file_path)
    except RuntimeError as ex:
        error_report = "\n".join(ex.args)
        print("Caught error:", error_report)
        return {'ERROR'}

    rows = file.readlines()
    file.close()

    numLights = int(rows[0].split()[0])

    for idx in range(1, numLights + 1):
        cols = rows[idx].split()
        x = float(cols[1])
        y = float(cols[2])
        z = float(cols[3])
        r, long, lat = cart2sph(x,y,z)
        r = dome_radius
        x, y, z = sph2cart(long,lat,r)
        light_positions.append((float(x),float(y),float(z)))

    return light_positions

############################ Generate n evenly spaced hemispherical points { ##################################

def generate_n_evenly_spaced_hemispherical_points(samples = 45):
    samples = 2*samples
    phi = math.pi * (3. - math.sqrt(5.))  # golden angle in radians
    cartesian_points = []
    polar_points = []
    for i in range(int(samples/2), samples):
        z = -1 + (i / float(samples - 1)) * 2  # z goes from 0 to 1
        radius = math.sqrt(1 - z * z)  # radius at z
        theta = phi * i  # golden angle increment
        y = math.cos(theta) * radius
        x = math.sin(theta) * radius
        r, az, el = cart2sph(x,y,z)
        # convert the angles to be in the range 0 to 2 pi
        az = (az + np.pi) % (2 * np.pi) - np.pi
        el = (el + np.pi) % (2 * np.pi) - np.pi
        polar_points.append((float(az),float(el)))
        cartesian_points.append((x, y, z))

    # plot.figure().add_subplot(111, projection='3d').scatter([p[0] for p in cartesian_points], [p[1] for p in cartesian_points], [p[2] for p in cartesian_points]);
    # plot.show()

    return polar_points, cartesian_points

############################ Generate n evenly spaced hemispherical points } ##################################

def generate_homogenous_points_along_theta(samples = 10, dome_radius=1.0, phi=45.0):
        light_positions_cartesian = []
        light_positions_polar = []
        dome_radius = dome_radius
        phi = math.radians(phi)
        if samples%2 != 0:
            samples = samples+1
        for i in range(0,samples):
            theta = i*(math.radians(360.0)/samples)
            x, y, z = sph2cart(theta, phi, dome_radius)
            light_positions_cartesian.append((x,y,z))
            light_positions_polar.append((theta, phi))

        return  light_positions_polar, light_positions_cartesian

#################################### Helpers } ####################################

#################################### NBLP algorithms { ####################################

#################################### NBLP Basic { ####################################

class Nblp:
    iterations = []
    class iteration:
        def __init__(self, lps_polar, lps_cartesian, iteration_nb):
            self.lps_polar = lps_polar
            self.nb_images = len(lps_polar)
            self.lps_cartesian = lps_cartesian
            self.filenames_subtext = "nblp_iteration_"+str(iteration_nb)+"_"
            self.iteration_nb = iteration_nb

        def plot_lps(self):
            fig, ax = plot.subplots(subplot_kw={'projection': 'polar'})
            for i in range(0,self.nb_images):
                theta = self.lps_polar[i][0]
                radius = math.degrees(self.lps_polar[i][1])
                ax.plot(theta, radius,"o")
                ax.set_rmax(2)
                ax.set_rticks([90, 60, 30, 0])
                ax.set_rlabel_position(-22.5)
                ax.grid(True)
                ax.set_title("Light positions projected to 2D plane")
            return plot

        def rename_files(self, path):
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                old_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+".png"
                new_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+"_theta_" + theta + "_phi_" + phi + ".png"
                os.rename(old_name, new_name)

    def __init__(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=45, dome_radius=1, phi=math.radians(45.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def dense_acquisition(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=45, dome_radius=1, phi=math.radians(45.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def generate_homogenous_points_along_theta(self, n, dome_radius, phi, iteration_nb):
        light_positions_cartesian = []
        light_positions_polar = []
        self.dome_radius = dome_radius
        if n%2 != 0:
            n = n+1
        for i in range(0,n):
            theta = i*(math.radians(360.0)/n)
            x, y, z = sph2cart(theta, phi, dome_radius)
            light_positions_cartesian.append((x,y,z))
            light_positions_polar.append((theta, phi))

        return  light_positions_polar, light_positions_cartesian

    def calculate_entropies(self,iteration_nb, file_path):
        print("Calculating entropies")
        img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(1)+".png"
        img_sum = cv2.imread(img_path)
        for i in range(0, self.iterations[iteration_nb].nb_images):
            img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(i+1)+".png"
            print(img_path)
            img = cv2.imread(img_path)
            img_sum = cv2.addWeighted(img_sum,0.5,img,0.5,0)

        for i in range(0, self.iterations[iteration_nb].nb_images):
            img_path = file_path+"\\..\\"+self.iterations[iteration_nb].filenames_subtext+str(i+1)+".png"
            img_diff = cv2.absdiff(img_sum,cv2.imread(img_path))
            normalized_img_diff = np.zeros(img_diff.shape)
            # min_val = img_diff[..., 0].min()
            # max_val = img_diff[..., 0].max()
            min_val = img_diff.min()
            max_val = img_diff.max()
            normalized_img_diff = img_diff * (255/(max_val-min_val))
            # cv2.normalize(img_diff, normalized_img_diff, min_val, max_val, cv2.NORM_MINMAX)
            if not os.path.exists(file_path):
                os.makedirs(file_path)
                cv2.imwrite(file_path+self.iterations[iteration_nb].filenames_subtext+str(i)+".png", img_diff)
                cv2.imwrite(file_path+self.iterations[iteration_nb].filenames_subtext+str(i)+"_normalized.png", normalized_img_diff)

    def generate_lp_file(self, iteration_nb, file_path):
        data = str(self.iterations[iteration_nb].nb_images)
        for i in range(0, self.iterations[iteration_nb].nb_images):
            step =self.iterations[iteration_nb]
            data = data+"\n"+step.filenames_subtext+str(i+1)+".png\t"+str(step.lps_cartesian[i][0])+"\t"+str(step.lps_cartesian[i][1])+"\t"+str(step.lps_cartesian[i][2])
        with open(file_path, 'w') as f:
            f.write(data)

    def write_log(self, path):
        with open(path, 'w') as file:
            iterations = []
            for i in range(0, len(self.iterations)):
                iteration = {{'iteration nb': i},
                             {'lps_polar': self.iterations[i].lps_polar},
                             {'nb_images': self.iterations[i].nb_images},
                             {'lps_cartesian':self.iterations[i].lps_cartesian},
                             {'filenames_subtext': self.iterations[i].filenames_subtext},
                             {'iteration_nb':self.iterations[i].iteration_nb}}

                iterations.append(iteration)
            yaml.dump(iterations, file)

#################################### NBLP Basic } ####################################

#################################### NBLP Basic approach 2 = Relighting loss gradient descent approach { ####################################

class Nblp_2:
    iterations = []
    class iteration:
        def __init__(self, lps_polar, lps_cartesian, iteration_nb, context):
            self.lps_polar = lps_polar
            self.nb_images = len(lps_polar)
            self.lps_cartesian = lps_cartesian
            self.filenames_subtext = "nblp_iteration_"+str(iteration_nb)+"_"
            self.iteration_nb = iteration_nb
            self.context = context
            self.generate_file_names()
            # self.loss = 1.0

        def plot_lps(self):
            fig, ax = plot.subplots(subplot_kw={'projection': 'polar'})
            for i in range(0,self.nb_images):
                theta = self.lps_polar[i][0]
                radius = math.degrees(self.lps_polar[i][1])
                ax.plot(theta, radius,"o")
                ax.set_rmax(2)
                ax.set_rticks([90, 60, 30, 0])
                ax.set_rlabel_position(-22.5)
                ax.grid(True)
                ax.set_title("Light positions projected to 2D plane")
            return plot

        def rename_files(self, path):
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                old_name = path+"nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+".png"
                new_name = path+self.file_names[i]
                os.rename(old_name, new_name)

        def generate_lp_file(self, file_path):
            data = str(self.nb_images)
            for i in range(0, self.nb_images):
                data = data+"\n"+self.file_names[i]+"\t"+str(self.lps_cartesian[i][0])+"\t"+str(self.lps_cartesian[i][1])+"\t"+str(self.lps_cartesian[i][2])
            with open(file_path, 'w') as f:
                f.write(data)

        def calculate_entropies(self,iteration_nb, file_path):
            print("Calculating entropies")
            img_path = file_path+"\\..\\"+self.file_names[0]
            img_sum = cv2.imread(img_path)
            for i in range(0, self.nb_images):
                img_path = file_path+"\\..\\"+self.file_names[i]
                print(img_path)
                img = cv2.imread(img_path)
                img_sum = cv2.addWeighted(img_sum,0.5,img,0.5,0)

            for i in range(0, self.nb_images):
                img_path = file_path+"\\..\\"+self.file_names[i]
                img_diff = cv2.absdiff(img_sum,cv2.imread(img_path))
                normalized_img_diff = np.zeros(img_diff.shape)
                # min_val = img_diff[..., 0].min()
                # max_val = img_diff[..., 0].max()
                min_val = img_diff.min()
                max_val = img_diff.max()
                normalized_img_diff = img_diff * (255/(max_val-min_val))
                # cv2.normalize(img_diff, normalized_img_diff, min_val, max_val, cv2.NORM_MINMAX)
                if not os.path.exists(file_path):
                    os.makedirs(file_path)
                    cv2.imwrite(file_path+self.file_names[i], img_diff)
                    cv2.imwrite(file_path+self.file_names[i].split(".")[0]+"_normalized.png", normalized_img_diff)

        def generate_file_names(self):
            self.file_names = []
            for i in range(0, self.nb_images):
                theta=str(math.floor(100*math.degrees(self.lps_polar[i][0]))/100)
                phi=str(math.floor(100*math.degrees(self.lps_polar[i][1]))/100)
                self.file_names.append("nblp_iteration_"+str(self.iteration_nb)+"_"+str(i+1)+"_theta_" + theta + "_phi_" + phi + ".png")

        def execute_acq(self):
            global updated_lps
            updated_lps = self.lps_cartesian
            bpy.context.scene.render.filepath = self.context.scene.acquisition_panel.output_path+self.filenames_subtext+"#.png"
            bpy.ops.rti.create_lights()
            bpy.context.scene.frame_end = len(self.context.scene.light_panel.light_list)
            bpy.ops.rti.set_animation()
            bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            bpy.ops.render.play_rendered_anim()
            self.plot_lps().savefig(self.context.scene.acquisition_panel.output_path+"iteration_"+str(self.iteration_nb)+"_"+str(self.nb_images)+".png")
            self.rename_files(self.context.scene.acquisition_panel.output_path)
            self.generate_lp_file(file_path=self.context.scene.acquisition_panel.output_path+"iteration_"+str(self.iteration_nb)+".lp")
            self.calculate_entropies(self.iteration_nb,self.context.scene.acquisition_panel.output_path+"\\entropies\\")

    def __init__(self, context):
        path = context.scene.acquisition_panel.output_path
        self.context = context
        if not os.path.exists(path +"log.yaml"):
            # lps_polar, lps_cartesian = generate_n_evenly_spaced_hemispherical_points(samples=25)
            lps_polar, lps_cartesian = generate_homogenous_points_along_theta(samples=10)
            step = self.iteration(lps_polar=lps_polar, lps_cartesian=lps_cartesian, iteration_nb=0, context=context)
            step.execute_acq()
            self.iterations.append(step)
        else:
            print(path + "log.yaml")
            data = None
            with open(path + "log.yaml") as f:
                data = yaml.load(f.read(), yaml.loader.Loader)

            for i in range(0, len(data)):
                step = self.iteration(data[i]['lps_polar'], data[i]['lps_cartesian'], len(data)-1, context=context)
                self.iterations.append(step)

            if os.path.exists(path + "next_iteration.lp"):
                lps_cartesian = read_lp_file(path + "next_iteration.lp", 1.0)
                print(len(lps_cartesian))
                lps_polar = []
                for i in range(0, len(lps_cartesian)):
                    r, az, el = cart2sph(lps_cartesian[i][0], lps_cartesian[i][1], lps_cartesian[i][2])
                    lps_polar.append((float(az),float(el)))
                step = self.iteration(lps_polar=lps_polar, lps_cartesian=lps_cartesian, iteration_nb=len(data), context=context)
                step.execute_acq()
                self.iterations.append(step)

        # for i in range(0,len(self.iterations)):

        #     data = str(self.nb_images)
        #     for i in range(0, self.nb_images):
        #         data = data+"\n"+self.file_names[i]+"\t"+str(self.lps_cartesian[i][0])+"\t"+str(self.lps_cartesian[i][1])+"\t"+str(self.lps_cartesian[i][2])
        #     with open(file_path, 'w') as f:
        #         f.write(data)

    def dense_acquisition(self):
        iteration_nb = len(self.iterations)
        lps_polar, lps_cartesian = self.generate_homogenous_points_along_theta(n=100, dome_radius=1, phi=math.radians(50.0), iteration_nb=iteration_nb)
        step = self.iteration(lps_polar, lps_cartesian, iteration_nb)
        self.iterations.append(step)

    def write_log(self, path):
        open(path, "w").close()
        with open(path, 'w') as file:
            iterations = []
            for i in range(0, len(self.iterations)):
                iteration = {'lps_polar': list(self.iterations[i].lps_polar),
                             'nb_images': self.iterations[i].nb_images,
                             'lps_cartesian': list(self.iterations[i].lps_cartesian),
                             'filenames_subtext': self.iterations[i].filenames_subtext,
                             'iteration_nb':self.iterations[i].iteration_nb,
                             'file_names': self.iterations[i].file_names}

                iterations.append(iteration)
            yaml.dump(iterations, file)

#################################### NBLP Basic approach 2 = Relighting loss } ####################################

#################################### NBLP algorithms } ####################################

#################################### PropertyGroups { ####################################
class light(bpy.types.PropertyGroup):
    light = bpy.props.PointerProperty(name="Light object",
                                      type = bpy.types.Object,
                                      description = "A light source")

class camera(bpy.types.PropertyGroup):
    camera = bpy.props.PointerProperty(name="Camera object",
                                       type = bpy.types.Object,
                                       description = "Camera")

class surface(bpy.types.PropertyGroup):
    surface = bpy.props.PointerProperty(name="Surface object",
                                       type = bpy.types.Object,
                                       description = "Surface")

#################################### PropertyGroups } ####################################

#################################### Menu { ####################################
class lightSettings(PropertyGroup):

    nblp: BoolProperty(
        name="NBLP",
        description="NBLP algorithm generated Light Positions",
        default=True,
    )

    lp_file_path : StringProperty(
        name="LP file",
        subtype="FILE_PATH",
        description="File path for light positions file (.lp)",
        default="",
        maxlen=1024,
    )

    dome_radius : FloatProperty(
        name="RTI dome radius",
        description="Radius of RTI dome [m]",
        default=1.0
    )

    light_strength : FloatProperty(
        name="Light strength",
        description="Strength of the light source",
        default=0.5
    )

    light_positions = []
    light_list = []

class surfaceSettings(PropertyGroup):

    def update(self, context):
        mat = context.scene.objects[context.scene.surface_panel.surface[0]].active_material
        node_tree = mat.node_tree
        nodes = node_tree.nodes
        bsdf = nodes.get("Principled BSDF")
        bsdf.inputs[6].default_value=self.metallic
        bsdf.inputs[7].default_value=self.specularity
        bsdf.inputs[9].default_value=self.roughness

    mesh_file_path : StringProperty(
        name="Surface mesh file",
        subtype="FILE_PATH",
        description="File path for the surface mesh file",
        default="",
        maxlen=1024
    )

    texture_image_path : StringProperty(
        name="Surface texture image file",
        subtype="FILE_PATH",
        description="File path for the surface texture image file",
        default="",
        maxlen=1024

    )

    metallic : FloatProperty(
        name="Metallic",
        description="Surface metallic",
        default=0.0,
        min=0.0,
        max=1.0,
        update=update
    )

    roughness : FloatProperty(
        name="Roughness",
        description="Surface roughness",
        default=0.0,
        update=update,
        min=0.0,
        max=1.0,
    )

    specularity : FloatProperty(
        name="Specularity",
        description="Specularity",
        default=0.5,
        update=update,
        min=0.0,
        max=1.0,
    )

    surface = []
    surface_bbox = []

class cameraSettings(PropertyGroup):

    def update(self, context):
        cameras_obj= [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        # data = cameras_obj[0].data
        data = bpy.data.objects[context.scene.camera_panel.camera[0]].data
        data.lens = self.focal_length
        bpy.context.scene.render.resolution_y = int(self.aspect_ratio*bpy.context.scene.render.resolution_x)
        data.display_size = self.view_port_size
        data.sensor_width = self.sensor_size

    def update_camera_height(self,context):
        cameras_obj = [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        if len(cameras_obj) != 1:
            self.report({'ERROR'}, "Camera doesn't exist in scene or there is more than 1 camera.")
            return {'FINISHED'}

        cameras_obj[0].location[2] = self.camera_height

    camera_height : FloatProperty(
        name="Camera height",
        description="Camera position height",
        default=1,
        min=0,
        max=4.0,
        update=update_camera_height
    )

    aspect_ratio : FloatProperty(
        name="Aspect ratio",
        description="Aspect ratio of the sensor",
        default=1,
        min=0.000,
        max=2,
        update=update
    )

    focal_length : FloatProperty(
        name="Focal length",
        description="Focal length",
        default=199.6,
        min=0.000,
        max=1000,
        step=1,
        update=update
    )

    subject : PointerProperty(
        name="Focus on",
        description="Subject to focus",
        type=bpy.types.Object,

    )

    resolution_factor : FloatProperty(
        name="Resolution",
        description="Resolution scale factor",
        min=0,
        max=5,
        default=1.0,
        update=update
    )

    view_port_size : FloatProperty(
        name="View port size",
        description="View port size",
        min=0,
        max=100,
        default=0.04,
        update=update
    )

    sensor_size : FloatProperty(
        name="Sensor size",
        description="Sensor size",
        min=0,
        max=1000,
        default=8,
        update=update
    )

    camera = []

class acquisitionSettings(PropertyGroup):
    output_path : StringProperty(
        name="Output path",
        subtype="FILE_PATH",
        description="File path for saving the rti acquisition",
        default="",
        maxlen=1024
    )

    csvOutputLines = []
#################################### Menu } ####################################

#################################### Operators { ####################################

class reset_scene(Operator):
    bl_label = "Reset all"
    bl_idname = "rti.reset_scene"

    def execute(self, context):
        scene = context.scene
        for current_light in bpy.data.lights:
            bpy.data.lights.remove(current_light)
        for current_object in bpy.data.objects:
            bpy.data.objects.remove(current_object)

        return {"FINISHED"}

class createLights(Operator):
    bl_label = "Create lights"
    bl_idname = "rti.create_lights"

    def execute(self, context):
        global updated_lps
        scene = context.scene
        mytool = scene.light_panel
        mytool.light_list.clear()

        for current_light in bpy.data.lights:
            current_light.animation_data_clear()
            bpy.data.lights.remove(current_light)

        mytool.light_positions = updated_lps

        if not os.path.isfile(mytool.lp_file_path) and not mytool.nblp:
            self.report({"ERROR"})

        light_sources = bpy.context.scene.objects.get("light_sources")
        if not light_sources:
            light_sources = bpy.data.objects.new(name = "light_sources", object_data = None)
            scene.collection.objects.link(light_sources)

        for idx in range(0, len(mytool.light_positions)):
            lightData = bpy.data.lights.new(name="RTI_light"+str(idx), type="SPOT")
            lightData.spot_size = 0.1

            current_light = bpy.data.objects.new(name="Light_{0}".format(idx), object_data=lightData)

            (x,y,z) = mytool.light_positions[idx]
            current_light.location = (x, y, z)

            scene.collection.objects.link(current_light)

            current_light.rotation_mode = 'QUATERNION'
            current_light.rotation_quaternion = Vector((x,y,z)).to_track_quat('Z','Y')

            current_light.parent = light_sources

            mytool.light_list.append(current_light.name)

        return {"FINISHED"}

class createCamera(Operator):
    bl_idname = "rti.create_camera"
    bl_label = "Create camera"
    def execute(self, context):
        scene = context.scene
        cameras_obj = [cam for cam in bpy.data.objects if cam.type == 'CAMERA']
        scene.camera_panel.camera.clear()
        if len(cameras_obj) != 0:
            # self.report({'ERROR'}, "Camera already exist in scene.")
            # return {'FINISHED'}
            for cam in bpy.data.cameras:
                cam.animation_data_clear()
                bpy.data.cameras.remove(cam)

        camera_data = bpy.data.cameras.new("Camera")
        camera_data.dof.use_dof = True
        camera_data.type="PERSP"
        camera_object = bpy.data.objects.new("Camera", camera_data)

        # Link camera to current scene
        scene.collection.objects.link(camera_object)

        # Move camera to default location at top of dome
        camera_object.location = (0,0,scene.light_panel.dome_radius)
        scene.camera_panel.camera.append(camera_object.name)

        return {'FINISHED'}

class importSurface(Operator):
    bl_idname = "rti.import_surface"
    bl_label = "Import surface"
    def execute(self, context):
        scene = context.scene
        surfacetool = scene.surface_panel
        objects = [obj for obj in bpy.data.objects if obj.type != 'CAMERA' and obj.type !="SUN"]

        if len(objects) != 0:
            self.report({'ERROR'}, "Surface already exist in scene. Delete the old surface to add new surface")
            return {'FINISHED'}

        if scene.surface_panel.mesh_file_path.endswith(".OBJ") or scene.surface_panel.mesh_file_path.endswith(".obj"):
            bpy.ops.import_scene.obj(filepath=scene.surface_panel.mesh_file_path)
            ob = bpy.context.selected_objects[0]
            # ob.rotation_euler[0] = 0.0523599
            surfacetool.surface.append(ob.name)

        return {'FINISHED'}

class addTexture(Operator):
    bl_idname = "rti.add_texture"
    bl_label = "Add texture"
    def execute(self, context):
        scene = context.scene
        mat = scene.objects[scene.surface_panel.surface[0]].active_material
        nodes = mat.node_tree.nodes
        nodes.clear()
        node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
        node_principled.location = 0,0
        node_tex = nodes.new('ShaderNodeTexImage')
        node_tex.image = bpy.data.images.load(scene.surface_panel.texture_image_path)
        node_tex.location = -400,0

        node_output = nodes.new(type='ShaderNodeOutputMaterial')
        node_output.location = 400,0

        links = mat.node_tree.links
        link = links.new(node_tex.outputs["Color"], node_principled.inputs["Base Color"])
        link = links.new(node_principled.outputs["BSDF"], node_output.inputs["Surface"])

        node_tex = nodes.new('ShaderNodeTexImage')
        node_tex.location = -400,0
        img = bpy.data.images.get(scene.surface_panel.texture_image_path)
        if img:
            node_tex.image = img

        return {'FINISHED'}

class SetAnimation(Operator):
    bl_idname = "rti.set_animation"
    bl_label = "Create animation"

    def execute(self, context):
        scene = context.scene

        # bpy.
        scene.timeline_markers.clear()
        scene.animation_data_clear()

        if len(bpy.data.worlds)>0:
            bpy.data.worlds.remove(bpy.data.worlds["World"], do_unlink=True)

        data = bpy.data.objects[context.scene.camera_panel.camera[0]].data
        if context.scene.camera_panel.subject is not None:
            data.dof.focus_object = context.scene.camera_panel.subject

        numLights = len(scene.light_panel.light_list)

        print("Setting new animation with",numLights)

        for i in range(0, numLights):
            current_light = bpy.data.objects[scene.light_panel.light_list[i]]
            current_light.data.energy = 0.0

        for k in range(0, numLights):
            for i in range(0, numLights):
                current_light = bpy.data.objects[scene.light_panel.light_list[i]]
                if i != k:
                    current_light.data.energy = 0.0
                    current_light.data.keyframe_insert(data_path="energy", frame=k+1)
                else:
                    current_light.data.energy = scene.light_panel.light_strength
                    current_light.data.keyframe_insert(data_path="energy", frame=k+1)

        return {'FINISHED'}

class acquire(Operator):
    bl_idname = "rti.acquire"
    bl_label = "Acquire"
    bl_use_preview = True

    def execute(self, context):
        global updated_lps
        bpy.context.scene.render.engine = 'CYCLES'
        bpy.context.scene.cycles.device = 'GPU'
        bpy.context.scene.cycles.preview_samples = 100
        bpy.context.scene.cycles.samples = 1000
        bpy.context.scene.cycles.use_preview_denoising = True
        bpy.context.scene.cycles.use_denoising = True
        bpy.context.scene.render.resolution_x = int(1920*context.scene.camera_panel.resolution_factor)
        bpy.context.scene.render.resolution_y = int(context.scene.camera_panel.aspect_ratio*bpy.context.scene.render.resolution_x)
        bpy.context.scene.frame_start = 1
        bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path
        bpy.context.scene.render.image_settings.color_mode = 'BW'
        bpy.context.scene.render.image_settings.color_depth = '8'
        bpy.context.scene.render.use_overwrite = True
        context.scene.camera = context.scene.objects[context.scene.camera_panel.camera[0]]

        if not context.scene.light_panel.nblp:
            bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            bpy.ops.rti.set_animation()
            bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            bpy.ops.render.play_rendered_anim()

        else:
            print("Executing NBLP")
            nblp = Nblp_2(context=context)

            # updated_lps = nblp.iterations[0].lps_cartesian
            # nblp.generate_lp_file(iteration_nb=0, file_path=context.scene.acquisition_panel.output_path+"iteration_"+str(len(nblp.iterations)-1)+".lp")
            # bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path+nblp.iterations[0].filenames_subtext+"#.png"
            # bpy.ops.rti.create_lights()
            # bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            # bpy.ops.rti.set_animation()
            # bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            # bpy.ops.render.play_rendered_anim()
            # nblp.iterations[0].plot_lps().savefig(context.scene.acquisition_panel.output_path+"30.png")
            # nblp.iterations[0].rename_files(context.scene.acquisition_panel.output_path)
            # nblp.calculate_entropies(len(nblp.iterations)-1,context.scene.acquisition_panel.output_path+"\\entropies\\")

            # nblp.dense_acquisition()
            # updated_lps = nblp.iterations[len(nblp.iterations)-1].lps_cartesian
            # nblp.generate_lp_file(iteration_nb=1, file_path=context.scene.acquisition_panel.output_path+"iteration_"+str(len(nblp.iterations)-1)+".lp")
            # bpy.context.scene.render.filepath = context.scene.acquisition_panel.output_path+"dense_acquisition\\"+nblp.iterations[len(nblp.iterations)-1].filenames_subtext+"#.png"
            # bpy.ops.rti.create_lights()
            # bpy.context.scene.frame_end = len(context.scene.light_panel.light_list)
            # bpy.ops.rti.set_animation()
            # bpy.ops.render.render(animation=True, use_viewport = True, write_still=True)
            # bpy.ops.render.play_rendered_anim()
            # nblp.iterations[len(nblp.iterations)-1].plot_lps().savefig(context.scene.acquisition_panel.output_path+"dense_acquisition\\30.png")
            # nblp.iterations[len(nblp.iterations)-1].rename_files(context.scene.acquisition_panel.output_path+"dense_acquisition\\")

            nblp.write_log(context.scene.acquisition_panel.output_path+"log.yaml")

        return {'FINISHED'}

#################################### Operators } ####################################
#################################### Panels { #######################################
class rti_panel(Panel):

    bl_label = "Surface Adaptive RTI"
    bl_idname = "VIEW3D_PT_surface_adaptive_rti"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"

    def draw(self, context):
        layout = self.layout
        scene = context.scene

class light_panel(Panel):
    bl_label = "Light positions"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_light_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"

    def draw(self, context):
        global updated_lps
        layout = self.layout
        scene = context.scene
        lighttool = scene.light_panel
        layout.label(text="Light positions")
        layout.prop(lighttool, "nblp")
        layout.prop(lighttool, "light_strength")
        layout.prop(lighttool, "dome_radius")

        if not lighttool.nblp:
            layout.prop(lighttool,"lp_file_path")
            row = layout.row(align = True)
            updated_lps = read_lp_file(lighttool.lp_file_path, lighttool.dome_radius)
            row = layout.row(align = True)
            row.operator("rti.create_lights")
        else:
            for current_light in bpy.data.lights:
                bpy.data.lights.remove(current_light)

class surface_panel(Panel):
    bl_label = "Surface"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_surface_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        surfacetool = scene.surface_panel
        layout.label(text="Surface")
        layout.prop(surfacetool, "mesh_file_path")
        row = layout.row(align = True)
        row.operator("rti.import_surface")
        layout.label(text="Texture")
        layout.prop(surfacetool, "texture_image_path")
        row = layout.row(align = True)
        row.operator("rti.add_texture")
        layout.prop(surfacetool, "metallic", slider=True)
        layout.prop(surfacetool, "roughness", slider=True)
        layout.prop(surfacetool, "specularity", slider=True)

class camera_panel(Panel):
    bl_label = "Camera"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_camera_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        cameratool = scene.camera_panel
        layout.label(text="Camera")
        layout.prop(cameratool, "camera_height", slider=True)
        layout.prop(cameratool, "aspect_ratio", slider=True)
        layout.prop(cameratool, "focal_length", slider=True)
        layout.prop(cameratool, "subject")
        layout.prop(cameratool, "resolution_factor")
        layout.prop(cameratool, "view_port_size", slider=True)
        layout.prop(cameratool, "sensor_size", slider=True)
        row = layout.row(align = True)
        row.operator("rti.create_camera")

class acquisition_panel(Panel):
    bl_label = "Acquisition"
    bl_parent_id = "VIEW3D_PT_surface_adaptive_rti"
    bl_idname = "VIEW3D_PT_acquisition_panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Surface Adaptive RTI"
    def draw(self, context):
        layout = self.layout
        scene = context.scene
        layout.label(text="Acquisition")
        layout.prop(scene.acquisition_panel, "output_path")
        # layout.operator("rti.set_animation")
        layout.operator("rti.acquire")
        row = layout.row(align = True)
        row.operator("rti.reset_scene")

#################################### Panels } #######################################

#################################### Register classes { #######################################
classes = (reset_scene,
            light,
            camera,
            surface,
            lightSettings,
            cameraSettings,
            surfaceSettings,
            acquisitionSettings,
            createLights,
            createCamera,
            importSurface,
            addTexture,
            SetAnimation,
            acquire,
            rti_panel,
            surface_panel,
            light_panel,
            camera_panel,
            acquisition_panel)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Scene.light_panel = PointerProperty(type=lightSettings)
    bpy.types.Scene.surface_panel = PointerProperty(type=surfaceSettings)
    bpy.types.Scene.camera_panel = PointerProperty(type=cameraSettings)
    bpy.types.Scene.acquisition_panel = PointerProperty(type=acquisitionSettings)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)
    del bpy.types.Scene.light_panel

if __name__ == "__main__":
    register()

#################################### Register classes } #######################################