import numpy as np
import pandas as pd
import streamlit as st

images = pd.read_csv(r'./product_images.csv')
images = images.to_numpy()
labels = pd.read_csv(r'./true_label.csv')
labels = labels.to_numpy()
labeldic = {0: "T-shirt", 1: "Trouser", 2: "Pullover", 3: "Dress", 4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker", 8: "Bag", 9: "Ankle boot"}
labelkey = {"T-shirt":0, "Trouser":1, "Pullover":2, "Dress":3, "Coat":4, "Sandal":5, "Shirt":6, "Sneaker":7, "Bag":8, "Ankle boot":9}

st.set_page_config(
    page_title="Clothing Shop",
    page_icon="🛒",
    layout="wide",
    initial_sidebar_state="expanded"
)

if "ballon" not in st.session_state:
    st.balloons()
    st.session_state['ballon'] = True

st.markdown('''
            # Clothing Shop 🛒
            ''')

st.sidebar.header("Feel free to browse clothes and add favorites to your cart!")
tabs = st.tabs([" All "]+list(labeldic.values()))

if "cart" not in st.session_state:
    st.session_state.cart = []
if "shown" not in st.session_state:
    st.session_state.shown = []
if "reclst" not in st.session_state:
    st.session_state.reclst = []

col_num, row_num = 5, 8

for tabidx, tab in enumerate(tabs):
    with tab:
        showname = str(tabidx) +"showlst"
        if showname not in st.session_state:
            if tabidx==0:
                # showlst = np.random.randint(0, len(images), size=(col_num*row_num))
                showlst = np.random.choice(np.setdiff1d(np.array(range(8000)), np.array(st.session_state.shown)), size = col_num*row_num, replace=False)
            else:
                showlst = np.random.choice(np.setdiff1d((labels==(tabidx-1)).nonzero()[0], np.array(st.session_state.shown)), size=col_num*row_num, replace=False)
            st.session_state.shown.extend(list(showlst))
            st.session_state[showname] = showlst
        else:
            showlst = st.session_state[showname]
        # st.write(showlst)
        cols = st.columns(col_num)
        for col_idx, col in enumerate(cols):
            with col:
                for i in range(row_num):
                    # pic_idx = showlst[col_idx*row_num+i]
                    # st.write(col_num*i+col_idx)
                    pic_idx = showlst[col_num*i+col_idx]
                    st.image(255-images[pic_idx].reshape(28,28), width=150)
                    if pic_idx in st.session_state.cart:
                        agree = st.checkbox(label="  I   Like   it   ❤", value= True, key=showname +str(pic_idx))
                    else:
                        agree = st.checkbox(label="  I   Like   it   ❤", value= False, key=showname +str(pic_idx))
                    if agree and pic_idx not in st.session_state.cart:
                        st.session_state.cart.append(pic_idx)
                        st.experimental_rerun()
                    elif not agree and pic_idx in st.session_state.cart:
                        if st.session_state.cart.index(pic_idx)<len(st.session_state.reclst):
                            st.session_state.reclst.pop(st.session_state.cart.index(pic_idx))
                        st.session_state.cart.remove(pic_idx)
                        st.experimental_rerun()

# st.button("Refresh for me!", key="refresh")


__author__ = 'jonathan'

#############
#  imports  #
#############

from functools import wraps
from flask import Flask, flash, redirect, render_template, \
    request, session, url_for, Blueprint

from .forms import AddTaskForm
from datetime import datetime
from project import db
from project.models import Task

##############
#   config   #
##############

tasks_blueprint = Blueprint('tasks', __name__)

#######################
#   helper functions  #
#######################

def login_required(test):
    @wraps(test)
    def wrap(*args, **kwargs):
        if 'logged_in' in session:
            return test(*args, **kwargs)
        else:
            flash('You must log in to view this page')
            return redirect(url_for('users.login'))
    return wrap

def open_tasks():
    return db.session.query(Task).filter_by(
        status='1').order_by(Task.due_date.asc())

def closed_tasks():
    return db.session.query(Task).filter_by(
        status='0').order_by(Task.due_date.asc())

# route to display tasks
@tasks_blueprint.route('/tasks/')
@login_required
def tasks():
    return render_template(
        'tasks.html',
        form=AddTaskForm(request.form),
        open_tasks=open_tasks(),
        closed_tasks=closed_tasks()
    )

# Add new task
@tasks_blueprint.route('/add/', methods=['GET', 'POST'])
@login_required
def new_task():
    error = None
    form = AddTaskForm(request.form)
    if request.method == 'POST':
        if form.validate_on_submit():
            task_to_add = Task(
                form.name.data,
                form.due_date.data,
                form.priority.data,
                datetime.utcnow(),
                '1',
                session['user_id']
            )
            db.session.add(task_to_add)
            db.session.commit()
            flash('New entry was successfully posted.')
            return redirect(url_for('tasks.tasks'))

    return render_template(url_for('tasks.tasks'), form=form, error=error, open_tasks=open_tasks(),
                           closed_tasks=closed_tasks())

# Mark task as complete, using dynamic routing
@tasks_blueprint.route('/complete/<int:task_id>/')
@login_required
def complete(task_id):
    db.session.query(Task) \
        .filter_by(task_id=task_id).update({"status": "0"})
    db.session.commit()
    flash('The task with id = ' + str(task_id) + ' was marked as complete')
    return redirect(url_for('tasks.tasks'))

# Delete task, using dynamic routing
@tasks_blueprint.route('/delete/<int:task_id>/')
@login_required
def delete_entry(task_id):
    db.session.query(Task) \
        .filter_by(task_id=task_id).delete()
    db.session.commit()
    flash('Deleted task with id = ' + str(task_id))
    return redirect(url_for('tasks.tasks'))


"""Types of conversational agents are available here."""
import logging
import os
from typing import Any, Dict, List, Tuple, Union

from moviebot.core.utterance.utterance import AgentUtterance, UserUtterance
from moviebot.database.database import DataBase
from moviebot.dialogue_manager.dialogue_act import DialogueAct
from moviebot.dialogue_manager.dialogue_manager import DialogueManager
from moviebot.nlg.nlg import NLG
from moviebot.nlu.nlu import NLU
from moviebot.ontology.ontology import Ontology
from moviebot.recorder.dialogue_recorder import DialogueRecorder
from moviebot.recorder.recorder_bot import RecorderBot

logger = logging.getLogger(__name__)
DialogueOptions = Dict[DialogueAct, Union[str, List[str]]]

def _get_ontology(ontology_path: str) -> Ontology:
    """Checks if the ontology exists and get the file.

    Args:
        ontology_path: The path to the file.

    Returns:
        The ontology class instance.
    """
    if os.path.isfile(ontology_path):
        return Ontology(ontology_path)
    else:
        raise FileNotFoundError(f"Ontology file {ontology_path} not found.")

def _get_db(db_path: str) -> DataBase:
    """Checks if the database file exists and get the file.

    Args:
        db_path: The path to the file.

    Returns:
        The database class instance.
    """
    if os.path.isfile(db_path):
        return DataBase(db_path)
    else:
        raise FileNotFoundError(f"Database file {db_path} not found.")

class Agent:
    def __init__(self, config: Dict[str, Any] = None) -> None:
        """The class Agent controls all the components of the basic architecture
        of IAI MovieBot.

        Initially the Conversational Agent is able to interact with human
        users via text.

        Args:
            config: Configuration. Defaults to None.
        """
        self.config = config
        self.new_user = False

        self.record = self.config.get("CONVERSATION_LOGS", {}).get("save")
        self.recorder = (
            DialogueRecorder(
                self.config["CONVERSATION_LOGS"]["path"],
                self.config["CONVERSATION_LOGS"]["nlp"],
            )
            if self.record
            else None
        )
        ontology_path = self.config.get("DATA", {}).get("ontology_path")
        self.ontology = _get_ontology(ontology_path) if ontology_path else None
        db_path = self.config.get("DATA", {}).get("db_path")
        self.database = _get_db(db_path) if db_path else None
        self.slot_values_path = self.config.get("DATA", {}).get(
            "slot_values_path"
        )

        nlu_tag_words_slots_path = self.config.get("NLU", {}).get(
            "tag_words_slots"
        )
        if not nlu_tag_words_slots_path:
            raise EnvironmentError(
                "Conversational Agent: No tag words provided for slots in user"
                " utterance"
            )

        data_config = dict(
            ontology=self.ontology,
            database=self.database,
            slot_values_path=self.slot_values_path,
            tag_words_slots_path=nlu_tag_words_slots_path,
        )
        self.nlu = NLU(data_config)
        self.nlg = NLG(dict(ontology=self.ontology))
        data_config["slots"] = list(self.nlu.intents_checker.slot_values.keys())

        self.isBot = self.config.get("TELEGRAM", False) or self.config.get(
            "FLASK", False
        )

        self.dialogue_manager = DialogueManager(
            data_config, self.isBot, self.new_user
        )

        if self.isBot and self.config.get("BOT_HISTORY", {}).get("save"):
            path = self.config.get("BOT_HISTORY", {}).get("path")
            if path:
                self.bot_recorder = RecorderBot(path)
            else:
                raise ValueError("Path to save conversation is not provided.")

    def start_dialogue(
        self, user_fname: str = None, restart: bool = False
    ) -> Union[
        Tuple[str, DialogueOptions],
        Tuple[str, DialogueOptions, Dict[str, Any]],
    ]:
        """Starts the conversation.

        Args:
            user_fname: User's first name. Defaults to None.
            restart: Whether to restart the conversation or not. Defaults to
              False.

        Returns:
            Agent response, dialogue options for quick reply, and data to be
              recorded if self.isBot is true.
        """
        if not restart:
            agent_dacts = self.dialogue_manager.start_dialogue(self.new_user)
        else:
            agent_dacts = self.dialogue_manager.generate_output(restart)
        agent_response, options = self.nlg.generate_output(
            agent_dacts, user_fname=user_fname
        )
        self.dialogue_manager.get_context().add_utterance(
            AgentUtterance({"text": agent_response})
        )
        if not self.isBot:
            logger.debug(
                str(self.dialogue_manager.dialogue_state_tracker.dialogue_state)
            )
            logger.debug(str(self.dialogue_manager.get_context()))
            return agent_response, options
        else:
            record_data = self.dialogue_manager.get_state().to_dict()
            context = self.dialogue_manager.get_context().movies_recommended
            record_data.update(
                {"Agent_Output": agent_response, "Context": context}
            )
            return agent_response, options, record_data

    def continue_dialogue(
        self,
        user_utterance: UserUtterance,
        user_options: DialogueOptions,
        user_fname: str = None,
    ) -> Union[
        Tuple[str, DialogueOptions],
        Tuple[str, DialogueOptions, Dict[str, Any]],
    ]:
        """Performs the next dialogue according to user response and current
        state of dialogue.

        Args:
            user_utterance: The input received from the user.
            user_options: Dialogue options that were provided to the user in
              previous turn.
            user_fname: User's first name. Defaults to None.

        Returns:
            Agent response, dialogue options for quick reply, and data to be
              recorded if self.isBot is true.
        """
        self.dialogue_manager.get_state().user_utterance = user_utterance
        self.dialogue_manager.get_context().add_utterance(user_utterance)
        user_dacts = self.nlu.generate_dact(
            user_utterance,
            user_options,
            self.dialogue_manager.get_state(),
            self.dialogue_manager.get_context(),
        )
        self.dialogue_manager.receive_input(user_dacts)
        agent_dacts = self.dialogue_manager.generate_output()
        dialogue_state = (
            self.dialogue_manager.dialogue_state_tracker.dialogue_state
        )
        agent_response, options = self.nlg.generate_output(
            agent_dacts, dialogue_state=dialogue_state, user_fname=user_fname
        )
        self.dialogue_manager.get_context().add_utterance(
            AgentUtterance({"text": agent_response})
        )
        if not self.isBot:
            logger.debug(
                str(self.dialogue_manager.dialogue_state_tracker.dialogue_state)
            )
            logger.debug(str(self.dialogue_manager.get_context()))
            return agent_response, options
        else:
            record_data = self.dialogue_manager.get_state().to_dict()
            context = self.dialogue_manager.get_context().movies_recommended
            record_data.update(
                {
                    "Agent_Output": agent_response,
                    "User_Input": user_utterance.get_text(),
                    "Context": context,
                }
            )

            if options:
                _options = {
                    str(key): val[0] if isinstance(val, list) else val
                    for key, val in options.items()
                }
                record_data.update({"Agent_Options": str(_options)})
            return agent_response, options, record_data

    def end_dialogue(self) -> None:
        """Ends the dialogue and save the experience if required."""
        # TODO: Save the experience
        pass

    def terminated_dialogue(self) -> bool:
        """Checks if the dialogue is terminated by either user or the number of
        dialogues have reached a maximum limit.

        Returns:
            True if conversation is finished.
        """
        return self.dialogue_manager.get_state().at_terminal_state


# Copied from fab.sampling.rejection_sampling_test.
import torch
from fab.sampling_methods.rejection_sampling import rejection_sampling
import matplotlib.pyplot as plt
from matplotlib import rc
import matplotlib as mpl

# Define target.
def target_log_prob(x):
    return -x**4 + 6 * x**2 + 1/2 * x

TARGET_Z = 11784.50927

# Define proposal
mix = torch.distributions.Categorical(torch.tensor([0.2, 0.8]))
com = torch.distributions.Normal(torch.tensor([-1.7, 1.7]), torch.tensor([0.5, 0.5]))

proposal = torch.distributions.MixtureSameFamily(mixture_distribution=mix,
                                             component_distribution=com)

k = TARGET_Z * 3

if __name__ == '__main__':
    mpl.rcParams['figure.dpi'] = 300
    rc('font', **{'family': 'serif', 'serif': ['Times']})
    rc('text', usetex=True)
    rc('figure', titlesize=15)
    rc('axes', titlesize=13, labelsize=13)  # fontsize of the axes title and labels
    #rc('legend', fontsize=6)
    rc('xtick', labelsize=11)
    rc('ytick', labelsize=11)

    # First plot contours so make sure that our rejection sampling meets condition that kq > p.
    fig, axs = plt.subplots(1, 2, figsize=(6, 3))
    x = torch.linspace(-4, 4, 200)
    p = torch.exp(target_log_prob(x))
    kq = k*torch.exp(proposal.log_prob(x))
    axs[0].plot(x, p, label="p")
    axs[0].plot(x, kq, label="kq")
    axs[0].set_xlabel(r"$x_1$")
    axs[0].set_ylabel(r"$f(x_1)$")
    axs[0].legend()

    n_samples = 10000
    samples = rejection_sampling(n_samples, proposal, target_log_prob, k)
    axs[1].plot(x, p/TARGET_Z, label="p (normalised)")
    axs[1].hist(samples, density=True, bins=100, label="sample density")
    axs[1].legend()
    axs[1].set_xlabel(r"$x_1$")
    axs[1].set_ylabel("PDF")
    plt.tight_layout()

    fig.savefig("plots/rejection_sampling.png", bbox_inches="tight")
    plt.show()


"""
A very simple web server for managing the stock levels of a company's products

"""
import sys
from uuid import uuid4

import lightbus
from flask import Flask, request, redirect
from flask.templating import render_template

from . import db

# Create a flask webserver
web = Flask(__name__, static_folder='../../../static')

@web.route('/set-stock/<product_uuid>', methods=['POST'])
def set_stock(product_uuid):
    # Insert/update the stock level in the database
    db['stock'].upsert(dict(
        uuid=product_uuid,
        quantity=int(request.form.get('quantity') or 0)
    ), keys=['uuid'])
    return redirect('/')

@web.route('/', methods=['GET'])
def list_stock():
    return render_template('list.html',
                    products=db['products'],
                    stock=db['stock'],
    )


from collections import defaultdict, Counter
from os import listdir

import torch
import torch.optim as optim
from aalpy.utils import load_automaton_from_file
from sklearn.cluster import estimate_bandwidth

from RNN import get_model, Optimization
from automata_data_generation import get_tomita, get_mqtt_mealy, generate_data_from_automaton, AutomatonDataset
from clustering import compute_linear_separability_classifier, \
    compute_clusters_and_map_to_states, compute_linear_separability_and_map_to_states
from dim_reduction import reduce_dimensions
from methods import extract_hidden_states, compute_lda_separation
from util import compute_ambiguity

# Unused function
def compute_lda_and_separation(automaton, model, validation_data):
    hs_processing_fun = 'flatten' if model.model_type != 'lstm' else 'flatten_lstm'

    import time
    before_extract = time.time()
    hidden_states = extract_hidden_states(model, validation_data, process_hs_fun=hs_processing_fun,
                                          save=False, load=False)

    before_lda = time.time()
    lda = compute_linear_separability_classifier(automaton, hidden_states, validation_data)
    before_separation = time.time()
    separation_values = compute_lda_separation(lda, automaton, hidden_states, validation_data)
    end = time.time()

    print(f"Separation: {separation_values}")
    print(f"Times: {before_lda - before_extract}, {before_separation - before_lda}, {end - before_separation}")
    return separation_values

# Unused function
def map_clusters_to_lda(automaton, model, validation_data):
    clustering_functions = ['k_means', 'mean_shift', 'spectral', 'DBSCAN', 'OPTICS']

    hs_processing_fun = 'flatten' if model.model_type != 'lstm' else 'flatten_lstm'
    hidden_states = extract_hidden_states(model, validation_data, process_hs_fun=hs_processing_fun,
                                          save=False, load=False)

    cluster_to_lda_maps = []
    lda = compute_linear_separability_classifier(automaton, hidden_states, validation_data)
    lda_predictions = [lda.predict(x.reshape(1, -1)) for x in hidden_states.copy()]
    for cf in clustering_functions:
        cf_predictions = compute_clusters_and_map_to_states(automaton, validation_data, hidden_states, cf,
                                                            return_fit_predict=True)
        lda_to_cf_map = defaultdict(Counter)
        for l, c in zip(lda_predictions, cf_predictions):
            lda_to_cf_map[f'l{l}'][f'c{c}'] += 1
            cluster_to_lda_maps.append(('cf', lda_to_cf_map))

    ambiguity_results = []
    for cf, lda_cluster_map in cluster_to_lda_maps:
        ambiguity_results.append((cf, compute_ambiguity(lda_cluster_map)))

    print(ambiguity_results)

def compare_clustering_methods(automaton, model, validation_data):
    clustering_functions = ['DBSCAN_half', 'DBSCAN', 'DBSCAN_double', 'DBSCAN_triple', 'DBSCAN_quad', 'k_means',
                            'k_means_dec', 'k_means_inc', 'k_means_double', 'k_means_4', 'k_means_6', 'k_means_8',
                            'OPTICS', 'mean_shift', 'mean_shift_2', 'mean_shift_4', 'mean_shift_8']

    model.eval()

    hs_processing_fun = 'flatten' if model.model_type != 'lstm' else 'flatten_lstm'
    hidden_states = extract_hidden_states(model, validation_data, process_hs_fun=hs_processing_fun,
                                          save=False, load=False)

    cluster_to_state_maps = []
    lda_state_map = compute_linear_separability_and_map_to_states(automaton, hidden_states, validation_data,
                                                                  method='lda')
    cluster_to_state_maps.append(('lda', lda_state_map, automaton.size))
    lr_state_map = compute_linear_separability_and_map_to_states(automaton, hidden_states, validation_data, method='lr')
    cluster_to_state_maps.append(('lr', lr_state_map, automaton.size))

    mean_shift_default_bandwidth = estimate_bandwidth(hidden_states)

    for cf in clustering_functions:
        cf_state_map, nr_clusters = compute_clusters_and_map_to_states(automaton, validation_data, hidden_states,
                                                                       cf, mean_shift_default_bandwidth)
        cluster_to_state_maps.append((cf, cf_state_map, nr_clusters))

    ambiguity_results = []
    for cf, state_cluster_map, nr_clusters in cluster_to_state_maps:
        ambiguity_results.append((cf, compute_ambiguity(state_cluster_map, weighted=True), nr_clusters))

    print(ambiguity_results)
    return ambiguity_results

if __name__ == '__main__':

    save_dir = 'rnn_data'

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    device_type = 'gpu' if device != 'cpu' else 'cpu'

    experiments = [(get_tomita(3), 'tomita3'),
                   (get_tomita(5), 'tomita5'),
                   (get_tomita(7), 'tomita7'),
                   (get_mqtt_mealy(), 'mqtt'),
                   (load_automaton_from_file('automata_models/regex_paper.dot', automaton_type='dfa'), 'regex')]

    randomly_generated_dfa = [f for f in listdir('automata_models/') if f[:3] == 'dfa']
    randomly_generated_moore_machines = [f for f in listdir('automata_models/') if f[:5] == 'moore']

    for aut in randomly_generated_dfa:
        experiments.append((load_automaton_from_file(f'automata_models/{aut}', automaton_type='dfa'), aut))

    for aut in randomly_generated_moore_machines:
        experiments.append((load_automaton_from_file(f'automata_models/{aut}', automaton_type='moore'), aut))

    rnn_types = ['tanh', 'relu', 'gru', 'lstm']
    num_repeats_per_config = 2

    num_training_samples = 50 * 1000
    num_validation_samples = 4 * 1000

    total_exp_configs = len(experiments) * len(rnn_types) * 3 * num_repeats_per_config
    print(f'Total number of trainings: {total_exp_configs}')

    current_iteration = 0
    with open('experiment_results/stats_mean_shift_lr.txt', 'a') as stats_file:
        for automaton, exp_name in experiments:
            for rnn in rnn_types:
                optimal_size = len(automaton.get_input_alphabet()) * automaton.size
                sizes = ((1, optimal_size), (1, int(optimal_size * 1.5)), (2, optimal_size))
                for layers, nodes in sizes:
                    for i in range(num_repeats_per_config):
                        current_iteration += 1
                        print(f'Automated driver progress: {round((current_iteration / total_exp_configs) * 100, 2)}%')

                        exp_rnn_config = f'{exp_name}_{rnn}_{layers}_{nodes}_{i + 1}_{device_type}'
                        model_weights_name = f'{save_dir}/models/exp_models/{exp_rnn_config}.pt'
                        print(exp_rnn_config)

                        training_data, input_al, output_al = generate_data_from_automaton(automaton,
                                                                                num_examples=num_training_samples)
                        validation_data, _, _ = generate_data_from_automaton(automaton,
                                                                             num_examples=num_validation_samples)

                        model_type = rnn if rnn in {'gru', 'lstm'} else 'rnn'
                        activation_fun = rnn
                        input_dim = len(input_al)
                        output_dim = len(output_al)
                        layer_dim = layers
                        hidden_dim = nodes
                        batch_size = 128
                        dropout = 0.1 if layer_dim > 1 else 0
                        n_epochs = 200
                        learning_rate = 0.0005
                        weight_decay = 1e-6

                        data_handler = AutomatonDataset(input_al, output_al, batch_size)

                        train, val = data_handler.create_dataset(training_data), data_handler.create_dataset(
                            validation_data)

                        model_params = {'input_dim': input_dim,
                                        'hidden_dim': hidden_dim,
                                        'layer_dim': layer_dim,
                                        'output_dim': output_dim,
                                        'nonlinearity': activation_fun,
                                        'dropout_prob': dropout,
                                        'data_handler': data_handler, }

                        model = get_model(model_type, model_params)
                        model.model_name = model_weights_name

                        optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
                        opt = Optimization(model=model, optimizer=optimizer)
                        # opt.train(train, val, n_epochs=n_epochs, exp_name=model_weights_name, early_stop=True, verbose=True,
                        #          save=False, load=True)

                        load_status = opt.load(model_weights_name)
                        if not load_status:
                            print(f'Can not find weights file of: {model_weights_name}')
                            continue

                        validation_data, _, _ = generate_data_from_automaton(automaton, num_examples=1000)

                        res = compare_clustering_methods(automaton, model, validation_data)
                        # res = compute_lda_and_separation(automaton, model, validation_data)
                        from methods import conformance_test

                        conf_test_res = conformance_test(model, automaton, min_test_len=5, max_test_len=13)
                        res.append(('conf_test', conf_test_res))
                        stats_file.write(
                            f'Automated driver progress: iteration {current_iteration} of {total_exp_configs}\n')
                        stats_file.write(f'{exp_rnn_config}\n')
                        stats_file.write(f'{res}\n')
                        stats_file.flush()


import django
django.setup()
from ..models import PublicKey, PrivateKey, Member
import  random
from ..Utils import NumberUtil, KeyUtil
import hashlib
import pathlib

def Hash(m):
    a=str(m)
    result=hashlib.sha512(a.encode('utf-8')).digest()
    return int.from_bytes(result, 'little')

def ChooseG(n, upperBlock):
    while True:
        number = NumberUtil.GetPrime(NumberUtil.lower(n), upperBlock)
        if number < upperBlock:
            return number
        else:
            continue

def Setup(lg=1200,ep=9/8,l=1200,l1=860,l2=600):
    PublicKey.objects.all().delete()
    PrivateKey.objects.all().delete()

    p = NumberUtil.GetPrime(NumberUtil.lower(lg / 2), NumberUtil.upper(lg / 2))
    q = NumberUtil.GetPrime(NumberUtil.lower(lg / 2), NumberUtil.upper(lg / 2))
    k = 256
    n = p * q
    g = ChooseG(lg - 1, n)

    hatZ = NumberUtil.nBitRandom(NumberUtil.lower(lg), NumberUtil.upper(lg))
    z = pow(g, hatZ, n)
    hatH = NumberUtil.nBitRandom(NumberUtil.lower(lg), NumberUtil.upper(lg))
    h = pow(g, hatH, n)

    x = random.randrange(0, NumberUtil.upper(lg))
    y = pow(g, x, n)

    KeyUtil.SavePublicKey(KeyUtil.Public(n,g,h,z,l,l1,l2,lg,k,ep,y))
    KeyUtil.SavePrivate(KeyUtil.Private(p,q,x))

def Registration():
    public= KeyUtil.GetPublicKey()

    hatE = NumberUtil.GetPrime(NumberUtil.lower(int(public.L)), NumberUtil.upper(int(public.L)))
    e = NumberUtil.GetPrime(2**int(public.L1)+1, 2**int(public.L1)+2**int(public.L2) - 1)
    tempE = hatE*e
    tempZ = pow(int(public.Z), hatE, int(public.N))

    return (e,tempE, tempZ)

def Hash(m):
    a=str(m)
    result=hashlib.sha512(a.encode('utf-8')).digest()
    return int.from_bytes(result, 'little')

def CheckMemberKey(u, e):
    publickey = KeyUtil.GetPublicKey()
    z= int(publickey.Z)
    n = int(publickey.N)
    temp = pow(u,e,n)
    z= z % n
    return (temp==(z % n))

def GenerateSignature(u, e, message):
    publickey = KeyUtil.GetPublicKey()
    lg =int(publickey.LG)
    g = int(publickey.G)
    n = int(publickey.N)
    y= int(publickey.Y)
    h = int(publickey.H)
    z= int(publickey.Z)
    ep = float(publickey.ESP)
    l2= int(publickey.L2)
    l1= int(publickey.L1)
    k= int(publickey.K)

    m = Hash(message)
    w = random.randrange(0, 2 ** lg)

    a = pow(g, w, n)
    b = (u * pow(y, w, n)) % n
    d = (pow(g, e, n) * pow(h, w, n)) % n
    r1 = random.randrange(0, pow(2, int(ep * (l2 + k))))
    r2 = random.randrange(0, pow(2, int(ep * (lg + l1 + k))))
    r3 = random.randrange(0, pow(2, int(ep * (lg + k))))
    t1 = (pow(b, r1, n) * pow(pow(y, r2, n), -1, n)) % n
    t2 = (pow(a, r1, n) * pow(pow(g, r2, n), -1, n)) % n
    t3 = pow(g, r3, n)
    t4 = (pow(g, r1, n) * pow(h, r3, n)) % n
    c = Hash(str(g) + str(h) + str(y) + str(z) + str(a) + str(b) + str(d) + str(t1) + str(t2) + str(t3) + str(t4) + str(m))
    s1 = r1 - c * (e - 2 ** l1)
    s2 = r2 - c * e * w
    s3 = r3 - c * w

    return c, s1, s2, s3, a, b, d

def Verifying(c,s1,s2,s3,a,b,d, message):
    publickey = KeyUtil.GetPublicKey()
    lg = int(publickey.LG)
    g = int(publickey.G)
    n = int(publickey.N)
    y = int(publickey.Y)
    h = int(publickey.H)
    z = int(publickey.Z)
    ep = float(publickey.ESP)
    l2 = int(publickey.L2)
    l1 = int(publickey.L1)
    k = int(publickey.K)

    m = Hash(message)
    tempHat = int(s1 - c * 2 ** l1)
    t1 = pow(z, c, n) * pow(b, tempHat, n) * pow(pow(y, s2, n), -1, n)
    t2 = pow(a, tempHat, n) * pow(pow(g, s2, n), -1, n)
    t3 = pow(a, c, n) * pow(g, s3, n)
    t4 = pow(d, c, n) * pow(g, tempHat, n) * pow(h, s3, n)

    t1 = t1 % n
    t2 = t2 % n
    t3 = t3 % n
    t4 = t4 % n

    tempC = Hash(str(g) + str(h) + str(y) + str(z) + str(a) + str(b) + str(d) + str(t1) + str(t2) + str(t3) + str(t4) + str(m))

    return tempC == c

def tracing(c,s1,s2,s3,a,b,d):
    publickey = KeyUtil.GetPublicKey()
    n = int(publickey.N)

    privatekey = KeyUtil.GetPrivate()
    x= int(privatekey.X)
    result = b * pow(pow(a, x, n), -1, n)
    result = result % n

    return result


from django.urls import path, include

from .views import (
                    CompanyListView,
                    CompanyView,
                    CompanyEmployeeListView,
                    CompanyEmployeeView,
                    EmployeeAssetView,
                    CompanyAssetListView,
                    CompanyAssetView,
                    )

urlpatterns = [
    #All companies name
    path('', CompanyListView.as_view(), name= "company_list"),
    #Input exact company name in url and see individual company information
    path('<company_name>/', CompanyView.as_view(), name= "company_view"),
    #Companies assets
    path('<int:company_id>/asset/', CompanyAssetListView.as_view(), name= "company_assets"),
    #Individual companies asset
    path('<int:company_id>/asset/<int:pk>', CompanyAssetView.as_view(), name= "company_assets"),
    #Individual companies all employees
    path('<int:company_id>/employee/', CompanyEmployeeListView.as_view(), name= "employee_list"),
    #Individual companies individual employee details
    path('<int:company_id>/employee/<int:pk>/', CompanyEmployeeView.as_view(), name= "employee_view"),
    #asset assigned to the employee through company
    path('<int:company_id>/asset-assign/', EmployeeAssetView.as_view(), name= "asset-assign"),

]

#for login
urlpatterns += [
    path('api-auth/', include('rest_framework.urls')),
]


import os
import typing

__all__ = ['split_all', 'join_all', 'normpath', 'join', 'sys2vfs']

def split_all(path: str) -> typing.List[str]:
    comps = path.split('/')
    new_comps = []
    for comp in comps:
        if comp in ('', '.'):
            continue

        if comp != '..' or not new_comps or new_comps and new_comps[-1] == '..':
            new_comps.append(comp)
        else:
            if new_comps:
                new_comps.pop()

    return new_comps

def join_all(*parts: str) -> str:
    return '/' + '/'.join(parts)

def sys2vfs(*paths: str) -> str:
    paths = os.path.normpath(os.path.join(*paths)).split(os.path.sep)
    return join_all(*paths)

def normpath(path: str) -> str:
    return join_all(*split_all(path))

def join(a: str, b: str) -> str:
    if a == '/':
        return b
    return a + b


# This script is used for populating google sheets with
# anomalies list for user input
# Author: atediarjo@gmail.com

import pygsheets as pg
import pandas as pd

gc = pg.authorize(service_file='key/key.json')

anomalies_list = pd.read_csv('input/anomalies_detection.csv')
ever_resolved = pd.read_csv('input/ever_resolved.csv')

# only output confirmed correct as ever resolved, if manual resolution is done but anomalies is still there, then populate google sheets
ever_resolved = ever_resolved[ever_resolved['resolution_status'] == 'confirmed_correct']

other_status = pd.read_csv('input/resolution_other_status.csv')[['resolution_id', 'resolution_status', 'resolver']]

data_for_spreadsheet = anomalies_list[~anomalies_list['resolution_id'].isin(ever_resolved['resolution_id'].unique())]
data_for_spreadsheet = pd.merge(data_for_spreadsheet, other_status, how='left', on = 'resolution_id')
data_for_spreadsheet['resolution_status'] = data_for_spreadsheet['resolution_status_y'].fillna('to_do')
data_for_spreadsheet.drop(['resolution_status_x', 'resolution_status_y'], inplace=True, axis = 1)
data_for_spreadsheet = data_for_spreadsheet[['resolution_id',
                                             'KEY',
                                             'form_id',
                                             'anomalies_id',
                                             'anomalies_description',
                                             'anomalies_reports_to_wid',
                                             'resolver',
                                             'resolution_status']].fillna('')

sh = gc.open('odk_form_anomalies')
wks = sh.worksheet_by_title('anomalies_resolution_tracker')
wks.clear(start='A1', end = 'Y20000')
wks.set_dataframe(data_for_spreadsheet, (0,0))


# %%
from __future__ import unicode_literals
from __future__ import print_function

# %%
import spacy
import neuralcoref
import re
import logging

# %%
logging.basicConfig( level=logging.INFO,filename='app.log', filemode='w', format='%(asctime)s - %(funcName)s - %(levelname)s - %(message)s')
# logging.warning('This will get logged to a file')

# %%
customWords = ['between','among','amongst']
stripCharacters=".!@#$%^&*()?_/\|<>,:; "

# %%
"""version 1.2
Config file for Comprehension Model"""
lang = 'en_core_web_sm'
greedyness = 0.55

unicode_ = str

# %%
nlp=spacy.load(lang)
coref=neuralcoref.NeuralCoref(nlp.vocab,greedyness=greedyness)
nlp.add_pipe(coref,name="neuralcoref")

# %%
def corefResoultion(text1,text2):
	logging.info("control inside corefResoultion")

	try:
		text1 =text1.lower()
		text2 =text2.lower()
		text = text1+". "+text2
		doc = nlp(unicode_(text))
		corefStatus=doc._.has_coref

		if not corefStatus:
			text=text1+"  "+text2
			doc = nlp(unicode_(text))
			corefStatus = doc._.has_coref

		if not corefStatus:
			text=text1.title()+"    "+text2
			doc = nlp(unicode_(text))
			corefStatus = doc._.has_coref

	except Exception as err:
		print("Exception Occured",err)
		doc=""
		corefStatus=False
		raise
	return doc,corefStatus

# %%
def endCharCheck(orgText,modText):
    logging.info("control inside endCharCheck")
    try:
        if orgText[-1].isalnum():
            modText = modText
        else:
            modText = modText+orgText[-1]
    except Exception as err:
        print("Exception Occured",err)
        modText=""
        raise
    return modText

# %%
def resolveText(doc,text1,text2,customWord): # text2 -> query
    logging.info("control inside resolve test")
    try:
        rText=""
        tempText1 = text1.rstrip(stripCharacters)
        tempText2 = text2.rstrip(stripCharacters)

        for cCount in range(len(doc._.coref_clusters)):
            mentionList=doc._.coref_clusters[cCount].mentions

            mCorefWord=doc._.coref_clusters[cCount].main.text.rstrip(stripCharacters)
            pronounList=[]
            mCount =0
            mCorefWord =mCorefWord.lstrip(stripCharacters)
            tokenMCorefWord=nlp(mCorefWord)
            for i in mentionList:
                x=nlp(str(i).lower())
                for token in x:
                    if token.pos_ == "PRON" :
                        pronounList.append(str(i))

            if mCorefWord.lower()!=tempText1.lower() and 	mCorefWord.lower() != tempText2.lower():
                corefWordList=[]
                corefWord =""
                for token in tokenMCorefWord:
                    if token.pos_ !="PRON" :
                        if token.text.upper() in text1:
                            corefWordList.append(token.text.upper())
                        elif token.text.title() in text1:
                            corefWordList.append(token.text.title())
                        elif token.text.lower() in text1:
                            corefWordList.append(token.text.lower())
                        else:
                             corefWordList.append(token.text)
                if len(corefWordList) >1 :
                    corefWord =' '.join(corefWordList)
                elif len(corefWordList) == 1:
                    corefWord = corefWordList[0]
                else:
                    corefWord =""

                text2 = text2.rstrip(stripCharacters)
                docNoun = nlp(text2.lower())
                NounList= [ token.text for token in docNoun if token.pos_ == "NOUN" or token.pos_ == "PROPN" ]

                if len(pronounList) > 0 and corefWord !="":
                    for element in pronounList:
                        if not customWord:
                            if element not in NounList:
                                if element.upper() in text2:
                                    subWord=r'\b'+element.upper()+'\\b'
                                    text2=re.sub(subWord,corefWord,text2)
                                elif element in text2.lower():
                                    subWord=r'\b'+element+'\\b'
                                    text2=re.sub(subWord,corefWord,text2)
                                else:
                                    text2 =text2
                        else:
                            if element.upper() in text2:
                                subWord=r'\b'+element.upper()+'\\b'
                                text2=re.sub(subWord,corefWord,text2)
                            elif element in text2.lower():
                                    subWord=r'\b'+element+'\\b'
                                    text2=re.sub(subWord,corefWord,text2)
                            else:
                                    text2 =text2
            else:
                text2 = text2

        rText = text2
    except Exception as err:
        print("Exception Occured",err)
        rText=""
        raise

    return rText

# %%
def customWordResolve(stringList,textR,query):
    logging.info("control inside customWordResolve")
    try:
        tempText = textR
        customRText =""
        stringList = list(set(stringList))
        if len(stringList) > 1:
            corefWord = ','.join(stringList)
        elif len(stringList) != 0 :
            corefWord = stringList[0]
        else:
            corefWord =""
        for ele in stringList:
            if ele.upper() in textR:
                subWord=r'\b'+str(ele).upper()+'\\b'
                textR=re.sub(subWord,corefWord,textR)
                break
            elif ele.title() in textR:
                subWord=r'\b'+str(ele).title()+'\\b'
                textR=re.sub(subWord,corefWord,textR)
                break
            elif len(ele)>1 and ele.lower() in textR:
                subWord=r'\b'+str(ele).lower()+'\\b'
                textR=re.sub(subWord,corefWord,textR)
                break
            else:
                textR =textR
        customRText = endCharCheck(query,textR)
    except Exception as err:
        print("Exception Occured",err)
        customRText=""

        raise
    return customRText

# %%
def evalute_result(previous_statements,query):
    logging.info('Input values previous_statements'+str(previous_statements)+" Query: "+query)
    if len(previous_statements)==0 or previous_statements[0]=="":
        print("Two statements are required \n -Please provide atleast one input / first statement cant be blank") #this print statements can be replaced with return
        logging.error("Two statements are required -Please provide atleast one input / first statement cant be blank")
        return
    if len(previous_statements)==1:
        logging.info("User provided only one value.Result will be on the basis of single value")
        data= previous_statements[0]
        previous_statements.insert(0, data)
        previous_statements.insert(1, data)
    elif previous_statements[0]=="" or previous_statements[1]=="":
        logging.info("User provided only one value,Result will be on the basis of single value")
        for items in range(len(previous_statements)):
            data=""
            if previous_statements[items]!="":
                data= previous_statements[items]
            previous_statements.insert(0, data)
            previous_statements.insert(1, data)
    try:
        logging.info("Control inside try block checking if any custom word exists in the query")
        customWordList = customWords
        customWord = False
        resolved = ""
        status = False
        output = {"resolved":resolved,"status": status }
        logging.info("Merging all the previous statemets together")
        for items in range(len(previous_statements)):
           previous_statements[items]= previous_statements[items].rstrip(stripCharacters)

        if len(customWordList)!=0:
            for word in customWordList:
                if word in query.lower():
                    logging.info("Custom is present in the given query")
                    mergedText=""
                    for i in range(len(previous_statements)):
                       mergedText= mergedText+previous_statements[i]+". "
                    tempMergedText =mergedText
                    mergedText = mergedText.title()
                    text = mergedText+"    "+query.lower()+"?"
                    customWord= True

        if customWord:
            logging.info("Applying NLP on the inputs and sperating NOUN and PRONoun form the given INPUTS ")
            doc_text1 = nlp(mergedText)
            doc1_text1= nlp(tempMergedText)

            stringList=[]
            list_tokens_text1= [token.text for token in doc_text1 if token.pos_ == "NOUN" or token.pos_ == "PROPN" ]
            list1_tokens_text1 = [token.text for token in doc1_text1]
            logging.info("Noun and pronoun list "+str(list_tokens_text1))

            for word in list1_tokens_text1:
                for nounEle in list_tokens_text1:
                    if word.title() == nounEle or word.lower() == nounEle or word.upper() == nounEle or word == nounEle:
                        stringList.append(word)
            text = unicode_(text)
            doc = nlp(text)
            status = doc._.has_coref
            if status:
                textR= resolveText(doc,tempMergedText,query,customWord)
                customResolvedtext=customWordResolve(stringList,textR,query)
                resolved = customResolvedtext

                if textR == customResolvedtext:
                    resolved = ""
                    status = False
                else:
                    output["resolved"]= resolved.lstrip().rstrip()
                    output["status"]= status
            logging.info("result "+str(output))
            print(output)
            return output

        else:
            logging.info("no custom word exists on the given query ")
            prevCombinedQuery = ""
            combineLatesQuery =""
            prevLatestQuery =""
            corefStatus = False
            corefStatus13 = False
            corefStatus23 = False
            if previous_statements[0] !="" :
                mergedText =""
                for i in range(len(previous_statements)):
                    mergedText= mergedText+previous_statements[i]+". "
                doc,corefStatus = corefResoultion(mergedText,query)
                doc12,corefStatus12 = corefResoultion(previous_statements[0],previous_statements[1])
                doc23,corefStatus23 = corefResoultion(previous_statements[1],query)
                prevCombinedQuery = resolveText(doc,mergedText,query,customWord)
                if corefStatus:
                    prevCombinedQuery = resolveText(doc,mergedText,query,customWord)
                    prevLatestQuery = resolveText(doc12,previous_statements[0],previous_statements[1],customWord)
                    combineLatesQuery =resolveText(doc23,previous_statements[1],query,customWord)
                    if corefStatus12 and corefStatus23:
                        doc223,corefStatus223 =corefResoultion(prevLatestQuery,query)
                        text223 =resolveText(doc223,prevLatestQuery,query,customWord)
                        if corefStatus223:
                            if text223 != query and text223 == prevCombinedQuery:
                                resolved =endCharCheck(query,text223)
                            elif prevCombinedQuery != text223 and combineLatesQuery == query and  prevCombinedQuery != combineLatesQuery:
                                resolved =endCharCheck(query,prevCombinedQuery)
                            elif prevCombinedQuery == combineLatesQuery and combineLatesQuery == query and text223 != query:
                                resolved =endCharCheck(query,text223)
                            elif combineLatesQuery == text223 and prevCombinedQuery != combineLatesQuery :
                                resolved =endCharCheck(query,combineLatesQuery)

                            else:
                                resolved =endCharCheck(query,prevCombinedQuery)

                        elif corefStatus23 and not(corefStatus12):
                            if combineLatesQuery.rstrip(stripCharacters) == query and prevCombinedQuery !=query:
                                resolved = endCharCheck(query,prevCombinedQuery)
                            else:
                                resolved = endCharCheck(query,combineLatesQuery)

                        else:
                            resolved =""

                    else:
                        if corefStatus23:
                            resolved = endCharCheck(query,combineLatesQuery)

                else:
                    doc,corefStatus = corefResoultion(previous_statements[1],query)
                    if corefStatus:
                        text = resolveText(doc,previous_statements[1],query,customWord)
                        text= endCharCheck(query,text)
                        resolved = text
                        status = corefStatus
            resolved=resolved.replace(query.lstrip(stripCharacters),"")
            resolvedTemp = resolved.rstrip().lstrip().rstrip(stripCharacters).lstrip(stripCharacters)

            if resolvedTemp == "":
                output["resolved"]= ""
                output["status"]= False
            else:
                output["status"]= True
                output["resolved"]= resolved.lstrip().rstrip()

            print(output)
            logging.info("result "+str(output))
            return output

    except Exception as err:
        output = {"resolved":"","status":"" }
        output["resolved"]= ""
        output["status"] = False
        logging.error("An error occured: "+str(err)+" affacted output: "+output)
        raise

# %%
#previous_statements = ["what is the job","how to apply job","What are the job requirements"]
#query="how to cancle it"
#evalute_result(previous_statements,query)

# %%
# previous_statements = ["where is China? Where is India?","Where is SINGAPORE? where is newYork?"]
# query="what is difference among them?"
# evalute_result(previous_statements,query)

# %%
# previous_statements = ["how to start the job", ""]
# query="how to cancel it"
# evalute_result(previous_statements,query)

# %%
# previous_statements = []
# query="how to cancel it"
# evalute_result(previous_statements,query)

# %%
# handle the list data (must have 2 elements)
# provide proper nameing convention  for the vars
# approch for the the list items handling - just copy the data of existing item
# kind of Readme.text for installation steps


import torch
from all.nn import RLNetwork
from .approximation import Approximation

class QNetwork(Approximation):
    def __init__(
            self,
            model,
            optimizer=None,
            name='q',
            **kwargs
    ):
        model = QModule(model)
        super().__init__(
            model,
            optimizer,
            name=name,
            **kwargs
        )

class QModule(RLNetwork):
    def forward(self, states, actions=None):
        values = super().forward(states)
        if actions is None:
            return values
        if isinstance(actions, list):
            actions = torch.tensor(actions, device=self.device)
        return values.gather(1, actions.view(-1, 1)).squeeze(1)


from django.contrib.auth.models import User
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .forms import UserForm
from django.core import serializers
from django.http import HttpResponse, HttpResponseRedirect
from rest_framework.views import APIView
from .serializers import UserSerializer
import json
from django.shortcuts import render
from django.urls import reverse_lazy
from django.contrib.auth.views import LoginView
from .models import UserWebSocketSession
from .message import publish
from django.contrib.auth.views import logout
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse

def customlogout(request):
    token = request.session.get('session_user_token', None)
    if token:
        session_user = UserWebSocketSession.objects.get(token=token)
        session_user.session_two_steps = False
        session_user.active_websocket = False
        session_user.save()

    return logout(request)

class CustomLoginView(LoginView):
    template_name = 'index.html'
    success_url = reverse_lazy("refuge:index")

    def dispatch(self, request, *args, **kwargs):
        response = super().dispatch(request, *args, **kwargs)
        valid = True

        if (request.method == 'POST' and request.POST['username'] and
                request.POST['password']):

            if request.session.get('session_user_token') is None:
                session_user = UserWebSocketSession.create_session_user(
                    user=request.user)
                request.session['session_user_token'] = session_user.token
                two_step = session_user.session_two_steps
                port = session_user.port
                request.session['session_user_two_steps'] = two_step
                request.session['session_user_port'] = port
                if not two_step:  # notifica al usuario que se ha iniciado otra sesion
                    publish(f'question_the_user.{session_user.user.id}',
                            {'user_id': session_user.user.id,
                             'token': session_user.token})
            else:
                token = request.session.get('session_user_token')
                session_user = UserWebSocketSession.objects.get(token=token)
                success = UserWebSocketSession.objects.filter(
                    user=session_user.user, session_two_steps=True).count() == 0
                if session_user.locked:
                    valid = False
                elif success:
                    valid = True
                    session_user.session_two_steps = True
                    session_user.save()
                session_user.session_two_steps = True
                session_user.save()
            two_step = session_user.session_two_steps
        else:  # metodo GET
            two_step = True

        if True:
            return response
        elif not valid:  # usuario bloqueado no acceso al sistema
            return HttpResponseRedirect(reverse_lazy('logout'))
        return HttpResponseRedirect(reverse_lazy('user:waiting_page'))

def user(request):
    return render(request, 'people/user_list.html')

def list(request):
    user = serializers.serialize('json', User.objects.all())
    return HttpResponse(user, content_type='application/json')

class UserRegistry(CreateView):
    model = User
    template_name = 'user/user_registry.html'
    form_class = UserForm
    success_url = reverse_lazy("login")

    def post(self, request, *arg, **kwargs):

        form = self.get_form()

        if form.is_valid():
            user = form.save()
            self.object = user
            return HttpResponseRedirect(self.get_success_url())
        else:
            kwargs['form'] = form

            return self.render_to_response(kwargs)

def user_session_token_authentication(request):
    first = False
    response = {'valid': False, 'first': first}
    token = request.GET.get('token', None)
    session_user = UserWebSocketSession.objects.filter(token=token)
    confirmation = session_user.exists()
    if confirmation:
        session_user = session_user.first()
        if not session_user.active_websocket and not session_user.locked:
            first = UserWebSocketSession.objects.filter(user=session_user.user,
                                                        active_websocket=True).count() == 0
            session_user.active_websocket = True
            session_user.save()
        response = {'valid': confirmation, 'first': first}

    return JsonResponse(response)

# confimacion de el usuario a una nueva session True o False

def user_session_confirm_token(request):
    response = {'valid': False}
    token = request.GET.get('token', None)
    campaign_id = request.GET.get('campaign_id', None)
    # si exite campaign id evaluar que esa campaña pertenesca a el usuario
    session_user = UserWebSocketSession.objects.filter(token=token)
    token_origin = request.GET.get('token_origin', None)
    valid_token_origin = UserWebSocketSession.objects.filter(
        token=token_origin).exists()
    valid_token_new = session_user.exists()
    session_user = session_user.get()

    if (not session_user.locked and not session_user.session_two_steps and
            valid_token_origin and valid_token_new):
        confirm = response_user = request.GET.get('confirm', None)
        if confirm == 'YES':
            session_user.session_two_steps = True
            response = {'valid': True}
        else:
            session_user.locked = True
        session_user.save()
        publish(f"session_confirm.{session_user.user.id}", {})
        publish(f'success_session_redirect.{session_user.token}',
                {'success': response["valid"]})

    return JsonResponse(response)

@login_required
def view_waiting_page(request):
    return render(request, 'user/waiting_page.html')

@login_required
def view_has_not_permits(request):
    return render(request, 'user/has_not_permits.html')

class UserAPI(APIView):
    serializers = UserSerializer

    def get(seft, request, format=None):
        list = User.objects.all()
        response = seft.serializers(list, many=True)

        return HttpResponse(json.dumps(response.data), content_type='application/json')


from typing import Union, List

from Helper import SYMBOLS, formatDateGerman, CouponType, formatPrice
from UtilsCouponsDB import Coupon, CouponSortMode, CouponSortModes

class CouponCategory:
    """ Takes a list/dict of given coupons and creates a 'category' container of it containing easily accessible fields with useful information about it. """

    def __init__(self, coupons: Union[CouponType, int, dict, List], title: Union[str, None] = None):
        self.titleOverride = title
        self.coupons = None
        self.mainCouponType = None
        self.couponTypes = set()
        self.displayDescription = False  # Display description for this category in bot menu?
        self.expireDatetimeLowest = None
        self.expireDatetimeHighest = None
        self.numberofCouponsTotal = 0
        self.numberofCouponsHidden = 0
        self.numberofCouponsEatable = 0
        self.numberofCouponsEatableWithPrice = 0
        self.numberofCouponsNew = 0
        self.numberofCouponsWithFriesAndDrink = 0
        self.numberofVeggieCoupons = 0
        self.totalPrice = 0
        if isinstance(coupons, dict):
            self.coupons = list(coupons.values())
        elif isinstance(coupons, list):
            self.coupons = coupons
        if self.coupons is not None:
            for coupon in self.coupons:
                self.updateWithCouponInfo(coupon)
            if len(self.couponTypes) == 1:
                for first_item in self.couponTypes:
                    self.mainCouponType = first_item
                    break
            else:
                self.mainCouponType = None
        else:
            self.mainCouponType = coupons
        if self.mainCouponType is None:
            self.nameSingular = "Coupon"
            self.namePlural = "Alle Coupons"
            self.namePluralWithoutSymbol = "Alle Coupons"
            self.description = "Coupons mehrerer Kategorien"
        elif self.mainCouponType == CouponType.APP:
            self.nameSingular = "App Coupon"
            self.namePlural = "App Coupons"
            self.namePluralWithoutSymbol = "App Coupons"
            self.description = "Coupons aus der BK App"
        elif self.mainCouponType == CouponType.PAPER:
            self.nameSingular = "Papiercoupon"
            self.namePlural = SYMBOLS.NEWSPAPER + "Papiercoupons"
            self.namePluralWithoutSymbol = "Papiercoupons"
            self.description = "Coupons der Papier Couponbögen"
        elif self.mainCouponType == CouponType.PAPER_UNSAFE:
            self.nameSingular = "Papiercoupon (unsafe)"
            self.namePlural = SYMBOLS.NEWSPAPER + "Papiercoupons (unsafe)"
            self.namePluralWithoutSymbol = "Papiercoupons (unsafe)"
            self.description = "Coupons aus der \"Coupons2\" API, die keinem anderen Coupon-Typen zugewiesen werden konnten."
        elif self.mainCouponType == CouponType.ONLINE_ONLY:
            self.nameSingular = "Online Only"
            self.namePlural = "Online only"
            self.namePluralWithoutSymbol = "Online Only"
            self.description = "Coupons, die mit hoher Wahrscheinlichkeit nur online oder am Terminal bestellbar sind"
        elif self.mainCouponType == CouponType.ONLINE_ONLY_STORE_SPECIFIC:
            self.nameSingular = "Online only (store specific)"
            self.namePlural = "Online only (store specific)"
            self.namePluralWithoutSymbol = "Online only (store specific)"
            self.description = "Coupons, die nur in bestimmten# Filialen gültig sind"
        elif self.mainCouponType == CouponType.SPECIAL:
            self.nameSingular = "Special Coupon"
            self.namePlural = SYMBOLS.GIFT + "Special Coupons"
            self.namePluralWithoutSymbol = "Special Coupons"
            self.description = "Diese Coupons sind evtl. nicht in allen Filialen einlösbar!"
        elif self.mainCouponType == CouponType.PAYBACK:
            self.nameSingular = "Payback Coupon"
            self.namePlural = SYMBOLS.PARK + "ayback Coupons"
            self.namePluralWithoutSymbol = "Payback Coupons"
            self.description = "Payback Papiercoupons"
        else:
            self.nameSingular = "Unbekannt"
            self.namePlural = "Unbekannt"
            self.namePluralWithoutSymbol = "Unbekannt"

    def getTotalPrice(self) -> float:
        return self.totalPrice

    def getNumberofCouponsEatableWithPrice(self) -> int:
        return self.numberofCouponsEatableWithPrice

    def getNumberofCouponsEatableWithoutPrice(self) -> int:
        return self.numberofCouponsEatable - self.numberofCouponsEatableWithPrice

    def setNumberofCouponsEatableWithPrice(self, newNumber: int):
        self.numberofCouponsEatableWithPrice = newNumber

    def setTotalPrice(self, newPrice: float):
        self.totalPrice = newPrice

    def isEatable(self) -> bool:
        """ Typically all coupon categories except Payback coupons will return True here as they do contain at least one item that is considered 'eatable'. """
        if self.numberofCouponsEatable > 0:
            return True
        else:
            return False

    def isVeggie(self):
        """ Returns True if all coupons in this categorie are veggie. """
        if len(self.couponTypes) == 1 and self.mainCouponType == CouponType.PAYBACK:
            # Only Payback coupons in this category -> Technically veggie but logically not ;)
            return False
        elif self.numberofCouponsTotal > 0 and self.numberofCouponsTotal == self.numberofVeggieCoupons:
            return True
        else:
            return False

    def isEligibleForDuplicateRemoval(self):
        if self.mainCouponType == CouponType.PAYBACK:
            return False
        else:
            return True

    def isEligableForSort(self):
        if len(self.getSortModes()) > 1:
            return True
        else:
            return False

    def getSortModes(self) -> List:
        """ Returns all SortModes which make sense for this set of coupons. """
        if self.coupons is None:
            return []
        sortModes = []
        if self.totalPrice > 0:
            sortModes.append(CouponSortModes.PRICE)
            sortModes.append(CouponSortModes.PRICE_DESCENDING)
        if self.numberofCouponsTotal != self.numberofCouponsWithFriesAndDrink:
            sortModes.append(CouponSortModes.MENU_PRICE)
        if len(self.couponTypes) > 1:
            sortModes.append(CouponSortModes.TYPE_MENU_PRICE)
        return sortModes

    def allowsSortMode(self, sortModeToCheckFor: CouponSortMode) -> bool:
        """ Checks if desired sortMode is currently allowed. """
        sortModes = self.getSortModes()
        for possibleSortMode in sortModes:
            if possibleSortMode == sortModeToCheckFor:
                return True
        return False

    def getNextPossibleSortMode(self, sortMode: CouponSortMode) -> CouponSortMode:
        possibleSortModes = self.getSortModes()
        for possibleSortMode in possibleSortModes:
            if possibleSortMode.getSortCode() > sortMode.getSortCode():
                return possibleSortMode
        # Fallback/Rollover to first sort
        return possibleSortModes[0]

    def getSortModeCode(self, desiredSortMode: CouponSortMode, fallbackSortMode: CouponSortMode) -> CouponSortMode:
        if self.allowsSortMode(desiredSortMode):
            return desiredSortMode
        else:
            return fallbackSortMode

    def getCategoryInfoText(self) -> str:
        if self.titleOverride is not None:
            text = '[{couponCount} Stück] {couponCategoryName}'
            couponCategoryName = self.titleOverride
        else:
            # Auto determine result
            if self.mainCouponType == CouponType.APP and self.numberofCouponsTotal == self.numberofCouponsHidden:
                # Only hidden App-coupons
                text = '[{couponCount} Stück] {couponCategoryName} versteckte'
            elif self.numberofCouponsWithFriesAndDrink == 0 and self.numberofCouponsEatable > 0:
                text = '[{couponCount} Stück] {couponCategoryName} ohne Menü'
            else:
                text = '[{couponCount} Stück] {couponCategoryName}'
            if self.numberofCouponsTotal == 1:
                couponCategoryName = self.nameSingular
            else:
                couponCategoryName = self.namePluralWithoutSymbol
        text = text.format(couponCount=self.numberofCouponsTotal, couponCategoryName=couponCategoryName)
        if self.displayDescription and self.description is not None:
            text += '\n' + self.description
        text += '\n' + self.getExpireDateInfoText()
        return text

    def getExpireDateInfoText(self) -> str:
        if self.expireDatetimeLowest is None or self.expireDatetimeHighest is None:
            return "Gültig bis ??"
        elif self.expireDatetimeLowest == self.expireDatetimeHighest:
            return "Gültig bis " + formatDateGerman(self.expireDatetimeLowest)
        else:
            return "Gültig bis min " + formatDateGerman(self.expireDatetimeLowest) + " max " + formatDateGerman(self.expireDatetimeHighest)

    def getPriceInfoText(self) -> Union[str, None]:
        if self.getNumberofCouponsEatableWithPrice() == 0:
            return None
        text = "Gesamtwert: " + formatPrice(self.getTotalPrice())
        if self.getNumberofCouponsEatableWithoutPrice() > 0:
            text += "*\n* außer " + str(
                self.getNumberofCouponsEatableWithoutPrice()) + " Coupons, deren Preis nicht bekannt ist."
        return text

    def updateWithCouponInfo(self, couponOrCouponList: Union[Coupon, List[Coupon]]):
        """ Updates category with information of given Coupon(s). """
        if isinstance(couponOrCouponList, Coupon):
            couponList = [couponOrCouponList]
        else:
            couponList = couponOrCouponList
        for coupon in couponList:
            self.couponTypes.add(coupon.type)
            self.numberofCouponsTotal += 1
            if coupon.isHidden:
                self.numberofCouponsHidden += 1
            if coupon.isEatable():
                self.numberofCouponsEatable += 1
            if coupon.isNewCoupon():
                self.numberofCouponsNew += 1
            if coupon.isContainsFriesAndDrink():
                self.numberofCouponsWithFriesAndDrink += 1
            if coupon.isVeggie():
                self.numberofVeggieCoupons += 1
            # Update expire-date info
            date = coupon.getExpireDatetime()
            if self.expireDatetimeLowest is None and self.expireDatetimeHighest is None:
                self.expireDatetimeLowest = date
                self.expireDatetimeHighest = date
            else:
                if date < self.expireDatetimeLowest:
                    self.expireDatetimeLowest = date
                elif date > self.expireDatetimeHighest:
                    self.expireDatetimeHighest = date
            if coupon.getPrice() is not None:
                self.setTotalPrice(self.getTotalPrice() + coupon.getPrice())
                self.setNumberofCouponsEatableWithPrice(self.getNumberofCouponsEatableWithPrice() + 1)
        # End of function
        return


"""
This script subsets robust MCS tracks initiated within a region and saves to a new netCDF file.
"""
__author__ = "Zhe.Feng@pnnl.gov"
__date__ = "08-Dec-2020"

import sys
import os
import numpy as np
import xarray as xr

def subset_file(infile, outdir, lon_box, lat_box):

    status = 0

    # Read input MCS track file
    ds = xr.open_dataset(infile)

    # Get track initiation lat/lon location
    lon0 = ds.meanlon.isel(times=0)
    lat0 = ds.meanlat.isel(times=0)

    # Get track index for MCS initiation location within a region
    trackid = np.where((lon0 >= np.min(lon_box)) & (lon0 <= np.max(lon_box)) & \
                        (lat0 >= np.min(lat_box)) & (lat0 <= np.max(lat_box)))[0]

    # Subset the tracks from the dataset
    dsout = ds.sel(tracks=trackid, drop=True)
    # Add subset lat/lon box to global attribute
    dsout.attrs['lon_box'] = lon_box
    dsout.attrs['lat_box'] = lat_box

    # Strip the path to get input file name
    infilename = os.path.basename(infile)
    # Make output file name
    outfile = f'{outdir}/{infilename}'

    # Write output file
    dsout.to_netcdf(path=outfile, mode='w', format='NETCDF4_CLASSIC', unlimited_dims='tracks')
    print(f'Output saved: {outfile}')

    status = 1
    return status

def main():
    # Read from input
    lon_min = float(sys.argv[1])
    lon_max = float(sys.argv[2])
    lat_min = float(sys.argv[3])
    lat_max = float(sys.argv[4])
    infile = sys.argv[5]
    outdir = sys.argv[6]

    # Subset region boundary
    lon_box = [lon_min, lon_max]
    lat_box = [lat_min, lat_max]
    # lon_box = [-15, 45]
    # lat_box = [-20, 30]
    # lon_box = [-15, 40]
    # lat_box = [-15, 25]

    # Define output directory
    # outdir = f'/global/cscratch1/sd/feng045/waccem/mcs_region/spac/subset_africa_stats/'
    os.makedirs(outdir, exist_ok=True)

    # Call function
    status = subset_file(infile, outdir, lon_box, lat_box)

if __name__ == "__main__":
    main()


from cmath import nan
from operator import concat
import pandas as pd
import numpy as np
import json
import dataset_preprocessing

def get_dataset(name='visualization.csv'):
    df = pd.read_csv(name, encoding='utf-8')
    return df

def get_labels_and_targets(df,df_corpus_preprocessed, verbose=True):
    #get the dataframe column of annotators

    df_annotators = df['annotators']

    #split the column using } as separator into multiple rows

    df_annotators = df_annotators.str.split('}', expand=True)

    #add '}' to the end of each row

    df_annotators = df_annotators+ '}'

    #get first row value and split it using "'label'" as separator into multiple rows
    #iterate for each row and get the first row value and split it using "'label'" as separator into multiple rows

    #estrazione target e labels
    keeper_label = []
    keeper_target = []
    keeper_corpus = []
    number_of_deled = 0
    #create empty dataframe to store short_df

    for i in range(0,len(df_annotators)):
        flag_eliminazione = 0
        current_row=df_annotators.iloc[i].values
        keeper_inner_label = ["","",""]
        keeper_inner_target = ["","",""]
        for j in range(0,3):
            current_sub_row=current_row[j]
            current_sub_row=current_sub_row[1:]
            current_sub_row = current_sub_row.replace("'", '"')
            current_sub_row = json.loads(current_sub_row)
            keeper_inner_label[j]=current_sub_row["label"]
            keeper_inner_target[j]=current_sub_row["target"]
            #append the array to the keeper array
        #check if the lements in keeper_inner_label are equal to each other
        if keeper_inner_label[0]!=keeper_inner_label[1] and keeper_inner_label[0]!=keeper_inner_label[2] and keeper_inner_label[1]!=keeper_inner_label[2]:
            #print("Tutti diversi per"+ 'keeper_inner_label'+ "riga"+str(i)+"eliminata")
            flag_eliminazione = 1
        elif keeper_inner_target[0]!=keeper_inner_target[1] and keeper_inner_target[0]!=keeper_inner_target[2] and keeper_inner_target[1]!=keeper_inner_target[2]:
            flag_eliminazione = 1
        else:
            keeper_target.append(keeper_inner_target)
            keeper_label.append(keeper_inner_label)
            #append the array to the keeper array
            keeper_corpus.append(df_corpus_preprocessed.iloc[i])
            #remove row of index i from df
        if flag_eliminazione == 1:
            flag_eliminazione = 0
            #add i to the dopping_index array

            number_of_deled = number_of_deled + 1

    if verbose:
        print("eliminate"+str(number_of_deled)+"righe")
        print("numero di righe"+str(len(keeper_target )))
        print("numero di rige di partenza"+str(len(df)))
    #cast keeper_corpus to a dataframe
    keeper_corpus = pd.DataFrame(keeper_corpus)
    return keeper_label, keeper_target, keeper_corpus

def mode_labels_and_targets(labels,targets, verbose=True):
    df_label = pd.DataFrame(labels)
    if verbose:
        print("Dataframe labels:")
        print(df_label)

    #get mode of each row for the array labels
    df_label = df_label.mode(axis=1)
    if verbose:
        print("Mode of labels:")
        print(df_label)

    df_target = pd.DataFrame(targets)
    if verbose:
        print("Dataframe targets:")
        print(df_target)

    #get mode of each row for the array targets
    df_target = df_target.mode(axis=1)
    if verbose:
        print("Mode of targets:")
        print(df_target)

    return df_label, df_target

def get_unique_values_labels_and_targets(df_label,df_target, verbose=True):
    #get all possible target values from the dataframe
    df_target_values = df_target.values
    df_target_values = df_target_values.flatten()

    target_values=[]
    for i in range(0,len(df_target_values)):
        #add all the elements to a np array
        #check if df_target_values[i] has a len
        #print(df_target_values[i])
        for j in range(0,len(df_target_values[i])):
            if df_target_values[i][j] not in target_values:
                target_values.append(df_target_values[i][j])
    if verbose:
        print("Possible target values:")
        print(target_values)

    #get all possible label values from the dataframe
    df_label_values = df_label.values
    df_label_values = df_label_values.flatten()

    label_values=[]
    for i in range(0,len(df_label_values)):
        #add all the elements to a np array
        if df_label_values[i] not in label_values:
            label_values.append(df_label_values[i])
    if verbose:
        print("Possible label values:")
        print(label_values)

    return label_values, target_values

def get_label_frequency(df_label, verbose=True):
    #get the frequency of each label value
    df_label_frequency = df_label.value_counts()
    if verbose:
        print("Frequency of labels:")
        print(df_label_frequency)
    return df_label_frequency

def extract_corpus(df, verbose=True):
    #print(df.head(15))
    #get the last column of df
    print(df.shape)
    df_corpus = df["post_tokens"]
    #print(df_corpus.head(15))
    if verbose:
        print("Dataframe corpus:")
        print(df_corpus)
    return df_corpus

def merge_copus_tokens(df_corpus, verbose=True):
    #for each row in the dataframe, merge the tokens in the row
    for i in range(0,len(df_corpus)):
        #remove "[", "]" and "'" from the row
        tmp = df_corpus.iloc[i].replace('[', '')
        tmp = tmp.replace(']', '')
        tmp = tmp.replace("'", '')
        tmp = tmp.split(',')
        #merge the tokens in the row
        tmp = ' '.join(tmp)
        #replace the row with the merged tokens
        df_corpus.iloc[i] = tmp
    return df_corpus

def preprocess_corpus(df_corpus, verbose=True):
    df_corpus=dataset_preprocessing.preprocess_text(df_corpus)
    return df_corpus

def merge_labels_targets_corpus(df_labels,df_targets,df_corpus, verbose=True):
    #merge the labels, targets and corpus in a single dataframe using array of column names
    df_merged = pd.concat([df_labels,df_targets,df_corpus], axis=1)
    df_merged.columns = ['Labels', 'Targets', 'Corpus']
    if verbose:
        print("Dataframe merged:")
        print(df_merged)
    return df_merged

def save_dataframe(df, filename, verbose=True):
    #save the dataframe in a csv file
    df.to_csv(filename, index=False)
    if verbose:
        print("Dataframe saved in "+filename)

def expand_targets(df,targets_names, verbose=True):
    #create a amtrix of size len(targets_names) x le
    filler = np.zeros((len(df),len(targets_names)), dtype=int)
    #cast filler to a dataframe

    filler = pd.DataFrame(filler)
    #concat the dataframe with the filler matrix
    df = pd.concat([df,filler], axis=1)
    df.columns = ['Labels', 'Targets', 'Corpus']+ targets_names
    #count the number of times each target value occurs in the dataframe and add a column for each target value
    for i in range(0,len(df["Targets"])):  #
        for j in range(0,len(targets_names)):
            #if the target name is in the wow
            if df["Targets"].iloc[i] is not nan:
                if targets_names[j] in df["Targets"].iloc[i]:
                    #update dataframe with 1 in the target column
                    df.iloc[i,j+3] = 1
            else:
                #remove the dataframe row
                df.drop(i, inplace=True)

    if verbose:
        print("Dataframe targets expanded:")
        print(df)

    return df


### fit and plot curves for IC50s ########################################
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
import math
from scipy.optimize import curve_fit
import warnings
###################################################################################################
colors=[(0.212395, 0.359683, 0.55171), (0.153364, 0.497, 0.557724), (0.122312, 0.633153, 0.530398), (0.288921, 0.758394, 0.428426), (0.626579, 0.854645, 0.223353),(0.275191, 0.194905, 0.496005)]
sns.set_palette(colors)
pal=sns.color_palette(colors)

def plot_pct_inhib_curves(compound, raw, curve, targs=['PARP1','PARP2'], save_fig=False, graphcol='intensity_inhibition', fitcrit='R^2', sharey=False, save_lab='HiTS', save_dir='./curve_fits'):
    ''' Plot and save IC50 curves on top of raw data for visualization across various targets.
    Raw data frame should at minimum have columns
    [Compound, Target, <response column graphcol>, logconc, Plate, Well_Type]. 'solubility' optional.
    Curve data frame should have columns
    [Compound, Target, 'bottom','top','LogIC50','hill','RMSE',<fitcrit col>,'IC50','relation','pIC50','hitc','coff','notes']
    '''
    curvecols=['bottom','top','LogIC50','hill','RMSE',fitcrit,'IC50','pIC50','hitc','coff']
    sns.set_context('poster')
    rows=int(np.ceil(len(targs)/4))
    cols=int(np.ceil(len(targs)*2/rows))
    realcurvecols=['Compound']
    for col in curvecols:
        if col in curve.columns:
            curve.loc[:,col]=curve[col].astype(float)
            realcurvecols.append(col)
        # except:
        #     # warnings.warn(f'{col} column does not exist or was unable to be cast as float')
        #     pass

    fig, ax = plt.subplots(rows,cols,figsize=(cols*6,rows*10),sharey=sharey,gridspec_kw={'width_ratios': [5,1]*int(np.ceil(cols/2))})
    ax=ax.ravel()
    i=0
    minmin=0
    maxmax=0
    for targ in targs:
        # get curve data
        ppc=curve[(curve.Compound==compound) & (curve.Target==targ)].copy()
        note=ppc[['notes']]
        ppc=ppc[realcurvecols]
        if len(ppc)>1:
            ppc.IC50=ppc.IC50.astype(float)
            ppc=ppc.groupby('Compound').mean()
        if len(ppc)>0:
            for col in ['top','bottom','hill','pIC50',fitcrit,'IC50',]:
                ppc[col]=ppc[col].astype(float)
            if 'relation' in ppc.columns:
                ppc.relation=ppc.relation.replace(np.nan,'=')
            else:
                ppc['relation']='='
            if 'coff' in ppc.columns:
                coff = ppc.iloc[0].coff
            else:
                coff=None
        else:
            coff=None
        note=list(set(note.notes.dropna()))
        ppc['notes']='\n'.join(note)

        # get raw data
        ppr=raw[(raw.Compound==compound) & (raw.Target==targ)].copy()
        if len(ppr)==0:
            continue
        if 'RealPlate' in ppr.columns:
            plateNegCtrl=raw[(raw.RealPlate.isin(ppr.RealPlate.unique()))&(raw.Well_Type=='negativeControl')&(raw.Target==targ)]
        else:
            plateNegCtrl=raw[(raw.Plate.isin(ppr.Plate.unique()))&(raw.Well_Type=='negativeControl')&(raw.Target==targ)]
        if coff is None:
            vals=plateNegCtrl[graphcol].values
            coff=3*np.median(np.absolute(vals - np.median(vals))) # 3*BMAD

        # get axes bounds
        logconcmin=ppr.logconc.min()-0.5
        logconcmax=ppr.logconc.max()+0.5
        pctmin = min(ppr[graphcol].min(), plateNegCtrl[graphcol].min(), coff)
        pctmax = max(ppr[graphcol].max(), plateNegCtrl[graphcol].max(), coff)
        pctmin = pctmin-0.05*pctmax
        pctmax = pctmax+0.05*pctmax

        # create curve if it's a hit and curve fit succeeded
        if len(ppc)>0:
            if (ppc.hitc.iloc[0]==1) and ('succeeded' in ppc.notes.iloc[0]):
                # try:
                x = np.linspace(logconcmin, logconcmax, 1000)
                y = ppc.bottom.iloc[0] + ((ppc.top.iloc[0] - ppc.bottom.iloc[0])/(1+10**((-ppc.pIC50.iloc[0] - x)*float(ppc.hill.iloc[0]))))
                ax[i].plot(x,y,c=pal[-1])
                # except:
                #     pass
                pctmin=min(pctmin, ppc.bottom.iloc[0])
                pctmax=max(pctmax, ppc.top.iloc[0])
                pctmin = pctmin-0.05*pctmax
                pctmax = pctmax+0.05*pctmax

        # create scatterplot whether it's a hit or not
        if 'RealPlate' in ppr.columns:
            sns.scatterplot(data=ppr, x='logconc', y=graphcol, hue='RealPlate', ax=ax[i], legend=False)
        else:
            ax[i].scatter(x=ppr.logconc.values, y=ppr[graphcol].values, s=50)
        if not isinstance(compound,str):
            try:
                compound=int(compound)
            except:
                pass
        if 'solubility' in ppc.columns:
            ax[i].set_title(f"{compound} {targ} IC50{ppc.relation.iloc[0]}{round(ppc.IC50.iloc[0], 4)} uM\n{ppc.notes.iloc[0]}\nSoluble: {ppc['solubility'].iloc[0]}; \nHITC: {int(ppc.hitc.iloc[0])};{fitcrit}={round(ppc[fitcrit].iloc[0], 4)}")
        elif len(ppc)==0:
            ax[i].set_title(f"{compound} {targ}\n No Fits")
        else:
            ax[i].set_title(f"{compound} {targ} IC50{ppc.relation.iloc[0]}{round(ppc.IC50.iloc[0], 4)} uM\n{ppc.notes.iloc[0]}\nHITC: {int(ppc.hitc.iloc[0])}; {fitcrit}={round(ppc[fitcrit].iloc[0], 4)}")
        ax[i].set_xlabel('log(Concentration in M)')
        ax[i].set_xlim(logconcmin, logconcmax)
        ax[i].set_ylim(pctmin, pctmax)
        ax[i].hlines(coff, xmin=logconcmin, xmax=logconcmax, color=pal[-2], zorder=0)

        if 'RealPlate' in plateNegCtrl.columns:
            sns.histplot(y=graphcol, data=plateNegCtrl, bins=np.arange(pctmin,pctmax,((pctmax-pctmin)/22)), hue='RealPlate', ax=ax[i+1], legend=False)
        else:
            ax[i+1].hist(plateNegCtrl[graphcol], orientation='horizontal', range=(pctmin,pctmax), bins=22)
        ax[i+1].set_ylim(pctmin, pctmax)
        ax[i+1].set_xlabel('negCtrlDist')
        i+=2

        # if sharey is true, use maxmax
        if pctmin < minmin:
            minmin=pctmin
        if pctmax > maxmax:
            maxmax=pctmax

    # finish creating plot
    if sharey:
        ax[0].set_ylim(minmin, maxmax)
    ax[0].set_ylabel(graphcol)
    fig.patch.set_facecolor('white')
    fig.tight_layout()

    # save plot if save
    if save_fig:
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
        plt.savefig(os.path.join(save_dir, f'{compound}_{save_lab}_curvefit.png'))
        plt.close()

def plot_sc_data(compound, raw, curve, targs=['PARP1','PARP2'], save_fig=False, graphcol='intensity_inhibition', save_lab='HiTS', save_dir='./curve_fits'):
    ''' Plot and save IC50 curves on top of raw data for visualization across various targets.
    Raw data frame should at minimum have columns
    [Compound, Target, <response column graphcol>, logconc, Plate, Well_Type]. 'solubility' optional.
    Curve data frame should have columns
    [Compound, Target, 'bottom','top','LogIC50','hill','RMSE',<fitcrit col>,'IC50','pIC50','hitc','coff','notes']
    '''
    sns.set_context('poster')
    rows=int(np.ceil(len(targs)/4))
    cols=int(np.ceil(len(targs)*2/rows))
    fig, ax = plt.subplots(rows,cols,figsize=(cols*6,rows*10),sharey=True,gridspec_kw={'width_ratios': [5,1]*int(np.ceil(cols/2))})
    ax=ax.ravel()
    i=0

    # minmax=raw[raw.Compound==compound]
    # if graphcol!='fold_change':
    #     pctmin = minmax[graphcol].min()-5
    #     pctmax = minmax[graphcol].max()+5
    #     pctmin = min(pctmin, -105)
    #     pctmax = max(pctmax, 105)
    # else:
    #     pctmin = minmax[graphcol].min()-0.5
    #     pctmax = minmax[graphcol].max()+0.5

    for targ in targs:
        # get curve data
        ppc=curve[(curve.Compound==compound) & (curve.Target==targ)].copy()
        # ppc=ppc[['Compound', 'Target', 'bottom','top','LogIC50','hill','RMSE',<fitcrit col>,'IC50','pIC50','hitc','coff','notes'
        if len(ppc)>1:
            note=list(set(ppc.notes.tolist()))[0]
            ppc.IC50=ppc.IC50.astype(float)
            note=set(ppc.notes.tolist())
            ppc=ppc.groupby('Compound').mean()
            ppc['notes']=str(note)
        coff = ppc.coff.iloc[0]

        # get raw data
        ppr=raw[(raw.Compound==compound) & (raw.Target==targ)].copy()
        if len(ppr)==0:
            continue
        plateNegCtrl=raw[(raw.Plate.isin(ppr.Plate.unique()))&(raw.Well_Type=='negativeControl')&(raw.Target==targ)]

        # get axes bounds
        logconcmin=ppr.logconc.min()-0.5
        logconcmax=ppr.logconc.max()+0.5

        pctmin = min(ppr[graphcol].min(), plateNegCtrl[graphcol].min())
        pctmax = max(ppr[graphcol].max(), plateNegCtrl[graphcol].max())
        pctmin = pctmin-0.05*pctmax
        pctmax = pctmax+0.05*pctmax

        # create curve if it's a hit and curve fit succeeded
        if (ppc.hitc.iloc[0]==1) and ('succeeded' in ppc.notes.iloc[0]):
            try:
                x = np.linspace(logconcmin, logconcmax, 1000)
                y = ppc.bottom.iloc[0] + ((ppc.top.iloc[0] - ppc.bottom.iloc[0])/(1+10**((-ppc.pIC50.iloc[0] - x)*float(ppc.hill.iloc[0]))))
                ax[i].plot(x,y,c='cornflowerblue')
            except:
                pass

        # create scatterplot whether it's a hit or not
        ax[i].scatter(x=ppr.logconc.values, y=ppr[graphcol].values, s=50)
        if 'solubility' in ppc.columns:
            ax[i].set_title(f"{int(compound)} {targ}\nIC50={round(ppc.IC50.iloc[0], 4)} uM HITC: {int(ppc.hitc.iloc[0])}; {ppc.notes.iloc[0]}\nSoluble: {ppc['solubility'].iloc[0]}; RMSE={round(ppc['RMSE'].iloc[0], 4)}")
        else:
            ax[i].set_title(f"{int(compound)} {targ}\nIC50={round(ppc.IC50.iloc[0], 4)} uM HITC: {int(ppc.hitc.iloc[0])}; {ppc.notes.iloc[0]}\nRMSE={round(ppc['RMSE'].iloc[0], 4)}")
        ax[i].set_xlabel('log(Concentration in M)')
        ax[i].set_xlim(logconcmin, logconcmax)

        ax[i].hlines(coff, xmin=logconcmin, xmax=logconcmax, color='orange', zorder=0)
        ax[i+1].hist(plateNegCtrl[graphcol], orientation='horizontal', range=(pctmin,pctmax), bins=22)
        ax[i+1].set_xlabel('negCtrlDist')
        i+=2

    # finish creating plot
    ax[0].set_ylim(pctmin, pctmax)
    ax[0].set_ylabel(graphcol)
    fig.patch.set_facecolor('white')
    fig.tight_layout()

    # save plot if save
    if save_fig:
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
        plt.savefig(os.path.join(save_dir, f'{compound}_{save_lab}_curvefit.png'))
        plt.close()

###################################################################################################
# from stack overflow
def ModelAndScatterPlot(xData, yData, color='blue', ax=None, label=None, init=[100, 0, -5.5, 1],
                       bounds=([80,-20,-np.inf,-np.inf], [120, 20, np.inf,np.inf])):
    if ax is None:
        f = plt.figure(figsize=(5,5))
        ax = f.add_subplot(111)

    # first the raw data as a scatter plot
    ax.plot(xData, yData,  'D', color=colors[1])

    fittedParameters, pcov = curve_fit(hillfunc, xData, yData, init, bounds=bounds)

    # create data for the fitted equation plot
    xModel = np.linspace(min(xData), max(xData), num=100)
    yModel = hillfunc(xModel, *fittedParameters)

    # now the model as a line plot
    ax.plot(xModel, yModel, color=colors[1], label=label)

    # ax.set_xlabel('X Data') # X axis data label
    # ax.set_ylabel('Y Data') # Y axis data label
    # axes.set_ylim(-20,105)
    return ax

###################################################################################################

def hillfunc(x, Bottom, Top, LogIC50, HillSlope):
    return (Bottom + (Top-Bottom)/(1+10**((LogIC50-x)*HillSlope)))

def fit_scipy_curves(cmpds, raw, graphcol, targs=['PARP1','PARP2'], init=np.array([100, -20, -5.5, 1]),
                          bounds=([80,-20,-np.inf,-np.inf], [120, 20, np.inf,np.inf]), bcomm=''):
    ''' fit IC50 curves to raw concentration-response data. Raw data frame should at minimum have columns
    [Compound, Target, <response column graphcol>, logconc]
    '''
    allparams = {}

    for cmpd in cmpds:
        for i, targ in enumerate(targs):
            ppr=raw[(raw.Compound==cmpd) & (raw.Target==targ)].copy()
            xData=ppr.logconc
            yData = ppr[graphcol].values

            # these are the same as the scipy defaults
            # initialParameters = np.array([init[top], init[bottom], np.log10(float(init[ic50])/1000000), init[hill]])
            initialParameters = init

            # do not print unnecessary warnings during curve_fit()
            warnings.filterwarnings("ignore")

            try:
                # curve fit the test data
                fittedParameters, pcov = curve_fit(hillfunc, xData, yData, initialParameters, bounds=bounds)

                modelPredictions = hillfunc(xData, *fittedParameters)

                absError = modelPredictions - yData

                SE = np.square(absError) # squared errors
                MSE = np.mean(SE) # mean squared errors
                RMSE = np.sqrt(MSE) # Root Mean Squared Error, RMSE
                Rsquared = 1.0 - (np.var(absError) / np.var(yData))
                IC50 = 10**fittedParameters[2]*1000000
                pIC50= -np.log10(IC50/1000000)

                fittedParameters=list(fittedParameters)
                fittedParameters.extend([RMSE, Rsquared, IC50, pIC50])
                fittedParameters.append(f'Fit succeeded with SciPy{bcomm}')
                allparams[targ+"_"+str(cmpd)]=fittedParameters

            except:
                # print(f'{targ} {cmpd} fit failed')
                allparams[targ+"_"+str(cmpd)]=[np.nan]*8+[f'Fit FAILED with SciPy{bcomm}']

    df=pd.DataFrame.from_dict(allparams).T
    df.columns=['top','bottom','LogIC50','hill','RMSE','R^2','IC50','pIC50','notes']
    # df.columns=['SciPy_'+col for col in df.columns]
    df.hill=df.hill*-1
    df=df.reset_index()
    df=df.rename(columns={'index':'Compound'})
    df[['Target','Compound']]=df.Compound.str.split('_', expand=True)
    # df.Compound=df.Compound.astype(int)
    df['relation']=np.nan
    df['hitc']=[1 if 'succeeded' in x else 0 for x in df.notes]

    return df

###################################################################################################

def tcpl_mc3_to_raw(mc3, graphcol='percent_inhibition', fix_raw=False):
    """Prep TCPL output to be used in plotting functions.

    Args:
        mc3: tcpl MC3 df to be prepped

    Returns:
        raw: modified df
    """
    raw=mc3.copy()
    raw.columns=['Compound', 'chid', 'casn', 'chnm', 'dsstox_substance_id', 'code', 'aeid',
       'Target', 'm0id', 'm1id', 'm2id', 'm3id', 'logc', graphcol, 'cndx', 'Well_Type',
       'Plate', 'rowi', 'coli', 'repi', 'resp_unit', 'conc_unit']
    raw['Conc_uM']=10**raw.logc
    raw['logconc']=np.log10(raw.Conc_uM/1000000)
    raw.Well_Type=raw.Well_Type.str.replace('n','negativeControl')
    nconcdict={}
    for plate in raw.Plate.unique():
        nconcdict[plate]=raw[raw.Plate==plate].cndx.max()
    raw['nconc']=raw.Plate.map(nconcdict)
    nplatedict={}
    for compound in raw.Compound.unique():
        nplatedict[compound]=raw[raw.Compound==compound].Plate.nunique()
    raw['nplate']=raw.Compound.map(nplatedict)
    raw['RealPlate']=raw.Plate
    raw.Plate = raw.Target
    if fix_raw:
        for targ in raw.Target.unique():
            for compound in raw.Compound.unique():
                if compound in [207,210]:
                    continue
            # print(compound, raw[raw.Compound==compound].nconc.unique().tolist())
                if (raw[(raw.Compound==compound)&(raw.Target==targ)].nconc.unique().tolist()==[3,9]):
                    raw=raw.drop(raw[(raw.Compound==compound)&(raw.Target==targ)&(raw.nconc==3)].index)
    return raw

def tcpl_mc5_to_curve(mc5):
    """Prep TCPL output to be used in plotting functions.

    Args:
        mc5: tcpl MC5 df to be prepped

    Returns:
        curve: modified df
    """
    curve=mc5.copy()
    curve['AC50_uM']=10**curve.modl_ga
    curve['pIC50']=-np.log10(curve.AC50_uM/1000000)
    curve=curve[['m5id', 'flag', 'spid', 'chid', 'casn', 'aenm', 'm4id', 'bmad', 'resp_max', 'resp_min',
       'max_mean', 'max_mean_conc', 'max_med', 'max_med_conc', 'logc_max',
       'logc_min', 'nconc', 'npts', 'nrep', 'nmed_gtbl', 'hitc', 'modl', 'fitc', 'coff', 'actp', 'modl_er',
       'modl_tp', 'modl_ga', 'modl_gw', 'modl_la', 'modl_lw', 'modl_rmse',
       'modl_prob', 'modl_acc', 'modl_acb', 'modl_ac10', 'resp_unit',
       'conc_unit', 'AC50_uM', 'pIC50']]
    curve.columns=['m5id', 'flag', 'Compound', 'chid', 'casn', 'Target', 'm4id', 'bmad', 'resp_max', 'resp_min',
           'max_mean', 'max_mean_conc', 'max_med', 'max_med_conc', 'logc_max',
           'logc_min', 'nconc', 'npts', 'nrep', 'nmed_gtbl', 'hitc', 'modl', 'fitc', 'coff', 'actp', 'modl_er',
           'top', 'modl_ga', 'hill', 'modl_la', 'modl_lw', 'RMSE',
           'modl_prob', 'modl_acc', 'modl_acb', 'modl_ac10', 'resp_unit',
           'conc_unit', 'IC50', 'pIC50']
    curve['bottom']=0
    curve['notes']=['TCPL fit succeeded' if x==1 else np.nan for x in curve.hitc]
    return curve


from utils import WebInput
from more_itertools import windowed
# from collections import Counter

class DayInput(WebInput):
    def __init__(self, day=14, **kwargs):
        super().__init__(day=day, **kwargs)
        self.polymer = dict()
        self.mapping = dict()
        self.count = dict()
        elts = set()
        for i, row in enumerate(self.get_content()):
            if i == 0:
                raw_polymer = row
            else:
                pair, elt = row.split(" -> ")
                self.mapping[pair] = {
                    "pairs": [
                        "".join([pair[0], elt]),
                        "".join([elt, pair[1]]),
                    ],
                    "elt": elt,
                }
                elts.add(elt)
        self.count = {e: 0 for e in elts}
        self.polymer = {k: 0 for k in self.mapping.keys()}
        for window in windowed(raw_polymer, 2):
            pair = "".join(window)
            self.polymer[pair] += 1
        for elt in raw_polymer:
            self.count[elt] += 1

    def loop(self):
        old_polymer = {k: v for k, v in self.polymer.items() if v > 0}
        self.polymer = {k: 0 for k in self.mapping.keys()}
        for pair, count in old_polymer.items():
            for new_pair in self.mapping[pair]["pairs"]:
                self.polymer[new_pair] += count
            elt = self.mapping[pair]["elt"]
            self.count[elt] += count
        if self.test:
            self.print()

    def print(self):
        print(self.count, sum(self.count.values()))

    def total(self):
        cnt = list(self.count.values())
        cnt.sort()
        return cnt[-1] - cnt[0]

    def part_one(self):
        self.print()
        for i in range(10):
            self.loop()
        print(f"Part one: {self.total()}")

    def part_two(self):
        self.print()
        for i in range(40):
            self.loop()
        print(f"Part two: {self.total()}")

test = False
DayInput(test=test).part_one()
DayInput(test=test).part_two()