import os
import sys
import gzip
import copy
import csv
if sys.version[0] == "2":
    import cPickle as pckl
elif sys.version[0] == "3":
    import pickle as pckl
import numpy as np
import scipy
from scipy import ndimage
import scipy.misc
import matplotlib.pyplot as plt

from keras import backend as K
from keras.utils import to_categorical
from keras.datasets.cifar import load_batch

# Here, source files / folders must be specified accordingly.
cifar10_folder = "/.../cifar-10-batches-py/"
mnist_file = "/.../mnist.pkl.gz"
gtsrb_folder = "/.../GTSRB_Final_Training_Images/GTSRB/Final_Training/Images/"

class Stdataset(object):
    """A class to provide access to used datasets.

    The main variables of this class are:

    self.res :: int
        The image resolution for x and y (e.g. 32 for CIFAR10 or 28 for MNIST).
    self.channels :: int
        The number of image channels (e.g. 3 for CIFAR10 or 1 for MNIST).
    self.classes :: int
        The number of classes (e.g. 10 for CIFAR10 or 43 for GTSRB).
    self.DATAX :: dictionary
        Dictionary of datasets 'train', 'valid', and 'test'. Each item
        contains a 4D tensor: [samples, res, res, channels] for MNIST and CIFAR10.
        For GTSRB entire tracks of 30 images are stored in each sample:
            [tracks, res, res, channels * 30]
    self.DATAY :: dictionary
        Dictionary of ground-truth labels 'train', 'valid', and 'test'. Each item
        contains a 2D tensor of one-hot encoded class labels:
            [samples / tracks, classes]

    Parameter:
    ----------
    dataset :: string
        Specifies the used dataset: 'mnist', 'cifar10', 'gtsrb'
    """
    def __init__(self, dataset):
        self.dataset = dataset

        # Load and store dataset.
        # ====================================================================
        if dataset == "cifar10":
        # ====================================================================
            self.res = 32
            self.channels = 3
            self.classes = 10
            # Load images from file.
            num_train_samples = 50000
            x = {}
            y = {}
            self.DATAX = {}
            self.DATAY = {}
            x["train"] = np.empty((num_train_samples, 3, 32, 32), dtype='uint8')
            y["train"] = np.empty((num_train_samples,), dtype='uint8')
            for i in range(1, 6):
                fpath = os.path.join(cifar10_folder, 'data_batch_' + str(i))
                (x["train"][(i - 1) * 10000: i * 10000, :, :, :], y["train"][(i - 1) * 10000: i * 10000]) = load_batch(fpath)
            # Load also the test images.
            fpath = os.path.join(cifar10_folder, 'test_batch')
            x["test"], y["test"] = load_batch(fpath)
            y["train"] = np.reshape(y["train"], (len(y["train"]), 1))
            y["test"] = np.reshape(y["test"], (len(y["test"]), 1))
            # Resort data dimensions.
            if K.image_data_format() == 'channels_last':
                x["train"] = x["train"].transpose(0, 2, 3, 1)
                x["test"] = x["test"].transpose(0, 2, 3, 1)
            # Permute training images to extract validation.
            p = np.random.permutation(range(x["train"].shape[0]))
            x["train"] = x["train"][p]
            y["train"] = y["train"][p]
            # Split training data in train / valid.
            valid_size = x["train"].shape[0] // 5
            x["valid"] = x["train"][0:valid_size]
            y["valid"] = y["train"][0:valid_size]
            x["train"] = x["train"][valid_size:-1]
            y["train"] = y["train"][valid_size:-1]
            # Convert / normalize images.
            for t in ["train", "valid", "test"]:
                x[t] = x[t].astype('float32')
                self.DATAX[t] = x[t] / 255
                # Convert class vectors to binary class matrices.
                self.DATAY[t] = to_categorical(y[t], self.classes)

        # ====================================================================
        elif dataset == "mnist":
        # ====================================================================
            self.res = 28
            self.channels = 1
            self.classes = 10
            self.DATAY = {}
            self.DATAX = {}
            # Load dataset from file.
            DATA = {}
            f = gzip.open(mnist_file, "rb")
            if sys.version[0] == "2":
                DATA["train"], DATA["valid"], DATA["test"] = pckl.load(f)
            elif sys.version[0] == "3":
                DATA["train"], DATA["valid"], DATA["test"] = pckl.load(f, encoding="latin1")
            f.close()
            # Convert to standardized format.
            for a in ["train", "valid", "test"]:
                samples = DATA[a][1].shape[0]
                self.DATAX[a] = np.reshape(DATA[a][0], [samples, self.res, self.res])
                self.DATAX[a] = self.DATAX[a].astype("float32")
                self.DATAX[a] = self.DATAX[a][:,:,:,np.newaxis]
                self.DATAY[a] = to_categorical(DATA[a][1], self.classes)

        # ====================================================================
        elif dataset == "gtsrb":
        # ====================================================================
            print("Preparing GTSRB dataset ...")
            self.res = 32
            self.channels = 3
            self.classes = 43
            self.DATAX = {}
            self.DATAY = {}
            # Extract dataset from files.
            images = [] # list of all images
            labels = [] # corresponding labels
            # Loop over all 43 classes and load files.
            for c in range(0, 43):
                prefix = gtsrb_folder + '/' + format(c, '05d') + '/' # subdirectory for class
                gtFile = open(prefix + 'GT-'+ format(c, '05d') + '.csv') # annotations file
                gtReader = csv.reader(gtFile, delimiter=';') # csv parser for annotations file
                # Loop over all images in current annotations file
                for row_idx, row in enumerate(gtReader):
                    if row_idx != 0:
                        images.append(plt.imread(prefix + row[0])) # the 1th column is the filename
                        labels.append(row[7]) # the 8th column is the label
                gtFile.close()
            # Determine number of tracks for single datasets.
            tracks = len(images) // 30
            num_tracks = {
                "test": tracks // 10,
                "valid": tracks // 10,
                "train": 0
            }
            num_tracks["train"] = tracks - num_tracks["valid"] - num_tracks["test"]
            # Generate overall dataset in DATAX, DATAY.
            # We use the tracks as temporal input sequences.
            # All samples in a track (30) are stored in the feature dimension.
            # Hence, the number of samples = number of tracks.
            DATAX = np.empty((tracks, self.res, self.res, 30 * 3))
            DATAY = np.empty((tracks,), dtype="uint8")
            for i in range(len(images)):
                images[i] = images[i].astype('float32')
                images[i] /= 255.0
            # Resize all images to same (res, res) size.
            for i in range(tracks):
                for f in range(30):
                    img_square = scipy.misc.imresize(images[f + i * 30], size=(self.res, self.res))
                    DATAX[i,:,:,f * 3:(f + 1) * 3] = img_square[:,:,:] / 255.0
                DATAY[i] = labels[i * 30]
            # Permute dataset (tracks, not single images) before split.
            # This is necessary, because tracks are ordered by classes.
            p = np.random.permutation(range(DATAX.shape[0]))
            DATAX = DATAX[p]
            DATAY = DATAY[p]
            # Split dataset into train / valid / test.
            self.DATAX["test"] = DATAX[0:num_tracks["test"],:,:,:]
            self.DATAY["test"] = to_categorical(DATAY[0:num_tracks["test"]], self.classes)
            self.DATAX["valid"] = DATAX[num_tracks["test"]:num_tracks["test"] + num_tracks["valid"],:,:,:]
            self.DATAY["valid"] = to_categorical(DATAY[num_tracks["test"]:num_tracks["test"] + num_tracks["valid"]], self.classes)
            self.DATAX["train"] = DATAX[num_tracks["test"] + num_tracks["valid"]:-1,:,:,:]
            self.DATAY["train"] = to_categorical(DATAY[num_tracks["test"] + num_tracks["valid"]:-1], self.classes)

        # Print some general information about dataset.
        print('Info for dataset: ' + self.dataset)
        print('    x/y_train shape: ' + str(self.DATAX["train"].shape) + "   " + str(self.DATAY["train"].shape))
        print('    valid samples  : ', self.DATAX["valid"].shape[0])
        print('    test samples   : ', self.DATAX["test"].shape[0])

    def shuffle(self):
        """Shuffle dataset and redraw 'train', 'valid', and 'test' data.
        """
        sets = ["train", "valid", "test"]
        # Get number of samples.
        samples = {}
        for t in sets:
            samples[t] = self.DATAX[t].shape[0]
        # Aggregate datasets.
        DATAX = np.concatenate([self.DATAX[t] for t in sets], axis=0)
        DATAY = np.concatenate([self.DATAY[t] for t in sets], axis=0)
        # Permutate data.
        p = np.random.permutation(range(DATAX.shape[0]))
        DATAX = DATAX[p]
        DATAY = DATAY[p]
        # Split dataset into train / valid / test.
        self.DATAX["test"] = DATAX[0:samples["test"],:,:,:]
        self.DATAY["test"] = DATAY[0:samples["test"]]
        self.DATAX["valid"] = DATAX[samples["test"]:samples["test"] + samples["valid"],:,:,:]
        self.DATAY["valid"] = DATAY[samples["test"]:samples["test"] + samples["valid"]]
        self.DATAX["train"] = DATAX[samples["test"] + samples["valid"]:-1,:,:,:]
        self.DATAY["train"] = DATAY[samples["test"] + samples["valid"]:-1]


import nest
import numpy.testing as nptest
import pandas as pd
import pytest

def build_net(num_threads=1):
    """
    Build network with specified number of threads.

    The following network is built:
        - Create 100 neurons
            - 1, 3, ..., 99 are sources
            - 2, 4, ..., 100 are targets
        - Connect 1, 3, ..., 69 -> 2, 4, ..., 70 with `static_synapse`
        - Connect 31, 33, ..., 99   -> 32, 34, ..., 100 with `stdp_synapse`
    """

    nest.ResetKernel()

    nest.local_num_threads = num_threads

    nest.Create("iaf_psc_alpha", 100)
    static_sources = nest.NodeCollection(list(range(1, 70, 2)))
    static_targets = nest.NodeCollection(list(range(2, 71, 2)))
    stdp_sources = nest.NodeCollection(list(range(31, 100, 2)))
    stdp_targets = nest.NodeCollection(list(range(32, 101, 2)))

    nest.Connect(static_sources, static_targets, "one_to_one", "static_synapse")
    nest.Connect(stdp_sources, stdp_targets, "one_to_one", "stdp_synapse")

    return {
        "static_synapse": {"sources": static_sources, "targets": static_targets},
        "stdp_synapse": {"sources": stdp_sources, "targets": stdp_targets},
    }

@pytest.fixture(scope="module")
def nodes():
    """
    Fixture that returns the nodes of the network built on a single thread.
    """

    nodes = build_net(num_threads=1)
    return nodes

@pytest.mark.parametrize("syn_model", ["static_synapse", "stdp_synapse"])
def test_retrieve_correct_sources_and_targets(nodes, syn_model):
    """
    Verify that the expected sources and targets are retrieved.
    """

    conns = nest.GetConnections(synapse_model=syn_model)
    actual_sources = conns.get("source")
    actual_targets = conns.get("target")

    expected_sources = nodes[f"{syn_model}"]["sources"].tolist()
    expected_targets = nodes[f"{syn_model}"]["targets"].tolist()

    nptest.assert_array_equal(actual_sources, expected_sources)
    nptest.assert_array_equal(actual_targets, expected_targets)

def test_retrieve_all_connections(nodes):
    """
    Test retrieval of all connections.

    Note that ``static_synapse`` connections will come first.
    """

    conns = nest.GetConnections()

    actual_sources = conns.get("source")
    actual_targets = conns.get("target")

    expected_static_sources = nodes["static_synapse"]["sources"].tolist()
    expected_stdp_sources = nodes["stdp_synapse"]["sources"].tolist()
    expected_all_sources = expected_static_sources + expected_stdp_sources

    expected_static_targets = nodes["static_synapse"]["targets"].tolist()
    expected_stdp_targets = nodes["stdp_synapse"]["targets"].tolist()
    expected_all_targets = expected_static_targets + expected_stdp_targets

    nptest.assert_array_equal(actual_sources, expected_all_sources)
    nptest.assert_array_equal(actual_targets, expected_all_targets)

@pytest.mark.parametrize(
    ("source_filter", "target_filter"),
    [(True, False), (False, True), (True, True)],
)
def test_retrieve_connections_with_sliced_node_collections(nodes, source_filter, target_filter):
    """
    Test retrieval of connections for a subset of sources and targets.

    The test ensures that retrieval of a subset of connections works when
    filtering by source and/or target nodes.

    .. note::

        The source and target ``NodeCollection``s returned by the fixture are
        first converted to lists of node ids. The source and target lists are
        then concatenated and a new ``NodeCollection`` with the sliced node ids
        is created if the nodes will be used as filter. We have to convert to
        lists first because it is not possible to add a ``NodeCollection`` to
        a sliced composite.
    """

    # Take first 3 static sources and targets
    src_static = nodes["static_synapse"]["sources"][:3].tolist()
    tgt_static = nodes["static_synapse"]["targets"][:3].tolist()

    # Take final 3 stpd sources and targes to avoid those with static+stdp
    src_stdp = nodes["stdp_synapse"]["sources"][-3:].tolist()
    tgt_stpd = nodes["stdp_synapse"]["targets"][-3:].tolist()

    src_all = src_static + src_stdp
    tgt_all = tgt_static + tgt_stpd

    sources = nest.NodeCollection(src_all) if source_filter else None
    targets = nest.NodeCollection(tgt_all) if target_filter else None

    conns = nest.GetConnections(source=sources, target=targets)

    actual_sources = conns.get("source")
    actual_targets = conns.get("target")

    nptest.assert_array_equal(actual_sources, src_all)
    nptest.assert_array_equal(actual_targets, tgt_all)

def test_retrieve_connections_with_nodes_and_synapse(nodes):
    """
    Test retrieval of connections with node subset and synapse model.

    The test ensures that retrieval of a subset of connections works when
    filtering by nodes and synapse model.
    """

    # Take first 3 static sources and targets
    src_static = nodes["static_synapse"]["sources"][:3].tolist()
    tgt_static = nodes["static_synapse"]["targets"][:3].tolist()

    # Take final 3 stpd sources and targes to avoid those with static+stdp
    src_stdp = nodes["stdp_synapse"]["sources"][-3:].tolist()
    tgt_stpd = nodes["stdp_synapse"]["targets"][-3:].tolist()

    src_all = src_static + src_stdp
    tgt_all = tgt_static + tgt_stpd

    conns = nest.GetConnections(
        source=nest.NodeCollection(src_all), target=nest.NodeCollection(tgt_all), synapse_model="static_synapse"
    )

    actual_sources = conns.get("source")
    actual_targets = conns.get("target")

    nptest.assert_array_equal(actual_sources, src_static)
    nptest.assert_array_equal(actual_targets, tgt_static)

@pytest.mark.skipif_missing_threads
@pytest.mark.parametrize("syn_model", ["static_synapse", "stdp_synapse"])
def test_retrieve_connections_multithreaded(syn_model):
    """
    Test multithreaded retrieval of connections filtered by synapse model.

    The relative ordering of connection data from threads is random under
    OpenMP. Hence, the retrieved connections must be sorted before comparing
    with expectation.
    """

    nodes = build_net(num_threads=4)

    syn_collection = nest.GetConnections(synapse_model=syn_model)
    actual_connections = pd.DataFrame.from_dict(syn_collection.get(["source", "target"]))
    actual_connections.sort_values(by=["source", "target"], ignore_index=True, inplace=True)
    actual_sources = actual_connections["source"]
    actual_targets = actual_connections["target"]

    expected_sources = nodes[f"{syn_model}"]["sources"].tolist()
    expected_targets = nodes[f"{syn_model}"]["targets"].tolist()

    nptest.assert_array_equal(actual_sources, expected_sources)
    nptest.assert_array_equal(actual_targets, expected_targets)


import io
import csv
import os

DICT_NAME = "Entries.csv"
current_path = os.path.dirname(os.path.realpath(__file__))
dict_addr = os.path.abspath(os.path.join(current_path, "../in/" + DICT_NAME))

phon_to_written = {}
written_to_phon = {}
with io.open(dict_addr, 'r', encoding="UTF8") as csvFile:
    reader = csv.reader(csvFile)
    for row in reader:
        phon_to_written[row[0]] = row[1]
        written_to_phon[row[1]] = row[0]
csvFile.close()

results = []

def calPossibleSequence(str, phon_to_written, prev_words):
    temp = ""
    length = len(str)
    for i in range(length):
        temp += str[i]
        if temp in phon_to_written:
            if i != length - 1:
                calPossibleSequence(str[i + 1:], phon_to_written ,prev_words + phon_to_written[temp] + " ")
            else:
                result = prev_words + phon_to_written[temp]
                results.append(result)

def saveResult(results, out_file_name):
    out_addr = "../out/" + out_file_name
    with open(out_addr, 'w', encoding="UTF8") as out:
        for result in results:
            out.write(result + "\n")

file_name = input()
file_addr = os.path.abspath(os.path.join(current_path, "../in/" + file_name))
with io.open(file_addr, 'r', encoding="UTF-8") as input_file:
    sentence = input_file.readline()
input_file.close()

phon_form = ""
for word in sentence.split():
    phon_form += written_to_phon[word]

calPossibleSequence(phon_form, phon_to_written, "")
saveResult(results, "out_" + file_name)


from wordcloud import WordCloud
from get_tweet_corpus import TweetCorpus
from BuildNMF import BuildNMF
import matplotlib.pyplot as plt
import numpy as np
import os

def topic_word_frequency(nmf_mod, topic_idx):
    ''' get dict w/ words in corpus as keys and their frequencies as values

    input:
    - nmf_mod (BuildNMF object): this should be a fitted BuildNMF object
    - topic_idx (int): index of the topic for which we want to build the dict

    returns:
    - dict w/ words in corpus as keys and their frequencies as values
      (since words were stemmed during TF-IDF construction, we also map
      each stemmed word back to a full word)

    '''

    tot = np.sum(nmf_mod.H[topic_idx])
    frequencies = [val / tot for val in nmf_mod.H[topic_idx]]

    # convert stems back to full words
    #(pick the shortest word of all the words
    # in the corpus that share the same stem)
    unstemmed_words = []
    for word in nmf_mod.words:
        unstemmed = min(list(nmf_mod.stem_lookup_table[word]), key=len)
        unstemmed_words.append(unstemmed)

    return dict(zip(unstemmed_words, frequencies))

def topic_word_cloud(nmf_mod, topic_idx, max_words=200, figsize=(14, 8),
                     width=2400, height=1300, ax=None, show=False,
                     savepath=None):
    ''' create wordcloud for a particular topic

    input:
    - nmf_mod (BuildNMF object): this should be a fitted BuildNMF object
    - topic_idx (int): index of the topic used to generate wordcloud
    - max_words (int): maximum number of words in wordcloud
    - figsize (tuple): size of the matplotlib figure that displays the wordcloud
    - width (int): width of the wordcloud image
    - height (int): height of the wordcloud image
    - ax (matplotlib axes): axes to use to display wordcloud
    - show (boolean): if True then wordcloud is displayed
    - savepath (string): file path where wordcloud image will be saved

    output:
    - if show == True, displays the wordcloud

    returns:
    - None

    '''

    wc = WordCloud(background_color='white', max_words=max_words, width=width,
                   height=height)
    word_freq = topic_word_frequency(nmf_mod, topic_idx)

    # Fit WordCloud object
    wc.fit_words(word_freq)

    if not ax:
        fig = plt.figure(figsize=figsize)
        ax = fig.add_subplot(111)
    ax.imshow(wc)
    ax.axis('off')

    if show:
        plt.show()

    if savepath:
        plt.savefig(savepath)

    plt.close('all')

def gen_wordclouds_for_all_topics(nmf_mod):
    """ generate wordclouds for all topics

    input:
    - nmf_mod (BuildNMF object): this should be a fitted BuildNMF object

    output:
    - wordcloud images are saved to directory images_##topics/ with the names
      'wordcloud_ranking#_topic#' where the ranking # corresponds to the UMass
      coherence score of that topic (ranking of zero is the most coherent),
      and topic # is the index of that topic

    returns:
    - None

    """

    # generate wordclouds for most coherent topics
    # NOTE: UMass scores are always negative and the closer a UMass score gets
    # to zero, the more coherent that topic is
    coherence_scores = np.abs(np.array(nmf_mod.get_umass_coherence_scores()))
    indices = np.argsort(coherence_scores)
    coherence_scores_sorted = coherence_scores[indices]

    directory = '../pres_wordclouds_' + str(nmf_mod.num_topics)+ 'topics/'
    if not os.path.exists(directory):
        os.makedirs(directory)

    for ranking, idx in enumerate(indices):
        topic_word_cloud(nmf_mod, idx,
                        savepath= directory + 'wordcloud_ranking'+ str(ranking)\
                        + '_topic' + str(idx) + '.png')

if __name__ == '__main__':
    tc = TweetCorpus(['../data/03_28_2018_18_02.pkl',
                      '../data/03_30_2018_15_37.pkl'])

    # Get wordclouds for all topics in tweet corpus
    ideal_num_topics = 50 # determined from coherence score boxplots
    nmf_mod = BuildNMF(tc.hashtag_aggregated_corpus,
                       num_topics=ideal_num_topics)
    nmf = nmf_mod.fit(display=True)

    gen_wordclouds_for_all_topics(nmf_mod)


from django.contrib.auth import get_user_model

from django.db import IntegrityError
from django.test import TestCase

from ...models import Vouch

class VouchCreationTest(TestCase):
    def setUp(self):
        """Initialise user instance, which creates a profile instance."""

        User = get_user_model()

        self.user_1 = User.objects.create(username="user_1")

        self.user_2 = User.objects.create(username="user_2")

    def test_string_representation(self):
        """Test vouch has appropriate string method"""

        vouch = Vouch.objects.create(voucher=self.user_2, vouchee=self.user_1)
        self.assertEqual(str(vouch), self.user_2.username)

    def test_no_duplicate_vouch(self):
        """Check that you can only vouch for a single user once"""

        Vouch.objects.create(voucher=self.user_1, vouchee=self.user_2)
        with self.assertRaisesMessage(
            IntegrityError,
            "UNIQUE constraint failed: accounts_vouch.voucher_id, accounts_vouch.vouchee_id",
        ):
            Vouch.objects.create(voucher=self.user_1, vouchee=self.user_2)


import pickle
import json
import os
from pathlib import Path
import wandb
class WandbLogger:
    def __init__(self, project_name:str, entity_name:str, model_params:dict):
        self.run = wandb.init(config=model_params,reinit=True, project=project_name, entity=entity_name)
    def log(self, kwargs):
        self.run.log(kwargs)
    def log_artifact(self, name:str, model, metrics_dict):
        temp_dir = Path("temp/")
        os.makedirs(temp_dir, exist_ok=True)

        pickle.dump(model, open(temp_dir / f"{name}.pickle", 'wb'))
        json.dump(metrics_dict, open(temp_dir /f"{name}.json", 'w'))
        self.artifact = wandb.Artifact(name=name, type='model')
        self.artifact.add_file(temp_dir /f"{name}.pickle")
        self.artifact.add_file(temp_dir /f"{name}.json")
        self.run.log_artifact(self.artifact)


import torch.nn as nn

class Conv2d(nn.Module):
    """Implementation of a simple cascade of 2D convolutions.

    If batchnorm is set to True, batch normalization layer is applied after each convolution.
    """

    def __init__(self, in_channels, out_channels, hidden_channels, n_convs=3, activation=nn.PReLU(), batchnorm=False):
        """Inits Conv2d.

        Parameters
        ----------
        in_channels: int
            Number of input channels.
        out_channels: int
            Number of output channels.
        hidden_channels: int
            Number of hidden channels.
        n_convs: int
            Number of convolutional layers.
        activation: nn.Module
            Activation function.
        batchnorm: bool
            If True a batch normalization layer is applied after every convolution.
        """
        super().__init__()

        self.conv = []
        for idx in range(n_convs):
            self.conv.append(
                nn.Conv2d(
                    in_channels if idx == 0 else hidden_channels,
                    hidden_channels if idx != n_convs - 1 else out_channels,
                    kernel_size=3,
                    padding=1,
                )
            )
            if batchnorm:
                self.conv.append(nn.BatchNorm2d(hidden_channels if idx != n_convs - 1 else out_channels, eps=1e-4))
            if idx != n_convs - 1:
                self.conv.append(activation)
        self.conv = nn.Sequential(*self.conv)

    def forward(self, x):
        """Performs the forward pass of Conv2d.

        Parameters
        ----------
        x: torch.Tensor
            Input tensor.

        Returns
        -------
        out: torch.Tensor
            Convoluted output.
        """
        out = self.conv(x)
        return out


import logging
import sys
import concurrent.futures
import urllib.request
import time

from urllib.error import URLError, HTTPError

from typing import (
    Dict,
    List,
)

logger = logging.getLogger(__name__)

## defaults
TIMEOUT = 30.0
URLS = (
    "https://google.com/",
    "http://archive.raspberrypi.org/debian",
    "http://raspbian.raspberrypi.org/raspbian",
)
VERBOSE = False
THREADS = 8

def check_url(url, timeout=TIMEOUT) -> bool:
    """
    Return True if a `url` is reachable, else False.

    >>> check_url(URLS[0])
    True
    """
    logger.debug(f"Trying to reach {url}")
    with urllib.request.urlopen(url, timeout=timeout) as _:
        logger.debug(f"Reached {url}")
        return True

def check_failed(url_up: Dict[str, bool]) -> List[str]:
    """
    >>> len(check_failed({
    ...     'https://www.google.com': True,
    ...     'http://archive.raspberrypi.org/debian': False,
    ...     'http://raspbian.raspberrypi.org/raspbian': False,
    ... }))
    2
    """
    return [u for u, ok in url_up.items() if not ok]

def i_can_has_threaded_connection(
    urls=URLS,
    timeout=TIMEOUT,
    verbose=VERBOSE,
    threads=THREADS,
) -> int:
    if not threads:
        threads = len(urls)
        logger.debug(f"Using {threads} threads.")

    # we need to keep track of the time since we might restart `as_completed`
    start = time.time()

    # we'll keep track of what urls are up with this
    url_up = {u: False for u in urls}

    with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as exe:
        try:
            while True:
                # check if we timed out
                time_left = timeout - (time.time() - start)
                logger.debug(f"{time_left:.2f} seconds left before timeout")
                if time_left < 0.0:
                    logger.debug(f"TIMEOUT:time_left < 0.0")
                    break

                # (re)submit all urls that aren't already up
                future_to_url = {
                    exe.submit(check_url, url, timeout=timeout): url
                    for url in urls
                    if not url_up[url]
                }

                # iterate through the futures as they complete
                for future in concurrent.futures.as_completed(
                    future_to_url, timeout=time_left,
                ):
                    url = future_to_url[future]
                    try:
                        url_up[url] = future.result()
                        logger.info(f"OK:{url}")
                    except (URLError, HTTPError) as e:
                        logger.debug(f"RETRY:{url}:{e}", exc_info=verbose)  # pragma no cover
                        continue  # pragma no cover

                # if we suceeded, we'll return 0
                if not check_failed(url_up):
                    logger.info(f"OK:All Urls.")
                    return 0

        except concurrent.futures.TimeoutError as e:
            logger.debug(f"TIMEOUT:executor:{e}")
            pass
        except ValueError as e:
            logger.error(f"{e}", exc_info=verbose)
            return -2
        except Exception as e:  # pragma no cover
            logger.error(f"Unexpected Error: {e}", exc_info=True)  # pragma no cover
            return -3  # pragma no cover

    failed = check_failed(url_up)
    logger.error(f"FAIL:{failed}")
    return len(failed) if len(failed) < 126 else -1

def cli_main(args=None):
    import argparse

    ap = argparse.ArgumentParser(
        description="Waits for a connection for `--timeout` seconds",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    ap.add_argument(
        "--timeout",
        help="before returning the number of inaccessable urls",
        type=float,
        default=TIMEOUT,
    )

    ap.add_argument(
        "--urls",
        help="to wait on in parallel",
        nargs="*",
        default=URLS,
    )

    ap.add_argument(
        "-v",
        "--verbose",
        action="store_true",
    )

    ap.add_argument(
        "--threads",
        type=int,
        help="size of thread pool (default is number of `--urls`)",
    )

    args = ap.parse_args(args)

    logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)

    return i_can_has_threaded_connection(**vars(args))

if __name__ == "__main__":
    sys.exit(cli_main())  # pragma no cover


from django.shortcuts import redirect, render
from django.contrib.auth.decorators import login_required
from .models import Category, Expense
from django.contrib import messages
from django.core.paginator import Paginator
import json
from django.http import JsonResponse, HttpResponse
from userpreferences.models import UserPreference
import datetime
import csv
import xlwt
from django.template.loader import render_to_string
from weasyprint import HTML
import tempfile
from django.db.models import Sum
from .forms import CategoryForm
from userincome.models import UserIncome
# Create your views here.

@login_required(login_url='login')
def index(request):
    expenses = Expense.objects.filter(owner=request.user)
    paginator = Paginator(expenses, 6)
    page_number = request.GET.get('page')
    page_obj = Paginator.get_page(paginator, page_number)
    try:
        currency = UserPreference.objects.get(user=request.user).currency
    except:
        currency = ""
    context = {
        'expenses': expenses,
        'page_obj': page_obj,
        'currency': currency,
    }
    return render(request, 'expenses/index.html', context)

def add_expense(request):
    categories = Category.objects.filter(owner=request.user)
    context = {
        'categories': categories,
        'values': request.POST
    }

    if request.method == 'GET':
        return render(request, 'expenses/add_expense.html', context)

    if request.method == 'POST':
        amount = request.POST['amount']

        if not amount:
            messages.error(request, 'Amount is required')

        description = request.POST['description']
        if not description:
            messages.error(request, 'Description is required')

        category = Category.objects.get(id=int(request.POST['category']))

        if amount and description and category:
            if request.POST['expense_date']:
                date = request.POST['expense_date']
                expense = Expense.objects.create(amount=amount, description=description, category=category, date=date, owner=request.user)
            else:
                expense = Expense.objects.create(amount=amount, description=description, category=category, owner=request.user)

            expense.save()
            messages.success(request, 'Expense saved successfully')
            return redirect('expenses')

        return render(request, 'expenses/add_expense.html', context)

def edit_expense(request, id):

    expense = Expense.objects.get(id=id)
    categories = Category.objects.filter(owner=request.user)
    context = {
        'expense': expense,
        'values': expense,
        'categories': categories,
    }

    if request.method == 'GET':
        return render(request, 'expenses/edit_expense.html', context)

    else:
        amount = request.POST['amount']

        if not amount:
            messages.error(request, 'Amount is required')

        description = request.POST['description']
        if not description:
            messages.error(request, 'Description is required')

        category = Category.objects.get(id=int(request.POST['category']))

        if amount and description and category:
            expense.amount=amount
            expense.description=description
            expense.category=category
            if request.POST['expense_date']:
                date = request.POST['expense_date']
                expense.date = date

            expense.save()

            messages.success(request, 'Expense updated successfully')
            return redirect('expenses')

        return render(request, 'expenses/edit_expense.html', context)

def delete_expense(request, id):
    expense = Expense.objects.get(id=id)
    expense.delete()
    messages.success(request, 'Expense deleted successfully')
    return redirect('expenses')

def search_expenses(request):
    if request.method == 'POST':
        search_str = json.loads(request.body).get('searchText')

        expenses = Expense.objects.filter(
            amount__istartswith=search_str,
            owner = request.user,
        ) | Expense.objects.filter(
            date__istartswith=search_str,
            owner = request.user,
        ) | Expense.objects.filter(
            description__icontains=search_str,
            owner = request.user,
        ) | Expense.objects.filter(
            category__name__icontains=search_str,
            owner = request.user,
        )

        data = expenses.values()
        for exp in data:
            exp['category'] = str(Category.objects.get(id=exp['category_id']))

        return JsonResponse(list(data), safe=False)

def expense_category_summary(request):
    todays_date = datetime.date.today()
    six_months_ago = todays_date-datetime.timedelta(days=30*6)
    expenses = Expense.objects.filter(owner=request.user,
                                      date__gte=six_months_ago, date__lte=todays_date)
    finalrep = {}

    def get_category(expense):
        return expense.category
    category_list = list(set(map(get_category, expenses)))

    def get_expense_category_amount(category):
        amount = 0
        filtered_by_category = expenses.filter(category=category)

        for item in filtered_by_category:
            amount += item.amount
        return amount

    for x in expenses:
        for y in category_list:
            finalrep[y.name] = get_expense_category_amount(y)

    amount_month = {}
    for exp in expenses:
        month = exp.date.strftime('%b')
        amount = exp.amount

        if month in amount_month.keys():
            amount_month[month] += amount
        else:
            amount_month[month] = amount

    return JsonResponse({'expense_category_data': finalrep, 'amount_month': amount_month}, safe=False)

def status_view(request):
    return render(request, 'expenses/stats.html')

def export_csv(request):
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = 'attachment; filename=Expenses' + str(datetime.datetime.now()) + '.csv'
    writer = csv.writer(response)
    writer.writerow(['Amount', 'Description', 'Category', 'Date'])
    expenses = Expense.objects.filter(owner=request.user)

    for expense in expenses:
        writer.writerow([expense.amount, expense.description, expense.category, expense.date])

    return response

def export_excel(request):
    response = HttpResponse(content_type='application/ms-excel')
    response['Content-Disposition'] = 'attachment; filename=Expenses' + str(datetime.datetime.now()) + '.xls'
    wb = xlwt.Workbook(encoding='utf-8')
    ws = wb.add_sheet('Expenses')
    row_num = 0
    font_style = xlwt.XFStyle()
    font_style.font.bold = True

    columns = ['Amount', 'Description', 'Category', 'Date']

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

    font_style = xlwt.XFStyle()

    rows = Expense.objects.filter(owner=request.user).values_list('amount', 'description', 'category', 'date')

    for row in rows:
        row_num += 1
        for col_num in range(len(row)):
            if isinstance(row[col_num], int):
                ws.write(row_num, col_num, str(Category.objects.get(id=row[col_num])), font_style)
            else:
                ws.write(row_num, col_num, str(row[col_num]), font_style)

    wb.save(response)

    return response

def export_pdf(request):
    response = HttpResponse(content_type='application/pdf')
    response['Content-Disposition'] = 'inline; attachment; filename=Expenses' + str(datetime.datetime.now()) + '.pdf'
    response['Content-Transfer-Encoding'] = 'binary'

    expenses = Expense.objects.filter(owner=request.user)

    sum = expenses.aggregate(Sum('amount'))

    html_string = render_to_string('expenses/pdf-output.html', {'expenses': expenses, 'total': sum['amount__sum']})
    html = HTML(string=html_string)

    result = html.write_pdf()

    with tempfile.NamedTemporaryFile(delete=True) as output:
        output.write(result)
        output.flush()
        output.seek(0)
        response.write(output.read())

    return response

def add_category(request, opt, pk):
    form = CategoryForm(request.POST)

    if form.is_valid():
        register = form.save(commit=False)
        register.owner = request.user
        register.save()
        messages.success(request, 'Category created successfully')

    if opt == 1:
        return redirect('add-expense')
    else:
        return redirect('edit-expense', pk)

def expense_income_charts_view(request):
    return render(request, 'expenses/expense_income_charts_view.html')

def expense_income_charts_summary(request):
    todays_date = datetime.date.today()
    six_months_ago = todays_date-datetime.timedelta(days=30*6)
    expenses_list = Expense.objects.filter(owner=request.user,
                                      date__gte=six_months_ago, date__lte=todays_date)

    incomes_list = UserIncome.objects.filter(owner=request.user,
                                      date__gte=six_months_ago, date__lte=todays_date)

    incomes = {}
    expenses = {}

    for exp in expenses_list:
        month = exp.date.strftime('%b')
        amount = exp.amount

        if month in expenses.keys():
            expenses[month] += amount
        else:
            expenses[month] = amount

    for inc in incomes_list:
        month = inc.date.strftime('%b')
        amount = inc.amount

        if month in incomes.keys():
            incomes[month] += amount
        else:
            incomes[month] = amount

    for month in incomes:
        if month not in expenses.keys():
            expenses[month] = 0

    for month in expenses:
        if month not in incomes.keys():
            incomes[month] = 0

    result_incomes = {}
    for key in sorted(incomes.keys()):
        result_incomes[key] = incomes[key]

    result_expenses = {}
    for key in sorted(expenses.keys()):
        result_expenses[key] = expenses[key]

    return JsonResponse({'incomes': result_incomes, 'expenses': result_expenses}, safe=False)


import datetime
from multiprocessing import context
from employees.forms import AdminAttendanceForm, AdminEmpAttendance, AdminLeaveForm, AdminRegularizationForm
from .tasks import calculate_salary, get_balance, get_month,get_year, leave_approval, leave_marked, leave_reject
from django.urls import reverse_lazy
from django.views.generic import DeleteView,UpdateView
from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import get_object_or_404, redirect, render
from django.contrib.auth.decorators import login_required
from admin.forms import AddEmployeeForm, DesignationForm, EditEmployeeForm, HolidayForm, InterviewForm, JobForm, MessageForm, PayrollEditForm, SalaryForm
from django.contrib import messages
from admin.models import Designations, Holidays, Payroll, Salary
from applicants.models import Applications, Interviews, Messages
from base.models import Department, Jobs
from employees.models import Attendance, AttendanceRegularization, EmployeeDesignation, Leave, LeaveCounter, YearCounter
from users.models import ApplicantProfile, Newuser
from django.db.models import Q
from django.core.paginator import Paginator,EmptyPage
from django.contrib.auth.views import PasswordChangeView

@login_required
def admin_home(request):
    employees = Newuser.objects.filter(is_employee=True).count()
    applicants = Newuser.objects.filter(is_applicant=True).count()
    department = Department.objects.all().count()
    designations = Designations.objects.all().count()
    interviews = Interviews.objects.filter(interview_date__gt=datetime.date.today()).count()
    leave_count = Leave.objects.filter(approval = 'pending').count()
    leaves = Leave.objects.filter().order_by('-id')[:3]
    regular_count = AttendanceRegularization.objects.filter(status='pending').count()
    regular = AttendanceRegularization.objects.filter().order_by('-id')[:3]
    att_count = Attendance.objects.filter(attendance_date = datetime.date.today(),shift='morning').exclude(entry_time = None).count()
    mon_leave = Leave.objects.filter(applied_date__month = datetime.date.today().month,applied_date__year=datetime.date.today().year).count()
    mon_app = Leave.objects.filter(applied_date__month = datetime.date.today().month,applied_date__year=datetime.date.today().year,approval='approved').count()
    att_per = int((att_count/employees)*100)
    reg_mon = AttendanceRegularization.objects.filter(date__month=datetime.date.today().month,date__year=datetime.date.today().year).count()
    reg_app = AttendanceRegularization.objects.filter(date__month=datetime.date.today().month,date__year=datetime.date.today().year,status='approved').count()
    up_int = Interviews.objects.filter(interview_date__gt=datetime.date.today()).order_by('interview_date').first()
    holiday = Holidays.objects.filter(date__gt = datetime.date.today()).order_by('date').first()
    jobs = Jobs.objects.filter().order_by('-id')[:2]
    job_count = Jobs.objects.all().count()
    context = {
       'employees':employees,
       'applicants':applicants,
       'department':department,
       'designations':designations,
       'interviews':interviews,
       'leave_count':leave_count,
       'leaves':leaves,
       'regular_count':regular_count,
       'regular':regular,
       'att_per':att_per,
       'att_count':att_count,
       'mon_leave':mon_leave,
       'mon_app':mon_app,
       'reg_mon':reg_mon,
       'reg_app':reg_app,
       'up_int':up_int,
       'holiday':holiday,
       'jobs':jobs,
       'job_count':job_count

    }
    return render(request, 'admin/admin_dashboard.html',context)

class AdPasswordChangeView(SuccessMessageMixin,PasswordChangeView):
   template_name = 'admin/admin_password_change.html'
   success_message = "Password Changed"
   success_url = reverse_lazy('admin_password_change')

# adding the employee

@login_required
def add_employee(request):
    if request.method == "POST":
        reg_form = AddEmployeeForm(request.POST)    #containes detials for newuser table
        salary_form = SalaryForm(request.POST)      #contines details for salary table
        desig_form = DesignationForm(request.POST)  #contains details for EmployeeDesignation table

        if reg_form.is_valid() and salary_form.is_valid() and desig_form.is_valid():
            user = reg_form.save(False)
            user.is_employee = True
            user.save()

            salary = salary_form.save(False)
            salary.user = user

            designation = desig_form.save(False)
            designation.user =user

           #to create month counter
            year = get_year()
            i = 1
            while i<=12:
                date = datetime.date(year,i,1)
                LeaveCounter.objects.create(cl=0,el=0,lp=0,sl=0,date=date,user=user)
                i = i+1

            #to create year
            YearCounter.objects.create(cl=0,el=0,lp=0,sl=0,user=user)

            #to create the attendance for that day
            Attendance.objects.create(attendance_date=datetime.date.today(),entry_time=None,exit_time=None,shift='morning',user=user)

            salary.save()
            designation.save()

            messages.success(request,f'Employee added')
            reg_form = AddEmployeeForm()
            salary_form = SalaryForm()
            desig_form = DesignationForm()
            return render(request, 'admin/add-employee.html', {'reg_form':reg_form, 'salary_form':salary_form, 'desig_form':desig_form })

        else:

            return render(request, 'admin/add-employee.html', {'reg_form':reg_form, 'salary_form':salary_form, 'desig_form':desig_form })

    else:
        reg_form = AddEmployeeForm()
        salary_form = SalaryForm()
        desig_form = DesignationForm()
        return render(request, 'admin/add-employee.html', {'reg_form':reg_form, 'salary_form':salary_form, 'desig_form':desig_form })

# view the employees in table
@login_required
def view_employee(request):
    employees = Newuser.objects.filter(is_employee = True).order_by('first_name')
    obj = EmployeeDesignation.objects.all()

    return render(request, 'admin/employee-view.html',{'employees':employees,'obj':obj})

#detailed view of an employee
@login_required
def employee_detail(request,pk):
    employee = Newuser.objects.get(id = pk)
    #to get designation of employee from EmployeeDesignation
    desig_obj = EmployeeDesignation.objects.get(user = pk)
    designation = Designations.objects.get(id = desig_obj.designation_id )

    #to get department of employee from EmployeeDesignation

    dep_obj = EmployeeDesignation.objects.get(user = pk)
    department = Department.objects.get(id = dep_obj.department_id )

    salary = Salary.objects.get(user = pk)

    return render(request, 'admin/employee-detail.html',{ 'employee':employee, 'designation':designation, 'department':department,'salary':salary})

#edit employee
@login_required
def employee_edit(request,pk):

    #get the instance that we are updateing currently
    emp = Newuser.objects.get(id = pk)
    sal = Salary.objects.get(user = pk)
    desig = EmployeeDesignation.objects.get(user = pk)

    if request.method == "POST":
        employee = EditEmployeeForm(request.POST, instance=emp)
        salary = SalaryForm(request.POST,instance=sal)
        designation = DesignationForm(request.POST,instance=desig)

        if employee.is_valid() and salary.is_valid() and designation.is_valid():
            employee.save()
            salary.save()
            designation.save()
            messages.success(request,f'Changes Saved for the Employee')
            return redirect(employee_detail,pk)
        else:
            return redirect(employee_detail,pk)

    else:

        reg_form = EditEmployeeForm(instance = emp)
        salary_form = SalaryForm(instance = sal)
        desig_form = DesignationForm(instance=desig)

        return render(request, 'admin/edit_employee.html', {'reg_form':reg_form, 'salary_form':salary_form, 'desig_form':desig_form })

#  Delete an employee
@login_required
def delete_employee(requset,pk):
    employee = Newuser.objects.get(id=pk)
    employee.delete()
    messages.success(requset,f'Employee Deleted')
    return redirect('view_employee')

# department
@login_required
def department(request):
    if request.method == "POST":
        department = Department()
        department.department_name = request.POST['department']
        department.save()
        messages.success(request,f'Department Added')
        return redirect('department')
    else:
        departments = Department.objects.all()
        return render(request, 'admin/department-view.html',{'departments':departments})

# Designations
@login_required
def designations(request):
    if request.method == "POST":
        designation = Designations()
        designation.designation = request.POST['designation']
        designation.save()
        messages.success(request,f'Designation Added')
        return redirect('designations')
    else:
        designations = Designations.objects.all()
        return render(request, 'admin/designation.html',{'designations':designations})

# Edit Designation
@login_required
def designation_edit(request,pk):
    if request.method == "POST":
        designation = Designations(id = pk)
        designation.designation = request.POST['designation']
        designation.save()
        messages.success(request,f'Designation Updated')
        return redirect('designations')
    else:
        edit = Designations.objects.get(id = pk)
        return render(request, 'admin/designation_edit.html',{'edit': edit})

# Edit Department
@login_required
def department_edit(request,pk):
    if request.method == "POST":
        department = Department(id = pk)
        department.department_name = request.POST['department']
        department.save()
        messages.success(request,f'Department Updated')
        return redirect('department')
    else:
        edit = Department.objects.get(id = pk)
        return render(request, 'admin/department_edit.html',{'edit': edit})

# delete Department
class DeleteDepartment(SuccessMessageMixin,DeleteView):
    template_name = 'admin/department_delete.html'
    success_message = "Department Deleted"
    success_url = reverse_lazy('department')

    def get_object(self):
        id =  self.kwargs.get("pk")
        # print(id)
        return get_object_or_404(Department, id=id)

# delete Designation
class DeleteDesignation(SuccessMessageMixin,DeleteView):
    template_name = 'admin/designation_delete.html'
    success_message = "Designation Deleted"
    success_url = reverse_lazy('designations')

    def get_object(self):
        id =  self.kwargs.get("pk")
        print(id)
        return get_object_or_404(Designations, id=id)

# job add for applicant
@login_required
def add_jobs(request):
    if request.method == "POST":
        form = JobForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request,f'Job Added')
            return redirect('add_jobs')
        else:
            return render(request, 'admin/add_jobs.html',{'form':form })
    else:
        form = JobForm()
        return render(request, 'admin/add_jobs.html',{'form':form })

# view all the jobs
@login_required
def view_all_jobs(request):
    jobs = Jobs.objects.all()
    return render(request, 'admin/view_jobs.html',{ 'jobs':jobs })

#detailed job view
@login_required
def job_detail_view(request,slug):
    job_id = Jobs.objects.get(id = slug)
    return render(request, 'admin/admin_jobdetail_view.html',{'job_id': job_id})

#edit Job
class EditJob(SuccessMessageMixin,UpdateView):
   model = Jobs
   form_class = JobForm
   template_name = 'admin/add_jobs.html'
   success_message = "Job Updated"

#delete job by admin
@login_required
def delete_job(request,pk):
    job = Jobs.objects.get(id=pk)
    job.delete()
    messages.success(request,f'Job Deleted')
    return redirect('view_all_jobs')

#view applicants by admin
@login_required
def view_applicants(request):
    applicants = Newuser.objects.filter(is_applicant=True)
    return render(request,'admin/view_applicants.html',{ 'applicants':applicants})

#view applicant details
@login_required
def applicant_details(request,pk):
    applicant = ApplicantProfile.objects.get(user=pk)
    return render(request, 'admin/view_applicant_details.html',{'applicant':applicant})

#  Delete an applicant
@login_required
def delete_applicants(requset,pk):
    applicant = Newuser.objects.get(id=pk)
    applicant.delete()
    messages.success(requset,f'Applicant Deleted')
    return redirect('view_applicants')

@login_required
def applications(request,pk):
    applications = Applications.objects.filter(job = pk).order_by('-applied_date')
    job = Jobs.objects.get(id = pk)
    context = {
        'applications':applications,
        'job':job
    }
    return render(request, 'admin/view_applications.html',context)

#to accept the applicant
@login_required
def applicant_status(request,id,jobid,btn):
    applicant = Applications.objects.get(id = id)
    user = applicant.user.first_name +' '+ applicant.user.middle_name +' '+ applicant.user.last_name
    if btn == 'accept':
        applicant.selected = 'accepted'
        messages.success(request,f'{user} Accepted')
    elif btn == 'reject':
        applicant.selected = 'rejected'
        messages.warning(request,f'{user} Rejected')
    applicant.save()
    return redirect('applications', pk= jobid)

#to check if any interview is ready to be scheduled
@login_required
def check_schedule(request):
    jobs = Jobs.objects.filter(withdraw_date__lt=datetime.date.today()).order_by('-withdraw_date')
    context = {
        'jobs':jobs
    }
    return render(request,'admin/check_schedule.html',context)

#scheule interview
@login_required
def add_interview(request,pk):
    job = Jobs.objects.get(id=pk)
    if request.method =="POST":
        form = InterviewForm(request.POST)
        if form.is_valid():
            interview = form.save(False)
            interview.job = job
            interview.department = job.department
            job.scheduled = True
            job.save()
            interview.save()
            messages.success(request,f'Interview added')
            return redirect('check_schedule')
        else:
            return render(request,'admin/add_interview.html',{'form':form,'job':job})
    else:
        form = InterviewForm()
        return render(request,'admin/add_interview.html',{'form':form,'job':job})

#view scheduled interviews
@login_required
def scheduled_interviews(request,pk):
    interview = Interviews.objects.get(job=pk)
    return render(request,'admin/view_scheduled_interviews.html',{'interview':interview})

#edit scheduled interviews
@login_required
def edit_interviews(request,pk):
    interview = Interviews.objects.get(job=pk)  #take the interview with the given job id
    job = Jobs.objects.get(id =pk)  #get job to display job title in template
    if request.method == "POST":
        form = InterviewForm(request.POST,instance=interview)
        if form.is_valid():
            form.save()
            messages.success(request,f'Interview Edited')
            return redirect('scheduled_interviews',pk=pk)
        else:
            return render(request,'admin/add_interview.html',{'form':form,'job':job})

    else:
        form = InterviewForm(instance=interview)
        return render(request,'admin/add_interview.html',{'form':form,'job':job})

@login_required
def selected_applicants(request,pk):
    applications = Applications.objects.filter(job = pk, selected ='accepted')
    return render(request, 'admin/view_selected_applicants.html',{'applications':applications})

#cancel a scheduled interview
@login_required
def cancel_interview(request,pk):
    if request.method == "POST":
        interview = Interviews.objects.get(job=pk)
        job = Jobs.objects.get(id=pk)
        job.scheduled = False
        job.save()
        interview.delete()
        messages.success(request,f'Interview for {job.job_title} cancelled')
        return redirect('check_schedule')

    else:
        interview = Interviews.objects.get(job=pk)
        return render(request, 'admin/cancel_interview.html',{'interview':interview})

#to view the leaves
@login_required
def admin_view_leaves(request,user=None):
    obj = EmployeeDesignation.objects.all() #for the department of employee
    if request.method == "POST":
        filter = request.POST['filter']
        if filter == 'month':
            if user:
                leaves = Leave.objects.filter(from_date__month = get_month(),user=user).order_by('-applied_date')
            else:
                leaves = Leave.objects.filter(from_date__month = get_month()).order_by('-applied_date')
        elif filter =='all':
            if user:
                leaves = Leave.objects.filter(user=user).order_by('-applied_date')
            else:
                leaves = Leave.objects.all().order_by('-applied_date')
        return render(request,'admin/admin_view_leaves.html',{'leaves':leaves,'obj':obj,'filter':filter})

    else:
        if user:
                leaves = Leave.objects.filter(user=user).order_by('-applied_date')
        else:
                leaves = Leave.objects.all().order_by('-applied_date')
        return render(request,'admin/admin_view_leaves.html',{'leaves':leaves,'obj':obj})

@login_required
def admin_leave_detail(request,pk):
    leave = Leave.objects.get(id = pk)
    #get current months leave counter
    month_counter = LeaveCounter.objects.get(date__month=get_month(),date__year=get_year(),user=leave.user)
    year_counter = YearCounter.objects.get(date__year=get_year(),user=leave.user)
    context = {
        'leave':leave,
        'month_counter':month_counter,
        'year_counter':year_counter
    }
    return render(request,'admin/admin_leave_detail.html',context)

# to edit admin added leave
@login_required
def admin_leave_edit(request,pk):
    attendance = Leave.objects.get(id = pk)  #leave is renamed to attendance
    #get current months leave counter
    leave_form = AdminLeaveForm(instance=attendance)
    month_counter = LeaveCounter.objects.get(date__month=get_month(),date__year=get_year(),user=attendance.user)
    year_counter = YearCounter.objects.get(date__year=get_year(),user=attendance.user)
    context = {
        'form':leave_form,
        'attendance':attendance,
        'month':month_counter,
        'count':year_counter
    }
    if request.method == "POST":
        leave_form = AdminLeaveForm(request.POST,instance=attendance)
        if leave_form.is_valid():
            #leave balance check
            balance = get_balance(leave_form,attendance.user)
            leave_type = leave_form.cleaned_data['leave_type']
            if balance:
                messages.warning(request, f'Please check your {leave_type.upper()} availability')
                return render(request, 'admin/admin_leave_apply.html',context)
            leave_form.save()
            return redirect('admin_leave_detail',pk=pk)
    else:
        return render(request,'admin/admin_leave_apply.html',context)

@login_required
def approve_leave(request,pk):
    leave = Leave.objects.get(id=pk)
    if leave.approval != 'approved':
        leave_approval(leave)
        messages.success(request,f'Leave Approved')
        return redirect('admin_leave_detail',pk=pk)

    else:
        messages.info(request,f'Already Approved')
        return redirect('admin_leave_detail',pk=pk)

@login_required
def reject_leave(request,pk):
    leave = Leave.objects.get(id=pk)
    if leave.approval == 'approved':
        leave_reject(leave)
        messages.warning(request,f'Leave Rejected')
        return redirect('admin_leave_detail',pk=pk)

    else:
        if leave.approval == 'rejected':
            messages.info(request,f'Already Rejected')
        else:
            leave.approval = 'rejected'
            leave.save()
            messages.warning(request,f'Leave Rejected')

        return redirect('admin_leave_detail',pk=pk)

def leave_history(request):
    leaves = YearCounter.objects.filter(date__year=get_year())
    obj = EmployeeDesignation.objects.all()
    context = {
        'leaves':leaves,
        'obj':obj
    }
    return render(request, 'admin/leave_history.html', context)

@login_required
def leave_history_detail(request,pk):
    year = YearCounter.objects.get(date__year=get_year(),user=pk)
    month = LeaveCounter.objects.filter(date__year=get_year(),user=pk)
    employee = Newuser.objects.get(id=pk)
    context = {
        'year':year,
        'month':month,
        'employee':employee
    }
    return render(request,'admin/leave_history_detail.html', context)

@login_required
def admin_attendance_view(request,user=None):
    if request.method == "POST":
      shift = request.POST['shift']
      if user:
            attendance =  Attendance.objects.filter(shift = shift,user=user).exclude(entry_time = None).order_by('-attendance_date')
      else:
            attendance =  Attendance.objects.filter(shift = shift).exclude(entry_time = None).order_by('-attendance_date')
      return render(request, 'admin/admin_attendance_view.html',{'attendance':attendance,'shift':shift})

    else:
        if user:
            attendance =  Attendance.objects.filter(shift='morning',user=user).exclude(entry_time = None).order_by('-attendance_date')
        else:
            attendance =  Attendance.objects.filter(shift='morning').exclude(entry_time = None).order_by('-attendance_date')

        return render(request, 'admin/admin_attendance_view.html',{'attendance':attendance})

@login_required
def admin_missing_regular(request):
    if request.method == "POST":
        filter = request.POST['filter']
        if filter == 'today':
            #todays attendance missing
            attendance =  Attendance.objects.filter(attendance_date =datetime.date.today(),shift='morning',entry_time=None).order_by('-attendance_date')
            for attendance in attendance:
                flag = leave_marked(attendance.user,attendance.attendance_date)
                if flag:
                    attendance.leave = True
                    attendance.save()
                else:          #if flag is false and leave is True (when rejected)
                    if attendance.leave == True:
                        attendance.leave = False
                        attendance.save()
                attendance =  Attendance.objects.filter(attendance_date =datetime.date.today(),shift='morning',entry_time=None).order_by('-attendance_date')

        elif filter == 'month':
            #this months attendance missing
            attendance =  Attendance.objects.filter(entry_time=None, attendance_date__month=get_month(),shift='morning').order_by('-attendance_date')
            for attendance in attendance:
                flag = leave_marked(attendance.user,attendance.attendance_date)
                if flag:
                    attendance.leave = True
                    attendance.save()
                else:          #if flag is false and leave is True (when rejected)
                    if attendance.leave == True:
                        attendance.leave = False
                        attendance.save()
            attendance =  Attendance.objects.filter(entry_time=None, attendance_date__month=get_month(),shift='morning').order_by('-attendance_date')

        elif filter == 'all':
            attendance =  Attendance.objects.filter(entry_time=None, shift='morning').order_by('-attendance_date')
            for attendance in attendance:
                flag = leave_marked(attendance.user,attendance.attendance_date)
                if flag:
                    attendance.leave = True
                    attendance.save()
                else:          #if flag is false and leave is True (when rejected)
                    if attendance.leave == True:
                        attendance.leave = False
                        attendance.save()
            attendance =  Attendance.objects.filter(entry_time=None, shift='morning').order_by('-attendance_date')

        return render(request, 'admin/admin_missing_regular.html',{'attendance':attendance,'filter':filter})

    else:
        attendance =  Attendance.objects.filter(entry_time=None, attendance_date = datetime.date.today(),shift='morning').order_by('-attendance_date')
        for attendance in attendance:
                flag = leave_marked( attendance.user,attendance.attendance_date)
                if flag:
                    attendance.leave = True
                    attendance.save()
                else:          #if flag is false and leave is True (when rejected)
                    if attendance.leave == True:
                        attendance.leave = False
                        attendance.save()
        attendance =  Attendance.objects.filter(entry_time=None, attendance_date = datetime.date.today(),shift='morning').order_by('-attendance_date')
        return render(request, 'admin/admin_missing_regular.html',{'attendance':attendance})

# to add leave by admin
@login_required
def admin_leave_apply(request,pk):
    attendance = Attendance.objects.get(id=pk)
    count = YearCounter.objects.get(user = attendance.user)
    month = LeaveCounter.objects.get(user = attendance.user, date__month=get_month())
    if request.method == "POST":
      leave_form = AdminLeaveForm(request.POST)
      if leave_form.is_valid():
         #leave balance check
         balance = get_balance(leave_form,attendance.user)
         leave_type = leave_form.cleaned_data['leave_type']
         if balance:
            messages.warning(request, f'Please check your {leave_type.upper()} availability')
            return render(request, 'admin/admin_leave_apply.html',{ 'form': leave_form,'attendance':attendance,'count':count,'month':month })

         leave = leave_form.save(False)
         leave.from_date = attendance.attendance_date
         leave.to_date = attendance.attendance_date
         leave.reason = "Leave Added by admin"
         leave.admin = True
         leave.user = attendance.user
         leave.save()
         return redirect('approve_leave', pk=leave.pk)
      else:
         return render(request, 'admin/admin_leave_apply.html',{ 'form': leave_form, 'attendance':attendance,'count':count,'month':month })
    else:
      form = AdminLeaveForm()
      return render(request, 'admin/admin_leave_apply.html',{ 'form': form, 'attendance':attendance,'count':count,'month':month })

#exit time not marked
@login_required
def exit_missing(request):
    if request.method == "POST":
        filter = request.POST['filter']
        if filter == 'today':
            #todays attendance missing
            attendance =  Attendance.objects.filter(attendance_date =datetime.date.today(),shift='morning',exit_time=None).exclude(entry_time = None).order_by('attendance_date')

        elif filter == 'month':
            #this months attendance missing
            attendance =  Attendance.objects.filter(exit_time=None, attendance_date__month=get_month(),shift='morning').exclude(entry_time = None).order_by('attendance_date')

        elif filter == 'all':
            attendance =  Attendance.objects.filter(exit_time=None, shift='morning').exclude(entry_time = None).order_by('attendance_date')

        return render(request, 'admin/exit_time_missing.html',{'attendance':attendance,'filter':filter})

    else:
        attendance =  Attendance.objects.filter(exit_time=None, attendance_date = datetime.date.today(),shift='morning').exclude(entry_time = None).order_by('attendance_date')
        return render(request, 'admin/exit_time_missing.html',{'attendance':attendance})

# Attendance Mark for Exit missing
@login_required
def exit_missing_mark(request,pk):
    attendance = Attendance.objects.get(id=pk)
    form = AdminAttendanceForm(instance=attendance)
    context = {
        'attendance':attendance,
        'form':form
    }
    if request.method == "POST":
        form = AdminAttendanceForm(request.POST ,instance=attendance)
        if form.is_valid():
            form.save()
            emp = attendance.user.first_name+' '+attendance.user.middle_name+' '+attendance.user.last_name
            messages.success(request,f'Exit time marked for {emp.upper()}')
            return redirect('admin_attendance_view')
    else:
        return render(request,'admin/admin_add_attendance.html',context)

#view night shift unmarked attendance:
@login_required
def night_unmarked(request):
    if request.method == "POST":
        filter = request.POST['filter']
        if filter == 'month':
            #this months attendance missing
            attendance =  Attendance.objects.filter(exit_time=None, attendance_date__month=get_month(),shift='night').exclude(entry_time = None).order_by('attendance_date')

        elif filter == 'all':
            attendance =  Attendance.objects.filter(exit_time=None, shift='night').exclude(entry_time = None).order_by('attendance_date')

        return render(request, 'admin/night_shift_unmarked.html',{'attendance':attendance,'filter':filter})

    else:
        attendance =  Attendance.objects.filter(exit_time=None, attendance_date__month=get_month(),shift='night').exclude(entry_time = None).order_by('attendance_date')
        return render(request, 'admin/night_shift_unmarked.html',{'attendance':attendance})

# Add an attendance to employee
@login_required
def add_emp_attendance(request,pk):
    emp = Newuser.objects.get(id=pk)
    form = AdminEmpAttendance()
    context = {
        'form':form,
        'emp':emp
    }
    if request.method == "POST":
        form = AdminEmpAttendance(request.POST)
        if form.is_valid():
            shift = form.cleaned_data['shift']
            date = form.cleaned_data['attendance_date']

            #to update the leve feild by checking if leave applied or not
            try:
                attendance = Attendance.objects.get(attendance_date=date,user=pk,shift='morning')
                flag = leave_marked(pk,date)
                if flag:
                        attendance.leave = True
                        attendance.save()
                else:          #if flag is false and leave is True (when rejected)
                    if attendance.leave == True:
                        attendance.leave = False
                        attendance.save()
            except:
                messages.warning(request,f'Check your attendance date')
                return render(request,'admin/admin_emp_attendance.html',{'form':form,'emp':emp})

            if shift == 'morning':
                attendance = Attendance.objects.get(attendance_date=date, user=pk, shift='morning')
                if attendance:
                    if attendance.entry_time == None and attendance.exit_time == None and attendance.leave == False:
                        attendance.entry_time = form.cleaned_data['entry_time']
                        attendance.exit_time = form.cleaned_data['exit_time']
                        attendance.save()
                        messages.success(request,f'Attendance Marked')
                        return redirect('admin_attendance_view',user=pk)
                    else:
                        messages.warning(request,f'Failed to mark attendance')
                else:
                    messages.warning(request,f'Failed to mark attendance')
                return render(request,'admin/admin_emp_attendance.html',{'form':form,'emp':emp})

            #  night shift
            else:
                if date > datetime.date.today():
                    messages.warning(request,f'Check your attendance date')
                    return render(request,'admin/admin_emp_attendance.html',{'form':form,'emp':emp})
                else:
                    leave = form.save(False)
                    leave.user = emp
                    leave.save()
                    messages.success(request,f'Attendance Added')
                    return redirect('admin_attendance_view',user=pk)
    else:
        return render(request,'admin/admin_emp_attendance.html',context)

# Attendance regularization
@login_required
def admin_regularize(request):
    attendance = AttendanceRegularization.objects.all().order_by('-id')
    context = {
        'attendance':attendance
    }
    return render(request,'admin/admin_regularization.html',context)

#apply regualrization
def apply_regularization(request,pk):
    attendance = AttendanceRegularization.objects.get(id=pk)
    id = attendance.attendance_id
    print(id)
    form = AdminRegularizationForm(instance=attendance)
    context = {
        'attendance':attendance,
        'form':form
    }
    if request.method == "POST":
        form = AdminRegularizationForm(request.POST, instance=attendance)
        if form.is_valid():
            old_attendance = Attendance.objects.get(id=id)
            old_attendance.entry_time = form.cleaned_data['new_entry']
            old_attendance.exit_time = form.cleaned_data['new_exit']
            old_attendance.save()
            attendance.status = 'approved'
            attendance.save()
            messages.success(request, f'Regularization Approved')
            return redirect('admin_regularize')

    return render(request,'admin/admin_regularization_detail.html', context)

#reject regularization
@login_required
def reject_regularization(request,pk):
    attendance = AttendanceRegularization.objects.get(id=pk)
    id = attendance.attendance_id
    if attendance.status == 'approved':
        old_attendance = Attendance.objects.get(id=id)
        old_attendance.entry_time = attendance.old_entry
        old_attendance.exit_time = attendance.old_exit
        old_attendance.save()
        attendance.status = 'rejected'
    else:
        attendance.status = 'rejected'
    attendance.save()
    messages.warning(request, f'Regularization rejected')
    return redirect('admin_regularize')

#notification
@login_required
def add_notification(request):
    form = MessageForm()
    message = Messages.objects.all().order_by('-id')
    count = Messages.objects.all().count()
    context = {
        'message':message,
        'count':count,
        'form':form
    }
    if request.method == "POST":
        if "search" in request.POST:
            value = request.POST['search_msg']
            message = Messages.objects.filter(Q (title__icontains=value) | Q(date__icontains=value)).order_by('-id')
            c = message.count()
            if c == 0:
                messages.info(request,f'No results found')
                return redirect('add_notification')
            else:
                p = Paginator(message,5)  # second argument is no of items to be displayed
                page_num = request.GET.get('page',1 ) #get the page no by url  and 1 is default
                try:
                    page = p.page(page_num)
                except EmptyPage:
                    page = p.page(1)
                return render(request, 'admin/add_notifications.html',{'message':page,'count':count,'form':form})

        elif "add" in request.POST:
            form = MessageForm(request.POST)
            if form.is_valid():
                msg = form.save(False)
                msg.date = datetime.datetime.now()
                msg.save()
                messages.success(request,'Message Added')
                return redirect('add_notification')

    else:
        if count != 0:
            p = Paginator(message,5)  # second argument is no of items to be displayed
            page_num = request.GET.get('page',1 ) #get the page no by url  and 1 is default
            try:
               page = p.page(page_num)
            except EmptyPage:
               page = p.page(1)
            return render(request, 'admin/add_notifications.html',{'message':page,'count':count,'form':form})
        else:
            return render(request, 'admin/add_notifications.html',context)

# edit notification
@login_required
def edit_notification(request,pk):
    message = Messages.objects.get(id=pk)
    if request.method == "POST":
        form = MessageForm(request.POST, instance=message)
        if form.is_valid():
            form.save()
            messages.success(request, f'Message Edited')
            return redirect('add_notification')
    else:
        form = MessageForm(instance=message)
        return render(request, 'admin/edit_notification.html',{'form':form})

#view message
@login_required
def view_notification(request,pk):
    message = Messages.objects.get(id=pk)
    return render(request, 'admin/view_message.html',{'message':message})

#delete message
@login_required
def delete_notification(request,pk):
    message = Messages.objects.get(id=pk)
    if request.method == "POST":
        message.delete()
        messages.warning(request,f'Message Deleted')
        return redirect('add_notification')
    else:
        return render(request, 'admin/delete_message.html',{'message':message})

#payroll month select
@login_required
def payroll_month(request):
    if request.method == "POST":
        date = request.POST['date']
        datem = datetime.datetime.strptime(date, "%Y-%m-%d")
        today = datetime.date.today()
        if  datem.year == today.year:
                if datem.month < today.month:
                    payroll = Payroll.objects.filter(date__month__gte=datem.month, date__year__gte=datem.year).count()
                    if payroll == 0:
                        return redirect('calculate_payroll',cdate=datem)

                    else:
                        messages.info(request,f'Payroll Already Calculated')
                        return redirect('payroll_month')
                else:
                    messages.warning(request,f'Select a valid month and year')
                    return redirect('payroll_month')

        elif  datem.year < today.year:
                payroll = Payroll.objects.filter(date__month__gte=datem.month, date__year__gte=datem.year).count()
                if payroll == 0:
                    return redirect('calculate_payroll',cdate=datem)
                else:
                    messages.info(request,f'Payroll Already Calculated')
                    return redirect('payroll_month')

        else:
                    messages.warning(request,f'Select a valid month and year')
                    return redirect('payroll_month')

    else:
         return render(request,'admin/payroll_month_selector.html')

#calculate payroll
@login_required
def calculate_payroll(request,cdate):
    cdate = datetime.datetime.strptime(cdate, "%Y-%m-%d %H:%M:%S")
    calculate_salary(cdate)
    messages.success(request, f'Calculation completed')
    return redirect('view_payroll', vdate = cdate)

#payroll view month select
@login_required
def view_payroll_month(request):
    if request.method == "POST":
        date = request.POST['date']
        datem = datetime.datetime.strptime(date, "%Y-%m-%d")
        payroll = Payroll.objects.filter(date__month=datem.month, date__year=datem.year).count()
        if payroll != 0:
            return redirect('view_payroll', vdate=datem)
        else:
            messages.warning(request,f'Not Available, please check your date')
            return redirect('view_payroll_month')
    else:
        return render(request, 'admin/payroll_view_month.html')

#view payroll
@login_required
def view_payroll(request,vdate):
    try:
        vdate = datetime.datetime.strptime(vdate, "%Y-%m-%d %H:%M:%S")
    except:
        vdate = datetime.datetime.strptime(vdate, "%Y-%m-%d")

    payroll = Payroll.objects.filter(date__month=vdate.month, date__year=vdate.year)
    obj = EmployeeDesignation.objects.all()
    context = {
        'payroll':payroll,
        'obj':obj,
        'vdate':vdate
    }
    return render(request, 'admin/view_payroll.html',context)

#hold salary
@login_required
def hold_salary(request, pk):
    salary = Payroll.objects.get(id=pk)
    emp = salary.user.first_name+' '+salary.user.middle_name+' '+salary.user.last_name
    if salary.status == 'hold':
        salary.status = 'pending'
        messages.success(request,f'Salary unholded for {emp.upper()}')
    else:
        salary.status = 'hold'
        messages.warning(request,f'Salary holded for {emp.upper()}')
    vdate = salary.date
    salary.save()
    return redirect('view_payroll', vdate = vdate)

#approve salary
@login_required
def approve_salary(request,pk):
    salary = Payroll.objects.get(id=pk)
    emp = salary.user.first_name+' '+salary.user.middle_name+' '+salary.user.last_name
    salary.status = 'approved'
    salary.save()
    vdate = salary.date
    messages.success(request,f'Salary Approved for {emp.upper()}')
    return redirect('view_payroll', vdate = vdate)

#view salary slip
@login_required
def salary_slip(request, pk):
    payroll = Payroll.objects.get(id=pk)
    emp = Newuser.objects.get(id = payroll.user.id)
    designation = EmployeeDesignation.objects.get(user = payroll.user.id)
    context = {
        'payroll':payroll,
        'emp':emp,
        'designation':designation
    }
    return render(request, 'admin/salary_slip.html',context)

#edit payroll
@login_required
def edit_payroll(request,pk):
    payroll = Payroll.objects.get(id=pk)
    form = PayrollEditForm()
    vdate = payroll.date
    if request.method == "POST":
        form = PayrollEditForm(request.POST)
        if form.is_valid():
            payroll.net_salary = payroll.net_salary + float(form.cleaned_data['other_benefits'])

            payroll.net_salary = payroll.net_salary - float(form.cleaned_data['other_deductions'])

            payroll.other_benefits = payroll.other_benefits + float(form.cleaned_data['other_benefits'])
            payroll.other_deductions = payroll.other_deductions +  float(form.cleaned_data['other_deductions'])

            payroll.save()
            messages.success(request, f'Payroll Edited Successfully')
            return redirect('view_payroll', vdate = vdate)
    else:
        return render(request, 'admin/edit_payroll.html',{'payroll':payroll,'form':form})

# to approve all pending payrolls
@login_required
def approve_all_payroll(request, vdate):
    vdate = datetime.datetime.strptime(vdate, "%Y-%m-%d %H:%M:%S")
    payrolls = Payroll.objects.filter(status='pending',date=vdate)
    for payroll in payrolls:
        payroll.status = 'approved'
        payroll.save()
    messages.success(request, f'Payrolls Approved')
    return redirect('view_payroll', vdate = vdate)

#add holidays
@login_required
def holidays(request):
    holidays = Holidays.objects.all().order_by('-date')
    form = HolidayForm()
    if request.method == "POST":
        form = HolidayForm(request.POST)
        if form.is_valid():
            date = form.cleaned_data['date']
            attendance = Attendance.objects.filter(attendance_date = date)
            if attendance:
                for attendance in attendance:
                    attendance.holiday = True
                    attendance.save()
            form.save()
            messages.success(request,f'Holiday Added')
            return redirect('holidays')
    else:
        return render(request,'admin/admin_holidays.html',{'holidays':holidays,'form':form})

#edit holidays
@login_required
def holiday_edit(request,pk):
    holiday = Holidays.objects.get(id = pk)
    form = HolidayForm(instance=holiday)
    if request.method == "POST":
        att = Attendance.objects.filter(attendance_date = holiday.date)
        if att:
            for att in att:
                att.holiday = False
                att.save()
        form = HolidayForm(request.POST,instance=holiday)
        if form.is_valid():
            date = form.cleaned_data['date']
            attendance = Attendance.objects.filter(attendance_date = date)
            if attendance:
                for attendance in attendance:
                    attendance.holiday = True
                    attendance.save()
            form.save()
            messages.success(request,f'Holiday Edited')
            return redirect('holidays')
    else:
        return render(request, 'admin/holiday_edit.html',{'form':form})

#delete holiday
@login_required
def delete_holiday(request,pk):
    holiday = Holidays.objects.get(id = pk)
    if request.method == "POST":
        att = Attendance.objects.filter(attendance_date = holiday.date)
        if att:
            for att in att:
                att.holiday = False
                att.save()
        holiday.delete()
        messages.success(request,f'Holiday Deleted')
        return redirect('holidays')
    else:
        return render(request,'admin/delete_holiday.html',{'holiday':holiday})


from openpyxl import Workbook, load_workbook
from openpyxl.utils.cell import coordinate_from_string
import time
import collections as coll
#### 두 번째 방법이 효율적

## 첫 번째 방법(나)
start = time.time()
wb = Workbook()
ws = wb.active

# 데이터를 입력(리스트,튜플)
ws.append(['학번', '출석', '퀴즈1', '퀴즈2', '중간고사', '기말고사', '프로젝트'])
source = [
    [1,10,8,5,14,26,12],
    [2,7,3,7,15,24,18],
    [3,9,5,8,8,12,4],
    [4,7,8,7,17,21,18],
    [5,7,8,7,16,25,15],
    [6,3,5,8,8,17,0],
    [7,4,9,10,16,27,18],
    [8,6,6,6,15,19,17],
    [9,10,10,9,19,30,19],
    [10,9,8,8,20,25,20]
    ]

# for i in range(10):  # 범위(range)로 지정 - 두번 호출(비효율)
#     ws.append(source[i])
# ↓ 수정
for i in source:  # 리스트를 지정- 한번 호출(효율)
    ws.append(i)

# 1. 퀴즈2 점수를 10으로 수정
# 2중 반복문(for 문) 사용, 두 번째 방법 보다 소모 시간이 작음
for col in ws.iter_cols(min_row=2, min_col=4, max_col=4):
    # print(col)
    for row in col:
        row.value = 10
        # print(row.value)

# 2. H열에 총점(SUM 이용)
# ws.insert_cols(8, 2)  # insert 는 셀 사이에 또 다른 셀을 추가할 때만 사용
ws['H1'] = '총점'
ws['I1'] = '성적'

for row in ws.iter_rows(min_row=2, min_col=2):
    # print(row)
    # print(row[0].coordinate, row[5].coordinate)
    ws[row[6].coordinate] = '=SUM({}:{})'.format(row[0].coordinate, row[5].coordinate)
    # print(ws[row[6].coordinate].value)

sum_score = []
for score in source:
    sum_score.append(sum(score[1:]) - score[3] + 10)
# print(sum_score)

# I열에 성적 정보 추가
# 출석이 5 미만인 학생은 총점 상관없이 F
count = 0
for row in ws.iter_rows(min_row=2, min_col=2):
    # print(ws[row[6].coordinate].value)

    # - 총점 90 이상 A, 80 이상 B, 70 이상 C, 나머지 D
    if int(ws[row[0].coordinate].value) < 5:  # 출석 5 미만
        ws[row[7].coordinate] = 'F'
    else:
        if sum_score[count] >= 90:  # 총점 90점 이상
            ws[row[7].coordinate] = 'A'
        elif sum_score[count] >= 80:  # 총점 80점 이상
            ws[row[7].coordinate] = 'B'
        elif sum_score[count] >= 70:  # 총점 70점 이상
            ws[row[7].coordinate] = 'C'
        else:
            ws[row[7].coordinate] = 'D'
    count += 1
    # print(ws[row[7].coordinate].value)

print('time :', time.time() - start)

wb.save("/Users/csh/jupytercreation/Python_Practice/RPA/basic/1_excel/data/scores.xlsx")

## 두 번째 방법(유튜버) - win
# start = time.time()
# wb = Workbook()
# ws = wb.active

# ws.append(['학번', '출석', '퀴즈1', '퀴즈2', '중간고사', '기말고사', '프로젝트'])
# source = [
#     [1,10,8,5,14,26,12],
#     [2,7,3,7,15,24,18],
#     [3,9,5,8,8,12,4],
#     [4,7,8,7,17,21,18],
#     [5,7,8,7,16,25,15],
#     [6,3,5,8,8,17,0],
#     [7,4,9,10,16,27,18],
#     [8,6,6,6,15,19,17],
#     [9,10,10,9,19,30,19],
#     [10,9,8,8,20,25,20]
#     ]

# for i in source:  # 리스트를 지정- 한번 호출(효율)
#     ws.append(i)

# # 1. 퀴즈2 점수를 10으로 수정
# # 반복문, 조건문을 한번씩 사용, 모든 셀의 데이터를 한번에 교체함, 첫 번째 방법 보다 소모 시간이 큼
# for idx, cell in enumerate(ws['D']):
#     if idx == 0:
#         continue
#     cell.value = 10

# ws['H1'] = '총점'
# ws['I1'] = '성적'

# # 2. H열에 총점(SUM 이용), I열에 성적 정보 추가
# for idx, score in enumerate(source, start=2):
#     sum_val = sum(score[1:]) - score[3] + 10  # 총점
#     ws.cell(row=idx, column=8).value = '=SUM(B{}:G{})'.format(idx, idx)

#     # - 총점 90 이상 A, 80 이상 B, 70 이상 C, 나머지 D
#     grade = None
#     if sum_val >= 90:
#         grade = 'A'
#     elif sum_val >= 80:
#         grade = 'B'
#     elif sum_val >= 70:
#         grade = 'C'
#     else:
#         grade = 'D'

#     # 3. 출석이 5 미만인 학생은 총점 상관없이 F
#     if score[1] < 5:
#         grade = 'F'

#     ws.cell(row=idx, column=9).value = grade

# print('time :', time.time() - start)

# wb.save("/Users/csh/jupytercreation/Python_Practice/RPA/basic/1_excel/data/scores.xlsx")


from typing import List, Tuple
import itertools
import networkx as nx
import numpy as np
import qcopt
import qiskit
from scipy.optimize import minimize

def optimize_circuit(circuit):

    backend = qiskit.Aer.get_backend("aer_simulator_statevector")
    def f(params: List):
        # Bind the parameters
        bound_circ = circuit.bind_parameters({theta: param for theta, param in zip(circuit.parameters, params)})

        # Compute the cost function
        bound_circ.save_statevector()

        result = qiskit.execute(bound_circ, backend=backend).result()
        probs = qiskit.quantum_info.Statevector(result.get_statevector(bound_circ)).probabilities_dict(decimals=7)

        avg_cost = 0
        for sample in probs.keys():
            x = [int(bit) for bit in list(sample)]
            # Cost function is Hamming weight
            avg_cost += probs[sample] * sum(x)

        # Return the negative of the cost for minimization
        # print('Expectation value:', avg_cost)
        return -avg_cost

    init_params = np.random.uniform(low=0.0, high=2 * np.pi, size=len(circuit.parameters))
    out = minimize(f, x0=init_params, method="COBYLA")
    return out

def get_circuit_from_graph(G, max_cuts, params, barriers=0, decompose_level=2):
    # Partition the graph
    partition = nx.algorithms.community.kernighan_lin_bisection(G)
    subgraphs, cut_edges = get_subgraphs(G, partition)

    # identify the subgraph of every node
    subgraph_dict = {}
    for i, subgraph in enumerate(subgraphs):
        for qubit in subgraph:
            subgraph_dict[qubit] = i

    # Select hot nodes
    cut_nodes, hot_nodes = simple_choose_nodes(G, partition, cut_edges, max_cuts)

    # Put active mixers at the front of the order
    cur_permutation = sort_mixers(G, subgraph_dict)
    active_mixers = [node for node in cur_permutation if node not in cut_nodes] + hot_nodes
    inactive_mixers = [node for node in cur_permutation if node not in active_mixers]
    active_mixer_order = [node for node in cur_permutation if node in active_mixers] + inactive_mixers

    if not params:
        #params = [(i+1) * np.pi/4 for i in range(len(active_mixers))] + [np.pi/2]
        params = [qiskit.circuit.Parameter(f'theta_{i}') for i in range(len(active_mixers) + 1)]

    circuit = qcopt.qlsa.gen_qlsa(
        G,
        P=1,
        params=params,
        barriers=barriers,
        init_state='0'*len(G.nodes),
        decompose_toffoli=decompose_level,
        mixer_order=active_mixer_order,
        param_lim=len(active_mixers)+1,
    )
    return circuit, subgraph_dict, cut_nodes, hot_nodes, active_mixers

def get_subgraphs(G, partition):
    subgraphs = []
    cut_edges = []
    all_edges = G.edges
    for subgraph_nodes in partition:
        subG = nx.Graph()
        subG.add_nodes_from(subgraph_nodes)

        for v1, v2 in all_edges:
            if v1 in subgraph_nodes and v2 in subgraph_nodes:
                subG.add_edge(v1, v2)
            if v1 in subgraph_nodes and v2 not in subgraph_nodes:
                cut_edges.append((v1, v2))

        subgraphs.append(subG)

    return subgraphs, cut_edges

def _cut_cost(graph, partition, hot_nodes, subgraph_dict, cut_nodes):
    subgraph_appearances = {cut_node: [] for cut_node in cut_nodes}

    for subgraph_idx, subgraph_nodes in enumerate(partition):
        for subgraph_node in subgraph_nodes:
            if (subgraph_node in cut_nodes) and (subgraph_node not in hot_nodes):
                continue
            for partial_mixer_node in list(graph.neighbors(subgraph_node)) + [subgraph_node]:
                if partial_mixer_node in cut_nodes:
                    subgraph_appearances[partial_mixer_node].append(subgraph_idx)

    num_cuts = 0
    for cut_node, appearances in subgraph_appearances.items():
        cut_node_subgraph = subgraph_dict[cut_node]
        for subgraph in set(appearances):
            if cut_node_subgraph != subgraph:
                num_cuts += 1
    return num_cuts

def _is_connected(graph, partition, hot_nodes, subgraph_dict):
    meta_graph = nx.Graph()
    for i in range(len(partition)):
        meta_graph.add_node(i)

    for hot_node in hot_nodes:
        for edge in graph.edges:
            if hot_node in edge:
                subgraph_i = subgraph_dict[edge[0]]
                subgraph_j = subgraph_dict[edge[1]]
                if subgraph_i != subgraph_j:
                    meta_graph.add_edge(subgraph_i, subgraph_j)

    return nx.is_connected(meta_graph)

def simple_choose_nodes(graph: nx.Graph, partition: List[List[int]],
                        cut_edges: List[Tuple[int, int]], max_cuts: int,
                        ) -> Tuple[List[int], List[int]]:
    subgraph_dict = {}
    for i, subgraph_nodes in enumerate(partition):
        for node in subgraph_nodes:
            subgraph_dict[node] = i

    cut_nodes = []
    for edge in cut_edges:
        cut_nodes.extend(edge)
    cut_nodes = list(set(cut_nodes))

    # Generate the set of all possible hot node sets
    # NOTE: this is extremely inefficient, for n hot nodes,
    # there are n-choose-1 + n-choose-2 + ... n-choose-n possible hot node sets
    all_possible_hot_nodes = []
    for r in range(1, len(cut_nodes)+1):
        if r > max_cuts:
            break
        for length_r_hot_nodes in itertools.combinations(cut_nodes, r):
            all_possible_hot_nodes.append(length_r_hot_nodes)

    # Eliminate those hot node sets that result in a disconnected graph
    all_connected_hot_nodes = []
    for possible_hot_nodes in all_possible_hot_nodes:
        if _is_connected(graph, partition, possible_hot_nodes, subgraph_dict):
            all_connected_hot_nodes.append(possible_hot_nodes)

    # Eliminate those hot node sets that require cuts > max_cuts
    all_feasible_hot_nodes = []
    for possible_hot_nodes in all_connected_hot_nodes:
        cost = _cut_cost(graph, partition, possible_hot_nodes, subgraph_dict, cut_nodes)
        if cost <= max_cuts:
            all_feasible_hot_nodes.append(possible_hot_nodes)

    # For now, uniform random sampling
    hot_nodes = all_feasible_hot_nodes[np.random.choice([i for i in range(len(all_feasible_hot_nodes))])]

    return cut_nodes, list(hot_nodes)

def sort_mixers(G, subgraph_dict):
    cur_mixer_order = list(G.nodes)

    # Group the nodes by subgraph
    subgraph_nodes = [[] for _ in range(len(set(subgraph_dict.values())))]
    for node in cur_mixer_order:
        subgraph_nodes[subgraph_dict[node]].append(node)

    # Order mixers sequentially in each subgraph
    new_mixer_order = []
    for sublist in subgraph_nodes:
        new_mixer_order.extend(sorted(sublist))

    return new_mixer_order


import cv2
from PIL import Image

def write_gif(filename_stem, frame_array):
    if len(frame_array) == 0:
        return False
    shape = frame_array[0].shape

    mode = 'RGB' if len(shape) == 3 else None
    filename_gif = filename_stem + ".gif"
    imgs = [Image.fromarray(img, mode) for img in frame_array]
    # duration is the number of milliseconds between frames; this is 40 frames
    # per second
    imgs[0].save(filename_gif, save_all=True,
                 append_images=imgs[1:], duration=50, loop=0)
    return True


from tweeter import tweet
import nltk
from collections import Counter
import json

stopwords_pt=nltk.corpus.stopwords.words('portuguese')
stopwords_en=nltk.corpus.stopwords.words('english')
pontos = ['?', '!', '.', ',', '&',' ']

def wordsTT():
    lista = tweet()
    new_list = []
    new_text = ''
    espaco = ' '

    #me retorna somente as palavras usadas em cada trend
    for text in lista:
        tokens = nltk.word_tokenize(text)
        for p in tokens:
            if p not in stopwords_pt or p not in stopwords_en:
                new_text = new_text + espaco + p

        new_list.append(new_text)
        new_text = ''

    return new_list

def top3_words():
    final_list = []
    #me retorna a quantidade de cada palavra achada
    for words in wordsTT():
        aux = nltk.word_tokenize(words)
        lista_teste = []
        for t in aux:
            if t not in pontos and len(t) > 3:
                lista_teste.append(t)

        counts = Counter(lista_teste)
        final_list.append(counts)

    #me retorna as 3 palavras mais utilizadas nos tt de cada trend
    top3 = []
    aux_list = []
    for iten in final_list:
        for keys, count in iten.most_common():
            aux_list.append(keys)
        top3.append(aux_list[0:3])
        aux_list = []

    return top3