import time
import copy
import numpy as np
import math

from options import Options
opt = Options().parse()  # set CUDA_VISIBLE_DEVICES before import torch

import torch
import torchvision
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import random
import numpy as np

from models.autoencoder import Model
from data.modelnet_shrec_loader import ModelNet_Shrec_Loader
from data.shapenet_loader import ShapeNetLoader
from util.visualizer import Visualizer

if __name__=='__main__':
    if opt.dataset=='modelnet' or opt.dataset=='shrec':
        trainset = ModelNet_Shrec_Loader(opt.dataroot, 'train', opt)
        dataset_size = len(trainset)
        trainloader = torch.utils.data.DataLoader(trainset, batch_size=opt.batch_size, shuffle=True, num_workers=opt.nThreads)
        print('#training point clouds = %d' % len(trainset))

        testset = ModelNet_Shrec_Loader(opt.dataroot, 'test', opt)
        testloader = torch.utils.data.DataLoader(testset, batch_size=opt.batch_size, shuffle=False, num_workers=opt.nThreads)
    elif opt.dataset=='shapenet':
        trainset = ShapeNetLoader(opt.dataroot, 'train', opt)
        dataset_size = len(trainset)
        trainloader = torch.utils.data.DataLoader(trainset, batch_size=opt.batch_size, shuffle=True, num_workers=opt.nThreads)
        print('#training point clouds = %d' % len(trainset))

        tesetset = ShapeNetLoader(opt.dataroot, 'test', opt)
        testloader = torch.utils.data.DataLoader(tesetset, batch_size=opt.batch_size, shuffle=False, num_workers=opt.nThreads)
    else:
        raise Exception('Dataset error.')

    model = Model(opt)

    visualizer = Visualizer(opt)

    best_loss = 99
    for epoch in range(601):

        epoch_iter = 0
        for i, data in enumerate(trainloader):
            iter_start_time = time.time()
            epoch_iter += opt.batch_size

            if opt.dataset=='modelnet' or opt.dataset=='shrec':
                input_pc, input_sn, input_label, input_node, input_node_knn_I = data
                model.set_input(input_pc, input_sn, input_label, input_node, input_node_knn_I)
            elif opt.dataset=='shapenet':
                input_pc, input_sn, input_label, input_seg, input_node, input_node_knn_I = data
                model.set_input(input_pc, input_sn, input_label, input_node, input_node_knn_I)

            model.optimize()

            if i % 100 == 0:
                # print/plot errors
                t = (time.time() - iter_start_time) / opt.batch_size

                errors = model.get_current_errors()

                visualizer.print_current_errors(epoch, epoch_iter, errors, t)
                visualizer.plot_current_errors(epoch, float(epoch_iter) / dataset_size, opt, errors)

                # print(model.autoencoder.encoder.feature)
                visuals = model.get_current_visuals()
                visualizer.display_current_results(visuals, epoch, i)

        # test network
        if epoch >= 0 and epoch%1==0:
            batch_amount = 0
            model.test_loss.data.zero_()
            for i, data in enumerate(testloader):
                if opt.dataset == 'modelnet' or opt.dataset=='shrec':
                    input_pc, input_sn, input_label, input_node, input_node_knn_I = data
                    model.set_input(input_pc, input_sn, input_label, input_node, input_node_knn_I)
                elif opt.dataset == 'shapenet':
                    input_pc, input_sn, input_label, input_seg, input_node, input_node_knn_I = data
                    model.set_input(input_pc, input_sn, input_label, input_node, input_node_knn_I)
                model.test_model()

                batch_amount += input_label.size()[0]

                # # accumulate loss
                model.test_loss += model.loss_chamfer.detach() * input_label.size()[0]

            model.test_loss /= batch_amount
            if model.test_loss.item() < best_loss:
                best_loss = model.test_loss.item()
            print('Tested network. So far lowest loss: %f' % best_loss )

        # learning rate decay
        if epoch%20==0 and epoch>0:
            model.update_learning_rate(0.5)

        # save network
        if epoch%1==0 and epoch>0:
            print("Saving network...")
            model.save_network(model.encoder, 'encoder', '%d_%f' % (epoch, model.test_loss.item()), opt.gpu_id)
            model.save_network(model.decoder, 'decoder', '%d_%f' % (epoch, model.test_loss.item()), opt.gpu_id)


from django import forms
from django.forms import BoundField as DjangoBoundField

from config.forms.widgets import TextInput

class BoundField(DjangoBoundField):
    def as_widget(self, widget=None, attrs=None, only_initial=False):
        """
        Render the field by rendering the passed widget, adding any HTML
        attributes passed as attrs. If a widget isn't specified, use the
        field's default widget.
        """
        widget = widget or self.field.widget
        if self.field.localize:
            widget.is_localized = True
        attrs = attrs or {}
        attrs = self.build_widget_attrs(attrs, widget)
        if self.auto_id and "id" not in widget.attrs:
            attrs.setdefault("id", self.html_initial_id if only_initial else self.auto_id)
        return widget.render(
            name=self.html_initial_name if only_initial else self.html_name,
            value=self.value(),
            attrs=attrs,
            renderer=self.form.renderer,
            bound_field=self,
        )

class CharField(forms.CharField):
    widget = TextInput

    def get_bound_field(self, form, field_name):
        """
        Return a BoundField instance that will be used when accessing the form
        field in a template.
        """
        return BoundField(form, self, field_name)


import faulthandler; faulthandler.enable()
import numpy as np
import pymeshlab
import polyscope as ps
import polyscope.imgui as psim
import Polymeshlabfusion
import os

class ChooseCC(Polymeshlabfusion.Mesh):
    '''This is the class responsible for choosing the connected components'''

    is_active = False
    connectedcomp = []
    ms = None
    current_cc = 1
    numberofcc = 0

    def __init__(self, Mesh, color=(0.3, 0.5, 1), mode = 'quad'):
        '''Slightly changed constructor method '''
        self.ms = pymeshlab.MeshSet()
        self.ms.add_mesh(Mesh)
        self.id = self.ms.current_mesh().id()
        self.Mesh = Mesh
        self.name = f"CC number {ChooseCC.numberofcc}"
        self.color = color
        self.VisibleMesh = self.register_mesh(mode)
        ChooseCC.connectedcomp.append(self)
        ChooseCC.numberofcc += 1

    def set_current_mesh(self):
        self.ms.set_current_mesh(self.id)

    def __del__(self):
        self.VisibleMesh.remove()
        self.ms.set_current_mesh(self.id)
        self.ms.delete_current_mesh()

    @classmethod
    def callback(cls, PolyGUI):

        if not psim.TreeNode('Choose connected component'):
            cls.remove_all()
            if PolyGUI.current_step>1:
                PolyGUI.layers[PolyGUI.current_step-1].set_enabled(True)
            return

        if psim.Button('Split up'):

            if not PolyGUI.ms.current_mesh().is_point_cloud():
                #ps.remove_all_structures()
                cls.is_active = True
                cls.ms = pymeshlab.MeshSet()
                m = PolyGUI.ms.current_mesh()
                cls.ms.add_mesh(m)
                cls.ms.generate_splitting_by_connected_components(delete_source_mesh=True)

                for cc in cls.ms:
                    ChooseCC(cc)
            else:
                ps.error("The current mesh is not a point cloud!")

        if cls.connectedcomp !=[]:
            cls.connectedcomp[cls.current_cc-1].set_current_mesh()

        current_step_clicked, cls.current_cc = psim.SliderInt('Connected component no', cls.current_cc, v_min=1, v_max=np.max([len(cls.connectedcomp), 1]))

        if psim.Button('This one'):
            Chosen_one = cls.connectedcomp[cls.current_cc-1]
            while len(cls.connectedcomp)>0:
                del cls.connectedcomp[0]
            cls.connectedcomp.append(Chosen_one)
            cls.current_cc = 1

            PolyGUI.ms.add_mesh(cls.connectedcomp[cls.current_cc-1].Mesh)
            PolyGUI.layers.append(Polymeshlabfusion.Mesh(PolyGUI.ms, f"Reconstructed component", (.2,.3,.4)))
            PolyGUI.current_step = len(PolyGUI.layers)
            cls.is_active = False

        cls.mainloop()

        if cls.is_active:
            cls.set_everything_disabled(PolyGUI.layers)

    @classmethod
    def set_everything_disabled(cls,structurelist):
        for structure in structurelist:
            structure.set_enabled(False)

    @classmethod
    def mainloop(cls):
        for i in range(len(cls.connectedcomp)):
            enabled = True if (i==cls.current_cc-1) else False
            cls.connectedcomp[i].set_enabled(enabled)

    @classmethod
    def remove_all(cls):
        while len(cls.connectedcomp)>0:
            del cls.connectedcomp[0]

MS = pymeshlab.MeshSet()

def callback():
    ChooseCC.callback(MS)

def main():
    MS.load_new_mesh(os.getcwd()+'/Experiment-78/Experiment-78+pointcloud.ply')
    #MS.load_new_mesh(os.getcwd()+'/step3.ply')
    ps.init()
    Original = Polymeshlabfusion.Mesh(MS, 'Super cool mesh', (0.5, 1, 0.5))
    ps.set_user_callback(lambda Mesh = MS : ChooseCC.callback(Mesh))
    ps.show()

if __name__ == '__main__':
    main()


#!/usr/bin/env python
# coding: utf-8

# In[7]:

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.init as init
import pandas as pd
import numpy as np
from torch.utils.data import DataLoader, Dataset
import os, pickle
import argparse
from torch.utils.tensorboard import SummaryWriter
from sklearn.metrics import classification_report
import logging

# In[14]:

class BreathData_cycle(Dataset):
    def __init__(self, data_path, mode="train", val_ratio=0.2, test_ratio=0.2):
        with open(data_path, "rb") as file:
            data_dict = pickle.load(file)
        self.mode = mode
        all_data, all_labels = [],[]
        self.people = [i for i in data_dict.keys()]
        self.label_mapping = {self.people[i]:i for i in range(len(self.people))}
        label2person = {self.label_mapping[person] for person in self.label_mapping.keys()}
        self.save_mapping = {"person2label":self.label_mapping, "label2person":label2person}
        for person, this_data in data_dict.items():
            this_cycle = len(this_data)
            for each in this_data:
                print(each.shape)
            all_data += this_data
            all_labels += [self.label_mapping[person]] * this_cycle
        all_labels = np.asarray(all_labels)

        num_data = len(all_labels)
        train_ratio = 1 - val_ratio - test_ratio
        np.random.seed(0)
        random_idx = np.random.permutation(num_data)
        train_end_idx, val_end_idx = round(num_data * train_ratio), round(num_data * (train_ratio+val_ratio))
        train_idx, val_idx = random_idx[:train_end_idx], random_idx[train_end_idx:val_end_idx]
        if test_ratio != 0.0:
            test_idx = random_idx[val_end_idx:]
        if self.mode=="train":
            self.data = [all_data[i] for i in train_idx]
            self.labels = all_labels[train_idx]
        elif self.mode=="val":
            self.data = [all_data[i] for i in val_idx]
            self.labels = all_labels[val_idx]
        elif self.mode=="test":
            self.data = [all_data[i] for i in test_idx]
            self.labels = all_labels[test_idx]

        print(self.mode)
        #for each in self.data:
        #    print(each.shape)

    def __len__(self):
        return len(self.labels)

    def __getitem__(self,item):
        return self.data[item], self.labels[item]

class BreathData_cycle2(Dataset):
    def __init__(self, data_path, mode="train", val_ratio=0.2, test_ratio=0.2):
        with open(data_path, "rb") as file:
            data_dict = pickle.load(file)
        self.mode = mode
        train_data, train_labels, val_data, val_labels, test_data, test_labels = [],[],[],[],[],[]
        self.people = [i for i in data_dict.keys()]
        self.label_mapping = {self.people[i]:i for i in range(len(self.people))}
        label2person = {self.label_mapping[person]:person for person in self.label_mapping.keys()}
        self.save_mapping = {"person2label":self.label_mapping, "label2person":label2person}
        self.idx_record, self.idx_start = {"train":{}, "val":{}, "test":{}}, {"train":{}, "val":{}, "test":{}}
        idx_start_train, idx_start_val, idx_start_test = 0,0,0
        for itid, (person, this_data) in enumerate(data_dict.items()):

            this_cycle = len(this_data)
            cut_idx_train = int(np.round(this_cycle * (1 - val_ratio - test_ratio)))
            cut_idx_val = int(np.round(this_cycle * (1 - test_ratio)))
            num_val, num_test = cut_idx_val - cut_idx_train, this_cycle - cut_idx_val
            np.random.seed(itid*2)
            rand_idx = np.random.permutation(this_cycle)
            train_idx, val_idx, test_idx = rand_idx[:cut_idx_train], rand_idx[cut_idx_train:cut_idx_val], rand_idx[cut_idx_val:]
            self.idx_record["train"][person] = train_idx
            self.idx_record["val"][person] = val_idx
            self.idx_record["test"][person] = test_idx

            train_data += [this_data[i] for i in train_idx]
            train_labels += [self.label_mapping[person]] * cut_idx_train
            val_data += [this_data[i] for i in val_idx]
            val_labels += [self.label_mapping[person]] * (cut_idx_val - cut_idx_train)
            test_data += [this_data[i] for i in test_idx]
            test_labels += [self.label_mapping[person]] * (this_cycle - cut_idx_val)

            self.idx_start["train"][person] = (idx_start_train, idx_start_train+cut_idx_train)
            self.idx_start["val"][person] = (idx_start_val, idx_start_val+num_val)
            self.idx_start["test"][person] = (idx_start_test, idx_start_test+num_test)

        if self.mode=="train":
            self.data = train_data
            self.labels = train_labels
        elif self.mode=="val":
            self.data = val_data
            self.labels = val_labels
        elif self.mode=="test":
            self.data = test_data
            self.labels = test_labels

        print(self.mode)

        #for each in self.data:
        #    print(each.shape)

    def __len__(self):
        return len(self.labels)

    def __getitem__(self,item):
        return self.data[item], self.labels[item]

class RNN_model(nn.Module):
    def __init__(self, num_classes):
        super(RNN_model, self).__init__()
        self.project_layer = nn.Linear(39, 128)
        self.rnn_layer = nn.LSTM(128,128, batch_first=True)
        self.classifier = nn.Linear(128, num_classes)

    def forward(self, x):
        rnn_outputs, (hn, cn) = self.rnn_layer(self.project_layer(x))
        logits = self.classifier(rnn_outputs[:,-1])
        return logits

# In[4]:
def initialize_weights(model):
    if isinstance(model, nn.LSTM):
        for name, param in model.named_parameters():
            if 'weight_ih' in name:
                torch.nn.init.xavier_uniform_(param.data)
            elif 'weight_hh' in name:
                torch.nn.init.orthogonal_(param.data)
            elif 'bias' in name:
                param.data.fill_(0)

def run_epoch(data_loader, epoch, model, criterion, device, use_cuda=True, mode = 'train',
    optimizer=None, writer=None, idx_record=None):
    if mode == "train":
        model.train()
    elif mode in ['val', 'test']:
        model.eval()
    else:
        print("Wrong mode input!")
        return

    model.to(device)
    FloatTensor = torch.cuda.FloatTensor if use_cuda else torch.FloatTensor
    LongTensor = torch.cuda.LongTensor if use_cuda else torch.LongTensor
    ByteTensor = torch.cuda.ByteTensor if use_cuda else torch.ByteTensor
    avg_loss, avg_acc = 0.0, 0.0
    batches = 0
    output_all, label_all = [],[]
    for batch_idx, sample in enumerate(data_loader):
        data, labels = sample[0], sample[1]
        label_all.append(labels.numpy())
        data = data.type(dtype=FloatTensor).to(device)
        labels = labels.type(dtype=LongTensor).to(device)
        #print(data.size())
        #print(labels.size())
        output = model(data)
        output_class = torch.argmax(output, dim=1)
        output_all.append(output_class.cpu().numpy())
        loss = criterion(output, labels)
        if mode == "train":
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        avg_loss += loss.item()
        avg_acc += (output_class == labels).sum().item()
        batches += labels.size()[0]
    avg_loss /= batches
    avg_acc /= batches
    print("all data num:{}".format(batches))
    output_all, label_all = np.concatenate(output_all), np.concatenate(label_all)

    if writer is not None:
        writer.add_scalar(f"Loss/{mode}", avg_loss, epoch)
        writer.add_scalar(f"Acc/{mode}", avg_acc, epoch)

    print('%s Epoch: %d  , Loss: %.4f,  Accuracy: %.2f'%( mode, epoch, avg_loss, avg_acc))

    return avg_acc, output_all, label_all

def get_fpr(output_all, label_all, label2person):
    person_all = np.unique(label_all)
    fpr_each = {}
    mis_cls_all, total = 0,0
    for person in person_all:
        corr_output = output_all[label_all!=person]
        num_other = len(corr_output)
        mis_cls = (corr_output==person).sum()
        mis_cls_all += mis_cls
        total += num_other
        fpr_each[label2person[person]] = round(mis_cls / num_other)

    fpr = mis_cls_all / total

    return fpr, fpr_each

# In[17]:

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='train for user identity classification')
    parser.add_argument('--base_path', type=str, default="/home/qiaomu/codes/CSE518")
    parser.add_argument('--ckpt_path', type=str, default="/data/add_disk0/qiaomu/ckpts/CSE518")
    parser.add_argument('--optim', type=str, default="SGD")
    parser.add_argument('--epochs', type=int, default=35)
    parser.add_argument('--batch_size', type=int, default=1)
    parser.add_argument('--mode', type=str, default="train")
    parser.add_argument('--lr', type=float, default=0.001)
    parser.add_argument('--weight_decay', type=float, default=5e-5)
    parser.add_argument('--ckpt_name', type=str, default="best_model.pt")

    args = parser.parse_args()
    data_path = os.path.join(args.base_path, "extracted_features_new.pickle")
    train_dataset = BreathData_cycle2(data_path, mode="train")
    val_dataset = BreathData_cycle2(data_path, mode="val")
    save_mapping = train_dataset.save_mapping
    label2person = save_mapping["label2person"]
    target_names = [label2person[key] for key in label2person.keys()]
    num_people = len(train_dataset.people)
    print(f"num_people:{num_people}")
    #print("train dataset indices: {}".format(train_dataset.idx_record))
    #print("val dataset indices: {}".format(val_dataset.idx_record))

    writer = SummaryWriter('./logs')
    train_loader = DataLoader(train_dataset, batch_size= args.batch_size, num_workers=4, shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size= args.batch_size, num_workers=4, shuffle=False)
    model = RNN_model(num_people)
    model.apply(initialize_weights)
    criterion = nn.CrossEntropyLoss()
    use_cuda = torch.cuda.is_available()
    print('use cuda: %s'%(use_cuda))
    if use_cuda:
        device = torch.device('cuda:0')
    else:
        device = torch.device('cpu')
    if args.optim=='SGD':
        optimizer = optim.SGD(model.parameters(),lr=args.lr, weight_decay=args.weight_decay)
    elif args.optim=="Adam":
        optimizer = optim.Adam(model.parameters(),lr=args.lr, weight_decay=args.weight_decay)
    elif args.optim=="RMSprop":
        optimizer = optim.RMSprop(model.parameters(),lr=args.lr, weight_decay=args.weight_decay)

    if not os.path.exists(args.ckpt_path):
        os.makedirs(args.ckpt_path)

    if args.mode=="train":
        for epoch in range(args.epochs):
            acc_train, output_train, label_train = run_epoch(train_loader, epoch, model, criterion, device, use_cuda, mode='train', optimizer=optimizer, writer=writer)
            acc_val, output_val, label_val = run_epoch(val_loader, epoch, model, criterion, device, use_cuda, mode="val", optimizer=optimizer, writer=None)
            fpr_val, fpr_val_each = get_fpr(output_val, label_val, label2person)
            print("Val fpr:{}".format(fpr_val))
            if acc_val > 0.90:
                save_path = os.path.join(args.ckpt_path, f"LSTM_{epoch}_{args.optim}_lr{args.lr}.pt")
                torch.save(model.state_dict(), save_path)
        writer.flush()
        writer.close()

    elif args.mode=="test":
        log_file = os.path.join(args.base_path, "logs", "test_log.log")
        logger = logging.getLogger("logger")
        logger.setLevel(logging.DEBUG)
        fh = logging.FileHandler(log_file)
        fh.setLevel(logging.DEBUG)
        sh = logging.StreamHandler()
        sh.setLevel(logging.INFO)
        logger.addHandler(sh)
        logger.addHandler(fh)

        model.load_state_dict(torch.load(os.path.join(args.ckpt_path, args.ckpt_name)))
        test_dataset = BreathData_cycle2(data_path, mode="test")
        test_loader = DataLoader(test_dataset, batch_size= args.batch_size, num_workers=8, shuffle=False)
        idx_record = test_dataset.idx_record["test"]
        acc_test, output_test, label_test = run_epoch(test_loader, 0, model, criterion, device, use_cuda, mode="test",
            optimizer=optimizer, writer=None, idx_record=idx_record)
        fpr_test, fpr_test_each = get_fpr(output_test, label_test, label2person)

        logger.info("Test TPR: {}, Test FPR:{}".format(acc_test, fpr_test))
        logger.info("Test FPR each: {}".format(fpr_test_each))
        logger.info(classification_report(label_test, output_test, target_names=target_names))

        val_dataset = BreathData_cycle2(data_path, mode="val")
        val_loader = DataLoader(val_dataset, batch_size= args.batch_size, num_workers=8, shuffle=False)
        acc_val, output_val, label_val = run_epoch(val_loader, 0, model, criterion, device, use_cuda, mode="val", optimizer=optimizer, writer=None)
        fpr_val, fpr_val_each = get_fpr(output_test, label_test, label2person)
        logger.info("Val TPR:{}, Val FPR:{}".format(acc_val, fpr_val))
        logger.info("Val FPR each: {}".format(fpr_val_each))
        logger.info(classification_report(label_val, output_val, target_names=target_names))

        with open("label_person_mapping_new.pickle","wb") as file:
            pickle.dump(save_mapping, file, protocol=pickle.HIGHEST_PROTOCOL)
        print(save_mapping)
        print(val_dataset.save_mapping)

# In[ ]:


#!/usr/bin/python3

#import keyboard #Using module keyboard
import sys
import curses
import pandas as pd
import matplotlib.pyplot as plt

turmas_color = pd.read_csv('Turmas/2018.2.color', sep=':', header=None, names=['Disciplina', 'Cor'])
turmas = pd.read_csv('Turmas/2018.2.csv')
max_color = int(turmas_color['Cor'].max()) + 1 # +1 para fazer o múdulo

def gerar_table(cor):
    idxs = turmas_color[turmas_color.Cor == cor]
    idxs['Disciplina'] += 1
    t = turmas.iloc[list(idxs['Disciplina'])]
    d = [di for di in list(t['Componente'])]
    return d

def main(win):
    start = 0
    end = 8
    cor = 0
    tab = gerar_table(cor)
    win.nodelay(True)
    key=""
    win.clear()
    while 1:
        win.addstr(1,2,'Bem-vindo ao Sigraade, um app pra você')
        win.addstr(2,2,'escolher sua grade de disciplinas do Sigaa')
        win.addstr(3,2,'Pressione "n" para gerar a próxima tabela e')
        win.addstr(4,2,'e os direcionais para navegar nos')
        win.addstr(5,2,'conjuntos de disciplinas')
        win.addstr(6,5,'Conjunto '+ str(cor))
        for i, d in enumerate(tab[start:end]):
            win.addstr(7+i,0,d)

        try:
           key = win.getkey()
           win.clear()
           if key == 'q':
              sys.exit()
           elif key == 'KEY_RIGHT':
               cor = (cor + 1) % max_color
               tab = gerar_table(cor)
               start = 0
               end = 8
           elif key == 'KEY_LEFT':
               cor = (cor - 1) % max_color
               tab = gerar_table(cor)
               start = 0
               end = 8
           elif key == 'KEY_DOWN':
               start = start + 1
               end = end + 1
           elif key == 'KEY_UP':
               start = max(start - 1, 0)
               end = max(end - 1, 8)
        except Exception as e:
           # No input
           pass

curses.wrapper(main)


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Standard Library
import copy
import itertools
import json
import re
from typing import Any, List, Mapping, Optional, Tuple, Union

# Third Party
import boltons.iterutils
import cachetools
from loguru import logger
from pydantic import BaseModel, Field

# Local
import bel.belspec.specifications
from bel.belspec.specifications import additional_computed_relations
from bel.core.utils import html_wrap_span, nsarg_pattern
from bel.lang.ast import Arg, BELAst, Function, NSArg, Relation, StrArg
from bel.schemas.bel import FunctionSpan, NsArgSpan, Pair, Span, ValidationError

def mask(string: str, start: int, end: int, replacement_char="#"):
    """Mask part of a string with replacement char"""

    return string[:start] + replacement_char * (end - start) + string[end:]

def parse_info(assertion_str: str, version: str = "latest"):
    """Create parse info for AST to use in parsing Assertion String"""

    errors = []
    (matched_quotes, errors) = find_matching_quotes(assertion_str, errors)
    (matched_parens, errors) = find_matching_parens(assertion_str, matched_quotes, errors)
    (commas, errors) = find_commas(assertion_str, matched_quotes, errors)
    (relations, errors) = find_relations(assertion_str, matched_quotes, errors, version)
    (functions, errors) = find_functions(
        assertion_str, matched_quotes, matched_parens, errors, version
    )
    nsargs = find_nsargs(assertion_str)

    components = relations + functions + nsargs

    # Find str arguments and floating strings after masking all other components
    strings = find_strings(assertion_str, components)
    components += strings

    # Add parens for function boundaries (AFTER processing string arguments)
    for parens in matched_parens:
        if parens.start:
            components.append(
                Span(start=parens.start, end=parens.start + 1, span_str="(", type="start_paren")
            )
        if parens.end:
            components.append(
                Span(start=parens.end, end=parens.end + 1, span_str=")", type="end_paren")
            )

    components.sort(key=lambda x: x.start)

    return {
        "matched_quotes": matched_quotes,
        "matched_parens": matched_parens,
        "commas": commas,
        "components": components,
        "errors": errors,
    }

def intersect(pos: int, spans: List[Optional[Span]]) -> bool:
    """Check to see if pos intersects the provided spans - e.g. quotes"""

    if spans:
        for span in spans:

            if span.start and span.end and span.start < pos < span.end:
                return True

    return False

def ordered_pairs(left: List[int], right: List[int]) -> List[Union[int, None]]:
    """Return ordered pairs such that every left, right pair has left < right"""

    alt = {"left": "right", "right": "left"}

    pairs = [("left", item) for item in left] + [("right", item) for item in right]
    pairs.sort(key=lambda x: x[1])

    # Must have left, right alternation - insert placeholders for left, left or right, right entries
    new_pairs = []
    for idx, pair in enumerate(pairs):
        next_idx = idx + 1
        # Trying to match two lefts together: pair[0] == pairs[idx + 1][0]?
        if (next_idx < len(pairs) and pair[0] == pairs[idx + 1][0]) or (
            next_idx >= len(pairs) and pair[0] == "left"
        ):
            new_pairs.append(pair)
            new_pairs.append((alt[pair[0]], None))

        elif idx == 0 and pair[0] == "right":
            new_pairs.append((alt[pair[0]], None))
            new_pairs.append(pair)

        else:
            new_pairs.append(pair)

    matched_quotes = [
        Pair(start=new_pairs[i][1], end=new_pairs[i + 1][1]) for i in range(0, len(new_pairs), 2)
    ]

    return matched_quotes

def find_matching_quotes(
    assertion_str: str, errors: List[ValidationError]
) -> Tuple[List[Pair], List[ValidationError]]:
    """Find matching quotes using BEL Assertion syntax"""

    quote_matches_left = re.compile(r"[\,\:\!\(]+\s*(\")")
    quote_matches_right = re.compile(r"(\")\s*[\!\)\,]")

    iter_left = re.finditer(quote_matches_left, assertion_str)
    iter_right = re.finditer(quote_matches_right, assertion_str)
    left_quotes = [m.span(1)[0] for m in iter_left]
    right_quotes = [m.span(1)[0] for m in iter_right]

    matched_quotes = ordered_pairs(left_quotes, right_quotes)

    # TODO suggest where the missing quote should be placed
    for idx, pair in enumerate(matched_quotes):
        if pair.start is None and idx == 0:
            errors.append(
                ValidationError(
                    type="Assertion",
                    severity="Error",
                    msg=f"Missing left quote before right quote at position {pair.end}",
                    visual=html_wrap_span(assertion_str, [(pair.end, pair.end + 1)]),
                    index=pair.end,
                )
            )
        elif pair.start is None:
            errors.append(
                ValidationError(
                    type="Assertion",
                    severity="Error",
                    msg=f"Missing left quote between right quotes at positions {matched_quotes[idx-1].end} and {pair.end}",
                    visual=html_wrap_span(
                        assertion_str, [(matched_quotes[idx - 1].end, pair.end + 1)]
                    ),
                    index=matched_quotes[idx - 1].end,
                )
            )
        elif pair.end is None and idx == len(matched_quotes):
            errors.append(
                ValidationError(
                    type="Assertion",
                    severity="Error",
                    msg=f"Missing right quote after left quote at position {pair.start}",
                    visual=html_wrap_span(assertion_str, [(pair.start, pair.start + 1)]),
                    index=pair.start,
                )
            )
        elif pair.end is None:

            next_pair_idx = idx + 1
            if next_pair_idx < len(matched_quotes):
                span_end = matched_quotes[next_pair_idx].start
            else:
                span_end = len(assertion_str)

            errors.append(
                ValidationError(
                    type="Assertion",
                    severity="Error",
                    msg=f"Missing right quote after left quote at position {pair.start} and before position {span_end}",
                    visual=html_wrap_span(assertion_str, [(pair.start, span_end)]),
                    index=pair.start,
                )
            )

    return (matched_quotes, errors)

def find_commas(
    assertion_str: str, matched_quotes: List[Span], errors: List[ValidationError]
) -> Tuple[List[int], List[ValidationError]]:
    """Find commas in chars list that are not in quoted strings"""

    if errors is None:
        errors = []

    commas: List[int] = []

    for idx, char in enumerate(assertion_str):
        if intersect(idx, matched_quotes):
            continue
        elif char == ",":
            commas.append(idx)

    return (sorted(commas), errors)

def find_matching_parens(
    assertion_str, matched_quotes, errors: List[ValidationError]
) -> Tuple[List[Pair], List[ValidationError]]:
    """Find and return the location of the matching parentheses pairs in s.

    Given a string, s, return a dictionary of start: end pairs giving the
    indexes of the matching parentheses in s. Suitable exceptions are
    raised if s contains unbalanced parentheses.

    """

    # The indexes of the start parentheses are stored in a stack, implemented as a list
    stack: List[int] = []
    matched_parens: List[Pair] = []

    for idx, char in enumerate(assertion_str):
        if char == "(" and not intersect(idx, matched_quotes):
            stack.append(idx)
        elif char == ")" and not intersect(idx, matched_quotes):
            if len(stack) > 0:
                matched_parens.append(Pair(start=stack.pop(), end=idx))
            else:
                errors.append(
                    ValidationError(
                        type="Assertion",
                        severity="Error",
                        msg=f"Too many close parentheses at index {idx}",
                        visual=html_wrap_span(assertion_str, [(idx, idx + 1)]),
                        index=idx,
                    )
                )
    if stack:
        for idx in stack:
            errors.append(
                ValidationError(
                    type="Assertion",
                    severity="Error",
                    msg=f"No matching close parenthesis for open parenthesis at index {idx}",
                    visual=html_wrap_span(assertion_str, [(idx, idx + 1)]),
                    index=idx,
                )
            )

    return (sorted(matched_parens, key=lambda e: e.start), errors)

@cachetools.cached(cachetools.TTLCache(maxsize=1, ttl=600))
def get_relations_regex(version: str = "latest"):

    relations_list = bel.belspec.specifications.get_all_relations(version)
    relations_list += additional_computed_relations
    relations_list = list(set(relations_list))
    relations_list.sort(key=len)
    relations_regex = "|".join(relations_list)

    return relations_regex

def find_relations(
    assertion_str: str, matched_quotes: List[Pair], errors: List[ValidationError], version: str
) -> Tuple[List[Span], List[ValidationError]]:
    """Find relation(s) e.g. handle nested objects as well

    Returns:
        List[Tuple[int, int, str]] = e.g. [(2, 4, '->')]
    """

    relations_regex = get_relations_regex(version=version)

    # Regex match all potential relations TODO make \S more specific to relation chars
    potential_relations = re.compile(f"\\s({relations_regex})\\s")
    iterator = re.finditer(potential_relations, assertion_str)
    pre_spans = [m.span(1) for m in iterator]

    # Filter quoted strings - can't have a relation in a quoted string
    relations = [
        Span(start=r[0], end=r[1], span_str=assertion_str[r[0] : r[1]], type="relation")
        for r in pre_spans
        if not intersect(r[0], matched_quotes)
    ]

    if len(relations) > 2:
        intervals = [(r.start, r.end) for r in relations]
        idx = relations[0].start
        error_str = ", ".join([f"{r.span_str}[{r.start}:{r.end}]" for r in relations])

        errors.append(
            ValidationError(
                type="Assertion",
                severity="Error",
                msg=f"Too many relationships: {error_str}",
                visual=html_wrap_span(assertion_str, intervals),
                index=idx,
            )
        )

    return (sorted(relations, key=lambda e: e.start), errors)

def find_functions(
    assertion_str: str,
    matched_quotes: List[Pair],
    matched_parens: List[Pair],
    errors: List[ValidationError],
    version: str,
) -> Tuple[List[FunctionSpan], List[ValidationError]]:
    """Find function(s)

    Returns:
        List[Tuple[int, int, str]] = e.g. [(2, 4, '->')]
    """

    functions_list = bel.belspec.specifications.get_all_functions(version)

    iterator = re.finditer("([a-zA-Z]+)\(", assertion_str)

    name_spans = []
    for m in iterator:
        matched = m.group(1)
        if matched not in functions_list:
            continue

        name_spans.append(m.span(1))

    # Filter quoted strings - can't have a relation in a quoted string
    name_spans = [r for r in name_spans if not intersect(r[0], matched_quotes)]

    functions = []
    for span in name_spans:

        name_start = span[0]
        name_end = span[1]
        name_str = assertion_str[name_start:name_end]

        # Initialize FunctionSpan with just name span
        function = FunctionSpan(
            start=name_start,
            end=name_end,
            span_str=name_str,
            type="function",
            name=Span(start=name_start, end=name_end, span_str=name_str, type="function_name"),
            args=None,
        )

        # then add the function full span and arguments span
        for parens in matched_parens:
            if name_end == parens.start:
                args_end = parens.end + 1
                function.end = args_end
                function.span_str = assertion_str[name_start:args_end]
                function.args = Span(
                    start=parens.start,
                    end=args_end,
                    span_str=assertion_str[parens.start : args_end],
                    type="function_args",
                )

        # TODO - check for relation after name_end and before end of Assertion string to bound the function
        # This covers function with missing end parenthesis
        if function.args is None:
            args_end = len(assertion_str)
            function.end = args_end
            function.span_str = assertion_str[name_start:args_end]
            function.args = Span(
                start=name_end,
                end=args_end,
                span_str=assertion_str[name_end:args_end],
                type="function_args",
            )

        functions.append(copy.deepcopy(function))

    return (sorted(functions, key=lambda e: e.start), errors)

def find_nsargs(assertion_str: str) -> List[Optional[NsArgSpan]]:
    """Namespace argument parsing

    Namespace IDs and Labels are NOT allowed to have internal double quotes.

    IDs or Labels with commas or end parenthesis in them must be quoted.

    The parser supports NS:ID!LABEL or NS : ID ! LABEL (e.g. with arbitrary whitespace between the separators)
    """

    nsarg_spans: List[NsArgSpan] = []

    for match in re.finditer(nsarg_pattern, assertion_str):

        # print("Span", match.group("ns_arg"), match.start("ns_arg"), match.end("ns_arg"))

        ns_arg_span = NsArgSpan(
            span_str=match.group("ns_arg"),
            start=match.start("ns_arg"),
            end=match.end("ns_arg"),
            type="ns_arg",
            namespace=Span(
                span_str=match.group("ns"),
                start=match.start("ns"),
                end=match.end("ns"),
                type="namespace",
            ),
            id=Span(
                span_str=match.group("id"),
                start=match.start("id"),
                end=match.end("id"),
                type="ns_id",
            ),
        )

        if match.group("label"):
            ns_arg_span.label = Span(
                span_str=match.group("label"),
                start=match.start("label"),
                end=match.end("label"),
                type="ns_label",
            )

        nsarg_spans.append(copy.deepcopy(ns_arg_span))

    return nsarg_spans

def find_strings(assertion_str, components):
    """Find str_args and unknown strings"""

    str_spans: List[Span] = []

    potential_replacement_chars = ["#", "$", "=", "@", "&"]

    for char in potential_replacement_chars:
        if char not in assertion_str:
            replacement_char = char
            break

    for comp in components:
        start = comp.start
        end = comp.end
        if comp.type == "function":  # just mask function name for functions
            start = comp.name.start
            end = comp.name.end
        assertion_str = mask(assertion_str, start, end, replacement_char)

    boundary_chars = [replacement_char, ",", ")", "("]
    space_boundary_chars = [" ", replacement_char, ",", ")", "("]
    start = None
    for idx, c in enumerate(assertion_str):
        if start is None and c not in space_boundary_chars:
            start, end = idx, idx

        elif start and c in boundary_chars:
            type_ = "string_arg"
            if c == replacement_char:
                type_ = "string"
            str_spans.append(
                Span(start=start, end=end + 1, span_str=assertion_str[start : end + 1], type=type_)
            )
            start = None

        elif start is not None and c not in space_boundary_chars:
            end = idx

    if start is not None:
        str_spans.append(
            Span(start=start, end=end + 1, span_str=assertion_str[start : end + 1], type="string")
        )

    return str_spans

def main():

    test = {}
    span = Span(start=1, end=2, span_str="H")
    test[f"{span.start}-{span.end}"] = span
    print(test)

if __name__ == "__main__":
    main()


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

"""YAMnet feature creation script."""

import os

import numpy as np
from omegaconf import OmegaConf

from src.data import load_metadata, find_paths
from src.features.YAMNetExtractor import YAMNetExtractor
import src.utils

def main():
    """Run the YAMnet feature extraction."""
    # Load the configuration
    conf = OmegaConf.load("./config.yaml")

    # Setup the GPUs to use
    src.utils.gpu_setup()

    # Load the metadata and get the subset to use
    metadata = load_metadata(conf.dataset_path)
    uri_list = np.loadtxt(conf.features_uri_path, dtype=str)
    sel = [uri in uri_list for uri in metadata.episode_uri]
    subset = metadata.iloc[sel]

    # Generate the input and output paths
    input_path = os.path.join(conf.dataset_path, "podcasts-audio")
    output_path = os.path.join(conf.features_output_path, "yamnet")
    print("Taking input from {}".format(input_path))
    print("Extracting output to {}".format(output_path))
    input_paths = find_paths(subset, input_path, ".ogg")
    embed_paths = find_paths(subset, os.path.join(output_path, "embedding"), ".h5")
    output_paths = find_paths(subset, os.path.join(output_path, "scores"), ".h5")

    # Run the YAMnet feature extraction
    ex = YAMNetExtractor(logfile=os.path.join(conf.features_output_path, "log_yamnet"))
    ex.embedding(input_paths, output_paths, embed_paths)

if __name__ == "__main__":
    main()


"""
1、导入模块
2、创建套接字
3、发送数据
4、接收数据（二进制）
5、解码数据，得到字符串
6、输出显示接收到的内容
7、关闭套接字
"""
# 1、导入模块
import socket
# 2、创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 3、发送数据
udp_socket.sendto("约吗？".encode("GBK"), ("192.168.0.142", 8888))
# 4、接收数据（二进制）
recv_data = udp_socket.recvfrom(2048)
print(recv_data)
# 5、解码数据，得到字符串
# encoding指定解码格式
# errors出现错误后，如何处理 strict：严格模式 ignore：忽略模式
# decode(encoding="UTF-8",errors="")
recv_text = recv_data[0].decode(encoding="UTF-8", errors="ignore")
# 6、输出显示接收到的内容
print("来自：", recv_data[1][0], "的消息,", recv_text)
# 7、关闭套接字
udp_socket.close()


from datetime import datetime, timedelta
import pandas as pd

def oneHourIntervalBrushing(data_hour):
    if len(data_hour['event_time']) == 1:
        # not brushing for sure
        return ""
    else:
#         calculate the concentrate rate
        unique_buyer_list = []
        suspicious_buyer = ""
        highest_order = 0
        unique_buyer_list = data_hour.userid.unique()
        num_orders = len(data_hour['shopid'])
        num_unique_buyers = len(unique_buyer_list)
        concentrate_rate = num_orders / num_unique_buyers
        if float(concentrate_rate) >= 3:
            # need to calculate propotion
            for each_buyer in unique_buyer_list:
                if len(data_hour[data_hour.userid == each_buyer]) > highest_order:
                    highest_order = len(data_hour[data_hour.userid == each_buyer])
                    suspicious_buyer = each_buyer
        return suspicious_buyer

def uniqueShopBrushing(data_shop):
    starting_time = data_shop['event_time'].iloc[0]
    # print(starting_time)
    buyerid_str_list = "0"
    if len(data_shop['event_time']) == 1 :
        buyerid = oneHourIntervalBrushing(data_shop)
        if buyerid == "":
            pass
    for i in range(0, len(data_shop['event_time'])):
        date_str = data_shop['event_time'].iloc[i]
        starting_time_obj = datetime(int(date_str[0:4]), int(date_str[5:7]), int(date_str[8:10]), int(date_str[11:13]),
                                     int(date_str[14:16]), int(date_str[17:19]))

        for j in range(i+1, len(data_shop['event_time'])):

            date_str = data_shop['event_time'].iloc[j]
            ending_time_obj = datetime(int(date_str[0:4]), int(date_str[5:7]), int(date_str[8:10]),
                                         int(date_str[11:13]),
                                         int(date_str[14:16]), int(date_str[17:19]))
            if ending_time_obj > (starting_time_obj + timedelta(hours=1)):
                buyerid = oneHourIntervalBrushing(data_shop[i:j])
                if buyerid == "" :
                    pass
                else:
                    if buyerid_str_list == "0":
                        buyerid_str_list = str(buyerid)
                    else:
                        if str(buyerid) in str(buyerid_str_list):
                            pass
                        else:

                            buyerid_str_list = str(buyerid_str_list) + ("&")
                            buyerid_str_list = str(buyerid_str_list) + (str(buyerid))
                break

    return buyerid_str_list

def brushing():
    data = pd.read_csv("./order_brush_order.csv")

    data = data.sort_values(by=['shopid', 'event_time'])
    # data = data.head(n=10000)
    shopid_list = data.shopid.unique()
    final_shopid = []
    final_buyerid_str_list = []
    for indiv_shop in shopid_list:
        # print(data.loc[data['shopid'] == indiv_shop])

        buyerid_str_list = uniqueShopBrushing(data.loc[data['shopid'] == indiv_shop])
        final_shopid.append(indiv_shop)
        final_buyerid_str_list.append(buyerid_str_list)

    final_dataframe = pd.DataFrame({'shopid':   final_shopid,
                   'userid': final_buyerid_str_list})
    # print(final_shopid)
    # print(final_buyerid_str_list)
    final_dataframe.to_csv("./output.csv", index=False)
if __name__ =="__main__":

    brushing()


import faiss
import json
import numpy as np

class FaissSearch():
    """
The search takes an array of embeddings (can be a single embedding in the array), and returns a list of lists
with the k specified nearest matches for each embedding in the array.
    """

    def __init__(self):
        """
        Loads the embeddings from a JSON file, builds a matrix of each elements UUID, and builds
        an faiss index of that matrix.
        """
        with open("image_embeddings.json", 'r') as f:
            self.embeddings = json.load(f)
        self.element_matrix = self.build_element_matrix()
        self.index = self.build_index() #so in theory I can just return self.index[index]

    def build_element_matrix(self):
        """
        Builds a matrix of each elements UUID.

        Returns:
            A matrix of the UUIDS the elements in the corpus.
        """
        count = 0 # for faster testing
        element_matrix = np.ndarray([0,1000]) # don't have to hard code this; could put embeddings.shape[1]
        for uuid in self.embeddings:
            element = np.array (self.embeddings[uuid])
            element_matrix = np.vstack((element, element_matrix))
            count += 1
            if count == 100: #for faster testing
                break
        return element_matrix.astype('float32')

    def build_index(self):
        """
        Creates a flat index of the element matrix using the faiss module.

        Returns:
            The index is being returned.
        """
        index = faiss.IndexFlatL2(1000)
        index.add(self.element_matrix)
        return index

    def search(self, query_vector, num_results):
        """
        Returns the distances and indices of the closest vectors to the queried vectors.
        The query vector can contain the features of one or several images. A set of results
        will be returned for each set of image features in the quesry vector.

        Args:
            query_vector: a vector containing one or more vectors you want to search for
            num_results: number of results to return

        Returns:
            The index of the closest vector to the query vector.
        """
        d, i = self.index.search(query_vector, num_results)
        return i

    def decode_search_result(self, result):
        decoded_results = [] # a list of lists of UUIDS of near images for each queried element
        for queried_element in result: # result is an array of arrays, each which is the collection of nearest images for each element queried.
            uuids = [] #creates a list of UUIDS for each queried element, to be appended to the total decoded results
            for position in queried_element:#position is a np ndarray with one element in [position]
                result_vector = self.element_matrix[position]
                for uuid, vector in self.embeddings.items(): #vector is a list, result_vector is an array
                    if result_vector.tolist() == vector[0]:
                        uuids.append (uuid)
            decoded_results.append(uuids)
        return decoded_results # a list of lists

if __name__ == '__main__':
    pass


#settings for all of the window, regarding icons, title and width/height.
def GUI_setting(window,app_width,app_height):

    window.title('Rock Paper Scissors Game')
    window.iconbitmap('imgg\icon.ico')

    #getting the user's screen size
    screen_width= window.winfo_screenwidth()
    screen_height= window.winfo_screenheight()

    #getting the x and y coordinates to be able to position the window in the center of the screen
    x=(screen_width/2)-(app_width/2)
    y=(screen_height/2)-(app_height/2)
    window.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')
    window.resizable(width=False,height=False)


# -*- coding: utf-8 -*-
"""
Created on Thu Jun  3 09:57:19 2021

@author: juandc
"""

# %%
### INITIALIZATION

# reset variables etc.
from IPython import get_ipython
get_ipython().magic('reset -sf')

# libraries
import community_simulator
import community_simulator.usertools
import community_simulator.visualization
import numpy as np
#import pandas as pd
# import matplotlib
#import matplotlib.pyplot as plt
# import seaborn as sns
# colors = sns.color_palette()
# %matplotlib inline

import random
# import math
import copy
import os

# check if running on Windows
if os.name == 'nt':
    par = False
else:
    par = True

import time
start_time = time.time()

np.random.seed(1)
random.seed(1)

# %%
### USER-DEFINED FUNCTIONS

# stabilize communities through serial passaging
def stabilizeCommunities(plate):

    # parameters
    dilution_factor = 1/100
    n_transfers = 20
    time_per_cycle = 1
    # extinction_threshold = 1e-4 # we don't use this because we don't use StadyState()

    # run stabilization protocol
    Ntraj, Rtraj = plate.RunExperiment(f = np.diag(np.ones(plate.n_wells))*dilution_factor,
                                       T = time_per_cycle,
                                       npass = n_transfers,
                                       compress_resources=False,
                                       compress_species=True)

    return([plate.N,plate.R])

# Bray-Curtis similarity
# p and q are lists (of absolute abundances, sum need not be 1)
def bray_curtis(p,q):

    # C_pq: sum of lesser values of common species
    c = sum([min(p[i],q[i]) for i in range(len(p)) if p[i]>0 and q[i]>0])

    # Bray-Curtis similarity
    bc = 2*c/(sum(p) + sum(q))
    return bc

# similarity index
# p: invasive
# q: resident
# pq: coalesced
def mysim(p,q,pq):

    # if any of the vectors is empty, return nan (this could happen e.g. if maintenance costs are too high)
    if sum(p)==0 or sum(q)==0 or sum(pq)==0:
        return np.nan

    # otherwise...
    else:

        # normalization
        p = [p_i/sum(p) for p_i in p]
        q = [q_i/sum(q) for q_i in q]
        pq = [pq_i/sum(pq) for pq_i in pq]

        # relative similarity
        return bray_curtis(p,pq)/(bray_curtis(p,pq) + bray_curtis(q, pq))

# %%
### GENERATE RANDOM PARAMETERS, RUN MODEL, GET R^2, REPEAT

# list of R^2
r_squared = ['NA' for i in range(100)]

# repeat simulation 100 times
for n_sim in range(100):

    # print progress
    print('Running simulation %s out of 100' % (n_sim+1))
    time.sleep(0.1)

    # general assumptions (common to all simulations)
    assumptions = community_simulator.usertools.a_default.copy()
    assumptions['n_wells'] = 50 # number of communities

    assumptions['response'] = 'type I'
    assumptions['regulation'] = 'independent' # 'independent' is standard, 'energy' or 'mass' enable preferential consumption of resources
    assumptions['sampling'] = 'Gamma' # 'Binary', 'Gaussian', 'Uniform' or 'Gamma' (sampling of the matrix c)
    assumptions['supply'] = 'off'
    assumptions['R0_food'] = 1000

    assumptions['c0'] = 0.0 # background consumption rate in binary model
    assumptions['c1'] = 1.0 # specific consumption rate in binary model

    assumptions['rs'] = 0.0 # control parameter for resource secretion: 0 means random secretions, 1 means species only secrete resources they can consume (relevant only if 'metabolism' is 'specific')

    # randomly sample variable parameters
    assumptions['S'] = random.randint(10,90) # number of species sampled at initialization -- uniformly distributed between 10 and 90

    n_families = random.randint(1,5) # number of species families -- uniformly distributed between 1 and 5
    n_species_per_family = random.randint(400,1200) # number of species per family -- uniformly distributed between 400 and 1200
    assumptions['SA'] = [n_species_per_family] * n_families
    assumptions['Sgen'] = random.randint(100,380) # number of generalists -- uniformly distributed between 100 and 380

    n_resource_classes = random.randint(3,8) # number of resource classes -- uniformly distributed between 1 and 5
    n_resources_per_class = random.randint(3,17) # number of resources per class -- uniformly distributed between 3 and 17
    assumptions['MA'] = [n_resources_per_class] * n_resource_classes

    #assumptions['l'] = [random.uniform(0.45,0.95) for i in range(sum(assumptions['MA']))] # leakage fraction -- uniformly distributed between 0.45 and 0.95, resource-specific
    assumptions['l'] = random.uniform(0.45,0.95) # leakage fraction -- uniformly distributed between 0.45 and 0.95, resource-unspecific

    #assumptions['m'] = [random.uniform(0,0.2) for i in range(sum(assumptions['SA']) + assumptions['Sgen'])] # species maintenance costs -- uniformly distributed between 0 and 0.2, species-specific
    assumptions['m'] = random.uniform(0,0.2) # species maintenance costs -- uniformly distributed between 0 and 0.2, species-unspecific

    assumptions['q'] = random.uniform(0.5,1) # preference strength (0 for generalists, 1 for specialists) -- uniformly distributed between 0.5 and 0.9
    assumptions['sigc'] = random.uniform(2,4) # standard deviation of sum of consumption rates for Gaussian and Gamma models -- uniformly distributed between 2 and 4
    assumptions['sparsity'] = random.uniform(0.05,0.95) # variability in secretion fluxes among resources (must be less than 1) -- uniformly distributed between 0.05 and 0.95
    assumptions['fs'] = random.uniform(0.05,0.45) # fraction of secretion flux to resources of the same type as the consumed one -- uniformly distributed between 0.05 and 0.45
    assumptions['fw'] = random.uniform(0.05,0.45) # fraction of secretion flux to waste resources -- uniformly distributed between 0.05 and 0.45
    assumptions['metabolism'] = ['common','specific'][random.randint(0,1)] # common or species-specific D matrix -- chosen arbitrarily with equal probabilities

    # parameters (make new matrices, etc)
    params = community_simulator.usertools.MakeParams(assumptions)

    # dynamics
    def dNdt(N,R,params):
        return community_simulator.usertools.MakeConsumerDynamics(assumptions)(N,R,params)
    def dRdt(N,R,params):
        return community_simulator.usertools.MakeResourceDynamics(assumptions)(N,R,params)

    dynamics = [dNdt,dRdt]

    # %%
    ### SEED AND STABILIZE COMMUNITIES

    # make species pools
    pool_overlap = 0.0 # 0: no overlap, 1: full overlap (same pool)
    n_species = sum(assumptions['SA']) + assumptions['Sgen']
    n_pool = int(int(n_species/2) + pool_overlap*int(n_species/2))

    n = list(range(n_species))
    random.shuffle(n)

    pools = [n[:n_pool],
             n[-n_pool:]]
    pools[0].sort()
    pools[1].sort()

    # function to sample from pool
    def sampleFromPool(pool_number):
        N = np.zeros((n_species,assumptions['n_wells']))
        for i in range(assumptions['n_wells']):
            Ni = random.sample(pools[pool_number],assumptions['S'])
            N[Ni,i] = 1
        return N

    # generic initial state (will modify this later for specific plate settings)
    init_state = community_simulator.usertools.MakeInitialState(assumptions)

    # initialize resident plate
    N0_resident = init_state[0]
    N0_resident.loc[:,:] = sampleFromPool(0)
    init_state_resident = (N0_resident,
                           init_state[1])
    resident_plate = community_simulator.Community(init_state_resident,
                                                   dynamics,
                                                   params,
                                                   parallel=par,
                                                   scale=1e6)

    # initialize invasive plate
    N0_invasive = init_state[0]
    N0_invasive.loc[:,:] = sampleFromPool(1)
    init_state_invasive = (N0_invasive,
                           init_state[1])
    invasive_plate = community_simulator.Community(init_state_invasive,
                                                   dynamics,
                                                   params,
                                                   parallel=par,
                                                   scale=1e6)

    # stabilize plates
    N_resident, R_resident = stabilizeCommunities(resident_plate)
    N_invasive, R_invasive = stabilizeCommunities(invasive_plate)

    # make and stabilize coalescence plate
    N0_coalescence = (N_resident + N_invasive)/2*(1/100) # added dilution factor
    init_state_coalescence = (N0_coalescence,
                              init_state[1]) # resource init_state same as before
    coalescence_plate = community_simulator.Community(init_state_coalescence,
                                                      dynamics,
                                                      params,
                                                      parallel=par,
                                                      scale=1e6)
    N_coalescence, R_coalescence = stabilizeCommunities(coalescence_plate)

    # %%
    ### MONOCULTURES

    # find most abundant species in each resident/invasive community
    dominants_resident = list(list(zip(*N_resident.idxmax()))[1])
    dominants_invasive = list(list(zip(*N_invasive.idxmax()))[1])
    all_species = list(list(zip(*N_resident.index))[1])

    # initialize resident monoculture plate
    init_state_monoculture = community_simulator.usertools.MakeInitialState(assumptions)
    N0_monoculture_resident = init_state_monoculture[0]
    N0_monoculture_resident.iloc[:,:] = 0
    N0_monoculture_invasive = copy.deepcopy(N0_monoculture_resident)

    # set dominant species' abundance to 1 for each well
    for i in range(len(dominants_resident)):
        n = np.where(np.isin(all_species,dominants_resident[i]))[0][0]
        N0_monoculture_resident.iloc[n,i] = 1

    for i in range(len(dominants_invasive)):
        n = np.where(np.isin(all_species,dominants_invasive[i]))[0][0]
        N0_monoculture_invasive.iloc[n,i] = 1

    # make plates
    init_state_monoculture_resident = (N0_monoculture_resident,
                                       init_state_monoculture[1])
    monoculture_resident_plate = community_simulator.Community(init_state_monoculture_resident,
                                                               dynamics,
                                                               params,
                                                               parallel=par,
                                                               scale=1e6)

    init_state_monoculture_invasive = (N0_monoculture_invasive,
                                       init_state_monoculture[1])
    monoculture_invasive_plate = community_simulator.Community(init_state_monoculture_invasive,
                                                               dynamics,
                                                               params,
                                                               parallel=par,
                                                               scale=1e6)

    # stabilize monocultures (we just allow to grow for a single cycle)
    monoculture_resident_plate.Propagate(1,compress_resources=False,compress_species=True)
    monoculture_invasive_plate.Propagate(1,compress_resources=False,compress_species=True)

    # %%
    ### PAIRWISE COMPETITION

    # make plate (rescale so most abundant species at t=0 has abundance 1 (times the scaling factor of the plate))
    N0_pairwise = (monoculture_resident_plate.N + monoculture_invasive_plate.N)
    N0_pairwise = N0_pairwise/N0_pairwise.max()
    init_state_pairwise = (N0_pairwise,
                           init_state[1])
    pairwise_plate = community_simulator.Community(init_state_pairwise,
                                                   dynamics,
                                                   params,
                                                   parallel=par,
                                                   scale=1e6)

    # stabilize
    N_pairwise, R_pairwise = stabilizeCommunities(pairwise_plate)

    # %%
    ### STATISTICS

    # get relative similarity between invasive and coalesced communities
    q = ['NA' for i in range(assumptions['n_wells'])]

    for i in range(assumptions['n_wells']):

        # community compositions as lists
        R = N_resident.iloc[:,i].tolist()
        I = N_invasive.iloc[:,i].tolist()
        C = N_coalescence.iloc[:,i].tolist()

        q[i] = mysim(I,R,C)

    # get fraction in pairwise competition
    f_pairwise = ['NA' for i in range(assumptions['n_wells'])]
    f_pairwise_null = ['NA' for i in range(assumptions['n_wells'])]
    F = N_pairwise/N_pairwise.sum()
    F_null = N0_pairwise/N0_pairwise.sum()
    F = F.droplevel(0)
    F_null = F_null.droplevel(0)
    for i in range(assumptions['n_wells']):
        f_pairwise[i] = F.loc[dominants_invasive[i]][i]
        f_pairwise_null[i] = F_null.loc[dominants_invasive[i]][i]

    # %%
    ### FIT AND SAVE R^2

    r_squared[n_sim] = np.corrcoef(f_pairwise,q)[0,1]**2


def add_time(start, duration , day_of_week=False):

    def extract_time(time_str):
        hour, minute, period = map(str.strip, time_str.replace(":", " ").split())
        return int(hour), int(minute), period

    def extract_time_duration(time_str):
        hour, minute = map(str.strip, time_str.replace(":", " ").split())
        return int(hour), int(minute)

    def calculate_total_min(start_min, duration_min,start_hour):
        total_min = duration_min + start_min
        final_start_hour = start_hour
        if(total_min>=60):
            final_start_hour +=1
            total_min = total_min % 60
        total_min = total_min if total_min >=10 else "0" + str(total_min)
        return total_min ,final_start_hour

    def calculate_total_hour(duration_hour,final_start_hour):
        total_hour = duration_hour + final_start_hour
        total_hour = int((final_start_hour + duration_hour) % 12)
        total_hour = total_hour = 12 if total_hour == 0 else total_hour
        return total_hour

    def calculate_am_or_pm(final_start_hour,duration_hour,am_or_pm):
        am_pm_obj = {"AM":"PM","PM":"AM"}
        amount_of_days = int(duration_hour / 24)
        change_am_pm = int((final_start_hour + duration_hour) / 12)
        if (am_or_pm == "PM" and final_start_hour + (duration_hour % 12) >= 12):
            amount_of_days += 1
        final_am_or_pm = am_pm_obj[am_or_pm] if change_am_pm % 2 == 1 else am_or_pm
        return final_am_or_pm , amount_of_days

    def calculate_result_time(total_hour,total_min,final_am_or_pm,day_of_week,amount_of_days):
        days_of_the_week_index = {"monday": 0, "tuesday": 1, "wednesday": 2, "thursday": 3, "friday": 4, "saturday": 5, "sunday": 6}
        days_of_the_week_array = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
        return_time = return_time = f"{total_hour}:{total_min} {final_am_or_pm}"
        if(day_of_week):
          day_of_week = day_of_week.lower()
          index = int((days_of_the_week_index[day_of_week]) + amount_of_days) % 7
          new_day = days_of_the_week_array[index]
          return_time += ", " + new_day

        if(amount_of_days == 1):
          return return_time + " " + "(next day)"
        elif(amount_of_days > 1):
          return return_time + " (" + str(amount_of_days) + " days later)"
        return return_time

    start_hour, start_min, am_or_pm = extract_time(start)
    duration_hour, duration_min = extract_time_duration(duration)
    total_min , final_start_hour = calculate_total_min( start_min, duration_min,start_hour)
    total_hour = calculate_total_hour(duration_hour,final_start_hour)
    final_am_or_pm , amount_of_days = calculate_am_or_pm(final_start_hour , duration_hour,am_or_pm)
    result_time = calculate_result_time(total_hour,total_min,final_am_or_pm,day_of_week,amount_of_days)

    return result_time


import vkapi
import requests
from settings import key_appid
import time

def get_weather_today(city):
    url = 'http://api.openweathermap.org/data/2.5/weather'

    try:
        data = requests.get(url, params={'lang': 'ru', 'appid': key_appid, 'q': city}).json()
    except ValueError:
        return

    result = ''

    try:
        if data['cod'] == '404':
            result = 'Город не найден.'
        else:
            weather = data['weather']
            result += weather[0]['description'].capitalize() + '\n'
            temp = data['main']
            result += 'Температура воздуха: ' + str(round(temp['temp']-273, 1)) + ' °С' + '\n'

    except KeyError:
        return

    return result

def get_weather_not_today(body):
    if len(body.split()) > 1:
        city = body.split()[0][0:-1]
        day = body.split()[1].split('.')[0]
        month = body.split()[1].split('.')[1]
        url = 'http://api.openweathermap.org/data/2.5/forecast'
    else:
        return 'Данные введены некорректно. Формат ввода: город, DD.MM'

    try:
        data = requests.get(url, params={'lang': 'ru', 'appid': key_appid, 'q': city}).json()
    except ValueError:
        return

    result = ''

    try:
        for i in data['list']:
            date = time.strptime(i['dt_txt'], "%Y-%m-%d %H:%M:%S")
            #date = i['dt_txt'].split()[0]
            i_day = date.tm_mday
            i_month = date.tm_mon

            if int(day)==i_day and int(month)==i_month:
                result += city.capitalize() + ' погода на ' + day + '.' + month + ':' + '\n'
                weather = i['weather']
                result += weather[0]['description'].capitalize() + '\n'
                temp = i['main']
                result += 'Температура воздуха: ' + str(round(temp['temp']-273, 1)) + ' °С' + '\n'
                break

            else:
                pass

        cod = data['cod']

    except KeyError:
        return

    if cod == '404':
        return 'Город не найден.'
    elif result == '':
        return 'Данные введены некорректно. Формат ввода: город, DD.MM. Прогноз погоды доступен только на 5 дней вперёд.'
    else:
        return result

def create_answer(data, token):
    user_id = data['user_id']

    if data['body'].split()[0][-1] != ',':
        message = get_weather_today(data['body'].lower())
    else:
        message = get_weather_not_today(data['body'].lower())

    vkapi.send_message(user_id, token, message)


# -*- coding: utf-8 -*-
"""
Created on Sun Feb 12 10:07:49 2023

@author: anush
"""
import cv2
import numpy as np

img1=cv2.imread("E:\data\dog1.jpg")
img2=cv2.imread("E:\data\cat1.jpg")

img1 = cv2.resize(img1,(300,400))
img2 = cv2.resize(img2,(300,400))
#cv2.imshow("dog",img1)
#cv2.imshow("cat",img2)

def blend(x):
    pass

img =np.zeros((400,400,3),np.uint8)
cv2.namedWindow("win")#create a track bar
cv2.createTrackbar("alpha","win",1,100,blend)

switch = "0 :OFF \n 1:ON"
cv2.createTrackbar(switch,"win",0,1,blend)

while True:
    s =cv2.getTrackbarPos(switch,"win")
    a =cv2.getTrackbarPos("alpha","win")
    n=float(a/100)
    print(n)

    if s==0:
        dst =img[:]
    else:
        dst =cv2.addWeighted(img1,1-n,img2,n,0)
        cv2.putText(dst,str(a),(20,60),cv2.FONT_ITALIC,
                    2,(0,125,255),2)
    cv2.imshow("dst",dst)
    k=cv2.waitKey(1)
    if k==27:
        break

cv2.waitKey(0)
cv2.destroyAllWindows()


import sys
import os
import logging
from sentence_transformers import SentenceTransformer, util

dir_path = os.path.dirname(os.path.realpath(__file__))
print(dir_path)

sys.path.append("{}/../src/".format(dir_path))
print(sys.path)

root = logging.getLogger()
root.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
root.addHandler(handler)

def test_st():
    image_model = SentenceTransformer('clip-ViT-B-32-multilingual-v1')
    model = SentenceTransformer("all-MiniLM-L6-v2")

    # Our sentences we like to encode
    sentences = [
        "This framework generates embeddings for each input sentence",
        "Sentences are passed as a list of string.",
        "The quick brown fox jumps over the lazy dog.",
    ]

    # Sentences are encoded by calling model.encode()
    sentence_embeddings = model.encode(sentences)

    # Print the embeddings
    for sentence, embedding in zip(sentences, sentence_embeddings):
        print("Sentence:", sentence)
        print("Embedding:", embedding)
        print("")

    # Single list of sentences

    """
    sentences = [
        "The cat sits outside",
        "A man is playing guitar",
        "I love pasta",
        "The new movie is awesome",
        "The cat plays in the garden",
        "A woman watches TV",
        "The new movie is so great",
        "Do you like pizza?",
    ]
    """
    sentences = [
        "猫坐在外面",
        "狗坐在上面",
        "狗坐在里面",
        "一个男人在弹吉他",
        "我爱意大利面",
        "新电影太精彩了",
        "猫在花园里玩耍",
        "一个女人在看电视",
        "新电影太棒了",
        "你喜欢披萨吗？",
    ]

    # Compute embeddings
    #embeddings = model.encode(sentences, convert_to_tensor=True)
    embeddings = model.encode(sentences)
    print("embeddings as follows: ")
    print(embeddings)

    # Compute cosine-similarities for each sentence with each other sentence
    cosine_scores = util.cos_sim(embeddings, embeddings)

    # Find the pairs with the highest cosine similarity scores
    pairs = []
    for i in range(len(cosine_scores) - 1):
        for j in range(i + 1, len(cosine_scores)):
            pairs.append({"index": [i, j], "score": cosine_scores[i][j]})

    # Sort scores in decreasing order
    pairs = sorted(pairs, key=lambda x: x["score"], reverse=True)

    for pair in pairs[0:10]:
        i, j = pair["index"]
        print(
            "{} \t\t {} \t\t Score: {:.4f}".format(
                sentences[i], sentences[j], pair["score"]
            )
        )

if __name__ == "__main__":
    test_st()


"""
Create the figure for the load growths for the linear and non-linear case.
Also add the plot for the PV growth that it is lineal.
"""

import pickle
import pandas as pd
from core.scenarios import ScenarioGenerator
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib.patches import Rectangle, Circle
from scipy.interpolate import UnivariateSpline, interp1d, pchip_interpolate
from core.figure_utils import set_figure_art
import matplotlib.pyplot as plt
import matplotlib

set_figure_art()
plt.rc('text', usetex=True)
plt.rc('text.latex', preamble=r'\usepackage{amsmath}')
# matplotlib.verbose.level = 'debug-annoying'

class OOMFormatter(mticker.ScalarFormatter):
    def __init__(self, order=0, fformat='%1.1f', offset=True, mathText=True):
        self.oom = order
        self.fformat = fformat
        mticker.ScalarFormatter.__init__(self,useOffset=offset,useMathText=mathText)
    def _set_order_of_magnitude(self):
        self.orderOfMagnitude = self.oom
    def _set_format(self, vmin=None, vmax=None):
        self.format = self.fformat
        if self._useMathText:
            self.format = r'$\mathdefault{%s}$' % self.format

def retrieve_energy_limits(scenario_generator):
    nodes_per_cluster = scenario_generator.nodes_per_cluster

    minimums_cluster = []
    maximus_cluster = []

    for ii in range(scenario_generator.n_clusters):
        minimums_cluster = minimums_cluster + \
                           [min(scenario_generator.mapper_load_growth[ii].values()) * nodes_per_cluster.loc[ii]]
        maximus_cluster = maximus_cluster + \
                          [max(scenario_generator.mapper_load_growth[ii].values()) * nodes_per_cluster.loc[ii]]

    data_frame = {"type_bus": list(range(scenario_generator.n_clusters)),
                  "minimums": minimums_cluster,
                  "maximums": maximus_cluster}

    energy_limits = pd.DataFrame(data_frame)
    energy_limits.set_index('type_bus', inplace=True)

    return energy_limits

def normalize_and_inverse(min_, max_):
    """
    Creates a normalizer function and its inverse that maps a value in GWh/Year to fraction of
    percentage, i.e., [0,1]

    Parameters:
    ----------
        min_: float: Minimum annual energy consumption per year of the whole grid [GWh/year]
        max_: float: Maximum annual energy consumption per year of the whole grid [GWh/year]
    """

    # f =      lambda x: np.round(np.clip(( (x - min_)/(max_ - min_) ), 0 , 1), 20)  #  y = (x-xmin)/(xmax - xmin)   : Function  [0.0 - 1.0]
    f = lambda x: np.round(np.clip(((x - min_) / (max_ - min_)),
                                   -0.5, 1.5),
                           20)  # y = (x-xmin)/(xmax - xmin)   : Function  [0.0 - 1.0]
    f_inv =  lambda x: ( x * (max_ - min_) + min_ )  #  x = y*(xmax-xmin) + xmin   : Inverse Function [0-1] -> [min, max]

    return f, f_inv

def growth_curve_normalized(x=None, y=None, k=3, curve="spline"):
    """
    Create a growth function with support [0,1] and range [0,1].

    Parameters:
    -----------
        x: np.ndarray: Control points for the interpolation that corresponds to the x-axis if curve=="spline"
        y: np.ndarray: Control points for the interpolation that corresponds to the y-axis if curve=="spline"
        k: int: Degree of the spline to fit.
        curve: str: Type of interpolation for the control points.
            The options are: ["spline", "lineal", "root", "square"]

    Returns:
    --------
        g_normalized: Function g() that maps any value between [0,1] to a range of [0,1]
            Example: to use the function just type g(vale).

    """

    if curve == "spline":
        assert k is not None, "Spline degree must be defined."
        g_normalized = UnivariateSpline(x, y, k=k)
    elif curve == "lineal":
        cp = np.array([(0,0),
                       (1,1)])
        g_normalized = interp1d(cp[:,0], cp[:,1])
    elif curve == "root":
        g_normalized = lambda x: np.sqrt(x)

    elif curve == "square":
        g_normalized = lambda x: x ** 2

    elif curve == "corrected":
        g_normalized = lambda w: np.clip(pchip_interpolate(x, y, w), 0, 1)

    else:
        raise NotImplementedError

    if curve != "corrected":
        assert np.allclose(g_normalized(0), 0, atol=0.01), "The non-lineal function should map closed to f(0) == 0.0"
        assert np.allclose(g_normalized(1), 1, rtol=0.01), "The non-lineal function should map closed to f(1) == 1.0"

    return g_normalized

def growth_curve(g_normalized, min_, max_):
    """
    Computes the de-normalization of the normalized growth curve g() to values in GWh/year, also computes a
    """

    g = lambda x: g_normalized(x) * (max_ - min_) + min_

    x = np.linspace(0.0, 1.0, 100000)
    y = g(x)  # Values in kw

    # Nearest value procedure: The function returns a value between [0,1]
    g_inv = lambda w: np.round(x[[np.argmin(np.abs(y - w_)) for w_ in w]], 20)  # w: Values in GWh/year.

    return g, g_inv

file_name_load_models = r"../models/copula_model_load.pkl"
with open(file_name_load_models, "rb") as pickle_file:
    copula_load = pickle.load(pickle_file)

file_name_irradiance_models = r"../models/copula_model_irradiance.pkl"
with open(file_name_irradiance_models, "rb") as pickle_file:
    mixture_model_irradiance = pickle.load(pickle_file)

file_name_network = r"../data/processed_data/network_data/Nodes_34.csv"
grid = pd.read_csv(file_name_network)
grid_info = grid.iloc[1:, :]  # Drop the slack node

#%%
#======================================================================================================================
# CREATE MODEL FOR THE LINEAL CASE
#======================================================================================================================
scenario_generator = ScenarioGenerator(copula_load=copula_load,
                                       mixture_model_irradiance=mixture_model_irradiance,
                                       grid_info=grid_info,
                                       n_levels_load_growth=10,
                                       n_levels_pv_growth=10,
                                       n_levels_mixtures=10)

#======================================================================================================================
# CREATE MODEL FOR THE NON LINEAL CASE
#======================================================================================================================

#%% Spline function
# Control points for the spline
points = np.array([(0.0, 0.0),
                   (0.3, 0.4),
                   (0.5, 0.5),
                   (0.7, 0.5),
                   (1.0, 1.0)])

x_ = np.linspace(0.0, 1.0, 100)
x = np.linspace(0.0, 1.0, 11)
f = UnivariateSpline(points[:,0], points[:, 1],k=3)

fig, ax = plt.subplots(1, 1, figsize=(5,5))
ax.scatter(points[:,0], points[:,1], s=20, zorder=3)
ax.scatter(x, f(x), s=20)
ax.plot(x_, f(x_), linewidth=0.8, color="C0")
ax.grid("both")
ax.set_title("Normalized spline non-linear growth")

#%%
# *********************************************************************************************************************
# PLOTTING SECTION
# *********************************************************************************************************************

fig, ax = plt.subplots(3, 1, figsize=(4, 7))
plt.subplots_adjust(top=0.95, left=0.15, wspace=1.05, right=0.7, bottom=0.05)
font_size = 10
highlight_step = 6
highlight_power = 25

max_ylim_node = 25
to_integer = 10  # Conversion of decimal points of the x axis to integer numbers
x_solutions_years = []
# =====================================================================================================================
# LINEAL CASE
# =====================================================================================================================
#  Load growth

# The mapper_load_growth() exists, because each cluster growth is bounded by the max and min energy consumption of the
# cluster. Remember that copula can not extrapolate.

# Count the nodes with the same number of clusters

n_clusters = scenario_generator.n_clusters
x_ = np.arange(0, 11)
labels_ = ["Commercial", "Residential", "Mixed activities"]
colors = ["C0", "C1", "C2"]

# From the real data
energy_limits = retrieve_energy_limits(scenario_generator)
energy_limits.loc["total"] = energy_limits.sum(axis=0, numeric_only=True)

# Made up by myself
energy_limits = pd.DataFrame({"type_bus": [0, 1, 2],
                              "minimums": [1.566978, 3.0, 1.212650],
                              "maximums": [15.507259, 9.076219, 12.0]})
energy_limits.set_index("type_bus", inplace=True)
energy_limits.loc["total"] = energy_limits.sum(axis=0, numeric_only=True)

# m = np.linspace(0, 1, 11)
m = np.linspace(0, 1, 1000)

ax_ = ax[0].twinx()
f, f_inv = normalize_and_inverse(energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])  # From GWh to [% Energy]
g_normalized = growth_curve_normalized(curve="lineal")
# g_normalized = growth_curve_normalized(x=points[:, 0], y=points[:, 1], curve="spline")
g, g_inv = growth_curve(g_normalized, energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])
x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
ax_.plot(m * to_integer, y_1_kw, color="C3", linewidth=1.8, zorder=0)

ax2 = ax_.secondary_yaxis(1.2, functions=(f, f_inv))
x_highlighted = g_inv(np.array([highlight_power]))
x_solutions_years.append(np.round(x_highlighted*10,2))
lgt = ax_.scatter(x_highlighted * to_integer, highlight_power, s=20, color="C3", edgecolors="k", zorder=3,
                  label=r"$w_{\mathrm{A}}(l_{\mathrm{A}})=\sum_{i=1}^{3}\bar{w}_{i,\mathrm{A}}(l_{\mathrm{A}})$")
lgt_label = lgt.get_label()
ax_.hlines(y=highlight_power, xmin=x_highlighted * to_integer, xmax=1.0 * to_integer, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)
ax_.vlines(x=x_highlighted * to_integer, ymin=0, ymax=highlight_power, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)

y_lineal = []
lgds = []
lgds_label = []
for ii in range(n_clusters):
    f, f_inv = normalize_and_inverse(energy_limits["minimums"][ii], energy_limits["maximums"][ii])  # From GWh to [% Energy]
    g_normalized = growth_curve_normalized(curve="lineal")
    # g_normalized = growth_curve_normalized(x=points[:,0], y=points[:,1], curve="spline")
    g, g_inv = growth_curve(g_normalized, energy_limits["minimums"][ii], energy_limits["maximums"][ii])
    x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
    ax[0].plot(m * to_integer, y_1_kw)
    lgx = ax[0].scatter(x_highlighted * to_integer, g(x_highlighted), s=20,
                        color=colors[ii], edgecolors="k",
                        label=r"$\bar{w}_{\mathrm{" + f"{ii + 1}" + r",\mathrm{A}}}(l_{\mathrm{A}})$")
    lgds_label.append(lgx.get_label())
    ax[0].hlines(y=g(x_highlighted), xmin=0, xmax=x_highlighted*to_integer, color=colors[ii], linewidths=0.5, linestyles="-.", alpha=0.8)

    lgds.append(lgx)
    y_lineal.append(g(x_highlighted))

ax[0].set_ylim((0,max_ylim_node))
ax_.set_ylim((energy_limits["minimums"]["total"], 40))
ax_.set_ylabel("Total grid Energy consump. [GWh/Year]")
ax_.spines['right'].set_color('red')
ax_.yaxis.label.set_color('red')
ax_.tick_params(axis='y', colors='red')

ax2.yaxis.set_major_locator(mticker.MultipleLocator(0.1))
ax2.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1.0))
ax2.set_ylabel("Annual energy\nconsumption growth [\%]", fontsize=font_size)
ax2.tick_params(axis='both', which='major', labelsize=font_size-1)
ax2.tick_params(axis='both', which='minor', labelsize=font_size-1)
ax[0].set_ylabel("Annual energy consumption\nper node [GWh/Year]")
ax[0].set_title("(a)")
lgds = [lgt] + lgds
lgds_label = [lgt_label] + lgds_label
ax[0].legend(lgds, lgds_label,
             loc="upper left",
             handletextpad=-0.1,
             handlelength=2.0)
ax[0].xaxis.set_major_locator(mticker.MultipleLocator(1.0))

# =====================================================================================================================
# SPLINE CASE
# =====================================================================================================================
#  Load growth

# The mapper_load_growth() exists, because each cluster growth is bounded by the max and min energy consumption of the
# cluster. Remember that copula can not extrapolate.

ax_ = ax[1].twinx()
f, f_inv = normalize_and_inverse(energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])  # From GWh to [% Energy]
# g_normalized = growth_curve_normalized(curve="lineal")
g_normalized = growth_curve_normalized(x=points[:, 0], y=points[:, 1], curve="spline")
g, g_inv = growth_curve(g_normalized, energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])
x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
ax_.plot(m * to_integer, y_1_kw, color="C3", linewidth=1.8, zorder=0)

ax2 = ax_.secondary_yaxis(1.2, functions=(f, f_inv))
x_highlighted = g_inv(np.array([highlight_power]))
x_solutions_years.append(np.round(x_highlighted*10,2))
lgt = ax_.scatter(x_highlighted * to_integer, highlight_power, s=20, color="C3", edgecolors="k", zorder=3,
                  label=r"$w_{\mathrm{B}}(l_{\mathrm{B}})=\sum_{i=1}^{3}\bar{w}_{i,\mathrm{B}}(l_{\mathrm{B}})$")
lgt_label = lgt.get_label()
ax_.hlines(y=highlight_power, xmin=x_highlighted * to_integer, xmax=1.0 * to_integer, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)
ax_.vlines(x=x_highlighted * to_integer, ymin=0, ymax=highlight_power, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)

# Count the nodes with the same number of clusters
energy_highlighted = []
y_spline = []
lgds = []
lgds_label = []
for ii in range(n_clusters):
    f, f_inv = normalize_and_inverse(energy_limits["minimums"][ii], energy_limits["maximums"][ii])  # From GWh to [% Energy]
    # g_normalized = growth_curve_normalized(curve="lineal")
    g_normalized = growth_curve_normalized(x=points[:,0], y=points[:,1], curve="spline")
    g, g_inv = growth_curve(g_normalized, energy_limits["minimums"][ii], energy_limits["maximums"][ii])
    x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
    ax[1].plot(m * to_integer, y_1_kw)
    lgx = ax[1].scatter(x_highlighted * to_integer, g(x_highlighted), s=20,
                        color=colors[ii], edgecolors="k", label=r"$\bar{w}_{\mathrm{" + f"{ii+1}" + r",B}}(l_{\mathrm{B}})$")
    lgds_label.append(lgx.get_label())
    ax[1].hlines(y=g(x_highlighted), xmin=0, xmax=x_highlighted * to_integer, color=colors[ii], linewidths=0.5, linestyles="-.", alpha=0.8)

    lgds.append(lgx)

    y_spline.append(g(x_highlighted))

ax[1].set_ylim((0,max_ylim_node))
ax_.set_ylim((energy_limits["minimums"]["total"], 40))
ax_.set_ylabel("Total grid Energy consump. [GWh/Year]")
ax_.spines['right'].set_color('red')
ax_.yaxis.label.set_color('red')
ax_.tick_params(axis='y', colors='red')

ax2.yaxis.set_major_locator(mticker.MultipleLocator(0.1))
ax2.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1.0))
ax2.set_ylabel("Annual energy\nconsumption growth [\%]", fontsize=font_size)
ax2.tick_params(axis='both', which='major', labelsize=font_size-1)
ax2.tick_params(axis='both', which='minor', labelsize=font_size-1)
ax[1].set_ylabel("Annual energy consumption\nper node [GWh/Year]")
ax[1].set_title("(b)")
lgds = [lgt] + lgds
lgds_label = [lgt_label] + lgds_label
ax[1].legend(lgds, lgds_label,
             loc="upper left",
             handletextpad=-0.1,
             handlelength=2.0)
ax[1].xaxis.set_major_locator(mticker.MultipleLocator(1.0))

# =====================================================================================================================
# ROOT CASE
# =====================================================================================================================
#  Load growth

# The mapper_load_growth() exists, because each cluster growth is bounded by the max and min energy consumption of the
# cluster. Remember that copula can not extrapolate.

ax_ = ax[2].twinx()
f, f_inv = normalize_and_inverse(energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])  # From GWh to [% Energy]
# g_normalized = growth_curve_normalized(curve="lineal")
g_normalized = growth_curve_normalized(x=points[:, 0], y=points[:, 1], curve="root")
g, g_inv = growth_curve(g_normalized, energy_limits["minimums"]["total"], energy_limits["maximums"]["total"])
x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
ax_.plot(m * to_integer, y_1_kw, color="C3", linewidth=1.8, zorder=0)

# ax_.arrow(x=0.9, y=0, dx=0, dy=25,
#           length_includes_head=True,
#           head_width=0.04,
#           head_length=3,
#           shape="full",
#           overhang=1,
#           color="C3")
# ax_.text(x=0.82,y=10,s=r"$\bar{\mathrm{A}}$", fontsize="x-large", color="C3")

ax2 = ax_.secondary_yaxis(1.2, functions=(f, f_inv))
x_highlighted = g_inv(np.array([highlight_power]))
x_solutions_years.append(np.round(x_highlighted*10,2))
lgt = ax_.scatter(x_highlighted * to_integer, highlight_power, s=20, color="C3", edgecolors="k", zorder=3,
                  label=r"$w_{\mathrm{C}}(l_{\mathrm{C}})=\sum_{i=1}^{3}\bar{w}_{i,\mathrm{C}}(l_{\mathrm{C}})$")
lgt_label = lgt.get_label()
ax_.hlines(y=highlight_power, xmin=x_highlighted * to_integer, xmax=1.0 * to_integer, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)
ax_.vlines(x=x_highlighted * to_integer, ymin=0, ymax=highlight_power, color="C3", linewidths=0.5, linestyles="-.", alpha=0.8)

# Count the nodes with the same number of clusters
energy_highlighted = []
y_root = []

x_arrow_position = [0.25, 0.05, 0.15]
arrow_names = ["B", "C", "D"]
lgds = []
lgds_label = []
for ii in range(n_clusters):
    f, f_inv = normalize_and_inverse(energy_limits["minimums"][ii], energy_limits["maximums"][ii])  # From GWh to [% Energy]
    # g_normalized = growth_curve_normalized(curve="lineal")
    g_normalized = growth_curve_normalized(x=points[:,0], y=points[:,1], curve="root")
    g, g_inv = growth_curve(g_normalized, energy_limits["minimums"][ii], energy_limits["maximums"][ii])
    x_1, y_1_kw, y_1_per = (m, g(m), f(g(m)))  # index, GWh/year, % Energy of GWh/year
    ax[2].plot(m * to_integer, y_1_kw, color=colors[ii])
    lgx = ax[2].scatter(x_highlighted * to_integer, g(x_highlighted), s=20,
                        color=colors[ii], edgecolors="k",
                        label=r"$\bar{w}_{\mathrm{" + f"{ii+1}" + r",\mathrm{C}}}(l_{\mathrm{C}})$" )
    lgds_label.append(lgx.get_label())
    ax[2].hlines(y=g(x_highlighted), xmin=0, xmax=x_highlighted * to_integer, color=colors[ii], linewidths=0.5, linestyles="-.", alpha=0.8)

    lgds.append(lgx)
    # ax[2].arrow(x=x_arrow_position[ii], y=0, dx=0, dy=g(x_highlighted)[0],
    #             length_includes_head=True,
    #             head_width=0.04,
    #             head_length=1.5,
    #             shape="full",
    #             overhang=1,
    #             color=colors[ii])
    # ax[2].text(x=x_arrow_position[ii] + 0.0125, y=1.25,
    #            s=r"$\bar{\mathrm{"+f"{arrow_names[ii]}"+ "}}$", fontsize="x-large", color=colors[ii])
    y_root.append(g(x_highlighted))

ax[2].set_ylim((0,max_ylim_node))
ax_.set_ylim((energy_limits["minimums"]["total"], 40))
ax_.set_ylabel("Total grid Energy consump. [GWh/Year]")
ax_.spines['right'].set_color('red')
ax_.yaxis.label.set_color('red')
ax_.tick_params(axis='y', colors='red')

ax2.yaxis.set_major_locator(mticker.MultipleLocator(0.1))
ax2.yaxis.set_major_formatter(mticker.PercentFormatter(xmax=1.0))
ax2.set_ylabel("Annual energy\nconsumption growth [\%]", fontsize=font_size)
ax2.tick_params(axis='both', which='major', labelsize=font_size-1)
ax2.tick_params(axis='both', which='minor', labelsize=font_size-1)

ax[2].set_ylabel("Annual energy consumption\nper node [GWh/Year]")
ax[2].set_title("(c)")
lgds = [lgt] + lgds
lgds_label = [lgt_label] + lgds_label
ax[2].legend(lgds, lgds_label,
             loc="upper left",
             handletextpad=-0.1,
             handlelength=2.0)
ax[2].xaxis.set_major_locator(mticker.MultipleLocator(1.0))
ax[2].set_xlabel("Step increment ($l$) - [years]")

comparison = pd.DataFrame(np.concatenate([y_lineal, y_root, y_spline], axis=1).T,
                          columns=["w1", "w2", "w3"],
                          index=["lineal", "root", "spline"])
print(comparison)
print(f"Years: {x_solutions_years}")
plt.savefig(".\path_growths\paths_appendix.pdf", dpi=700, bbox_inches='tight' )
#%%