import plum
from plum.types import Variable
import torch
import numpy as np

class GreedySearch:

    def __init__(self, vocab, max_steps=999):
        self.vocab = vocab
        self.max_steps = max_steps
        self.reset()

    def reset(self):
        self.is_finished = False
        self.steps = 0
        self._states = []
        self._outputs = []

    def init_state(self, batch_size, device, source_indices,
                   encoder_state=None):

        output = Variable(
            torch.LongTensor([self.vocab.start_index] * batch_size)\
                .view(1, -1),
            lengths=torch.LongTensor([1] * batch_size),
            length_dim=0, batch_dim=1, pad_value=self.vocab.pad_index)

        if str(device) != "cpu":
            output = output.cuda(device)

        mask = source_indices.mask.t()

        return {"target": output, "decoder_state": encoder_state,
                "inputs": output, 'pointer_mask': mask}

    def next_state(self, decoder, prev_state, context, active_items,
                   controls):

        # Get next state from the decoder.
        next_state = decoder.next_state(prev_state, context, controls=controls)

        next_pointer = next_state['log_probs']\
            .permute_as_sequence_batch_features().max(2)[1]\

        target = context['source_indices'].data.t().gather(
            1, next_pointer.data.t())
        target = prev_state['target'].new_with_meta(target.t())
        next_state['target'] = target

        pm = prev_state['pointer_mask']
        pm.scatter_(1, next_pointer.data.t(), 1)
        next_state['pointer_mask'] = pm

        # Mask outputs if we have already completed that batch item.
        next_state["target"].data.view(-1).masked_fill_(
            ~active_items, int(self.vocab.pad_index))

        next_inputs = torch.cat(
            [
                prev_state['inputs'].data,
                next_state['target'].data,
            ],
            0)
        next_inputs = Variable(
            next_inputs,
            prev_state['inputs'].lengths + 1,
            length_dim=0, batch_dim=1,
            pad_value=prev_state['inputs'].pad_value)
        next_state['inputs'] = next_inputs
#
        return next_state

    def check_termination(self, next_state, active_items):

        # Check for stop tokens and batch item inactive if so.
        nonstop_tokens = next_state["target"].data.view(-1).ne(
            int(self.vocab.stop_index))
        active_items = active_items.data.mul_(nonstop_tokens)

        return active_items

    def _collect_search_states(self, active_items):
        # TODO implement search states api.
        #search_state = self._states[0]
        #for next_state in self._states[1:]:
        #    search_state.append(next_state)
        #self._states = search_state
        self._outputs = torch.cat([o.data for o in self._outputs], dim=0)

    def __call__(self, decoder, encoder_state, controls=None):
        self.reset()
        batch_size = encoder_state["batch_size"]
        device = encoder_state["device"]

        search_state = self.init_state(batch_size, device,
                                       encoder_state['source_indices'],
                                       encoder_state.get("state", None))

        context = {
            "encoder_output": encoder_state["output"],
            "source_indices": encoder_state['source_indices'],
        }
        active_items = torch.BoolTensor(batch_size).fill_(1)
        output_length = torch.LongTensor(batch_size).fill_(0)
        if str(encoder_state["device"]) != "cpu":
            active_items = active_items.cuda(encoder_state["device"])
            output_length = output_length.cuda(encoder_state["device"])

        prev_decoder_inputs = search_state["target"]
        step_masks = []
        # Perform search until we either trigger a termination condition for
        # each batch item or we reach the maximum number of search steps.
        while self.steps < self.max_steps and not self.is_finished:

            inactive_items = ~active_items

            # Mask any inputs that are finished, so that greedy would
            # be identitcal to forward passes.
            search_state["target"].data.view(-1).masked_fill_(
                inactive_items, int(self.vocab.pad_index))

            step_masks.append(inactive_items)
            self.steps += 1
            search_state = self.next_state(
                decoder, search_state, context, active_items, controls)

            self._states.append(search_state)
            self._outputs.append(search_state["target"].clone()\
                .permute_as_sequence_batch_features())
            output_length = output_length + active_items.long()
            temp_outputs = torch.cat(
                    [prev_decoder_inputs.tensor] + \
                            [x.tensor for x in self._outputs], 0)
            temp_outputs = Variable(temp_outputs, lengths=output_length + 1,
                                    batch_dim=1, length_dim=0,
                                    pad_value=self.vocab.pad_index)
            search_state["prev_output"] = temp_outputs

            active_items = self.check_termination(search_state, active_items)
            self.is_finished = torch.all(~active_items)

        # Finish the search by collecting final sequences, and other
        # stats.
        self._collect_search_states(active_items)
        self._incomplete_items = active_items
        self._is_finished = True

        self._mask_T = torch.stack(step_masks)
        self._mask = self._mask_T.t().contiguous()
        return self

    def __getitem__(self, key):
        if key == "target":
            return self._outputs

    def output(self, as_indices=False):

        if as_indices:
            lengths = (~self._mask_T).sum(0)
            return Variable(self._outputs, lengths=lengths, length_dim=0,
                            batch_dim=1, pad_value=self.vocab.pad_index)\
                            .apply_sequence_mask()

        tokens = []
        for output in self._outputs.t():
            tokens_i = []
            for index in output:
                if index in [self.vocab.pad_index, self.vocab.stop_index]:
                    break
                tokens_i.append(self.vocab.token(index))
            tokens.append(tokens_i)

        return tokens

    def state_dict(self):
        skip_keys = ["_parameter_names", "_parameter_tags", "_submodule_names",
                     "_submodule_tags", "_backend", "_parameters", "_buffers",
                     "_backward_hooks", "_forward_hooks", "_forward_pre_hooks",
                     "_state_dict_hooks", "_load_state_dict_pre_hooks",
                     "_modules", "_hyperparameters", "_hyperparameter_tags",
                     "_name2hyperparameter"]

        hyperparameters = {}
        for name, hp in self._name2hyperparameter.items():
            hyperparameters[name] = _state_dict_helper(
                self._hyperparameters[hp])

        modules = {}
        parameters = {}
        if hasattr(self, "_modules"):
            submod_names = set(self._submodule_names.values())
            for name, module in self._modules.items():
                if name in submod_names:
                    hyperparameters[name] = _state_dict_helper(module)
                else:
                    modules[name] = _state_dict_helper(module)
                    for n, p in module.named_parameters():
                        parameters[tuple([name]) + tuple(n.split("."))] = p
        if hasattr(self, "_parameters"):
            for k, p in self._parameters.items():
                parameters[k] = p
        return {
            "__plum_type__": self.plum_id,
            "attributes": {},
            "hyperparameters": hyperparameters,
            "modules": modules,
            "parameters": parameters,
        }

def _state_dict_helper(obj):
    if isinstance(obj, (list, tuple)):
        return [_state_dict_helper(x) for x in obj]
    elif isinstance(obj, dict):
        return {k: _state_dict_helper(v) for k, v in obj.items()}
    elif hasattr(obj, "state_dict"):
        return obj.state_dict()
    else:
        return obj

class BeamSearch:
    def __init__(self, vocab, beam_size=8, max_steps=999):
        self.vocab = vocab
        self.beam_size = beam_size
        self.max_steps = max_steps
        self.reset()

    def reset(self):
        self.is_finished = False
        self.steps = 0
        self._states = []
        self._outputs = []

    def init_state(self, batch_size, device, source_indices, encoder_state):

        beam_state = {}

        if encoder_state:
            n, bs, ds = encoder_state.size()
            assert batch_size == bs
            beam_state["decoder_state"] = encoder_state.unsqueeze(2)\
                .repeat(1, 1, self.beam_size, 1)\
                .view(n, batch_size * self.beam_size, ds)

        beam_state["target"] = Variable(
            torch.LongTensor(
                [self.vocab.start_index] * batch_size * self.beam_size)\
                .view(1, -1),
            lengths=torch.LongTensor([1] * batch_size * self.beam_size),
            length_dim=0, batch_dim=1, pad_value=self.vocab.pad_index)

        beam_state["target"] = beam_state["target"].to(device)
        beam_state['inputs'] = beam_state['target']

        source_indices = source_indices.permute_as_batch_sequence_features()
        mask = source_indices.mask.unsqueeze(1).repeat(1, self.beam_size, 1)\
            .contiguous().view(-1, source_indices.size(1))
        beam_state['pointer_mask'] = mask

        # Start the first beam of each batch with 0 log prob, and all others
        # with -inf.
        beam_state["accum_log_prob"] = (
            self._init_accum_log_probs(batch_size, device)
        )

        # At the first time step no sequences have been terminated so this mask
        # is all 0s.
        beam_state["terminal_mask"] = (
            torch.BoolTensor(
                1, batch_size * self.beam_size, 1).fill_(0).to(device)
        )

        return beam_state

    def _init_accum_log_probs(self, batch_size, device):
        lp = torch.FloatTensor(1, batch_size, self.beam_size, 1)
        if "cuda" in str(device):
            lp = lp.cuda(device)
        lp.data.fill_(0)
        lp.data[:,:,1:].fill_(float("-inf"))
        return lp.view(1, batch_size * self.beam_size, 1)

    def init_context(self, encoder_state):
        n, bs, ds = encoder_state["output"].size()
        beam_encoder_output = encoder_state["output"].repeat_batch_dim(
            self.beam_size)
            #.repeat(1, 1, self.beam_size, 1)\
            #.view(n, bs * self.beam_size, ds)

        source_indices = encoder_state['source_indices']\
            .permute_as_batch_sequence_features()
        source_indices = source_indices.data.unsqueeze(1)\
            .repeat(1, self.beam_size, 1).view(-1, source_indices.size(1))

        return {"encoder_output": beam_encoder_output,
                'source_indices': source_indices}

    def __call__(self, decoder, encoder_state, controls=None):
        self.reset()

        batch_size = encoder_state["batch_size"]
        device = encoder_state['device']
        search_state = self.init_state(
            batch_size,
            device,
            encoder_state['source_indices'],
            encoder_state.get("state", None))

        search_context = self.init_context(encoder_state)
        active_items = torch.BoolTensor(batch_size).fill_(1).to(device)

        if controls is not None:
            controls = controls.repeat_batch_dim(self.beam_size)

        self._beam_scores = [list() for _ in range(batch_size)]
        self._num_complete = torch.LongTensor(batch_size).fill_(0).to(device)
        self._terminal_info = [list() for _ in range(batch_size)]

        # Perform search until we either trigger a termination condition for
        # each batch item or we reach the maximum number of search steps.
        while self.steps < self.max_steps and not self.is_finished:

            search_state = self.next_state(
                decoder, batch_size, search_state, search_context,
                active_items, controls)
            active_items = self.check_termination(search_state, active_items)
            self._is_finished = torch.all(~active_items)

            self._states.append(search_state)
            self.steps += 1

        # Finish the search by collecting final sequences, and other
        # stats.
        self._collect_search_states(active_items)
        self._incomplete_items = active_items
        self._is_finished = True

        return self

    def next_state(self, decoder, batch_size, prev_state, context,
                   active_items, controls):

        # Get next state from the decoder.
        next_state = decoder.next_state(prev_state, context, controls=controls)

        # Compute the top beam_size next outputs for each beam item.
        # topk_lps (1 x batch size x beam size x beam size)
        # candidate_outputs (1 x batch size x beam size x beam size)
        topk_lps, candidate_outputs = torch.topk(
            next_state["log_probs"].data \
                .view(1, batch_size, self.beam_size, -1),
            k=self.beam_size, dim=3)

        # If any sequence was completed last step, we should mask it's log
        # prob so that we don't generate from the terminal token.
        # slp (1 x batch_size x beam size x 1)
        slp = prev_state["accum_log_prob"] \
            .masked_fill(prev_state["terminal_mask"], float("-inf")) \
            .view(1, batch_size, self.beam_size, 1)

        # Combine next step log probs with the previous sequences cumulative
        # log probs, i.e.
        #     log P(y_t) = log P(y_<t) + log P(y_t)
        # candidate_log_probs (1 x batch size x beam size x beam size)
        candidate_log_probs = slp + topk_lps

        # Rerank and select the beam_size best options from the available
        # beam_size ** 2 candidates.
        # b_seq_lps (1 x (batch size * beam size) x 1)
        # b_scores (1 x (batch size * beam size) x 1)
        # b_pointer (1 x (batch size * beam size))
        # b_indices ((batch size * beam size))
        b_seq_lps, b_scores, b_pointer, b_indices = self._next_candidates(
            batch_size, candidate_log_probs, candidate_outputs)

        b_pointer_mask = (
            (b_pointer.data >= context['source_indices'].size(1))
            |
            (b_pointer.data < 0)
        )

        b_pointer = b_pointer.masked_fill(b_pointer_mask, 0)
#        print(context['source_indices'])
#        print(b_pointer)
        b_output = context['source_indices'].gather(1, b_pointer.data.t())
        b_output = b_pointer.new_with_meta(b_output.t())
#        print(b_output)

#        print(b_indices)
        pm = prev_state['pointer_mask']
#        print(pm.size())
#        print(pm.long())
        pm = pm.index_select(0, b_indices)
#        print(pm.long())
        pm.scatter_(1, b_pointer.data.t(), 1)

#        print(pm.long())
#        print(pm.size())

        # TODO re-implement this behavior
        #next_state.stage_indexing("batch", b_indices)

#        print("BEAM INDICES")
#        print(b_indices)
#        print(prev_state['inputs'])
#        print(prev_state['inputs'].index_select(1, b_indices))
#        print(type(prev_state['inputs'].index_select(1, b_indices)))
#        print(b_output)
#        print(type(b_output))
        next_inputs = torch.cat(
            [
                prev_state['inputs'].index_select(1, b_indices).data,
                b_output.data,
            ],
            0)
        next_inputs = Variable(
            next_inputs,
            prev_state['inputs'].lengths + 1,
            length_dim=0, batch_dim=1,
            pad_value=prev_state['inputs'].pad_value)
#        print(next_inputs)

        next_state = {
            "decoder_state": next_state["decoder_state"]\
                .index_select(1, b_indices),
            "target": b_output,
            "accum_log_prob": b_seq_lps,
            "beam_score": b_scores,
            "beam_indices": b_indices,
            "inputs": next_inputs,
            'pointer_mask': pm,
        }
        return next_state
        #exit()
        #next_state = {"decoder_state": next_state["decoder_state"]
        #print(next_state.keys())

        next_state["output"] = (b_output, ("batch", "sequence"))
        next_state["cumulative_log_probability"] = (
            b_seq_lps, ("sequence", "batch", "placeholder")
        )
        next_state["beam_score"] = (
            b_scores, ("sequence", "batch", "placeholder")
        )
        next_state["beam_indices"] = (b_indices, ("batch"))

        return next_state

    def _next_candidates(self, batch_size, log_probs, candidates):
        # TODO seq_lps should really be called cumulative log probs.

        # flat_beam_lps (batch size x (beam size ** 2))
        flat_beam_lps = log_probs.view(batch_size, -1)

        flat_beam_scores = flat_beam_lps / (self.steps + 1)

        # beam_seq_scores (batch size x beam size)
        # relative_indices (batch_size x beam size)
        beam_seq_scores, relative_indices = torch.topk(
            flat_beam_scores, k=self.beam_size, dim=1)
        relative_indices_mask = (
            (relative_indices >= flat_beam_scores.size(1))
            |
            (relative_indices < 0)
        )
        relative_indices = relative_indices.masked_fill(
            relative_indices_mask, 0)

        # beam_seq_lps (batch size x beam size)
        beam_seq_lps = flat_beam_lps.gather(1, relative_indices)

        # TODO make these ahead of time.
        offset1 = (
            torch.arange(batch_size, device=beam_seq_lps.device)
                * self.beam_size
        ).view(batch_size, 1)

        offset2 = offset1 * self.beam_size

        beam_indexing = ((relative_indices // self.beam_size) + offset1) \
            .view(-1)

        # beam_seq_lps (1 x (batch_size * beam_size) x 1)
        beam_seq_lps = beam_seq_lps \
            .view(1, batch_size * self.beam_size, 1)

        # beam_seq_scores (1 x (batch_size * beam_size) x 1)
        beam_seq_scores = beam_seq_scores \
            .view(1, batch_size * self.beam_size, 1)

        # next_output (1 x (batch size * beam size))
        next_candidate_indices = (relative_indices + offset2).view(-1)
        next_output = Variable(
            candidates.view(-1)[next_candidate_indices].view(1, -1),
            lengths=candidates.new().long().new(batch_size * self.beam_size)\
                .fill_(1),
            length_dim=0, batch_dim=1, pad_value=self.vocab.pad_index)

        return beam_seq_lps, beam_seq_scores, next_output, beam_indexing

    def check_termination(self, next_state, active_items):

        # view as batch size x beam size
        next_output = next_state["target"].data \
            .view(-1, self.beam_size)
        batch_size = next_output.size(0)

        is_complete = next_output.eq(int(self.vocab.stop_index))
        complete_indices = np.where(is_complete.cpu().data.numpy())

        for batch, beam in zip(*complete_indices):
            if self._num_complete[batch] == self.beam_size:
                continue
            else:
                self._num_complete[batch] += 1

                # Store step and beam that finished so we can retrace it
                # later and recover arbitrary search state item.
                self._terminal_info[batch].append(
                    (self.steps, beam + batch * self.beam_size))

                IDX = batch * self.beam_size + beam
                self._beam_scores[batch].append(
                    next_state["beam_score"][0, IDX, 0].view(1))

        next_state["terminal_mask"] = (
            is_complete.view(1, batch_size * self.beam_size, 1)
        )
        active_items = self._num_complete < self.beam_size

        return active_items

    def _collect_search_states(self, active_items):

        batch_size = active_items.size(0)

        last_state = self._states[-1]
        last_step = self.steps - 1
        for batch in range(batch_size):
            beam = 0
            while len(self._beam_scores[batch]) < self.beam_size:
                IDX = batch * self.beam_size + beam
                self._beam_scores[batch].append(
                    last_state["beam_score"][0, IDX, 0].view(1))
                self._terminal_info[batch].append(
                    (last_step, beam + batch * self.beam_size))
                beam += 1

        # TODO consider removing beam indices from state
        beam_indices = torch.stack([state["beam_indices"]
                                    for state in self._states])

        self._beam_scores = torch.stack([torch.cat(bs)
                                         for bs in self._beam_scores])

        lengths = self._states[0]["target"].new(
            [[step + 1 for step, beam in self._terminal_info[batch]]
             for batch in range(batch_size)])

        selector = self._states[0]["target"].new(
            batch_size, self.beam_size, lengths.max())
        mask = selector.new().bool().new(selector.size()).fill_(1)

        for batch in range(batch_size):
            for beam in range(self.beam_size):
                step, real_beam = self._terminal_info[batch][beam]
                mask[batch, beam,:step + 1].fill_(0)
                self._collect_beam(batch, real_beam, step,
                                   beam_indices,
                                   selector[batch, beam])
        selector = selector.view(batch_size * self.beam_size, -1)

        ## RESORTING HERE ##
        #if self.sort_by_score:
        # TODO make this an option again
        if True:
            self._beam_scores, I = torch.sort(self._beam_scores, dim=1,
                                              descending=True)
            offset1 = (
                torch.arange(batch_size, device=I.device) * self.beam_size
            ).view(batch_size, 1)
            II = I + offset1
            selector = selector[II.view(-1)]
            mask = mask.view(batch_size * self.beam_size,-1)[II]\
                .view(batch_size, self.beam_size, -1)
            lengths = lengths.gather(1, I)
        ##

        # TODO reimplement staged indexing
#        for step, sel_step in enumerate(selector.split(1, dim=1)):
#            self._states[step].stage_indexing("batch", sel_step.view(-1))

        self._output = []
        for step, sel_step in enumerate(selector.split(1, dim=1)):
            self._output.append(
                self._states[step]["target"].index_select(1, sel_step.view(-1))
            )
        #print(self._states[0]["output"].size())
        self._output = plum.cat([o.data for o in self._output], 0).t()\
            .view(batch_size, self.beam_size, -1)

        for i in range(batch_size):
            for j in range(self.beam_size):
                self._output[i,j,lengths[i,j]:].fill_(
                    int(self.vocab.pad_index))

        self._lengths = lengths

        return self

#        for batch_out in self._output:
#        #print(self._output.t().view(batch_size)
#        #for batch_out in self._output.t().view(batch_size, self.beam_size, -1):
#            for row in batch_out:
#                print(" ".join([self.vocab[t] for t in row if t != self.vocab.pad_index]))
#            print()
#        print(lengths)
#        print(lengths.size())
#        print(batch_size)
#        exit()
#
#        states = self._states[0]
#        for state in self._states[1:]:
#            states.append(state)
#
#        self._states = states
#        self._selector = selector
#        self._lengths = lengths
#        self._selector_mask = mask.view(self.batch_size * self.beam_size, -1)
#        self._selector_mask_T = self._selector_mask.t().contiguous()

    def _collect_beam(self, batch, beam, step, beam_indices,
                      selector_out):
        selection = [0] * beam_indices.size(0)
        selector_out[step + 1:].fill_(0)
        while step >= 0:
            selection[step] = beam
            selector_out[step].fill_(beam)
            next_beam = beam_indices[step, beam].item()
            beam = next_beam
            step -= 1

    def output(self, as_indices=False, n_best=-1):
        if n_best < 1:
            o = self._output[:,0]
            if as_indices:
                olen = (self._lengths[:,0])
                return Variable(o, lengths=olen, batch_dim=0, length_dim=1,
                                pad_value=int(self.vocab.pad_index))
            tokens = []
            for row in o:
                tokens.append([self.vocab.token(t) for t in row
                               if t != self.vocab.pad_index])
            return tokens

        elif n_best < self.beam_size:
            o = self._output[:,:n_best]
        else:
            o = self._output

        if as_indices:
            print(o)
            print(self._lengths)
            return o

        beams = []
        for beam in o:
            tokens = []
            for row in beam:
                tokens.append([self.vocab.token(t) for t in row
                               if t != self.vocab.pad_index])
            beams.append(tokens)
        return beams


from ast import literal_eval
from pathlib import Path

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.metrics.pairwise import cosine_similarity

from config.default import RUTA_SAVE

def load_csv_pokemon():
    lista = ['index', 'pokedex_id', 'name', 'hp', 'attack', 'defense', 'special', 'speed', 'tipos']
    df = pd.read_csv('pokemon_csv6.csv', sep=';', names=lista, engine='python')

    # La columna tipos se lee como un string "[]", para que se una lista hacemos el apply
    df.tipos = df.tipos.apply(literal_eval)
    return df

class Recomendador:
    def __init__(self, dataframe):
        self.dataframe = dataframe.sort_values(by=['pokedex_id']).reset_index(drop=True)
        self.df_cosine = self.load()

    def create_dummies_tipos(self):
        '''
        Create dummies de los tipos de los pokemons
        :param dataframe:
        :return:
        '''
        df_tipos = self.dataframe[['pokedex_id', 'tipos']]
        dummies_df = pd.get_dummies(
            df_tipos.join(pd.Series(df_tipos['tipos'].apply(pd.Series).stack().reset_index(1, drop=True),
                                    name='tipos1')).drop('tipos', axis=1).rename(columns={'tipos1': 'tipos'}),
            columns=['tipos']).groupby('pokedex_id', as_index=False).sum()
        return dummies_df

    def scale(self):
        scaler = StandardScaler()
        scaled_df = scaler.fit_transform(
            self.dataframe.drop(columns=['index', 'pokedex_id', 'name', 'tipos']))
        scaled_df = pd.DataFrame(scaled_df, columns=['hp', 'attack', 'defense', 'special', 'speed'])
        return scaled_df

    def join_dataframes(self):
        new_poke_df = pd.concat([self.create_dummies_tipos(), self.scale()], axis=1)
        return new_poke_df.drop(columns=['pokedex_id'])

    def cosine_similarity(self):
        cos_sim = cosine_similarity(self.join_dataframes().values, self.join_dataframes().values)
        self.save(cos_sim)
        return cos_sim

    @staticmethod
    def load():
        my_file = Path(RUTA_SAVE + 'cosine_similarity.csv')
        if my_file.exists():
            return pd.read_csv(RUTA_SAVE + 'cosine_similarity.csv')

    @staticmethod
    def save(matrix):
        matrix.to_csv(RUTA_SAVE + 'cosine_similarity.csv')

    def get_index_pokemon(self, pokedex_id):
        poke_index = pd.Series(self.dataframe.index, index=self.dataframe['pokedex_id'])
        return poke_index[pokedex_id]

    def recommend(self, pokedex_id, recommendations=5):
        index = self.get_index_pokemon(pokedex_id)
        if not self.df_cosine:
            self.df_cosine = self.cosine_similarity()
        similarity_score = list(enumerate(self.df_cosine[index]))
        sorted_score = sorted(similarity_score, key=lambda x: x[1], reverse=True)
        similar_pokemon = sorted_score[1:recommendations + 1]
        poke_indices = [i[0] for i in similar_pokemon]
        return self.dataframe.iloc[poke_indices].to_dict(orient="records")


from database import ActiveDatabase
from MatchClass import Match
from UserClass import User
from SensitiveConfig import *
from config import *
import requests
import time
import logging

logger = logging.getLogger(__name__)

def initLogger():
    logger.setLevel(logging.INFO)

    #Creating file handler
    handler = logging.FileHandler('{}/MatchFinder_{}.log'.format(LOG_DIRECTORY,time.strftime('%m%d_%H%M%S')))
    handler.setLevel(logging.INFO)

    #Creating logging format
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)

    #add handlers to the logger
    logger.addHandler(handler)

def fetchActiveUsers():
    ''' fetches all active users
        Args: None

        Return:
            userList = dictionary of active users
            success = boolean on whether it grabbed users
    '''

    logger.info("Fetching active users")

    #accessing active database
    dbConnection = ActiveDatabase().getConn()
    userConnection= dbConnection.collection(USERS)
    try:
        userList = userConnection.stream()
        logger.info("Query was successful.")
        success = True
    except Exception as e:
        logger.warning("Exception was thrown!!! --- {}".format(e))
        userList = None
        success = False

    logger.info("Returning -- Success: {}".format(success))
    logger.info("          -- UserList: {}".format(userList))
    return userList,success

def RateLimitFromHeader(limitCount):
    ''' Detects if script gets close to rate limit for api, then slows it down
        Args:
            limitCount = response.header["X-App-Rate-Limit-Count"]
        Return:
            None
    '''
    list = limitCount.split(',')
    current,limit = list[1].split(':',1)
    ratio = float(current)/float(limit)
    sleepTime = 0

    logger.info("Checking rate limit. current: {} limit: {} ratio: {}".format(current,limit,ratio))

    #Will need to be changed with producer key. Built for 120 rate limit every 2 minutes
    if ratio == .75:
        sleepTime = 2

    #Keeping this open for change, more distict sleep times may be added
    if sleepTime:
        time.sleep(sleepTime)

def fetchMatchEntries(user):
    ''' fetches n number of matches from RiotGames api
    Args: dictionary
        region: "NA1"
        accountId: "6UoWroRfGgxqnI0Oj-KahipCC2TcQWiKkAar-BWyYGoxAw"
        summonerId: "afternoonview'
    Returns:

    '''
    logger.info("Fetching match entries for user: {}".format(user["summonerId"]))

    try:
        url = RIOT_FETCH_MATCHES_URL.format(user["region"], user["accountId"], RIOT_API_KEY, MAX_MATCH_FETCH)
        logger.info("GET method: {}".format(url))
        response = requests.get(url)
        if response.status_code == 200:

            if "X-App-Rate-Limit-Count" in response.headers:
                RateLimitFromHeader(response.headers["X-App-Rate-Limit-Count"])
            matchList = response.json()['matches']
            success = True
        else:
            raise Exception("Invalid response code: {}".format(response.status_code))
    except Exception as e:
        logger.warning("Exception was thrown!!! --- {}".format(e))
        matchList = None
        success = False

    logger.info("Returning -- Success: {}".format(success))
    logger.info("          -- matchList: {}".format(matchList))
    return matchList, success

def fetchUsersInMatch(region,matchId):
    '''Attempts to retrieve match details
    Args:   region = NA1
            matchId = 38853692

    Returns: Dictionary of match details

    '''

    logger.info("Fetching summoners from match: {}-{}".format(region,matchId))
    url = RIOT_FETCH_MATCH_DETAILS_URL.format(region,matchId,RIOT_API_KEY)
    logger.info("GET Method: {}".format(url))

    response = requests.get(url)
    if "X-App-Rate-Limit-Count" in response.headers:
        RateLimitFromHeader(response.headers["X-App-Rate-Limit-Count"])
    users = response.json()['participantIdentities']

    logger.info("Returns -- users: {}".format(str(users)))
    return users

def insertSingleMatch(match,account_id):
    '''Attempts to insert a match
    Args: dictionary of a Match class

    Returns: a response message
    '''
    logger.info("Inserting single match into db")
    try:
        dbConnection = ActiveDatabase()
        matchConnection = dbConnection.getConn().collection(MATCHES)
        inMatchConnection = dbConnection.getConn().collection(IN_MATCH)
        logger.info("Checking if match exists")
        doesExist = inMatchConnection.where(u'match_id',u'==',match['matchId']).where(u'personal_summoner_id',u'==',account_id).stream()
        for doc in doesExist:
            logger.info("Match already exists")
            logger.info(doc.to_dict())

            logger.info("Returns -- Exists")
            return 'Exists'
        else:
            matchConnection.document(match['matchId']).set(match)
            logger.info("Match not found!!!")
            logger.info("Attempting to insert match")

            logger.info("Returns -- Success")
            return 'Success'
    except Exception as e:
        logger.warning("An error has occured: {}".format(e))

        logger.info("Returns -- Error")
        return 'Error'

def insertParticipant(participant,matchId,userAccountId):
    ''' Inserts a document into the IN_MATCH database
        Args:
            participant = the participant's accountId
            matchId = current matchId
            userAccountId = the active user's personal accountId

    '''
    logger.info("Inserting participant: participant: {} matchId: {} userAccountId {}".format(participant,matchId,userAccountId))
    try:
        insertDict = dict()
        insertDict['match_id'] = matchId
        insertDict['summoner_id'] = participant
        insertDict['personal_summoner_id'] = userAccountId
        dbConnection = ActiveDatabase()
        inMatchConnection = dbConnection.getConn().collection(IN_MATCH)
        inMatchConnection.add(insertDict)

        logger.info("Successfully added participant")
    except Exception as e:
        logger.warning("An error has occured: {}".format(e))

def main():
    logger.info("... Initializing MatchFinder ...")

    userList, userListSuccess = fetchActiveUsers()
    if userListSuccess:
        logger.info("Found users...")
        for user in userList:
            #Changing connection into dictionary
            user = user.to_dict()

            logger.info("Initializing run for user: {}".format(user))

            #Creating User Class out of data received
            user = User(user['region'],user['summoner_id'],user['account_id'])

            logger.info("Converted user into User class: {}".format(user.toDict()))

            matchList, matchListSuccess = fetchMatchEntries(user.toDict())
            if matchListSuccess:
                logger.info("Found matches...")
                for match in matchList:
                    match = Match(match['gameId'],match['timestamp'],user.getRegion())

                    logger.info("Initializing run for match: {}".format(match.toDict()))

                    insertMatchMessage = insertSingleMatch(match.toDict(),user.getAccountId())
                    if insertMatchMessage is 'Success':
                        usersInMatch = fetchUsersInMatch(match.getRegion(),match.getMatchId())
                        for participant in usersInMatch:
                            logger.info("Initializing participant: {}".format(participant['player']['accountId']))

                            insertParticipant(participant['player']['accountId'],match.getMatchId(),user.getAccountId())
                    elif insertMatchMessage is 'Exists':
                        logger.info("Match found that currently exists. Skipping user...")
                        break
                    elif insertMatchMessage is 'Error':
                        logger.info("Error has occured when inserting match. Skipping singular match...")
            else:
                logger.info("Failed to retrieve any active runs")
    else:
        logger.info("Failed to retrieve any active users")

if __name__ == "__main__":
    initLogger()
    main()


"""
Old-fashioned DQAS code on QAOA ansatz design, now deprecated.
"""

# pylint: disable=wildcard-import

import sys

sys.path.insert(0, "../")

from collections import namedtuple
from pickle import dump
from matplotlib import pyplot as plt
import numpy as np
import tensorflow as tf
import tensorcircuit as tc
from tensorcircuit.applications.dqas import *
from tensorcircuit.applications.vags import *
from tensorcircuit.applications.layers import *
from tensorcircuit.applications.graphdata import regular_graph_generator

# qaoa_block_vag_energy = partial(qaoa_block_vag, f=(_identity, _neg))

tc.set_backend("tensorflow")

def main_layerwise_encoding():
    p = 5
    c = 7

    def noise():
        n = np.random.normal(loc=0.0, scale=0.002, size=[p, c])
        return tf.constant(n, dtype=tf.float32)

    def penalty_gradient(stp, nnp, lbd=0.15, lbd2=0.01):
        c = stp.shape[1]
        p = stp.shape[0]
        cost = tf.constant(
            [1.0, 1.0, 1.0, 1.0, 27 / 2 * 2.0, 27 / 2 * 2.0, 15 / 2.0], dtype=tf.float32
        )
        with tf.GradientTape() as t:
            prob = tf.math.exp(stp) / tf.tile(
                tf.math.reduce_sum(tf.math.exp(stp), axis=1)[:, tf.newaxis], [1, c]
            )
            penalty = 0.0
            for i in range(p - 1):
                penalty += lbd * tf.tensordot(prob[i], prob[i + 1], 1)
                penalty += lbd2 * tf.tensordot(cost, prob[i], [0, 0])
            penalty += lbd2 * tf.tensordot(cost, prob[p - 1], [0, 0])
            penalty_g = t.gradient(penalty, stp)
        return penalty_g

    learning_rate_sch = tf.keras.optimizers.schedules.InverseTimeDecay(0.12, 1, 0.01)

    DQAS_search(
        qaoa_vag_energy,
        g=regular_graph_generator(n=8, d=3),
        p=p,
        batch=8,
        prethermal=0,
        prethermal_preset=[0, 6, 1, 6, 1],
        epochs=3,
        parallel_num=2,
        pertubation_func=noise,
        nnp_initial_value=np.random.normal(loc=0.23, scale=0.06, size=[p, c]),
        stp_regularization=penalty_gradient,
        network_opt=tf.keras.optimizers.Adam(learning_rate=0.03),
        prethermal_opt=tf.keras.optimizers.Adam(learning_rate=0.04),
        structure_opt=tf.keras.optimizers.SGD(learning_rate=learning_rate_sch),
    )

result = namedtuple("result", ["epoch", "cand", "loss"])

def main_block_encoding():
    learning_rate_sch = tf.keras.optimizers.schedules.InverseTimeDecay(0.3, 1, 0.01)
    p = 6
    c = 8

    def record():
        return result(
            get_var("epoch"), get_var("cand_preset_repr"), get_var("avcost1").numpy()
        )

    def noise():
        # p = 6
        # c = 6
        n = np.random.normal(loc=0.0, scale=0.2, size=[2 * p, c])
        return tf.constant(n, dtype=tf.float32)

    stp, nnp, h = DQAS_search(
        qaoa_block_vag_energy,
        g=regular_graph_generator(n=8, d=3),
        batch=8,
        prethermal=0,
        prethermal_preset=[0, 6, 1, 6, 1],
        epochs=3,
        parallel_num=2,
        pertubation_func=noise,
        p=p,
        history_func=record,
        nnp_initial_value=np.random.normal(loc=0.23, scale=0.06, size=[2 * p, c]),
        network_opt=tf.keras.optimizers.Adam(learning_rate=0.04),
        prethermal_opt=tf.keras.optimizers.Adam(learning_rate=0.04),
        structure_opt=tf.keras.optimizers.SGD(learning_rate=learning_rate_sch),
    )

    with open("qaoa_block.result", "wb") as f:
        dump([stp.numpy(), nnp.numpy(), h], f)

    epochs = np.arange(len(h))
    data = np.array([r.loss for r in h])
    plt.plot(epochs, data)
    plt.xlabel("epoch")
    plt.ylabel("objective")
    plt.savefig("qaoa_block.pdf")

layer_pool = [Hlayer, rxlayer, rylayer, rzlayer, xxlayer, yylayer, zzlayer]
block_pool = [
    Hlayer,
    rx_zz_block,
    zz_ry_block,
    zz_rx_block,
    zz_rz_block,
    xx_rz_block,
    yy_rx_block,
    rx_rz_block,
]
set_op_pool(block_pool)

if __name__ == "__main__":
    # main_layerwise_encoding()
    main_block_encoding()


from __future__ import print_function
import datetime
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import file, client, tools
import os
import sys
import pygame
import gui
from gui import *
import json
import socket
from time import sleep
import serial
import platform

#read the config file
SETUP = json.loads(open("configs/config.JSON","r").read())

OS=""
raw = platform.platform(terse=True).split("-")
for element in raw:
    OS += element+" "
OS = OS.split(" ")[0]
print(OS)
print(SETUP["Ports"][OS])

print("\nCONFIG:\n",json.dumps(SETUP, indent=4),"\n")
def initTeensy(state=0):
    try:
        conn = serial.Serial(SETUP["Ports"][OS],timeout=20)
        conn.write(b"con_init\n")
        answer = conn.readline()
        answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
        print(answer)
        if answer =="Ready":
            print(("con_setup:"+str(state)+"\n").encode("UTF-8"))
            conn.write(("con_setup:"+str(state)+"\n").encode("UTF-8"))
            answer = conn.readline()
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
        conn.close()
    except Exception as e:
        print("connection failed!!\n")
        print(e)
def testSerial():
    try:
        conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

        conn.write(b"con_test\n")
        answer = conn.readline()
        answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
        print(answer)
        if answer =="success":
            SETUP["Serial"]=True
        else:
            SETUP["Serial"]=False
        conn.close()
    except Exception as e:
        SETUP["Serial"]=False
        print(e)

#DO NOT CHANGE SCOPES VARIABLE
SCOPES = 'https://www.googleapis.com/auth/calendar.readonly'
def is_connected(hostname):
  try:
    # see if we can resolve the host name -- tells us if there is
    # a DNS listening
    host = socket.gethostbyname(hostname)
    # connect to the host -- tells us if the host is actually
    # reachable
    s = socket.create_connection((host, 80), 2)
    return True
  except:
     pass
  return False

def calendar():
    # Uses the Google Calendar API and the calendarId from main.conf to load the upcoming 10 events from specified calendar.

    store = file.Storage(SETUP["token"])
    creds = store.get()
    if not creds or creds.invalid:
        flow = client.flow_from_clientsecrets(SETUP["cliSecret_G"], SCOPES)
        creds = tools.run_flow(flow, store)
    service = build('calendar', 'v3', http=creds.authorize(Http()))

    # Call the Calendar API
    now = datetime.datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time
    print('Getting the upcoming 10 events')
    events_result = service.events().list(calendarId=SETUP["calID"],
                                        timeMin=now,
                                        maxResults=10, singleEvents=True,
                                        orderBy='startTime').execute()
    events = events_result.get('items', [])

    if not events:
        print('No upcoming events found.')
    for event in events:
        start = event['start'].get('dateTime', event['start'].get('date'))
        print(start, event['summary'])
        print(event)
    f = open(SETUP["events"],"w")
    f.write(json.dumps(events, indent=4))
    f.close()
testSerial()
#get info, wether or not the calendar should be loaded and wether or not it actually can be loaded
user="n"
if is_connected("www.google.com"):
    print("\ninternet connection works")
if is_connected("www.google.com")==False:
    print("Loading from google calendar not possible, no internet connection.")
    user = "noconnect"
elif SETUP["loadCal"] and SETUP["autoload"]==False:
    print("To enable autoload, check file configs/main.conf")
    user = input("Do you want to load from Calendar? (y,yes/n,no)")
elif SETUP["loadCal"]==False:
    user = "disconf"
elif SETUP["autoload"]:
    print("autoloading enabled, to disable check the file configs/main.conf")
    user = "y"
print(user)
#test connection, config and or user input
if is_connected("www.google.com") and (user == "y" or user == "yes"):
    print("writing events to configs/events.json\n")

    calendar()
elif user=="noconnect":
    print("no connection, calendar not loaded")

elif user == "n" or user == "no" or user == "":
    print("Calendar not loaded")

elif user =="disconf":
    print("loading Calendar disabled in configs/main.conf")

#get info from SETUP (SETUP takes the main.conf file as JSON input)
color = SETUP["color"]
header_draw = SETUP["header_draw"]

# initialize the window
ver = SETUP["version"]
pygame.init()
pygame.font.init()
#set window size
info = pygame.display.Info()
if SETUP["fullscreen"]:
    winsize = (info.current_w,info.current_h)
else:
    winsize = (info.current_w//2,info.current_h//2)

font = pygame.font.Font(SETUP["font"], int(((winsize[0]/100)*header_draw['text_h'])/1.09))
#font2 = pygame.font.Font('monofonto.ttf', int((winsize[0]/100)*header_draw['text_h'])//2)
print(font)
icon = pygame.image.load(SETUP["icon_path"])
pygame.display.set_icon(icon)
pygame.display.set_caption("Pip-Boy 3000 " + ver)

if SETUP["fullscreen"]:
    win = pygame.display.set_mode(winsize,pygame.FULLSCREEN)
else:
    win = pygame.display.set_mode(winsize)

pygame.mouse.set_visible(False)

#base variables
run = True
#this is used to hold information about the button inputs
select={
'knob':0,
'wheel':0,
'buttons':1,
'selectors':[0,0]
}
initTeensy(select["buttons"])

#this is the setup part, here i create the tabs
head = gui.header.head(win, winsize,header_draw, color,font,SETUP["player"])
SETUP["player"] = head.player
items = gui.items.items(win, winsize,font,color,header_draw,select,SETUP["player"],SETUP)
stats = gui.stats.stats(win, winsize,font,color,header_draw,SETUP)
print("\n",SETUP["player"],"\n")

#TIMEOUT VARIABLES
scroll = 0
switch = 0
settings = 0
selScroll = 0
opt = 0
set = 0
delay = 25
serialTest=0
#REDRAW (inefficient)
def redraw():
    win.fill(SETUP["background"])
    if select['buttons']==0:
        stats.update(select,SETUP["player"])
    if select['buttons']==1:
        SETUP["player"] = items.update(select,stats.itemlist,SETUP["player"])
    if select['buttons'] == 2:
        pass
    head.update(select,SETUP,SETUP["player"])
    pygame.display.update()

#MAIN LOOP
while run:
    if serialTest == 0:
        testSerial()
        serialTest=100000000
    else:
        serialTest-=1
    keys = pygame.key.get_pressed()
    if keys[pygame.K_ESCAPE]:
        run = False
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    # print(SETUP["player"]["STR"]["timeout"])
    for name in SETUP["effectlist"]:
        if SETUP["player"][name]["timeout"]>0:
            SETUP["player"][name]["timeout"]-=1
        if SETUP["player"][name]["amount"]>0 and SETUP["player"][name]["timeout"]==0:
            SETUP["player"][name]["amount"]=0
    #Spam protection/input timeout (in cycles)
    if scroll > 0:
        scroll +=1
    if scroll > delay:
        scroll = 0
    if set > 0:
        set +=1
    if set > delay:
        set = 0
    if switch > 0:
        switch +=1
    if switch > delay:
        switch = 0
    # if settings > 0:
    #     settings +=1
    # if settings > delay:
    #     settings = 0
    if selScroll > 0:
        selScroll +=1
    if selScroll > delay:
        selScroll = 0
    if opt > 0:
        opt +=1
    if opt > delay:
        opt = 0

    if keys[pygame.K_1]:
        select['buttons']=0
        select['wheel']=0
        select['selectors'][0] = 0
        select['selectors'][1] = 0
        stats.selid[0] = 0
        stats.selid[1] = 5
    if keys[pygame.K_2]:
        select['buttons']=1
        select['wheel']=0
        select['selectors'][0] = 0
        select['selectors'][1] = 0
        stats.selid[0] = 0
        stats.selid[1] = 5
    if keys[pygame.K_3]:
        select['buttons']=2
        select['wheel']=0
        select['selectors'][0] = 0
        select['selectors'][1] = 0
        stats.selid[0] = 0
        stats.selid[1] = 5
    if keys[pygame.K_RIGHT] and select['knob']+1<5 and switch ==0:
        select['knob']+=1
        select['wheel']=0
        switch+=1
        select['selectors'][0] = 0
        select['selectors'][1] = 0
        stats.selid[0] = 0
        stats.selid[1] = 5
    if keys[pygame.K_LEFT] and select['knob']-1>-1 and switch ==0:
        select['knob']-=1
        select['wheel']=0
        switch+=1
        select['selectors'][0] = 0
        select['selectors'][1] = 0
        stats.selid[0] = 0
        stats.selid[1] = 5
    if keys[pygame.K_DOWN] and scroll == 0:
        if select['buttons']==1:
            if select['wheel']+1<len(items.screenlist):
                select['wheel']+=1
                scroll+=1
            elif items.sel[1]<len(items.visible):
                items.sel[0]+=1
                items.sel[1]+=1
        elif select['knob']==4 and select['buttons']==0:
            if select['wheel']+1<len(stats.screenlist) and select['knob']==4 and select['buttons']==0:
                select['wheel']+=1
            elif stats.sel[1]<len(stats.visible):
                stats.sel[0]+=1
                stats.sel[1]+=1

            select['selectors'][0] = 0
            select['selectors'][1] = 0
            stats.selid[0] = 0
            stats.selid[1] = 5
        scroll+=1
        print(stats.sel)
    if keys[pygame.K_UP] and scroll == 0:
        if select['buttons']==1:
            if select['wheel']-1>-1:
                select['wheel']-=1
            elif items.sel[0]>0:
                items.sel[0]-=1
                items.sel[1]-=1
        elif select['knob']==4 and select['buttons']==0:
            if select['wheel']-1>-1:
                select['wheel']-=1
            elif stats.sel[0]>0:
                stats.sel[0]-=1
                stats.sel[1]-=1
            select['selectors'][0] = 0
            select['selectors'][1] = 0
            stats.selid[0] = 0
            stats.selid[1] = 5
        print(stats.sel)
        scroll+=1

    if keys[pygame.K_s] and selScroll==0:

        if select['knob']==4 and select['buttons']==0:
            if select['selectors'][0]+1<len(stats.idvisible) and select['knob']==4 and select['buttons']==0:
                select['selectors'][0]+=1
                if stats.selid[1]<len(stats.idvisible):
                    print("S")
                    stats.selid[0]+=1
                    stats.selid[1]+=1
                    selScroll+=1
    if keys[pygame.K_w] and selScroll==0:

        if select['knob']==4 and select['buttons']==0:
            if select['selectors'][0]-1>-1 and select['knob']==4 and select['buttons']==0:
                select['selectors'][0]-=1
                if stats.selid[0]>0:
                    print("W")
                    stats.selid[0]-=1
                    stats.selid[1]-=1
                    selScroll+=1
    if keys[pygame.K_d] and opt ==0:
        if select['knob']==4 and select['buttons']==0 and select['selectors'][1]+1<len(stats.options[select["knob"]]):
            select['selectors'][1]+=1
        if select["buttons"] == 1 and select['selectors'][1]+1<len(items.options[select["knob"]]):
            select["selectors"][1]+=1
        opt+=1
    if keys[pygame.K_e] and opt ==0:
        if select['knob']==4 and select['buttons']==0 and select['selectors'][1]-1>-1 :
            select['selectors'][1]-=1
        if select["buttons"]==1 and select['selectors'][1]-1>-1:
            select['selectors'][1]-=1
        opt+=1

    if keys[pygame.K_RETURN] and set ==0:
        if select['knob']==4 and select['buttons']==0 and select['selectors'][1] ==  0 and SETUP["Serial"]:
            size = [int(winsize[0]//2),int(winsize[1]//5)]
            pos = [int((winsize[0]-size[0])//2),int((winsize[1]-size[1])//2)]
            pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
            pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
            text = font.render("connecting to RFID reader...",1,SETUP["color"])
            win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
            pygame.display.update()
            print(select)
            import serial
            conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

            conn.write(b"con_start\n")
            answer = conn.readline()
            print(answer)
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
            # print("test1")
            if answer=="Ready":
                # print("test")

                conn.write(b"req_ID\n")
                answer = conn.readline()
                answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                print(answer)
                if answer == "req_ID-READ:START":
                    pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
                    pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
                    text = font.render("Place Card on RFID reader...",1,SETUP["color"])
                    win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
                    pygame.display.update()
                    status = ""
                    answer = ""
                    while status != "SUCCESS":
                        answer = conn.readline()
                        print(answer)
                        if len(answer)>0:
                            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                            status = answer.split("-")[1].split(":")[0]
                            print(answer)
                            print(status)
                        if(status == "ERROR"):
                            print(answer)
                        elif len(answer)>0:
                            id=answer.split("-")[1].split(":")[1].strip(" ")
                            print(id)
                            stats.add(select,id)
                            sleep(.5)
                            items.compile()
            conn.close()
        elif select['knob']==4 and select['buttons']==0 and select['selectors'][1] ==  2 and SETUP["Serial"]:
            size = [int(winsize[0]//2),int(winsize[1]//5)]
            pos = [int((winsize[0]-size[0])//2),int((winsize[1]-size[1])//2)]
            pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
            pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
            text = font.render("connecting to RFID reader...",1,SETUP["color"])
            win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
            pygame.display.update()
            print(select)
            import serial
            conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

            conn.write(b"con_start\n")
            answer = conn.readline()
            print(answer)
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
            # print("test1")
            if answer=="Ready":
                pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
                pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
                text = font.render("Place Card on RFID reader...",1,SETUP["color"])
                win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
                pygame.display.update()
                # print("test")

                conn.write(b"req_write\n")
                answer = conn.readline()
                answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                print(answer)
                pygame.display.update()
                if answer == "req_write-WRITE:START":
                    id=(" "+stats.generateID()+"#").encode("UTF-8")
                    print(id)
                    conn.write(id)
                    answer = conn.readline()
                    print(answer)
                    answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                    status = answer.split("-")[1].split(":")[0]
                    print(answer)

            conn.close()
        elif select['knob']==4 and select['buttons']==0 and select['selectors'][1] ==  1 and SETUP["Serial"]:
            size = [int(winsize[0]//2),int(winsize[1]//5)]
            pos = [int((winsize[0]-size[0])//2),int((winsize[1]-size[1])//2)]
            pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
            pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
            text = font.render("connecting to RFID reader...",1,SETUP["color"])
            win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
            pygame.display.update()

            print(select)
            import serial
            conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

            conn.write(b"con_start\n")
            answer = conn.readline()
            print(answer)
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
            print("test1")
            if answer=="Ready":
                print("test")

                conn.write(b"req_ID\n")
                answer = conn.readline()
                answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                print(answer)
                if answer == "req_ID-READ:START":
                    pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
                    pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
                    text = font.render("Place Card on RFID reader...",1,SETUP["color"])
                    win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
                    pygame.display.update()
                    status = ""
                    answer = ""
                    while status != "SUCCESS":
                        answer = conn.readline()
                        print(answer)
                        if len(answer)>0:
                            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                            status = answer.split("-")[1].split(":")[0]
                            print(answer)
                            print(status)
                        if(status == "ERROR"):
                            print(answer)
                        elif len(answer)>0:
                            id=answer.split("-")[1].split(":")[1].strip(" ")
                            print(id)
                            stats.delete(select,id)
                            sleep(.5)
                            items.compile()
            conn.close()
        elif select['buttons']==1 and select['selectors'][1] ==  0 and len(items.screenlist)>0:
            items.consume(select)
            if select["wheel"]>len(items.screenlist)-2 and select["wheel"]-1 >=0 and select["knob"] !=1:
                select["wheel"] -=1
        elif select['buttons']==1 and select['selectors'][1] ==  1 and select["knob"]==1 and len(items.screenlist)>0:
            items.consume(select,True)
            if select["wheel"]>len(items.screenlist)-2 and select["wheel"]-1 >=0 and select["knob"] ==1:
                select["wheel"] -=1
        elif select['buttons']==1 and select['selectors'][1] ==  2 and select["knob"] ==1 and SETUP["Serial"]:
            size = [int(winsize[0]//2),int(winsize[1]//5)]
            pos = [int((winsize[0]-size[0])//2),int((winsize[1]-size[1])//2)]
            pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
            pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
            text = font.render("connecting to RFID reader...",1,SETUP["color"])
            win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
            pygame.display.update()
            print(select)
            import serial
            conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

            conn.write(b"con_start\n")
            answer = conn.readline()
            print(answer)
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
            print("test1")
            if answer=="Ready":
                print("test")

                conn.write(b"req_ID\n")
                answer = conn.readline()
                answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                print(answer)
                if answer == "req_ID-READ:START":
                    pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
                    pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
                    text = font.render("Place Card on RFID reader...",1,SETUP["color"])
                    win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
                    pygame.display.update()
                    status = ""
                    answer = ""
                    while status != "SUCCESS":
                        answer = conn.readline()
                        if len(answer)>0:
                            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                            status = answer.split("-")[1].split(":")[0]
                            print(answer)
                            print(status)
                        if(status == "ERROR"):
                            print(answer)
                        elif len(answer)>0:
                            id=answer.split("-")[1].split(":")[1].strip(" ")
                            print(answer)
                            items.add(id = id)
            conn.close()
        elif select['buttons']==1 and select['selectors'][1] ==  1 and select["knob"] !=1 and SETUP["Serial"]:
            size = [int(winsize[0]//2),int(winsize[1]//5)]
            pos = [int((winsize[0]-size[0])//2),int((winsize[1]-size[1])//2)]
            pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
            pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
            text = font.render("connecting to RFID reader...",1,SETUP["color"])
            win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
            pygame.display.update()
            print(select)
            import serial
            conn = serial.Serial(SETUP["Ports"][OS],timeout=20)

            conn.write(b"con_start\n")
            answer = conn.readline()
            print(answer)
            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
            print(answer)
            print("test1")
            if answer=="Ready":
                print("test")

                conn.write(b"req_ID\n")
                answer = conn.readline()
                answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                print(answer)
                if answer == "req_ID-READ:START":
                    pygame.draw.rect(win,SETUP['background'],(pos[0],pos[1],size[0],size[1]))
                    pygame.draw.rect(win,(0,255,0),(pos[0],pos[1],size[0],size[1]),int(items.oneph*header_draw["header_line_thick"]*1.5))
                    text = font.render("Place Card on RFID reader...",1,SETUP["color"])
                    win.blit(text,(pos[0]+(items.oneph*4),pos[1]+(items.oneph*4)))
                    pygame.display.update()
                    answer = b""
                    status = ""
                    while status != "SUCCESS":
                        answer = conn.readline()
                        print(len(answer))
                        if len(answer)>0:
                            print(answer)
                            print(len(answer))
                            answer = answer[:len(answer)-len(b"\r\n")].decode("UTF-8")
                            status = answer.split("-")[1].split(":")[0]
                            print(answer)
                            print(status)
                        if(status == "ERROR"):
                            print(answer)
                        elif len(answer)>0:
                            id=answer.split("-")[1].split(":")[1].strip(" ")
                            print(answer)
                            items.add(id = id)
            conn.close()
            # print(len(items.screenlist))
        elif SETUP["Serial"] == False:
            print("No Serial Connection possible")
        set+=1
    if keys[pygame.K_4]:
        select["knob"]=0
    if keys[pygame.K_5]:
        select["knob"]=1
    if keys[pygame.K_6]:
        select["knob"]=2
    if keys[pygame.K_7]:
        select["knob"]=3
    if keys[pygame.K_8]:
        select["knob"]=4

    if select['buttons'] == 0:
        head.title = "STATS"
    if select['buttons'] == 1:
        head.title = "ITEMS"
    if select['buttons'] == 2:
        head.title = "DATA"
    # print(select)
    redraw()

pygame.quit()


from main_download import DOWNLOAD_FARC
import datetime
import configparser
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
import xlwings as xw
import traceback
import chromedriver_autoinstaller
from selenium.webdriver.chrome.service import Service
import os
import sys
import logging
from time import sleep
from selenium.webdriver.chrome.options import Options
import pywinauto

class UPLOAD_FARC(DOWNLOAD_FARC):

    def qms_login(self):
        self.driver=webdriver.Chrome(self.driver_path,options=self.options)
        self.driver.maximize_window()
        self.driver.get('https://www-plmprd.cisco.com/Agile/')
        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@id="userInput"]'))).send_keys(self.config["email"])

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="login-button"]'))).click()

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@id="passwordInput"]'))).send_keys(self.config["password"])

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@id="login-button"]'))).click()

        count_render_2fa = 0

        while (self.driver.title != "Universal Prompt"):

            sleep(1)

            count_render_2fa+=1

            self.logger.info("Wait for Universal Prompt render:"+str(count_render_2fa))

        while True:

            try:

                WebDriverWait(self.driver, 60).until(ec.visibility_of_element_located((By.XPATH, '//button[@id="trust-browser-button"]'))).click()

                break

            except:

                self.logger.warning('Not found trust browser button')

        two_fa_url=self.driver.current_url

        count_duo_pass = 0

        while(two_fa_url==self.driver.current_url):

            sleep(1)

            count_duo_pass+=1

            self.logger.info("Wait for count_duo_pass:"+str(count_duo_pass))

            if count_duo_pass == 30:

                self.logger.warning("!!! LOGIN TIMEOUT !!!")

                self.driver.quit()

                sys.exit()

        self.logger.info("Login to QIS is success!!!")

        sleep(1)

        self.driver.get('https://www-plmprd.cisco.com/Agile/')

        self.main_page = self.driver.current_window_handle

        self.logger.debug("Main Page:"+self.main_page)

        handles = self.driver.window_handles

        for handle in handles:

            sleep(1)

            self.driver.switch_to.window(handle)

            if self.main_page != self.driver.current_window_handle:

                self.driver.close()

        self.driver.switch_to.window(self.main_page)

        self.driver.maximize_window()

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//div[@title="Collapse Left Navigation"]'))).click()

        return True

    def read_excel(self):

        #iterate untill last row to get data in each row
        self.farc_dict = dict()
        for row in range(2,self.lRow+1):
            if self.ws[f'D{row}'].value != None and self.ws[f'E{row}'].value == None:
                self.farc_dict.update({self.ws[f'A{row}'].value : {'sn': self.ws[f'B{row}'].value,
                                                                    'golf_id':self.ws[f'C{row}'].value,
                                                                    'farc_file':self.ws[f'D{row}'].value,
                                                                    'farc_link':self.ws[f'E{row}'].value,
                                                                    'farc_file_cell':f'D{row}',
                                                                    'farc_upload_status':f'E{row}',
                                                                    }})

    def upload(self,farc_case):

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).clear()

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).send_keys(farc_case)

        WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="top_simpleSearch"]'))).click()

        #click on attachment btn
        WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.XPATH, '//div[@id="tabsDiv"]//li')))[-2].click()

        # check duplicate file
        rows_exact = int(WebDriverWait(self.driver, 10).until(ec.visibility_of_element_located((By.XPATH, '//strong[@id="totalCount_ATTACHMENTS_FILELIST"]'))).text)
        rows = WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.XPATH, '//tr[@class="GMDataRow"]')))
        rows_len = len(rows)
        self.logger.info("Exact rows >>> "+str(rows_exact)+" Rows number >>> "+str(rows_len))

        if int(rows_exact)*2 != rows_len:

            self.logger.critical(farc_case + ': Rows number does not match !!!')

            self.err.update({farc_case: 'Rows number does not match'})

        row_start = int(rows_len/2)

        for i in range(row_start, rows_len):

            row = rows[i]

            self.logger.debug(row)

            entries = row.find_elements(By.TAG_NAME,'td')

            file_name = entries[5].text.strip()

            if file_name in self.farc_dict[farc_case]['farc_file']:

                return False

            self.logger.debug("File Name >>> "+file_name)

            #upload file
            WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="MSG_AddAttachment_10"]'))).click()
            WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="browserFiles"]'))).click()
            sleep(1)

            while(True):
                try:
                    app = pywinauto.Application().connect(title="Open")
                    break
                except:
                    self.logger.warning("Wait for Browse Window")
                    sleep(1)

            dlg = app.window(title="Open")
            sleep(1)
            dlg.Edit.set_text(f'"{self.farc_dict[farc_case]["farc_file"]}"')
            dlg.Edit.type_keys("{ENTER}")
            sleep(1)

        WebDriverWait(self.driver, 5).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="uploadFilesUM"]'))).click()
        sleep(1)

        #handle for click upload not response
        downloading =True
        while(downloading):

            try:
                close_upload_box = WebDriverWait(self.driver, 5).until(ec.visibility_of_element_located((By.XPATH, '//a[@id="lfuploadpalette_window_close"]'))).click()
                sleep(1)
                self.logger.debug(close_upload_box)
                downloading = False
                self.ws[self.farc_dict[farc_case]['farc_upload_status']].value = 'Done'

            except:

                self.logger.critical("Can not click \"upload\"")
                WebDriverWait(self.driver, 5).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="uploadFilesUM"]'))).click()
                sleep(1)

    def get_farc_link(self,farc_case):

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).clear()

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).send_keys(farc_case)

        WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="top_simpleSearch"]'))).click()

        #click on attachment btn
        WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.XPATH, '//div[@id="tabsDiv"]//li')))[-2].click()

        # check duplicate file
        rows_exact = int(WebDriverWait(self.driver, 10).until(ec.visibility_of_element_located((By.XPATH, '//strong[@id="totalCount_ATTACHMENTS_FILELIST"]'))).text)
        rows = WebDriverWait(self.driver, 10).until(ec.visibility_of_all_elements_located((By.XPATH, '//tr[@class="GMDataRow"]')))
        rows_len = len(rows)
        self.logger.info("Exact rows >>> "+str(rows_exact)+" Rows number >>> "+str(rows_len))

        if int(rows_exact)*2 != rows_len:

            self.logger.critical(farc_case + ': Rows number does not match !!!')

            self.err.update({farc_case: 'Rows number does not match'})

        row_start = int(rows_len/2)

        for i in range(row_start, rows_len):

            row = rows[i]

            self.logger.debug(row)

            entries = row.find_elements(By.TAG_NAME,'td')

            file_name = entries[5].text.strip()

            if file_name in self.farc_dict[farc_case]['farc_file']:

                # click to select row not work
                # entries[0].click()
                # row.click()

                WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="MSG_More_10"]'))).click()

                WebDriverWait(self.driver, 10).until(ec.element_to_be_clickable((By.XPATH, '//a[ text() = "Get Shortcut"]'))).click()

                #switch to get link window
                handles = self.driver.window_handles
                for handle in handles:
                    sleep(1)
                    self.driver.switch_to.window(handle)
                    if self.main_page != self.driver.current_window_handle:
                        self.ws[self.farc_dict[farc_case]['farc_upload_status']].value = WebDriverWait(self.driver, 10).until(ec.visibility_of_element_located((By.XPATH, '//textarea[@id="clip"]'))).text
                        self.driver.close()
                self.driver.switch_to.window(self.main_page)
                self.logger.debug("Found Uploaded File >>> "+file_name)

                return True

        return False

    def check_farc_status(self,case):

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).clear()

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//input[@name="QUICKSEARCH_STRING"]'))).send_keys(case)

        self.logger.info('FARC Case >>> '+case)

        while True:

            try:

                WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="top_simpleSearch"]'))).click()

                break

            except:

                self.logger.warning("Wait for against click intercepted !!!")

                sleep(1)

        farc_status = WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//h2[@style="color:Blue;"]'))).text

        if farc_status not in ['RMA','Ship']:

            return False

        not_reset_audit = True

        while(not_reset_audit):

            try :

                WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//em[@id="MSG_NextStatus_em"]'))).click()

                WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[ text() = "Prelim Analysis" ]'))).click()

                WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//a[@id="ewfinish"]'))).click()

                not_reset_audit = False

            except:

                reset_handle = False

                handles = self.driver.window_handles

                self.logger.debug("No. Of window handles1: "+str(len(handles))+", "+str(handles))

                for handle in handles:

                    self.driver.switch_to.window(handle)

                    window_title = self.driver.title

                    if window_title == 'Application Error':

                        self.logger.error("Application Error Window: "+window_title+" , " +handle)

                        reset_handle = True

                        self.driver.close()

                if reset_handle:

                    self.driver.switch_to.window(self.main_page)

                    while True:

                        try:

                            WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="top_simpleSearch"]'))).click()

                            break

                        except:

                            self.logger.warning("Wait for against click intercepted !!!")

                            sleep(1)

        #window handle
        found_change_status_window = False
        count_open_change_status_window = 0
        while(not found_change_status_window):

            reset_handle = False
            sleep(1)#9-Dec-2021  add delay
            handles = self.driver.window_handles
            size = len(handles)
            self.logger.debug("No. Of window handles2: "+str(size)+' >>>  '+str(handles))

            for handle in handles:

                self.driver.switch_to.window(handle)
                window_title = self.driver.title
                if window_title == 'Change Status':
                    self.logger.debug("Change Status Window: "+window_title+' >>> '+str(handles))
                    found_change_status_window = True
                    break
                elif window_title == 'Application Error':
                    self.logger.error("Application Error Window: "+window_title+' >>> '+str(handles))

                    sleep(1)#9-Dec-2021  add delay
                    reset_handle = True
                    sleep(1)#9-Dec-2021  add delay
                    self.driver.close()

            if reset_handle:

                self.driver.switch_to.window(self.main_page)

                WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//span[@id="MSG_NextStatusspan"]'))).click()  #9-Dec-2021  visible to clickable

                WebDriverWait(self.driver, 20).until(ec.element_to_be_clickable((By.XPATH, '//a[@id="ewfinish"]'))).click()    #9-Dec-2021

            count_open_change_status_window+=3

            if count_open_change_status_window > 10:

                self.logger.critical("Can't open change_status Window: "+str(count_open_change_status_window)+" second")
                self.can_not_update_state[case] = "Can't Open Change Status Window"
                break

        sleep(5)

        WebDriverWait(self.driver, 20).until(ec.visibility_of_element_located((By.XPATH, '//a[@id="save"]'))).click()

        count_close_change_status_window = 0
        while (len(self.driver.window_handles) > 1):
            sleep(1)
            count_close_change_status_window+=1
            self.logger.warning("Wait for Close Change Status Window: "+ str(count_close_change_status_window)+" second")
            if count_close_change_status_window > 10:
                self.logger.critical("Can't Close Change Status Window: "+str(count_close_change_status_window))
                self.can_not_update_state[case] = "Can't Close Change Status Window"
                sleep(1)  #9-Dec-2021  add delay
                self.driver.close()
                sleep(1)  #9-Dec-2021  add delay
                break
        sleep(1)

        self.driver.switch_to.window(self.main_page)

    def main(self):
        self.qms_login()
        self.read_excel()
        for farc in self.farc_dict:
            self.upload(farc)
            self.check_farc_status(farc)

if __name__ == '__main__':

    inst_tst = UPLOAD_FARC()
    inst_tst.main()


# The purpose of this Text Editor:
# Created the Save, Save As, Open, Clear, Undo, Redo, and Close choices
# Toggle a dark or light theme
# Toggle random color text theme
# Created a Font style menu
# Created a Font size menu
# Created an Autosave feature that lets you choose to autosave every five or ten minutes
# Created line numbers to the left side of the text screen
# Needed to change dark theme text green, text not visible while the line is being highlighted

# Encrypt the text
# Generates the Encryption key
# Use Text_Editor_Decryption.py to decrypt messages

# all the things work :)

import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget, QLabel,
                             QMenuBar, QMenu, QAction, QFileDialog, QFontDialog, QComboBox, QWidgetAction, QTabWidget,
                             QActionGroup, QPlainTextEdit)
from PyQt5.QtGui import QIcon, QPalette, QColor, QTextCharFormat, QFont, QFontDatabase, QTextFormat, QPainter
from cryptography.fernet import Fernet
from PyQt5.QtCore import QTimer, QSize, QRect, Qt
import random  # for generating random colors

# Class to introduce line numbering to the text editor
class LineNumberArea(QWidget):
    def __init__(self, editor):
        super().__init__(editor)
        self.codeEditor = editor

    def sizeHint(self):
        return QSize(self.editor.lineNumberAreaWidth(), 0)

    def paintEvent(self, event):
        self.codeEditor.lineNumberAreaPaintEvent(event)

# Main editor class which inherits from QPlainTextEdit
class CodeEditor(QPlainTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.lineNumberArea = LineNumberArea(self)

        self.blockCountChanged.connect(self.updateLineNumberAreaWidth)
        self.updateRequest.connect(self.updateLineNumberArea)
        self.cursorPositionChanged.connect(self.highlightCurrentLine)

        self.updateLineNumberAreaWidth(0)

    def lineNumberAreaWidth(self):
        digits = 1
        count = max(1, self.blockCount())
        while count >= 10:
            count /= 10
            digits += 1
        space = 3 + self.fontMetrics().horizontalAdvance('9') * digits
        return space

    def updateLineNumberAreaWidth(self, _):
        self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0)

    def updateLineNumberArea(self, rect, dy):
        if dy:
            self.lineNumberArea.scroll(0, dy)
        else:
            self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
        if rect.contains(self.viewport().rect()):
            self.updateLineNumberAreaWidth(0)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        cr = self.contentsRect()
        self.lineNumberArea.setGeometry(QRect(cr.left(), cr.top(), self.lineNumberAreaWidth(), cr.height()))

    def highlightCurrentLine(self):
        extraSelections = []
        if not self.isReadOnly():
            selection = QTextEdit.ExtraSelection()
            lineColor = QColor(Qt.yellow).lighter(300)  # trying to adjust, so maybe green isn't necessary
            selection.format.setBackground(lineColor)
            selection.format.setProperty(QTextFormat.FullWidthSelection, True)
            selection.cursor = self.textCursor()
            selection.cursor.clearSelection()
            extraSelections.append(selection)
        self.setExtraSelections(extraSelections)

    def lineNumberAreaPaintEvent(self, event):
        painter = QPainter(self.lineNumberArea)

        painter.fillRect(event.rect(), Qt.lightGray)

        block = self.firstVisibleBlock()
        blockNumber = block.blockNumber()
        top = self.blockBoundingGeometry(block).translated(self.contentOffset()).top()
        bottom = top + self.blockBoundingRect(block).height()

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                number = str(blockNumber + 1)
                painter.setPen(Qt.black)
                painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(),
                                 Qt.AlignRight, number)

            block = block.next()
            top = bottom
            bottom = top + self.blockBoundingRect(block).height()
            blockNumber += 1

# Main class for the text editor application, inheriting from QMainWindow
class TextEditor(QMainWindow):

    def __init__(self):
        super().__init__()

        # Generate Fernet key
        self.key = Fernet.generate_key()
        self.cipher = Fernet(self.key)

        # Setting up the main window
        self.setWindowTitle("MacN_ Text Editor")
        self.setGeometry(100, 100, 1000, 800)

        # Status bar
        self.status_bar = self.statusBar()

        # Main text editor
        self.text_edit = CodeEditor(self)
        self.text_edit.textChanged.connect(self.update_word_count)  # Status bar word count
        self.text_edit.textChanged.connect(self.update_encrypted_text)

        # Encrypted text display
        self.encrypted_text_display = QTextEdit(self)
        self.encrypted_text_display.setReadOnly(True)

        # Key display
        self.key_display = QTextEdit(self.key.decode())
        self.key_display.setReadOnly(True)

        # Introduce a boolean attribute to keep track of the random color feature
        self.random_color = False

        # Introduce a boolean attribute to keep track of the theme
        self.dark_theme = False  # Default is light theme

        # Font ComboBox
        self.font_combobox = QComboBox(self)
        self.font_combobox.addItems(QFontDatabase().families())
        self.font_combobox.currentTextChanged.connect(self.change_font)

        # Font Size ComboBox
        self.font_size_combobox = QComboBox(self)
        for i in range(8, 33, 2):  # Adding sizes 8, 10,...,32
            self.font_size_combobox.addItem(str(i))
        self.font_size_combobox.setCurrentText('12')  # Default size set to 12
        self.font_size_combobox.currentTextChanged.connect(self.change_font_size)

        # Layout
        main_layout = QVBoxLayout()
        main_layout.addWidget(QLabel("Text:"))
        main_layout.addWidget(self.text_edit)
        main_layout.addWidget(QLabel("Encrypted Text:"))
        main_layout.addWidget(self.encrypted_text_display)
        main_layout.addWidget(QLabel("AES Key:"))
        main_layout.addWidget(self.key_display)

        # Adding font ComboBoxes to the layout
        main_layout.addWidget(self.font_combobox)
        main_layout.addWidget(self.font_size_combobox)

        container = QWidget()
        container.setLayout(main_layout)
        self.setCentralWidget(container)

        # Initialize autosave timer and current filename
        self.autosave_timer = QTimer(self)
        self.autosave_timer.timeout.connect(self.autosave)
        self.current_filename = None

        # Create the menu bar
        self.create_menu_bar()

    # Functions to create and handle the menu bar
    def create_menu_bar(self):
        menubar = self.menuBar()
        file_menu = QMenu("File", self)
        menubar.addMenu(file_menu)

        open_action = QAction("Open", self)
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)

        save_action = QAction("Save", self)
        save_action.triggered.connect(self.save_file)
        file_menu.addAction(save_action)

        save_as_action = QAction("Save As", self)
        save_as_action.triggered.connect(self.save_file_as)
        file_menu.addAction(save_as_action)

        # Clear action
        clear_action = QAction("Clear", self)
        clear_action.triggered.connect(self.clear_fields)
        file_menu.addAction(clear_action)

        # 'Undo' last input feature
        undo_action = QAction("Undo", self)
        undo_action.setShortcut("Ctrl+Z")
        undo_action.triggered.connect(self.text_edit.undo)
        file_menu.addAction(undo_action)

        # 'Redo' last input feature
        redo_action = QAction("Redo", self)
        redo_action.setShortcut("Ctrl+Shift+Z")
        redo_action.triggered.connect(self.text_edit.redo)
        file_menu.addAction(redo_action)

        # Toggle theme action
        toggle_theme_action = QAction("Toggle Theme", self)
        toggle_theme_action.triggered.connect(self.toggle_theme)
        file_menu.addAction(toggle_theme_action)

        # Toggle random text color action
        toggle_random_color_action = QAction("Toggle Random Color", self)
        toggle_random_color_action.triggered.connect(self.toggle_random_color)
        file_menu.addAction(toggle_random_color_action)

        # "Close" action
        close_action = QAction("Close", self)
        close_action.setShortcut("Ctrl+Q")
        close_action.triggered.connect(self.close)  # Connect to the closed method
        file_menu.addAction(close_action)

        # Font ComboBox
        font_menu = QMenu("Font", self)
        font_widget_action = QWidgetAction(self)
        font_widget_action.setDefaultWidget(self.font_combobox)
        font_menu.addAction(font_widget_action)
        menubar.addMenu(font_menu)

        # Font Size ComboBox
        font_size_menu = QMenu("Size", self)
        font_size_widget_action = QWidgetAction(self)
        font_size_widget_action.setDefaultWidget(self.font_size_combobox)
        font_size_menu.addAction(font_size_widget_action)
        menubar.addMenu(font_size_menu)

        # Create auto-save menu
        autosave_menu = QMenu("AutoSave", self)
        autosave_5min_action = QAction("Every 5 minutes", self)
        autosave_5min_action.setCheckable(True)
        autosave_5min_action.triggered.connect(lambda: self.set_autosave_interval(5))

        autosave_10min_action = QAction("Every 10 minutes", self)
        autosave_10min_action.setCheckable(True)
        autosave_10min_action.triggered.connect(lambda: self.set_autosave_interval(10))

        # Group the actions to ensure that only one option can be active
        autosave_action_group = QActionGroup(self)
        autosave_action_group.addAction(autosave_5min_action)
        autosave_action_group.addAction(autosave_10min_action)

        autosave_menu.addAction(autosave_5min_action)
        autosave_menu.addAction(autosave_10min_action)
        self.menuBar().addMenu(autosave_menu)

    # Autosave interval function
    def set_autosave_interval(self, minutes):
        self.autosave_timer.start(minutes * 60 * 1000)  # Convert minutes to milliseconds

    # Autosave timer setup function
    def autosave(self):
        if self.current_filename:
            with open(self.current_filename, 'w') as file:
                file.write(self.text_edit.toPlainText())
        else:
            self.save_file()

    # Function to open a file
    def open_file(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(self, "Open File", "", "Text Files (*.txt);;All Files (*)",
                                                   options=options)
        if file_name:
            with open(file_name, 'r') as file:
                self.text_edit.setPlainText(file.read())
                self.current_filename = file_name

    # Function to save a file
    def save_file(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Text Files (*.txt);;All Files (*)",
                                                   options=options)
        if file_name:
            with open(file_name, 'w') as file:
                file.write(self.text_edit.toPlainText())
                self.current_filename = file_name

    def save_file_as(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(self, "Save File As", "", "Text Files (*.txt);;All Files (*)",
                                                   options=options)
        if file_name:
            with open(file_name, 'w') as file:
                file.write(self.text_edit.toPlainText())
                self.current_filename = file_name  # Update the current filename

    # Toggle random color
    def toggle_random_color(self):
        self.random_color = not self.random_color

    def update_encrypted_text(self):
        plain_text = self.text_edit.toPlainText()
        # Apply random color if the feature is enabled
        if self.random_color:
            self.apply_random_color()
        encrypted_text = self.cipher.encrypt(plain_text.encode())
        self.encrypted_text_display.setPlainText(encrypted_text.decode())

    def clear_fields(self):
        self.text_edit.clear()
        self.encrypted_text_display.clear()
        self.status_bar.showMessage("Word count: 0")  # for word count

    # toggle dark or light theme
    def toggle_theme(self):
        dark_palette = QPalette()
        light_palette = QApplication.style().standardPalette()  # default light theme palette

        # If a dark theme is currently applied, change to light, else change to dark
        if self.dark_theme:
            QApplication.setPalette(light_palette)
            self.status_bar.setStyleSheet("color: black")  # Set the status bar text color for light theme
            self.dark_theme = False
        else:
            # Set colors for the dark theme
            dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
            dark_palette.setColor(QPalette.WindowText, QColor(255, 255, 255))
            dark_palette.setColor(QPalette.Base, QColor(25, 25, 25))
            dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
            dark_palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255))
            dark_palette.setColor(QPalette.ToolTipText, QColor(255, 255, 255))
            dark_palette.setColor(QPalette.Text, QColor(0, 255, 0))  # Text box text
            dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
            dark_palette.setColor(QPalette.ButtonText, QColor(0, 0, 0))  # File drop down
            dark_palette.setColor(QPalette.BrightText, QColor(255, 0, 0))
            dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
            dark_palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255))

            QApplication.setPalette(dark_palette)
            self.status_bar.setStyleSheet("color: white")  # Set the status bar text color for dark theme
            self.dark_theme = True

    # Apply the random text color
    def apply_random_color(self):
        # Generate a random color
        color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

        # Create a QTextCharFormat object with the color
        char_format = QTextCharFormat()
        char_format.setForeground(color)

        # Temporarily block the signals to avoid recursion
        self.text_edit.blockSignals(True)

        # Apply the color to the last character entered
        cursor = self.text_edit.textCursor()
        cursor.movePosition(cursor.Left, cursor.KeepAnchor)
        cursor.mergeCharFormat(char_format)
        self.text_edit.mergeCurrentCharFormat(char_format)

        # Unblock the signals
        self.text_edit.blockSignals(False)

    # Change font based on the ComboBox selection
    def change_font(self, font_name):
        font = QFont(font_name)
        font_size = int(self.font_size_combobox.currentText())
        font.setPointSize(font_size)
        self.text_edit.setFont(font)

    # Change font size based on the ComboBox selection
    def change_font_size(self, size):
        font_name = self.font_combobox.currentText()
        font = QFont(font_name)
        font.setPointSize(int(size))
        self.text_edit.setFont(font)

    # Status bar word count
    def update_word_count(self):
        text = self.text_edit.toPlainText()
        word_count = len(text.split())
        self.status_bar.showMessage(f"Word count: {word_count}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    main_window = TextEditor()
    main_window.show()
    sys.exit(app.exec_())


from werkzeug.utils import redirect

from init import app
from flask import render_template, request
from helpers.validation import validate_table, validate_file_type, _is_already_exists
from service.crud_operations import create_table, get_all_tables_info, create_table_by, delete_table_from_db

@app.route('/home')
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/add_new_table', methods=["GET", "POST"])
def add_new_table():
    if request.method == "GET":
        return render_template('add-new-table.html', data={'success-span': None})

    elif request.method == "POST":
        is_success_validation = True if validate_table(request.form) else False

        if is_success_validation:
            create_table(request.form)
        return render_template(f'add-new-table.html', data={'success-span': is_success_validation})

@app.route('/tables')
def show_all_tables():
    tables_info = get_all_tables_info()
    return render_template(f'show-all-tables.html',
                           data={'info': enumerate(tables_info.items()), 'amount_of_tables': len(tables_info)})

@app.route('/uploader', methods=["GET", "POST"])
def uploader():
    if request.method == 'GET':
        return redirect('/tables')

    if request.method == 'POST':
        if not request.files['file'].filename:
            return render_template(f'add-new-table.html', data={'success-span': False})

        f = request.files['file']
        is_success_validation, f_type = validate_file_type(f)
        name = f.filename.split('.')[0]
        if is_success_validation and not _is_already_exists(name):
            create_table_by(file=f, f_type=f_type)
            return redirect(f'/tables/{name}')

        return render_template(f'add-new-table.html', data={'success-span': False})

@app.route('/tables/<table_name>/delete', methods=["POST"])
def delete_table(table_name):
    delete_table_from_db(table_name)
    return redirect('/tables')