from selenium import webdriver
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import urllib.request
import os
import datetime
process = "on"
while process == "on":
    driver = webdriver.Firefox()
    board = input("Which board do you want to search? ")
    if len(board) < 4:
        board = board.lower()
        driver.get("https://boards.4chan.org/{}/archive".format(board))
    else:
        print("Board can't be longer than 3 characters!")
        board = input("Which board do you want to search? Make to sure to use less than 4 characters ")
    matching_threads = []
    key = input("What do you want to search for? ")
    def threadSearcher():
        for i in driver.find_elements_by_class_name("teaser-col"):
            if key in i.text:
                matching_threads.append(i.text)
                confirm = input("Thread found with title of " + i.text + " do you want to download? Type yes or no ")
                if confirm == "yes":
                    threadSearcher.folderName = i.text
                    locationt = i.find_element_by_xpath(".//following::td/a")
                    locationt.click()
                    return
                elif confirm == "no":
                    continue

            elif key == "q":
                driver.quit()
                quit()
            else:
                print("Searching...")

    threadSearcher()

    folderName = threadSearcher.folderName.replace(" ","_")
    folderName = folderName.replace("/","")
    folderName = folderName.replace(":","")
    folderName = folderName.replace("?","")
    imagehrefs = []
    for i in driver.find_elements_by_class_name("fileThumb"):
        imagehrefs.append(i.get_attribute("href"))
    print(imagehrefs)
    c = 0
    name = input("What do you want to name the files? ")
    date = datetime.datetime.now()
    date = str(date).replace(" ","")
    date = date.replace(":","_")
    filelocation = input("Where do you want to save the files? Insert full path! ")
    filepath = "{location}{folder}{date}/".format(location = filelocation, folder = folderName, date = date)
    os.mkdir(filepath)
    print(filepath)
    for i in imagehrefs:
        if i[-1] == "m":
            fileType = i[-4:]
        else:
            fileType = i[-3:]
        print("File #" + str(c) + " downloaded with file type " + fileType)
        c += 1
        num = imagehrefs.index(i)
        try:
            urllib.request.urlretrieve(i,"{path}{filename}{count}.{file}".format(count = num,file = fileType, filename = name, path = filepath))
            continue
        except:
            continue
    process = input("Do you want to download another thread? ")

driver.quit()


# https://towardsdatascience.com/perplexity-intuition-and-derivation-105dd481c8f3
# https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse
import copy
import datetime
import json
import logging
import os
import time

import numpy as np
import pandas as pd
import torch
import torch.nn.functional as F
from torch.distributions import Categorical
from torch.nn.utils import clip_grad_norm_
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

from RL_toolbox.reward import Bleu_sf0
from RL_toolbox.reward import Bleu_sf2
from train.rl.truncation import mask_inf_truncature, truncations
# from train.train_functions import evaluate_policy
from utils.utils_train import create_logger, write_to_csv

logger = logging.getLogger()

class RLAlgo:
    def __init__(self, model, train_dataset, val_dataset, test_dataset, args, lm, max_len, alpha_lm, truncate_mode,
                 truncation_params=None, is_correction=False, baseline=True, s_min=1, s_max=-1):
        self.lm = lm
        self.model = model
        self.dataset_name = args.dataset
        self.train_dataset, self.val_dataset, self.test_dataset = train_dataset, val_dataset, test_dataset
        self.train_generator = DataLoader(dataset=train_dataset, batch_size=args.bs, drop_last=True,
                                          num_workers=args.num_workers)
        self.val_generator = DataLoader(dataset=val_dataset, batch_size=args.bs, drop_last=True,
                                        num_workers=args.num_workers)
        self.batch_size = args.bs
        self.lr = args.lr
        self.optimizer = torch.optim.Adam(params=model.parameters(), lr=self.lr)
        PAD_IDX = train_dataset.vocab_questions["<PAD>"]
        self.criterion = torch.nn.NLLLoss(ignore_index=PAD_IDX)
        self.mse = torch.nn.MSELoss(reduction="none")

        self.EPOCHS = args.ep
        self.grad_clip = args.grad_clip if args.grad_clip is not None else 5.
        self.print_interval = args.print_interval
        self.device = torch.device("cuda:{}".format(args.device_id) if torch.cuda.is_available() else "cpu")
        self.create_out_path(args)
        self.writer = SummaryWriter(log_dir=os.path.join(self.out_path, "runs"))

        self.train_function, self.eval_function = self.get_algo_functions(args)
        self.task = args.task
        self.check_batch()
        self.s_min = s_min
        self.s_max = s_max
        self.truncation_params = truncation_params
        self.truncation = truncations[truncate_mode](pretrained_lm=self.lm,
                                                     s_min=s_min, s_max=s_max,
                                                     truncation_params=truncation_params)  # adding the truncation class.
        self.reward_function = Bleu_sf2()
        self.gamma = 0.99
        self.max_len = max_len
        self.alpha_lm = alpha_lm
        self.language_metrics = {k: v for k, v in zip(["bleu-1", "bleu-2", "bleu"],
                                                      [Bleu_sf0(sf_id=args.bleu_sf, n_gram=2),
                                                       Bleu_sf0(sf_id=args.bleu_sf, n_gram=3),
                                                       Bleu_sf0(sf_id=args.bleu_sf, n_gram=4)])}
        self.writer_iteration = 0
        self.is_correction = is_correction
        self.baseline = baseline
        self.rl_all, self.vf_all, self.rewards_all, self.ranks_all = [], [], [], []
        self.dialog_all, self.in_va_all, self.log_probs_truncated_all = [], [], []
        self.episode_idx = 0
        self.update_iteration = 1

    def create_out_path(self, args):
        if args.model_path is not None:
            out_path = os.path.join(args.model_path, "eval_from_loaded_model")
            self.out_path = os.path.join(out_path,
                                         "{}".format(datetime.datetime.now().strftime("%Y%m%d-%H%M%S")))
        else:
            out_path = '{}_{}_{}_layers_{}_emb_{}_hidden_{}_pdrop_{}_gradclip_{}_bs_{}_lr_{}'.format(args.dataset,
                                                                                args.task,
                                                                                args.model,
                                                                                args.num_layers,
                                                                                args.emb_size,
                                                                                args.hidden_size,
                                                                                args.p_drop,
                                                                                args.grad_clip,
                                                                                args.bs, args.lr)
            if args.task == 'policy':
                out_path = out_path + '_cond-answer_{}'.format(args.condition_answer)
            self.out_path = os.path.join(args.out_path, out_path,
                                         "{}".format(datetime.datetime.now().strftime("%Y%m%d-%H%M%S")))
        if not os.path.exists(self.out_path):
            os.makedirs(self.out_path)
        out_file_log = os.path.join(self.out_path, 'training_log.log')
        self.logger = create_logger(out_file_log)
        self.out_csv = os.path.join(self.out_path, 'train_history.csv')
        self.out_lm_metrics = os.path.join(self.out_path, 'lm_metrics_sf{}.csv'.format(args.bleu_sf))
        self.model_path = os.path.join(self.out_path, 'model.pt')
        self.logger.info("hparams: {}".format(vars(args)))
        self.logger.info('train dataset length: {}'.format(self.train_dataset.__len__()))
        self.logger.info("val dataset length: {}".format(len(self.val_dataset)))
        if self.dataset_name == "vqa":
            self.logger.info("number of filtered entries:{}".format(len(self.train_dataset.filtered_entries)))
        self.logger.info('number of tokens: {}'.format(self.train_dataset.len_vocab))
        self._save_hparams(args, self.out_path)

    def _save_hparams(self, args, out_path):
        dict_hparams = vars(args)
        dict_hparams = {key: str(value) for key, value in dict_hparams.items()}
        config_path = os.path.join(out_path, "config.json")
        with open(config_path, 'w') as fp:
            json.dump(dict_hparams, fp, sort_keys=True, indent=4)

    def check_batch(self):
        if self.task == 'policy':
            (temp_inp, temp_tar), answers, img = self.train_dataset.__getitem__(0)
            if isinstance(img, tuple):
                feats = img[0]
            else:
                feats = img
            self.logger.info('img features shape:{}'.format(feats.shape))
        elif self.task == 'lm':
            if self.dataset_name == "clevr":
                temp_inp, temp_tar = self.train_dataset.__getitem__(0)
            elif self.dataset_name == "vqa":
                (temp_inp, temp_tar), _, _ = self.train_dataset.__getitem__(0)

        self.logger.info("input shape: {}".format(temp_inp.shape))
        self.logger.info("target shape: {}".format(temp_tar.shape))
        self.logger.info("input:{}".format(temp_inp))
        self.logger.info("target: {}".format(temp_tar))

    def get_algo_functions(self, args):
        train_function = self.train_one_epoch_policy
        eval_function = self.evaluate_policy
        return train_function, eval_function

    def get_answers_img_features(self, dataset, index):
        entry = dataset.filtered_entries[index]
        img_feats, _, _ = dataset.get_img_data(entry)
        answer, _ = dataset.get_answer_data(entry)
        return img_feats, answer

    def train(self):
        self.logger.info("start training...")
        train_loss_history, train_ppl_history, val_loss_history, val_ppl_history = [], [], [], []
        best_val_loss = None
        for epoch in range(self.EPOCHS):
            self.logger.info('epoch {}/{}'.format(epoch + 1, self.EPOCHS))
            train_loss, elapsed = self.train_function(train_generator=self.train_generator,
                                                      optimizer=self.optimizer,
                                                      criterion=self.criterion,
                                                      device=self.device,
                                                      grad_clip=self.grad_clip,
                                                      print_interval=self.print_interval)
            self.logger.info('train loss {:5.3f} '.format(train_loss))
            self.logger.info('time for one epoch...{:5.2f}'.format(elapsed))
            # val_loss = self.eval_function(model=self.model, val_generator=self.val_generator, criterion=self.criterion,
            #                              device=self.device)
            # self.logger.info('val loss: {:5.3f} - val perplexity: {:8.3f}'.format(val_loss, math.exp(val_loss)))

            # saving loss and metrics information.
            train_loss_history.append(train_loss)
            # train_ppl_history.append(math.exp(train_loss))
            # val_loss_history.append(val_loss)
            # val_ppl_history.append(math.exp(val_loss))
            self.logger.info('-' * 89)

            # Save the model if the validation loss is the best we've seen so far.
            if not best_val_loss or train_loss < best_val_loss:
                with open(self.model_path, 'wb') as f:
                    torch.save(self.model, f)
                best_val_loss = train_loss

        self.logger.info("saving loss and metrics information...")
        self.save_results()
        hist_keys = ['train_loss', 'train_ppl', 'val_loss', 'val_ppl']
        hist_dict = dict(zip(hist_keys, [train_loss_history, train_ppl_history, val_loss_history, val_ppl_history]))
        write_to_csv(self.out_csv, hist_dict)

    def _sample_(self, input, num_words=20, img_feats=None,
                 index_img=None,
                 answer=None, write=True):
        answer_ = answer[0].cpu().item() if answer is not None else answer
        input_idx = input
        with torch.no_grad():
            for i in range(num_words):
                img_feats = img_feats.to(self.device)
                answer = answer.to(self.device)
                logits, _ = self.model(state_text=input_idx, state_img=img_feats,
                                       state_answer=answer)  # output = logits (S, num_tokens)
                word_idx = logits[-1].squeeze().argmax()
                input_idx = torch.cat([input_idx, word_idx.view(1, 1)], dim=-1)
            words = self.val_dataset.question_tokenizer.decode(input_idx.squeeze().cpu().numpy())
        dict_words = words
        if write:
            out_file_generate = os.path.join(self.out_path,
                                             'generate_words_img_{}_answer_{}.txt'.format(index_img, answer_))
            with open(out_file_generate, 'w') as f:
                f.write(dict_words)
                f.close()

        return dict_words

    def _generate_text(self, input, temperatures=["greedy", 0.5, 1, 2], num_words=20, img_feats=None, index_img=None,
                       answer=None, write=True):
        dict_words = {k: [] for k in temperatures}
        for temp in temperatures:
            answer_ = answer[0].cpu().item() if answer is not None else answer
            input_idx = input
            with torch.no_grad():
                for i in range(num_words):
                    img_feats = img_feats.to(self.device)
                    answer = answer.to(self.device)
                    logits, _ = self.model(state_text=input_idx, state_img=img_feats,
                                           state_answer=answer)  # output = logits (S, num_tokens)

                    word_idx = logits[:, -1].squeeze().argmax()
                    input_idx = torch.cat([input_idx, word_idx.view(1, 1)], dim=-1)
                words = self.val_dataset.question_tokenizer.decode(input_idx.squeeze().cpu().numpy())
            dict_words[temp] = words
            if write:
                out_file_generate = os.path.join(self.out_path,
                                                 'generate_words_temp_{}_img_{}_answer_{}.txt'.format(temp, index_img,
                                                                                answer_))
                with open(out_file_generate, 'w') as f:
                    f.write(dict_words[temp])
                    f.close()

        return dict_words

    def generate_text(self, temperatures=["greedy", 0.5, 1, 2], words=20):
        input = self.test_dataset.vocab_questions["<SOS>"]
        input = torch.LongTensor([input]).view(1, 1).to(self.device)
        indexes = list(range(3))
        for index in indexes:
            print("Generating text conditioned on img: {}".format(index))
            img_feats, answer = self.get_answers_img_features(dataset=self.val_dataset, index=index)
            img_feats = img_feats.unsqueeze(0)
            _ = self._generate_text(input, temperatures=temperatures, num_words=words, img_feats=img_feats,
                                    index_img=index,
                                    answer=answer)

    def evaluate_policy(self, val_generator, criterion, device):
        self.model.eval()  # turn on evaluation mode which disables dropout.
        total_loss = 0.
        start_time = time.time()
        with torch.no_grad():
            for batch, ((inputs, targets), answers, img) in enumerate(val_generator):
                if isinstance(img, list):
                    feats = img[0]
                else:
                    feats = img
                answers = answers.squeeze()
                inputs, feats, answers = inputs.to(device), feats.to(device), answers.to(device)
                targets = targets.view(targets.size(1) * targets.size(0)).to(device)
                logits, _ = self.model(inputs, feats, answers)
                logits = logits.view(-1, logits.size(-1))
                log_probs = F.log_softmax(logits, dim=-1)
                total_loss += criterion(log_probs, targets).item()
        print("Evaluation time {:5.2f}".format(time.time() - start_time))
        return total_loss / (batch + 1)

    def compute_language_metrics(self, temperatures):
        """
        Compute different versions of BLEU: try smoothing techniques seven at first. then 5, 6.
        METEOR
        :return:
        """
        input = self.test_dataset.vocab_questions["<SOS>"]
        input = torch.LongTensor([input]).view(1, 1).to(self.device)
        dict_metrics = {k: 0. for k in self.language_metrics.keys()}
        result_metrics = {k: dict_metrics for k in temperatures}
        for ((inputs, targets), answers, img) in self.val_generator:
            if isinstance(img, list):
                feats = img[0]
            else:
                feats = img
            for i in range(targets.shape[0]):
                question_decoded = [self.val_dataset.question_tokenizer.decode(targets[i].cpu().numpy())]
                num_words = len(question_decoded[0].split(" ")) + 1
                if self.task == "lm":
                    dict_questions = self._generate_text(input, temperatures=temperatures, num_words=num_words,
                                                         write=False)
                elif self.task == "policy":
                    dict_questions = self._generate_text(input, temperatures=temperatures,
                                                         img_feats=feats[i].unsqueeze(0), answer=answers[i].view(1),
                                                         num_words=num_words, write=False)
                for temp in temperatures:
                    for name, metric in self.language_metrics.items():
                        result, _, _ = metric.get(dict_questions[temp], question_decoded, step_idx=None,
                                                  done=True)
                        result_metrics[temp][name] = result_metrics[temp][name] + result
        # getting the average
        result_metrics = {k: {k_: v_ / len(self.val_dataset) for k_, v_ in v.items()} for k, v in
                          result_metrics.items()}
        df = pd.DataFrame.from_dict(result_metrics)
        df.to_csv(self.out_lm_metrics, index_label="metrics", columns=temperatures)
        return result_metrics

    def update(self, log_probs, log_probs_truncated, gts, inputs, targets, feats, answers):

        self.model.zero_grad()
        logits, values = self.model(state_text=inputs, state_img=feats,
                                    state_answer=answers)
        values = values.squeeze()
        log_probs_all = F.log_softmax(logits, dim=-1)
        log_probs_actions = log_probs_all.gather(-1, targets.unsqueeze(dim=-1)).view(
            log_probs_all.size(0), log_probs_all.size(1))

        advs = gts.clone()
        if self.baseline:
            advs -= values.cpu().detach().view(gts.size(0), gts.size(1))

        log_probs_advs = log_probs_actions * advs
        if self.is_correction:
            is_ratios = torch.exp(log_probs.detach() - log_probs_truncated.detach())
            cumprod_ratios = torch.cumprod(is_ratios, dim=-1)
            log_probs_advs *= cumprod_ratios
        rl_loss_per_episode = -log_probs_advs.sum(dim=1)
        rl_loss = rl_loss_per_episode.mean()
        value_loss = torch.square(gts.view(-1) - values.view(-1)).sum()

        loss = rl_loss
        if self.baseline:
            loss += 0.5 * value_loss

        self.optimizer.zero_grad()
        loss.mean().backward()

        clip_grad_norm_(self.model.parameters(), self.grad_clip)
        self.optimizer.step()
        return loss, rl_loss, value_loss

    def train_one_epoch_policy(self, train_generator, optimizer, criterion, device, grad_clip,
                               print_interval=10):
        self.model.train()  # Turns on train mode which enables dropout.
        total_loss = 0.
        start_time = time.time()
        start_time_epoch = time.time()

        for batch, ((inputs, targets), answers, img) in enumerate(train_generator):
            if isinstance(img, list):
                feats = img[0]
            else:
                feats = img
            answers = answers.squeeze()
            inputs, feats, answers = inputs.to(device), feats.to(device), answers.to(device)
            inputs_ = inputs[:, 0:1].to(device)
            log_probs = torch.zeros((inputs.size(0), self.max_len)).to(self.device)
            ranks = torch.zeros_like(log_probs)
            in_valid_actions = torch.zeros_like(log_probs)
            log_probs_truncated = torch.zeros_like(log_probs)

            with torch.no_grad():
                for t in range(self.max_len):
                    log_probas_lm, logits_lm, origin_log_probs_lm = self.lm.forward(inputs_, 1.)
                    mask_valid_actions = self.truncation.get_mask(log_probas_lm, logits_lm)

                    sort_lm, sort_lm_ind = torch.sort(logits_lm, descending=True)
                    ranks[:, t] = (sort_lm_ind == targets[:, t].view(-1, 1)).nonzero()[:, -1]
                    # in_valid_actions[:, t] = (targets[:, t].view(-1, 1) == valid_actions).sum(dim=1)
                    logits_, _ = self.model(state_text=inputs_, state_img=feats,
                                            state_answer=answers)  # output = logits (S, num_tokens)
                    last_logits = (1 - self.alpha_lm) * logits_[:, -1, :] + self.alpha_lm * logits_lm
                    probs = F.softmax(last_logits, dim=-1)
                    dist = Categorical(probs)

                    dist_truncated = mask_inf_truncature(mask_valid_actions, logits_[:, -1, :], self.device,
                                                         logits_.size(-1))
                    sort_probs, sort_ind = torch.sort(dist_truncated.probs, descending=True)
                    sort_words = [self.train_dataset.question_tokenizer.decode(sort_ind[j, :10].numpy()).split() for j
                                  in range(sort_ind.size(0))]
                    logger.info("sort words {}".format(sort_words))
                    logger.info("sort probs {}".format(sort_probs[:, :10]))
                    actions = dist_truncated.sample().to(self.device)
                    log_probs_truncated[:, t] = torch.log(dist_truncated.probs.gather(-1, actions.view(-1, 1)).view(-1))
                    prob_actions = dist.probs.to(self.device).gather(-1, actions.view(-1, 1)).view(-1)
                    log_probs[:, t] = torch.log(prob_actions).to(self.device)
                    inputs_ = torch.cat([inputs_.to(device), actions.view(-1, 1)], dim=-1)

            gts, rewards, dialog, targets_dialog = self.compute_rewards(inputs_, targets)

            # estimate the loss using one MonteCarlo rollout
            inputs_sampled, targets_sampled = inputs_[:, :-1], inputs_[:, 1:]
            loss, rl_loss, value_loss = self.update(log_probs, log_probs_truncated, gts, inputs_sampled,
                                                    targets_sampled, feats, answers)

            total_loss += loss.mean().item()

            self.dialog_all.append([dialog[0], targets_dialog[0]])
            self.rewards_all.append(np.mean(rewards))
            self.vf_all.append(value_loss.detach().item())
            self.rl_all.append(rl_loss.detach().item())
            self.log_probs_truncated_all.append(log_probs_truncated.detach().tolist())
            self.in_va_all.append(in_valid_actions.mean(dim=0).tolist())
            ranks_median, _ = ranks.median(dim=-1)
            self.ranks_all.append(ranks_median.view(1, -1))

            # print loss every number of batches
            if (batch + 1) % print_interval == 0:
                print('loss for batch {}: {:5.3f}'.format(batch + 1, total_loss / (batch + 1)))
                print('time for {} training steps: {:5.2f}'.format(print_interval, time.time() - start_time))
                logger.debug('rl loss {}'.format(np.mean(self.rl_all[-print_interval:])))
                logger.debug('value loss {}'.format(np.mean(self.vf_all[-print_interval:])))
                logger.info("rewards:{}".format(np.mean(self.rewards_all[-print_interval:])))
                logger.info("convergence :{}".format(np.exp(np.mean(self.log_probs_truncated_all[-print_interval:]))))
                first_log_probs_tr = np.array(self.log_probs_truncated_all[-print_interval:])[:, :, 0]
                logger.info(" 1rst prob trunc :{}".format(np.exp(np.mean(first_log_probs_tr))))

                logger.info("dialog:{}".format(dialog[0]))
                logger.info("true dialog:{}".format(targets_dialog[0]))
                ranks_medians = torch.cat(self.ranks_all[-print_interval:], dim=0)
                logger.debug("ranks :{}".format(ranks_medians))
                self.writer.add_scalar("rewards", np.mean(rewards), self.writer_iteration)
                self.writer.add_scalar("vf_loss", value_loss.detach().item(), self.writer_iteration)
                self.writer.add_scalar("rl_loss", rl_loss.detach().item(), self.writer_iteration)
                self.writer_iteration += 1

                start_time = time.time()

            curr_loss = total_loss / (batch + 1)
            elapsed = time.time() - start_time_epoch
            self.episode_idx += 1

        return curr_loss, elapsed

    def compute_rewards(self, inputs_, targets):
        dialog = [self.train_dataset.question_tokenizer.decode(question) for question in
                  inputs_.cpu().numpy()]
        targets_dialog = [self.train_dataset.question_tokenizer.decode(question[:self.max_len]) for question
                          in targets.cpu().numpy()]

        rewards = [self.reward_function.get(dialog[t_], [targets_dialog[t_]], done=True)[0] for t_ in
                   range(len(dialog))]

        rewards_ = torch.zeros((inputs_.size(0), inputs_.size(1) - 1))
        rewards_[:, -1] = torch.tensor(rewards).view(-1)
        gts = torch.zeros((inputs_.size(0), inputs_.size(1) - 1))

        discounted_reward = 0
        for timestep in range(self.max_len):
            discounted_reward = rewards_[:, -timestep - 1] + (self.gamma * discounted_reward)
            gts[:, -timestep - 1] = discounted_reward

        return gts, rewards, dialog, targets_dialog

    def save_results(self):
        pd.DataFrame(self.rewards_all).to_csv(os.path.join(self.out_path, "rewards.csv"), index=False, header=False)
        pd.DataFrame(self.rl_all).to_csv(os.path.join(self.out_path, "rl_losses.csv"), index=False, header=False)
        pd.DataFrame(self.vf_all).to_csv(os.path.join(self.out_path, "vf_losses.csv"), index=False, header=False)
        pd.DataFrame(self.dialog_all).to_csv(os.path.join(self.out_path, "dialogs.csv"), index=False, header=False)
        pd.DataFrame(self.in_va_all).to_csv(os.path.join(self.out_path, "in_vas.csv"), index=False, header=False)

class PPO_algo(RLAlgo):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.K_epochs = 1
        self.eps_clip = 0.02
        self.new_model = copy.deepcopy(self.model)
        self.new_model.load_state_dict(self.model.state_dict())
        self.optimizer = torch.optim.Adam(params=self.new_model.parameters(), lr=self.lr)
        self.entropy_coeff = 0.01
        self.update_iteration = 1

    def update(self, log_probs, log_probs_truncated, gts, inputs, targets, feats, answers):

        self.model.zero_grad()
        old_logits, old_values = self.model(state_text=inputs, state_img=feats,
                                            state_answer=answers)
        old_values = old_values.squeeze()
        old_log_probs_all = F.log_softmax(old_logits, dim=-1)
        old_log_probs_actions = old_log_probs_all.gather(-1, targets.unsqueeze(dim=-1)).view(
            old_log_probs_all.size(0), old_log_probs_all.size(1))

        advs = gts.clone()
        if self.baseline:
            advs -= old_values.cpu().detach().view(gts.size(0), gts.size(1))

        for _ in range(self.K_epochs):
            self.model.zero_grad()
            logits, values = self.new_model(state_text=inputs, state_img=feats,
                                            state_answer=answers)
            values = values.squeeze()
            log_probs_all = F.log_softmax(logits, dim=-1)
            log_probs_actions = log_probs_all.gather(-1, targets.unsqueeze(dim=-1)).view(
                log_probs_all.size(0), log_probs_all.size(1))
            ratios = torch.exp(log_probs_actions - old_log_probs_actions.detach())
            if self.is_correction:
                # computing the Importance Sampling ratio (pi_theta_old / rho_theta_old)
                is_ratios = torch.exp(log_probs - log_probs_truncated.to(self.device))
                ratios = ratios * is_ratios
            surr1 = ratios * advs
            surr2 = torch.clamp(ratios, 1 - self.eps_clip, 1 + self.eps_clip) * advs
            surr = -torch.min(surr1, surr2)
            entropy_loss = self.entropy_coeff * Categorical(torch.exp(log_probs_all)).entropy()
            vf_loss = 0.5 * self.mse(values.squeeze(), gts)
            loss = surr.sum(dim=1).mean() + vf_loss.sum() - entropy_loss.sum()
            # take gradient step
            self.optimizer.zero_grad()
            loss.mean().backward()
            clip_grad_norm_(self.new_model.parameters(), self.grad_clip)
            self.optimizer.step()
        # return loss, rl_loss, value_loss
        self.model.load_state_dict(self.new_model.state_dict())
        return loss, surr.mean(), vf_loss.mean()


import atexit
import os
import readline
import rlcompleter

# Register this only once
historyPath = os.path.join(os.getcwd(), '.pyhistory')
if os.path.exists(historyPath):
    readline.read_history_file(historyPath)

atexit.register(lambda: readline.write_history_file(historyPath))

# Generator to get text from the user. Also does some nice things like use
# a readline prompt and save user's history.
def get_input(prompt='> '):
    from six.moves import input

    try:
        while True:
            text = input(prompt)
            yield text
    except (KeyboardInterrupt, EOFError):
        print
        raise StopIteration()
    # We need to add a newline after the prompt when exiting.
    print


from __future__ import annotations

from typing import List, Optional, Tuple

import numpy as np
from sklearn.cluster import DBSCAN

from clientAis.ais.discreteV1.physics.PhysicsEstimator import PhysicsEstimator
from clientAis.ais.discreteV1.Plans.DiscreteMove import DiscreteMove
from clientAis.games.GameState import GameState, Singuity, Spawner
from utils import arrays

class DiscretePlayer:
    def __init__(
        self, isCurrentPlayer: bool, id: str, singuityCount: int, singuitiesMeanPosition: np.ndarray, singuitiesStd: float, singuitiesAverageHealth: float,
        inCluster: Optional[List[str]] = None
        ):
        self.isCurrentPlayer = isCurrentPlayer
        self.id = id
        self.singuityCount = singuityCount
        self.singuitiesMeanPosition = singuitiesMeanPosition
        self.singuitiesStd = singuitiesStd
        self.singuitiesAverageHealth = singuitiesAverageHealth
        self.inCluster = inCluster

    @staticmethod
    def fromGameState(gameState: GameState, playerId: str, currentPlayerId: str) -> DiscretePlayer:
        playerSinguities = [s for s in gameState.singuities if s.playerId == playerId]

        if len(playerSinguities) == 0:
            return DiscretePlayer(currentPlayerId == playerId, playerId, 0, np.zeros(2), PhysicsEstimator.getMinimumStd(0), 0, [])

        singuityPositions = [s.position for s in playerSinguities]

        singuityClusterIds = DBSCAN(eps=100, min_samples=25).fit_predict(singuityPositions)
        singuityClusters: List[List[Singuity]] = [[] for _ in range(np.max(singuityClusterIds) + 1)]

        for index, singuity in [(index, singuity) for index, singuity in enumerate(playerSinguities) if singuityClusterIds[index] != -1]:
            singuityClusters[singuityClusterIds[index]].append(singuity)

        def getClusterForce(singuities: List[Singuity]) -> float:
            clusterStd = arrays.mad([s.position for s in singuities], axis=0, minMad=PhysicsEstimator.getMinimumStd(len(singuities)))
            averageHealth = np.mean([s.healthPoints for s in singuities]).item()
            return PhysicsEstimator.getClusterForce(len(singuities), np.linalg.norm(clusterStd), averageHealth, 0)

        clusterForces = [getClusterForce(cluster) for cluster in singuityClusters]

        if len(clusterForces) == 0:
            singuityStd, singuityCenter = arrays.mad(singuityPositions, axis=0, returnMedian=True, minMad=PhysicsEstimator.getMinimumStd(len(singuityPositions)))
            singuityAverageHealth = np.mean([s.healthPoints for s in playerSinguities]).item()
            return DiscretePlayer(
                currentPlayerId == playerId, playerId, len(playerSinguities), singuityCenter, np.linalg.norm(singuityStd), singuityAverageHealth, [s.id for s in playerSinguities]
                )

        else:
            keptCluster = singuityClusters[np.argmax(clusterForces)]

            singuityStd, singuityCenter = arrays.mad([s.position for s in keptCluster], axis=0, returnMedian=True, minMad=PhysicsEstimator.getMinimumStd(len(keptCluster)))
            singuityAverageHealth = np.mean([s.healthPoints for s in keptCluster]).item()
            return DiscretePlayer(
                currentPlayerId == playerId, playerId, len(keptCluster), singuityCenter, np.linalg.norm(singuityStd), singuityAverageHealth, [s.id for s in keptCluster]
                )

    def executeMovement(self, move: DiscreteMove, targetPosition: np.ndarray, restrictedDuration: int = None, acceptableSinguityCount: int = None):
        def getLastingMoveDuration(moveDuration: int) -> int:
            minimumBoundDuration = moveDuration if move.minimumMoveTime is None else max(moveDuration, move.minimumMoveTime)
            maximumBoundDuration = minimumBoundDuration if restrictedDuration is None else min(minimumBoundDuration, restrictedDuration)
            return maximumBoundDuration

        originalMovementDuration = PhysicsEstimator.estimateMovementDuration(
            self.singuitiesMeanPosition, targetPosition, clusterStd=self.singuitiesStd, acceptableSinguityCount=None if acceptableSinguityCount is None else (
            self.singuityCount, acceptableSinguityCount)
            )
        constrainedMovementDuration = getLastingMoveDuration(originalMovementDuration)

        if constrainedMovementDuration == 0:
            return 0, self

        targetIsWithinStd = PhysicsEstimator.distance2(self.singuitiesMeanPosition, targetPosition) <= self.singuitiesStd ** 2

        if targetIsWithinStd:
            ratioOfTimeSpentInside = min(constrainedMovementDuration * Singuity.MAXIMUM_SPEED_UNITS_PER_FRAME, self.singuitiesStd * 2) / (self.singuitiesStd * 2)
            newStd = min(PhysicsEstimator.getMinimumStd(self.singuityCount) * ratioOfTimeSpentInside + self.singuitiesStd * (1 - ratioOfTimeSpentInside), self.singuitiesStd)
        else:
            # maximum duration on a 5000 unit map
            newStd = self.singuitiesStd * (1 + constrainedMovementDuration / 1768)

        return constrainedMovementDuration,\
               DiscretePlayer(
                   self.isCurrentPlayer,
                   self.id,
                   self.singuityCount,
                   self.singuitiesMeanPosition + (targetPosition - self.singuitiesMeanPosition) * (constrainedMovementDuration / constrainedMovementDuration),
                   newStd,
                   self.singuitiesAverageHealth
               )

    def fought(self, remainingSinguityCount):
        return DiscretePlayer(self.isCurrentPlayer, self.id, remainingSinguityCount, self.singuitiesMeanPosition, self.singuitiesStd, self.singuitiesAverageHealth)

    def tryClaimFor(self, remainingSinguitiesToClaim):
        return DiscretePlayer(
            self.isCurrentPlayer, self.id, max(self.singuityCount - remainingSinguitiesToClaim, 0), self.singuitiesMeanPosition, self.singuitiesStd, self.singuitiesAverageHealth
            )

    def appendNewSpawned(self, spawners: List[Tuple[np.ndarray, int]], anteMoveFrameCount: int, postMoveFrameCount: int) -> DiscretePlayer:
        spawnerLastClaimedFrames = np.array([s[1] for s in spawners])
        spawnerPositions = np.array([s[0] for s in spawners])

        matureSpawnerDurations = np.maximum(0, postMoveFrameCount - np.maximum(anteMoveFrameCount, spawnerLastClaimedFrames + Spawner.GESTATION_FRAME_LAG))
        newSinguityCounts = np.round(matureSpawnerDurations / Spawner.SPAWN_FRAME_LAG)

        usefulSpawnersMask = newSinguityCounts > 0
        if np.count_nonzero(usefulSpawnersMask) <= 0:
            return self

        spawnerPositions = spawnerPositions[usefulSpawnersMask]
        matureSpawnerDurations = matureSpawnerDurations[usefulSpawnersMask]
        newSinguityCounts = newSinguityCounts[usefulSpawnersMask]

        distanceFromSpawnersToMeanPosition = PhysicsEstimator.distance(self.singuitiesMeanPosition, spawnerPositions)
        durationsToTarget = distanceFromSpawnersToMeanPosition / Singuity.MAXIMUM_SPEED_UNITS_PER_FRAME
        atTargetCounts = np.round(np.maximum((matureSpawnerDurations - durationsToTarget) * PhysicsEstimator.SPAWNER_SPAWN_PER_FRAME, 0))

        atTargetCount = np.sum(atTargetCounts)

        furthestSinguityRatios = np.ones_like(distanceFromSpawnersToMeanPosition)
        matureSpawnerDurationIsLowerThanTargetDurationMask = matureSpawnerDurations < atTargetCounts
        furthestSinguityRatios[matureSpawnerDurationIsLowerThanTargetDurationMask] = matureSpawnerDurations[matureSpawnerDurationIsLowerThanTargetDurationMask] / atTargetCounts[
            matureSpawnerDurationIsLowerThanTargetDurationMask]

        inLineCounts = newSinguityCounts - atTargetCounts
        inLineMeanPositions = spawnerPositions + (spawnerPositions - self.singuitiesMeanPosition) * furthestSinguityRatios[:, np.newaxis] / 2
        inLineStds = distanceFromSpawnersToMeanPosition * furthestSinguityRatios / 4
        lineUsageMask = PhysicsEstimator.distance2(spawnerPositions, self.singuitiesMeanPosition) <= (self.singuitiesStd * 2) ** 2

        means = np.concatenate([[self.singuitiesMeanPosition, self.singuitiesMeanPosition], inLineMeanPositions[lineUsageMask]])
        stds = np.concatenate([[self.singuitiesStd, PhysicsEstimator.getMinimumStd(atTargetCount)], inLineStds[lineUsageMask]])
        counts = np.concatenate([[self.singuityCount, atTargetCount], inLineCounts[lineUsageMask]])

        mean, std, count = arrays.combineMeanStdAndCount(means, stds, counts, minStd=np.ones(2))

        return DiscretePlayer(
            self.isCurrentPlayer, self.id, count, mean, np.mean(std),
            0 if count == 0 else (self.singuitiesAverageHealth * self.singuityCount + Singuity.MAX_HEALTH_POINT * (count - self.singuityCount)) / count
            )


from threading import Event

import pytest

from application import Application

@pytest.fixture
def config(default_config):
    default_config.thresholds.pressure.min = -1
    default_config.thresholds.volume.min = 0
    default_config.thresholds.respiratory_rate.min = 3.5
    return default_config

@pytest.fixture
def app(measurements, events, driver_factory, sim_sampler):
    app = Application(
        measurements=measurements,
        events=events,
        arm_wd_event=Event(),
        drivers=driver_factory,
        sampler=sim_sampler,
        simulation=True)
    yield app
    app.root.destroy()


from flask import Flask,render_template, request, redirect,send_file,url_for
from pytubef import *
app = Flask(__name__)

@app.route("/", methods=['POST', 'GET'])
def index():
    if request.method=='POST':
        bro=request.form.get("link")
        bruh=request.form.get("type")
        k=foobart(bro,bruh)
        return send_file('test.'+k)
    return render_template('index.html')

if (__name__ == "__main__"):
    app.run(debug=True)


#Currently kept for notes and reference on SQLite syntax
#Will be removed

import sqlite3

connection = sqlite3.connect("securityLog.db")
cursor = connection.cursor()

cursor.execute("create table log (name text, time text, cameraName text)")

cursor.execute("insert into log values('Connor','4pm','webcam')")

connection.commit()

#print database rows
for row in cursor.execute("select * from log"):
    print(row)

connection.close()

"""
#how to execute sql commands
cursor.execute("insert into log values (?,?,?)")

#print database rows
for row in cursor.execute("select * from log"):
    print(row)

#How to create a table
# cursor.execute("create table log (name text, time text, cameraName text)")

#open connection
connection = sqlite3.connect("securityLog.db")
cursor = connection.cursor()

#saves changes made. if closed without doing this all changes will be rolled back.
connection.commit()

#close connection
connection.close()
"""


import numpy as np
from itertools import permutations, chain
from math import factorial

from libics.env.logging import get_logger
from libics.core.data.arrays import ArrayData
from libics.core.util import misc

LOGGER = get_logger("libics.tools.math.correlate")

###############################################################################
# Helper functions
###############################################################################

def _slice_by_lengths(lengths, the_list):
    idx = 0
    for length in lengths:
        new = the_list[idx:idx + length]
        idx += length
        yield new

def _partition(number):
    return {
        (x,) + y for x in range(1, number)
        for y in _partition(number-x)
    } | {(number,)}

def _subgroups(my_list):
    partitions = _partition(len(my_list))
    permed = [
        set(permutations(each_partition, len(each_partition)))
        for each_partition in partitions
    ]
    for each_tuple in chain(*permed):
        yield list(_slice_by_lengths(each_tuple, my_list))

def _get_partition(my_list, num_groups):
    """Gets prefactor of permutation"""
    # FIXME: highly inefficient
    filtered = []
    for perm in permutations(my_list, len(my_list)):
        for sub_group_perm in _subgroups(list(perm)):
            if len(sub_group_perm) == num_groups:
                # within each partition
                sorted_list = [sorted(i) for i in sub_group_perm]
                # by number of elements and first element of each partition
                sorted_list = sorted(sorted_list, key=lambda t: (len(t), t[0]))
                if sorted_list not in filtered:
                    filtered.append(sorted_list)
    return filtered

###############################################################################
# Sample-averaged autocorrelation
###############################################################################

def autocorrelate_single_dist(
    data, vdists, connected=True, vdist_err_warn=True, vdist_err_val=np.nan
):
    """
    Calculates the sample-averaged autocorrelation for one distance.

    Parameters
    ----------
    data : `Array`
        Sample data with dimensions: `[n_samples, ...]`.
    vdists : `Array[2, int]` or `Array[1, int]`
        Vectorial index distance for which to evaluate correlator.
        If `vdists` is 2D, dimensions: `[n_order - 1, data_ndim]`.
        If `vdists` is 1D and data is 1D, dimensions: `[n_order - 1]`.
        If `vdists` is 1D and data is >1D, assumes `n_order == 2`.
    connected : `bool`
        Whether to calculate the connected correlator,
        i.e., to subtract lower order correlators.
    vdist_err_warn : `bool` or `Exception`
        Sets the behavior when an invalid value for `vdists` is found.
        If `False`, silently returns `vdist_err_val`.
        If `True`, additionally logs a warning message.
        If `Exception`, raises the given exception.
    vdist_err_val : `scalar`
        Return value on `vdists` error (if no exception is raised).

    Returns
    -------
    corr : `float` or `complex`
        Sample-averaged autocorrelation at the specified relative distances.
    """
    # Parse data
    data_ar = np.array(data)
    data_shape = np.array(data_ar.shape[1:])
    data_ndim = len(data_shape)
    n_samples = len(data_ar)
    # Parse vectorial distances
    vdists = np.array(vdists, dtype=int)
    if vdists.ndim == 1:
        if data_ndim == 1:
            vdists = vdists[:, np.newaxis]
        else:
            vdists = vdists[np.newaxis, :]
    if vdists.ndim != 2 or vdists.shape[1] != data_ndim:
        raise ValueError("invalid shape of `vdists`")
    vdists = np.concatenate([[np.zeros(data_ndim, dtype=int)], vdists])
    order_ndim = len(vdists)
    # Set up working memory for intermediate product array
    tmp_starts = np.max(-vdists, axis=0)
    tmp_stops = np.min(data_shape - vdists, axis=0)
    tmp_starts[tmp_starts < 0] = 0
    tmp_stops[tmp_stops > data_shape] = data_shape[tmp_stops > data_shape]
    tmp_sizes = tmp_stops - tmp_starts
    if np.any(tmp_sizes <= 0):
        if vdist_err_warn is False:
            return vdist_err_val
        err_msg = "invalid `vdists`"
        if vdist_err_warn is True:
            LOGGER.warning(err_msg)
            return vdist_err_val
        else:
            raise vdist_err_warn(err_msg)
    tmp_dtype = complex if data_ar.dtype == complex else float
    tmp_shape = (order_ndim, n_samples) + tuple(tmp_sizes)
    tmp_ar = np.full(tmp_shape, np.nan, dtype=tmp_dtype)

    # Calculate intermediate product array
    for order_dim in range(order_ndim):
        _roi = [
            slice(tmp_starts[data_dim] + vdists[order_dim, data_dim],
                  tmp_stops[data_dim] + vdists[order_dim, data_dim])
            for data_dim in range(data_ndim)
        ]
        tmp_ar[order_dim] = data_ar[tuple([slice(None)] + _roi)]
    # Return bare (non-connected) autocorrelator
    if connected is False:
        corr = np.nanmean(np.prod(tmp_ar, axis=0))
    # Calculate connected autocorrelator
    else:
        order_dims = list(range(order_ndim))
        corr = 0
        for order_dim in order_dims:
            _coeff = (-1)**order_dim * factorial(order_dim)
            _groups = _get_partition(order_dims, order_dim + 1)
            for _group in _groups:
                _res = 1
                for _sub in _group:
                    _res = _res * np.nanmean(
                        np.prod(tmp_ar[_sub], axis=0), axis=0
                    )
                corr = corr + _coeff * _res
        corr = np.nanmean(corr)
    return corr

def autocorrelate(
    data, connected=True, n_order=2, max_vdist=None, center_val=None,
    vdist_err_val=np.nan, print_progress=False
):
    """
    Gets sample-averaged n-th order autocorrelation at multiple distances.

    Parameters
    ----------
    data : `Array`
        Sample data with dimensions: `[n_samples, ...]`.
    connected : `boolean`
        Whether to calculate the connected correlator,
        i.e., to subtract lower order correlators.
        Example for `n_order=2`:
        If `True`: <XY>-<X><Y>, if `False`: <XY>.
    n_order : `int`
        Order of correlator.
    max_vdist : `Array[int or None]` or `int` or `None`
        Maximum correlator distance for each data dimension.
        If scalar, uses the scalar for all dimensions.
        If `None`, uses the maximally possible distance
        for the respective dimension.
    center_val : `scalar` or `None`
        If not `None`, sets the correlator at the origin to the given value.
    vdist_err_val : `scalar`
        Correlator value at invalid distance (combinations).

    Returns
    -------
    corr : `ArrayData(float or complex)`
        Sample-averaged autocorrelator.
        Number of dimensions: `(n_order - 1) * (data.ndim - 1)`.
        Dimensions: `[dx1, dy1, dz1, ..., dx2, dy2, dz2, ..., ...]`.

    Examples
    --------
    Example for 1D nearest-neighbor-correlated data:
    >>> ar = np.tile([-1, 1], 3)
    >>> data = [ar, -ar]
    >>> corr = autocorrelate(data)
    >>> corr.data
    array([-1.,  1., -1.,  1., -1.,  1., -1.,  1., -1.,  1., -1.])
    """
    # Check data format
    n_order = int(n_order)
    if n_order < 2:
        raise ValueError("Correlation order `n_order` must be >2")
    try:
        data_ar = np.array(data)
        if data_ar.dtype == object:
            raise ValueError()
    except ValueError:
        raise ValueError(
            "All samples in the parameter `data` must have the same shape"
        )
    data_shape = data_ar.shape[1:]
    data_ndim = len(data_shape)

    # Check for metadata
    is_ad = isinstance(data[0], ArrayData)
    if is_ad:
        ad = data[0]
        # Check whether ArrayData steps are equidistant
        try:
            for dim in range(ad.ndim):
                ad.get_step(dim, check_mode=RuntimeError)
        except RuntimeError:
            LOGGER.warning("`data` does not contain equidistant steps")

    # Parse maximum relative coordinates
    max_vdist = misc.assume_list(max_vdist)
    if len(max_vdist) < data_ndim:
        _factor = int(np.ceil(data_ndim / len(max_vdist)))
        max_vdist = _factor * max_vdist
    max_vdist = [
        data_shape[i] - 1 if max_vdist[i] is None else max_vdist[i]
        for i in range(data_ndim)
    ]

    # TODO: make use of point symmetry of correlators
    # Setup correlation data container
    corr_shape = tuple((n_order - 1) * [2*x + 1 for x in max_vdist])
    corr_dtype = complex if data_ar.dtype == complex else float
    corr = ArrayData(np.full(corr_shape, np.nan, dtype=corr_dtype))
    for dim in range(corr.ndim):
        corr.set_dim(dim, center=0, step=1)
    if is_ad:
        q = ad.data_quantity.copy()
        q.name += " correlation"
        corr.set_data_quantity(quantity=q)
        for dim in range(corr.ndim):
            data_dim = dim % data_ndim
            order_dim = dim // data_ndim + 1
            q = ad.var_quantity[data_dim]
            if n_order > 2:
                q.name += f" [{order_dim:d}]"
            corr.set_var_quantity(dim, quantity=q)
    # All vectorial index distances with dimensions: [{...}, vdist]
    all_vdists = np.moveaxis(corr.get_var_meshgrid(), 0, -1).astype(int)

    # Iterate vectorial distances
    _iter = np.ndindex(*corr_shape)
    if print_progress:
        _iter = misc.iter_progress(_iter)
    for idx in _iter:
        vdists = all_vdists[idx].reshape((n_order - 1, -1))
        _c = autocorrelate_single_dist(
            data_ar, vdists, connected=connected,
            vdist_err_warn=False, vdist_err_val=vdist_err_val
        )
        corr[idx] = _c
    # Apply correct variable steps
    if is_ad:
        for dim in range(corr.ndim):
            data_dim = dim % data_ndim
            corr.set_dim(
                dim, center=0, step=ad.get_step(data_dim, check_mode=False)
            )
    # Apply center value
    if center_val is not None:
        idx = tuple(np.array(corr.shape) // 2)
        corr[idx] = center_val
    return corr

def autocorrelate_bootstrap(
    data, func=None, bs_groups=None, bs_size=None, seed=None,
    print_progress=True, **kwargs
):
    """
    Performs bootstrapping for autocorrelator functions.

    Parameters
    ----------
    data : `Array`
        Sample data with dimensions: `[n_samples, ...]`.
    func : `callable`
        Autocorrelation function to call:
        :py:func:`autocorrelate_single_dist` or :py:func:`autocorrelate`.
        Call signature: `func(resampled_data, **kwargs)`.
    bs_groups, bs_size : `int` or `None`
        Bootstrap parameters for
        the number of resampled groups
        and the sample size of each group.
        If `None`, uses the size of the data.
    seed : `int` or `None`
        Random number generator seed.
    print_progress : `bool`
        Whether to print a progress bar.
    **kwargs
        Keyword arguments passed to `func`.

    Returns
    -------
    res : `dict(str->Any)`
        Results dictionary containing:
    data : `list(ArrayData)`
        List of bootstrap autocorrelation calculation results.
    sem : `ArrayData`
        Standard error of the mean of the autocorrelation.

    Examples
    --------
    Example for 1D nearest-neighbor-correlated data with only two samples:
    >>> ar = np.tile([-1, 1], 3)
    >>> data = [ar, -ar]
    >>> corr_bs = autocorrelate_bootstrap(
    ...     data, func=autocorrelate, bs_groups=10, seed=0
    ... )
    >>> corr_bs["sem"]
    array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5])
    """
    # Parse parameters
    bs_groups = len(data) if bs_groups is None else int(bs_groups)
    bs_size = len(data) if bs_size is None else int(bs_size)
    if func not in [autocorrelate, autocorrelate_single_dist]:
        raise ValueError(
            "`func` must be either `autocorrelate` "
            "or `autocorrelate_single_dist`"
        )
    # Perform bootstrapping
    rng = np.random.RandomState(seed=seed)
    corrs = []
    _iter = range(bs_groups)
    if print_progress:
        _iter = misc.iter_progress(_iter)
    for _ in _iter:
        resampled_idxs = rng.choice(
            np.arange(len(data)), size=bs_size, replace=True
        )
        resampled_data = [data[i] for i in resampled_idxs]
        corr = func(resampled_data, **kwargs)
        corrs.append(corr)
    sem = np.std(corrs, axis=0)
    # Package results
    res = {"data": corrs, "sem": sem}
    return res


from time import gmtime, strftime
from django.shortcuts import render, HttpResponse, get_object_or_404, redirect, render_to_response
from django.urls import reverse_lazy
from django.contrib import messages

# Importación de modelos
from .models import Person
from .models import Solicitud
from .models import Metas
from .models import Tarifas
from .models import Feriados

# Importación de formularios
from .forms import PersonForm
from .forms import SolicitudForm
from .forms import MetaForm
from .forms import TarifaForm
from .forms import FeriadoForm

# Importación de vistas basadas en clases
from django.views.generic.list import ListView
from django.views.generic.edit import UpdateView
from django.views.generic import CreateView

from django.core.paginator import Paginator
from datetime import datetime, timedelta, date
import calendar
import json
import xlwt

# ----------------------------------------------------------------------------
#   Vista basada en clase para listar las personas o vendedores registrados
# -----------------------------------------------------------------------------

class personIndex(ListView):
    model = Person
    template_name = 'sellingsapp/person_list.html'
    context_object_name = 'person_list'
    paginate_by = 7
    ordering = ['id']

# ------------------------------------------------------------
#       Método o función para crear una persona o vendedor
# -------------------------------------------------------------

def personAdd(request):
    if request.method == 'POST':
        form = PersonForm(request.POST)
        if form.is_valid():
            person = form.save(commit=False)
            person.first_name = request.POST['first_name']
            person.last_name = request.POST['last_name']
            person.celphone = request.POST['celphone']
            person.address = request.POST['address']
            person.email = request.POST['email']
            person.canal_de_venta = request.POST['canal_de_venta']
            person.save()
            messages.success(request, 'El registro ha sido ingresado.')
            form = PersonForm()
    else:
        form = PersonForm()

    return render(request, 'sellingsapp/person_add.html', {'form': form})

# ------------------------------------------------------------
#       Vista basada en clase que utiliza la clase UpdateView
#       para editar una persona o vendendor
# -------------------------------------------------------------
class personDetail(UpdateView):
    model = Person
    fields = ['cc_id', 'first_name', 'last_name',
              'celphone', 'address', 'email', 'canal_de_venta']
    template_name = 'sellingsapp/person_detail.html'

    def form_valid(self, form):
        person = form.save(commit=False)
        person.save()
        messages.success(self.request, 'El registro ha sido actualizado.')
        return self.render_to_response(self.get_context_data(form=form))

# ---------------------------------------------------------
#            Método que elimina una persona o vendedor
# ----------------------------------------------------------

def personDelete(request, id_person):
    # busca el modelo idenficado por el id
    person_delete = get_object_or_404(Person, pk=id_person)
    person_delete.delete()
    custom_message = " | "+person_delete.first_name + \
        " " + person_delete.last_name + " | "
    messages.success(request, "El registro " + str(id_person) +
                     "-" + custom_message+" ha sido eliminado")

    return redirect('personList')

def handler404(request, exception, template_name="404.html"):
    response = render_to_response("404.html")
    response.status_code = 404
    return response

# --------------------------------------------------------------------------------
#       Utiliza la clase ListView para para crear una vista basada en clase
# --------------------------------------------------------------------------------
class solicitudIndex(ListView):
    model = Solicitud
    template_name = 'sellingsapp/solicitud_list.html'
    context_object_name = 'solicitud_list'
    paginate_by = 7
    ordering = ['product_name']

    def get_queryset(self):

        if self.request.user.is_anonymous == False:
            user_email = self.request.user.email
        else:
            result = []
            return result

        if self.request.user.get_username() == '@dm1n':
            result = Solicitud.objects.all()
            return result

        try:
            ObjPerson = Person.objects.get(email=user_email)

            if ObjPerson.id:
                findId = ObjPerson.id
                result = Solicitud.objects.filter(asesor_id=findId)

        except Person.DoesNotExist:
            result = []

        return result

# --------------------------------------------------------------------------------
#       Utiliza la clase ListView para para crear una vista basada en clase
# --------------------------------------------------------------------------------

def proyectionIndex(request):

    model = Solicitud
    template_name = 'sellingsapp/proyection_list.html'
    context_object_name = 'proyection_list'

    today = date.today()
    search_mes = ["enero", "febrero", "marzo", "abril", "mayo", "junio",
                  "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"]

    if request.user.is_anonymous == True:
        return redirect('sign_out')

    query_person = ''  # Se establece un query para admin o usuario común

    # if (request.user.get_username() == "@dm1n" or request.user.get_username() == "supervis@r"):
    search_cc_id = request.GET.get('search_cc_id')

    if search_cc_id != '':
        query_person = "Person.objects.get(cc_id=search_cc_id)"
    else:
        search_email = ""

    # Buscamos el email asociado al cc_id o al usuario de la sesión
    if query_person != '':
        try:
            ObjPerson = eval(query_person)
            search_email = ObjPerson.email
        except Person.DoesNotExist:
            search_email = ""

    # Consultamos la cantidad de ventas realizadas (ingresadas e instaladas)
    if search_email != '' and search_email != None:
        cant_solicitadas = proyectionReview(
            search_email, search_mes[today.month-1], today.day, 'solicitado')
        cant_instaladas = proyectionReview(
            search_email, search_mes[today.month-1], today.day, 'instalado')
    else:
        result = {}
        return render(request, template_name, result)

    # Obtenemos días hábiles,días transcurridos,y total de ventas
    dias_habiles = validDays(lastDayMonth())
    dias_transcurridos = validDays("")-1
    tot_ingresadas = cant_solicitadas+cant_instaladas

    # Calculamos la proyección de ventas ingresadas e instaladas
    if dias_transcurridos == 0:
        proyeccion_ingresadas = 0
        proyeccion_instaladas = 0
    else:
        proyeccion_ingresadas = (
            (cant_solicitadas+cant_instaladas)/dias_transcurridos)*dias_habiles
        proyeccion_instaladas = (
            cant_instaladas/dias_transcurridos)*dias_habiles

    # Calculamos el porcentaje de ventas ingresadas e instaladas con respecto a la meta
    if (request.GET.get('meta_instalada') != "" and request.GET.get('meta_ingresada') != "" and int(request.GET.get('meta_instalada')) > 0 and int(request.GET.get('meta_ingresada')) > 0):
        meta_instalada = int(request.GET.get('meta_instalada'))
        meta_ingresada = int(request.GET.get('meta_ingresada'))
        porc_meta_ingresadas = (proyeccion_ingresadas/meta_ingresada)*100
        porc_meta_instaladas = (proyeccion_instaladas/meta_instalada)*100
    else:
        meta_ingresada = 0
        meta_instalada = 0
        porc_meta_ingresadas = 0
        porc_meta_instaladas = 0

    # calcula el valor de la tarifa de comisión
    comision = getComision(cant_instaladas)

    if comision != []:
        tabla_comision = comision
        rango_comision = comision["nombre_rango"]
    else:
        valor_comision = 0
        rango_comision = 'no aplica'
        tabla_comision = 'no aplica'

    return render(request, template_name,
                  {"dias_habiles": dias_habiles,
                   "dias_transcurridos": dias_transcurridos,
                   "cant_instaladas": cant_instaladas,
                   "cant_solicitadas": tot_ingresadas,
                   "proyeccion_ingre": format(proyeccion_ingresadas, ".2f"),
                   "proyeccion_instal": format(proyeccion_instaladas, ".2f"),
                   "meta_ingresadas": meta_ingresada,
                   "meta_instaladas": meta_instalada,
                   "porc_meta_ingresadas": format(porc_meta_ingresadas, ".2f"),
                   "porc_meta_instaladas": format(porc_meta_instaladas, ".2f"),
                   "nombre_rango": rango_comision,
                   "tabla_comision": tabla_comision
                   })

#----------------------------------------------------------#
#   Obtiene la comisión a partir del rango de venta
# -----------------------------------------------------------
def getComision(cant_instaladas):

    array_range = {}

    try:
        sql_query = "limite_inf <= %s and limite_sup >=%s"
        result = Tarifas.objects.extra(where=[sql_query], params=[
                                       cant_instaladas, cant_instaladas])
    except Tarifas.DoesNotExist:
        result = []

    if result.exists():

        for record in result:
            array_range[record.porce_title] = format(
                (int(record.comision) * cant_instaladas), ",d")
            array_range["nombre_rango"] = record.nombre_rango
            array_range["escala"] = str(
                record.limite_inf)+"-"+str(record.limite_sup)

        return array_range

    return []
# ---------------------------------------------------------------------------
#       Calcula la proyección de cada asesor
# ---------------------------------------------------------------------------

def proyectionReview(user_email, mes, dia, status):
    tot_status = 0
    try:
        ObjPerson = Person.objects.get(email=user_email)
        asesor_id = ObjPerson.id
    except Person.DoesNotExist:
        return 0

    if user_email != '':
        sql_query = "status=%s and asesor_id=%s and mes=%s and dia != %s"
        result = Solicitud.objects.extra(where=[sql_query], params=[
                                         status, asesor_id, mes, dia])
        for record in result:
            tot_status += record.product_cant

    return tot_status

# ----------------------------------------------------------------------------
#       Función para obtener días hábiles del  mes transcurridos hasta hora
# ----------------------------------------------------------------------------
def validDays(finalday):
    # Día actual
    today = date.today()

    formato = '%d/%m/%Y'
    contador = 0

    fechadesde = "01/"+str(today.month)+"/"+str(today.year)

    if finalday != "":
        fechahasta = finalday
    else:
        fechahasta = str(today.day)+"/"+str(today.month)+"/"+str(today.year)

    fechadesde = datetime.strptime(fechadesde, formato)
    fechahasta = datetime.strptime(fechahasta, formato)

    while fechadesde <= fechahasta:
        if datetime.weekday(fechadesde) != 6:
            if isHoliday(fechadesde) == False:
                contador += 1
        fechadesde = fechadesde + timedelta(days=1)

    return contador

# -----------------------------------------------------------------------
#      Función que verifica si el día es feriado
# -----------------------------------------------------------------------

def isHoliday(dia):
    formato = '%Y-%m-%d'
    hollidays = '[{"feriado":"14/10/2019"}]'
    dias_feriados = json.loads(hollidays)

    if existFeriado(datetime.strftime(dia, formato)) == True:
        return True

    return False

# -----------------------------------------------------------------------
#       Función que obtiene el último día del mes
# -----------------------------------------------------------------------

def lastDayMonth():
    today = date.today()
    day = calendar.monthrange(int(strftime("%Y", gmtime())),
                              int(strftime("%m", gmtime())))[1]
    lastDate = str(day)+"/"+str(today.month)+"/"+str(today.year)
    return lastDate

# -----------------------------------------------------------------------
#   Función para verificar si ya existe la meta resgistrada
# -------------------------------------------------------------------------

def getMeta(mes_meta, anio_meta, canal_venta):

    try:
        sql_query = "mes=%s and anio=%s and canal_venta=%s"
        result = Metas.objects.extra(where=[sql_query], params=[
                                     mes_meta, anio_meta, canal_venta])
    except Metas.DoesNotExist:
        result = []

    return result

# -------------------------------------------------------------------------
#    Función para verificar si ya existe la TARIFA resgistrada
# -------------------------------------------------------------------------

def getTarifa(nombre_rango, porce_title):

    try:
        sql_query = "nombre_rango=%s and porce_title=%s"
        result = Tarifas.objects.extra(where=[sql_query], params=[
                                       nombre_rango, porce_title])
    except Tarifas.DoesNotExist:
        result = []

    return result

# -----------------------------------------------------------------------
#       Clase ListView que utiliza filter para búsqueda personalizada
# -----------------------------------------------------------------------

class searchSolicitud(ListView):
    model = Solicitud
    template_name = 'sellingsapp/solicitud_list.html'
    context_object_name = 'solicitud_list'
    paginate_by = 7
    ordering = ['product_name']

    def get_queryset(self):
        queryset = super(searchSolicitud, self).get_queryset()

        search_field = self.request.GET.get('search_field').strip()
        search_value = self.request.GET.get('search_text').strip()

        if self.request.user.is_anonymous == False:
            user_email = self.request.user.email
        else:
            result = []
            return result

        if self.request.user.get_username() == '@dm1n':
            result = Solicitud.objects.extra(
                where=[search_field+"= %s"], params=[search_value])
            return result

        try:
            ObjPerson = Person.objects.get(email=user_email)
            asesor_id = ObjPerson.id

        except Person.DoesNotExist:
            result = []

        if search_value != '':
            result = Solicitud.objects.extra(
                where=[search_field+"=%s and asesor_id=%s"], params=[search_value, asesor_id])
        else:
            result = Solicitud.objects.filter(asesor_id=asesor_id)

        return result

# -------------------------------------------------------------------------
#             Auto-complete del campo de búsqueda de campañas
#  ------------------------------------------------------------------------

def autoCompleteSearchSolicitud(request):

    if request.is_ajax():
        q = request.GET.get('term')
        field_search = request.GET.get('field')
        solicitudes = Solicitud.objects.extra(
            where=[field_search+" LIKE %s"], params=["%"+str(q)+"%"])
        results = []

        for pl in solicitudes:
            place_json = {}
            place_json = str(eval("pl."+field_search))
            results.append(place_json)

        data = json.dumps(results)
        mimetype = ''

    else:
        data = 'fail'
        mimetype = 'application/json'

    return HttpResponse(data, mimetype)

# -----------------------------------------------------------------------
#                Utiliza una función para la view
# -----------------------------------------------------------------------
def solicitudAdd(request):

    if request.user.is_anonymous == True:
        return redirect('sign_out')

    if request.method == 'POST':
        form = SolicitudForm(request.POST)
        solicitud = form.save(commit=False)
        solicitud.product_name = request.POST['product_name']
        solicitud.status = request.POST['status']
        solicitud.dia = request.POST['dia']
        solicitud.mes = request.POST['mes']
        solicitud.anio = request.POST['anio']
        solicitud.nomcliente = request.POST['nomcliente']
        solicitud.celcliente = request.POST['celcliente']
        solicitud.product_cant = request.POST['product_cant']
        solicitud.notes = request.POST['notes']
        solicitud.asesor = Person.objects.get(id=request.POST['asesor'])
        solicitud.save()
        messages.success(request, 'El registro ha sido ingresado.')
    else:
        form = SolicitudForm(request)

    return render(request, 'sellingsapp/solicitud_add.html', {'form': form})

# ---------------------------------------------------------------------------
#    Utiliza una vista basada en clase inyectando la clase Createview
# ---------------------------------------------------------------------------
# class campaignAdd(CreateView):
#     model = Campaign
#     fields = ['title', 'url', 'status', 'mes', 'anio', 'notes', 'num_campaign']
#     template_name = 'sellingsapp/campaign_add.html'

# ---------------------------------------------------------------------------
#    Utiliza la vista basada en clase inyectando la clase UpdateView
# ---------------------------------------------------------------------------
# class campaignDetail(UpdateView):
#     model = Campaign
#     fields = ['title', 'url', 'status', 'mes', 'anio', 'notes', 'num_campaign']
#     template_name = 'sellingsapp/campaign_detail.html'

#     def form_valid(self, form):
#         campaign = form.save(commit=False)
#         campaign.save()
#         messages.success(self.request, 'El registro ha sido actualizado.')
#         return self.render_to_response(self.get_context_data(form=form))

def solicitudDetail(request, pk):
    solicitud = get_object_or_404(Solicitud, pk=pk)

    if request.method == "POST":
        form = SolicitudForm(request.POST, instance=solicitud)
        solicitud = form.save(commit=False)
        solicitud.product_name = request.POST['product_name']
        solicitud.status = request.POST['status']
        solicitud.dia = request.POST['dia']
        solicitud.mes = request.POST['mes']
        solicitud.anio = request.POST['anio']
        solicitud.nomcliente = request.POST['nomcliente']
        solicitud.celcliente = request.POST['celcliente']
        solicitud.product_cant = request.POST['product_cant']
        solicitud.notes = request.POST['notes']
        solicitud.asesor = Person.objects.get(id=request.POST['asesor'])
        solicitud.save()
        messages.success(request, 'El registro ha sido actualizado.')

    form = SolicitudForm(request, instance=solicitud)

    return render(request, 'sellingsapp/solicitud_detail.html', {'form': form})

# -----------------------------------------------------------------------------
#               Función para eliminar una solicitud
# -----------------------------------------------------------------------------
def solicitudDelete(request, id_solicitud):
    # busca el modelo idenficado por el id
    solicitud_delete = get_object_or_404(Solicitud, pk=id_solicitud)
    solicitud_delete.delete()
    custom_message = " | "+solicitud_delete.product_name + " | "
    messages.success(request, "El registro " + str(id_solicitud) +
                     "-" + custom_message+" ha sido eliminado")

    return redirect('solicitudList')

# ---------------------------------------------------------------------------
#       Funcion para registrar una nueva Meta para diferente canal de venta
# ----------------------------------------------------------------------------
def metaAdd(request):
    if request.method == 'POST':
        form = MetaForm(request.POST)
        if form.is_valid():
            if (getMeta(request.POST['mes'], request.POST['anio'], request.POST['canal_venta']).exists() == False):
                meta = form.save(commit=False)

                if (meta.meta_ingresada != 0 and meta.meta_instalada != 0):
                    meta.meta_ingresada = request.POST['meta_ingresada']
                    meta.meta_instalada = request.POST['meta_instalada']
                    meta.mes = request.POST['mes']
                    meta.anio = request.POST['anio']
                    meta.canal_venta = request.POST['canal_venta']
                    meta.save()
                    messages.success(
                        request, 'El registro ha sido ingresado!.')
                    form = MetaForm()
                else:
                    messages.error(
                        request, 'Las metas no pueden tener un valor  = 0 (cero)!.')
            else:
                messages.error(
                    request, 'Ya existe una meta para el mes indicado!.')

    else:
        form = MetaForm()

    return render(request, 'sellingsapp/metas_add.html', {'form': form})

# ---------------------------------------------------------
#   Vista basada en clase para listar las metas de venta
# ------------------------------------------------------------

class metaIndex(ListView):
    model = Metas
    template_name = 'sellingsapp/metas_list.html'
    context_object_name = 'metas_list'
    paginate_by = 7
    ordering = ['id']

# -----------------------------------------------------------
#   Vista basada en clase para Editar las metas de venta
# ------------------------------------------------------------
class metaDetail(UpdateView):
    model = Metas
    fields = ['id', 'meta_ingresada', 'meta_instalada',
              'mes', 'anio', 'canal_venta']
    template_name = 'sellingsapp/metas_detail.html'

    def form_valid(self, form):
        meta = form.save(commit=False)
        objTarifa = getMeta(meta.mes, meta.anio, meta.canal_venta)

        if (meta.meta_ingresada != 0 and meta.meta_instalada != 0):

            if (objTarifa.exists() == True):
                if (objTarifa[0].id == meta.id):
                    meta.save()
                    messages.success(
                        self.request, 'El registro ha sido actualizado!.')
                else:
                    messages.error(
                        self.request, 'Ya existe una meta para el mes indicado!.')
            else:
                meta.save()
                messages.success(
                    self.request, 'El registro ha sido actualizado!.')
        else:
            messages.error(
                self.request, 'Las metas no pueden tener un valor  = 0 (cero)!.')

        return self.render_to_response(self.get_context_data(form=form))

# ------------------------------------------------------------
#       Función para eliminar las metas registradas
# ------------------------------------------------------------
def metaDelete(request, id_meta):
    # busca el modelo idenficado por el id
    meta_delete = get_object_or_404(Metas, pk=id_meta)
    meta_delete.delete()
    custom_message = " | "+meta_delete.mes + \
        " " + str(meta_delete.anio) + " | "
    messages.success(request, "El registro " + str(id_meta) +
                     "-" + custom_message+" ha sido eliminado")

    return redirect('metaList')

# ---------------------------------------------------------------------------
#       Funcion para registrar una nueva Meta para diferente canal de venta
# ----------------------------------------------------------------------------

def tarifaAdd(request):
    if request.method == 'POST':
        form = TarifaForm(request.POST)
        if form.is_valid():
            if (getTarifa(request.POST['nombre_rango'], request.POST['porce_title']).exists() == False):
                tarifa = form.save(commit=False)

                if (tarifa.limite_inf != 0 and tarifa.limite_sup != 0):

                    if(tarifa.limite_inf < tarifa.limite_sup):
                        tarifa.limite_inf = request.POST['limite_inf']
                        tarifa.limite_sup = request.POST['limite_sup']
                        tarifa.nombre_rango = request.POST['nombre_rango']
                        tarifa.porce_title = request.POST['porce_title']
                        tarifa.comision = request.POST['comision']
                        tarifa.save()
                        messages.success(
                            request, 'El registro ha sido ingresado!.')
                        form = TarifaForm()
                    else:
                        messages.error(
                            request, 'El límite inferior del rango debe ser menor al límite superior!')

                else:
                    messages.error(
                        request, 'Los  límites de las tarifas no pueden tener un valor  = 0 (cero)!.')
            else:
                messages.error(
                    request, 'Ya existe una tarifa registrada para el rango límite indicado!.')

    else:
        form = TarifaForm()

    return render(request, 'sellingsapp/tarifas_add.html', {'form': form})

# ----------------------------------------------------------------------
#   Vista basada en clase para listar las TARIFAS de comision de ventas
# ----------------------------------------------------------------------
class tarifaIndex(ListView):
    model = Tarifas
    template_name = 'sellingsapp/tarifas_list.html'
    context_object_name = 'tarifas_list'
    paginate_by = 7
    ordering = ['id']

# -----------------------------------------------------------
#   Vista basada en clase para Editar las metas de venta
# ------------------------------------------------------------

class tarifaDetail(UpdateView):
    model = Tarifas
    fields = ['id', 'limite_inf', 'limite_sup',
              'nombre_rango', 'porce_title', 'comision']
    template_name = 'sellingsapp/tarifas_detail.html'

    def form_valid(self, form):
        isValid = True
        tarifa = form.save(commit=False)
        objTarifa = getTarifa(tarifa.nombre_rango, tarifa.porce_title)

        if (tarifa.limite_inf == 0 and tarifa.limite_sup == 0):
            isValid = False
            messages.error(
                self.request, 'Los  límites de las tarifas no pueden tener un valor  = 0 (cero)!.')

        if (objTarifa.exists() == True):
            if (objTarifa[0].id != tarifa.id):
                isValid = False
                messages.error(
                    self.request, 'Ya existe una tarifa registrada para el rango límite indicado!.')

        if(tarifa.limite_inf > tarifa.limite_sup):
            isValid = False
            messages.error(
                self.request, 'El límite inferior del rango debe ser menor al límite superior!')

        if isValid == True:
            tarifa.save()
            messages.success(self.request, 'El registro ha sido actualizado!.')

        return self.render_to_response(self.get_context_data(form=form))

# ------------------------------------------------------------
#       Función para Eliminar las tarifas registradas
# ------------------------------------------------------------
def tarifaDelete(request, id_tarifa):
    # busca el modelo idenficado por el id
    tarifa_delete = get_object_or_404(Tarifas, pk=id_tarifa)
    tarifa_delete.delete()
    custom_message = " | rango "+tarifa_delete.nombre_rango + \
        " Comisión " + str(tarifa_delete.comision) + " | "
    messages.success(request, "El registro " + str(id_tarifa) +
                     "-" + custom_message+" ha sido eliminado")

    return redirect('tarifaList')

# ---------------------------------------------------------------
#                Función para adicionar días Off
# --------------------------------------------------------------
def feriadoAdd(request):
    if request.method == 'POST':
        form = FeriadoForm(request.POST)

        if form.is_valid():
            if existFeriado(request.POST['fecha'])==False:
                feriado = form.save(commit=False)
                feriado.fecha = request.POST['fecha']
                feriado.notes = request.POST['notes']
                feriado.save()
                messages.success(request, 'El registro ha sido ingresado!.')
                form = FeriadoForm()
            else:
                messages.error(request, 'La fecha indicada ya existe')

    else:
        form = FeriadoForm()

    return render(request, 'sellingsapp/feriados_add.html', {'form': form})

# -------------------------------------------------------------------------------------
#     Función para verificar si una fecha ya se encuentra ingresada como feriado
# -------------------------------------------------------------------------------------
def existFeriado(fecha,pk='none'):
    try:
       if pk=='none':
            sql_query = "fecha=%s"
            result = Feriados.objects.extra(where=[sql_query], params=[fecha])
       else:
            sql_query = "fecha=%s and id!=%s"
            result = Feriados.objects.extra(where=[sql_query], params=[fecha,pk])

    except Feriados.DoesNotExist:
        result = []

    if (result.exists() == True):
        return True
    else:
        return False

# ------------------------------------------------------------------
#   Vista basada en clase para Editar los días feriados o días Off
# ------------------------------------------------------------------

class feriadoDetail(UpdateView):
    model = Feriados
    fields = ['fecha','notes']
    template_name = 'sellingsapp/feriados_detail.html'

    def form_valid(self, form):
        feriado = form.save(commit=False)

        if (existFeriado(self.request.POST['fecha'],feriado.id)):
            messages.error(self.request, 'La fecha indicada ya se encuentra registrada!')
        else:
            feriado.save()
            messages.success(self.request, 'El registro ha sido actualizado!.')

        return self.render_to_response(self.get_context_data(form=form))

# ----------------------------------------------------------------------
#   Vista basada en clase para listar los días feriados o días OFF
# ----------------------------------------------------------------------
class feriadoIndex(ListView):
    model = Feriados
    template_name = 'sellingsapp/feriados_list.html'
    context_object_name = 'feriados_list'
    paginate_by = 7
    ordering = ['id']

# ---------------------------------------------------------------------
#    Función para Eliminar los días feriados o dias Off registrados
# ---------------------------------------------------------------------
def feriadoDelete(request, id_feriado):
    # busca el modelo idenficado por el id
    feriado_delete = get_object_or_404(Feriados, pk=id_feriado)
    feriado_delete.delete()
    custom_message = " | fecha "+str(feriado_delete.fecha)
    messages.success(request, "El registro " + str(id_feriado) +
                     "-" + custom_message+" ha sido eliminado")

    return redirect('feriadoList')

#----------------------------------------------------------------------
#     Función para exportar las ventas registradas a EXCEL
# ---------------------------------------------------------------------
def export_solicitudes_xls(request):
    response = HttpResponse(content_type='application/ms-excel')
    response['Content-Disposition'] = 'attachment; filename="solicitudes.xls"'

    wb = xlwt.Workbook(encoding='utf-8')
    ws = wb.add_sheet('Solicitudes')

    # Sheet header, first row
    row_num = 0

    font_style = xlwt.XFStyle()
    font_style.font.bold = True

    columns = ['Producto', 'Estado', 'dia', 'mes','anio','notes','Rel.productos','Asesor','Cliente','cel. cliente' ]

    for col_num in range(len(columns)):
        ws.write(row_num, col_num, columns[col_num], font_style)

    # Sheet body, remaining rows
    font_style = xlwt.XFStyle()

    sql_query = "mes=%s and anio=%s"
    rows = Solicitud.objects.extra(where=[sql_query], params=[
        request.POST['search_mes_excel'], request.POST['anio']])

    # rows = Solicitud.objects.all().select_related('asesor')

    for row in rows:
        row_num = row_num + 1
        ws.write(row_num, 0, row.product_name, font_style)
        ws.write(row_num, 1, row.status, font_style)
        ws.write(row_num, 2, row.dia, font_style)
        ws.write(row_num, 3, row.mes, font_style)
        ws.write(row_num, 4, row.anio, font_style)
        ws.write(row_num, 5, row.notes, font_style)
        ws.write(row_num, 6, row.product_cant, font_style)
        ws.write(row_num, 7, row.asesor.cc_id, font_style)
        ws.write(row_num, 8, row.nomcliente, font_style)
        ws.write(row_num, 9, row.celcliente, font_style)

    wb.save(response)
    return response