import theano
import theano.tensor as T
import numpy as np
import logging
import copy

from nn.layers.embeddings import Embedding
from nn.layers.core import Dense, Layer
from nn.layers.recurrent import BiLSTM, LSTM, CondAttLSTM
from nn.utils.theano_utils import ndim_itensor, tensor_right_shift, ndim_tensor, alloc_zeros_matrix, shared_zeros
import nn.initializations as initializations
import nn.activations as activations
import nn.optimizers as optimizers

import config
from lang.grammar import Grammar
from parse import *
from astnode import *

class PointerNet(Layer):
    def __init__(self, name='PointerNet'):
        super(PointerNet, self).__init__()

        self.dense1_input = Dense(config.encoder_hidden_dim, config.ptrnet_hidden_dim, activation='linear', name='Dense1_input')

        self.dense1_h = Dense(config.decoder_hidden_dim + config.encoder_hidden_dim, config.ptrnet_hidden_dim, activation='linear', name='Dense1_h')

        self.dense2 = Dense(config.ptrnet_hidden_dim, 1, activation='linear', name='Dense2')

        self.params += self.dense1_input.params + self.dense1_h.params + self.dense2.params

        self.set_name(name)

    def __call__(self, query_embed, query_token_embed_mask, decoder_states):
        query_embed_trans = self.dense1_input(query_embed)
        h_trans = self.dense1_h(decoder_states)

        query_embed_trans = query_embed_trans.dimshuffle((0, 'x', 1, 2))
        h_trans = h_trans.dimshuffle((0, 1, 'x', 2))

        # (batch_size, max_decode_step, query_token_num, ptr_net_hidden_dim)
        dense1_trans = T.tanh(query_embed_trans + h_trans)

        scores = self.dense2(dense1_trans).flatten(3)

        scores = T.exp(scores - T.max(scores, axis=-1, keepdims=True))
        scores *= query_token_embed_mask.dimshuffle((0, 'x', 1))
        scores = scores / T.sum(scores, axis=-1, keepdims=True)

        return scores

class Hyp:
    def __init__(self, *args):
        if isinstance(args[0], Hyp):
            hyp = args[0]
            self.grammar = hyp.grammar
            self.tree = hyp.tree.copy()
            self.t = hyp.t
            self.hist_h = list(hyp.hist_h)
            self.log = hyp.log
            self.has_grammar_error = hyp.has_grammar_error
        else:
            assert isinstance(args[0], Grammar)
            grammar = args[0]
            self.grammar = grammar
            self.tree = DecodeTree(grammar.root_node.type)
            self.t=-1
            self.hist_h = []
            self.log = ''
            self.has_grammar_error = False

        self.score = 0.0

        self.__frontier_nt = self.tree
        self.__frontier_nt_t = -1

    def __repr__(self):
        return self.tree.__repr__()

    def can_expand(self, node):
        if self.grammar.is_value_node(node):
            # if the node is finished
            if node.value is not None and node.value.endswith('<eos>'):
                return False
            return True
        elif self.grammar.is_terminal(node):
            return False

        # elif node.type == 'epsilon':
        #     return False
        # elif is_terminal_ast_type(node.type):
        #     return False

        # if node.type == 'root':
        #     return True
        # elif inspect.isclass(node.type) and issubclass(node.type, ast.AST) and not is_terminal_ast_type(node.type):
        #     return True
        # elif node.holds_value and not node.label.endswith('<eos>'):
        #     return True

        return True

    def apply_rule(self, rule, nt=None):
        if nt is None:
            nt = self.frontier_nt()

        # assert rule.parent.type == nt.type
        if rule.parent.type != nt.type:
            self.has_grammar_error = True

        self.t += 1
        # set the time step when the rule leading by this nt is applied
        nt.t = self.t
        # record the ApplyRule action that is used to expand the current node
        nt.applied_rule = rule

        for child_node in rule.children:
            child = DecodeTree(child_node.type, child_node.label, child_node.value)
            # if is_builtin_type(rule.parent.type):
            #     child.label = None
            #     child.holds_value = True

            nt.add_child(child)

    def append_token(self, token, nt=None):
        if nt is None:
            nt = self.frontier_nt()

        self.t += 1

        if nt.value is None:
            # this terminal node is empty
            nt.t = self.t
            nt.value = token
        else:
            nt.value += token

    def frontier_nt_helper(self, node):
        if node.is_leaf:
            if self.can_expand(node):
                return node
            else:
                return None

        for child in node.children:
            result = self.frontier_nt_helper(child)
            if result:
                return result

        return None

    def frontier_nt(self):
        if self.__frontier_nt_t == self.t:
            return self.__frontier_nt
        else:
            _frontier_nt = self.frontier_nt_helper(self.tree)
            self.__frontier_nt = _frontier_nt
            self.__frontier_nt_t = self.t

            return _frontier_nt

    def get_action_parent_t(self):
        """
        get the time step when the parent of the current
        action was generated
        WARNING: 0 will be returned if parent if None
        """
        nt = self.frontier_nt()

        # if nt is a non-finishing leaf
        # if nt.holds_value:
        #     return nt.t

        if nt.parent:
            return nt.parent.t
        else:
            return 0

    # def get_action_parent_tree(self):
    #     """
    #     get the parent tree
    #     """
    #     nt = self.frontier_nt()
    #
    #     # if nt is a non-finishing leaf
    #     if nt.holds_value:
    #         return nt
    #
    #     if nt.parent:
    #         return nt.parent
    #     else:
    #         return None

class CondAttLSTM(Layer):
    """
    Conditional LSTM with Attention
    """
    def __init__(self, input_dim, output_dim,
                 context_dim, att_hidden_dim,
                 init='glorot_uniform', inner_init='orthogonal', forget_bias_init='one',
                 activation='tanh', inner_activation='sigmoid', name='CondAttLSTM'):

        super(CondAttLSTM, self).__init__()

        self.output_dim = output_dim
        self.init = initializations.get(init)
        self.inner_init = initializations.get(inner_init)
        self.forget_bias_init = initializations.get(forget_bias_init)
        self.activation = activations.get(activation)
        self.inner_activation = activations.get(inner_activation)
        self.context_dim = context_dim
        self.input_dim = input_dim

        # regular LSTM layer

        self.W_i = self.init((input_dim, self.output_dim))
        self.U_i = self.inner_init((self.output_dim, self.output_dim))
        self.C_i = self.inner_init((self.context_dim, self.output_dim))
        self.H_i = self.inner_init((self.output_dim, self.output_dim))
        self.P_i = self.inner_init((self.output_dim, self.output_dim))
        self.b_i = shared_zeros((self.output_dim))

        self.W_f = self.init((input_dim, self.output_dim))
        self.U_f = self.inner_init((self.output_dim, self.output_dim))
        self.C_f = self.inner_init((self.context_dim, self.output_dim))
        self.H_f = self.inner_init((self.output_dim, self.output_dim))
        self.P_f = self.inner_init((self.output_dim, self.output_dim))
        self.b_f = self.forget_bias_init((self.output_dim))

        self.W_c = self.init((input_dim, self.output_dim))
        self.U_c = self.inner_init((self.output_dim, self.output_dim))
        self.C_c = self.inner_init((self.context_dim, self.output_dim))
        self.H_c = self.inner_init((self.output_dim, self.output_dim))
        self.P_c = self.inner_init((self.output_dim, self.output_dim))
        self.b_c = shared_zeros((self.output_dim))

        self.W_o = self.init((input_dim, self.output_dim))
        self.U_o = self.inner_init((self.output_dim, self.output_dim))
        self.C_o = self.inner_init((self.context_dim, self.output_dim))
        self.H_o = self.inner_init((self.output_dim, self.output_dim))
        self.P_o = self.inner_init((self.output_dim, self.output_dim))
        self.b_o = shared_zeros((self.output_dim))

        self.params = [
            self.W_i, self.U_i, self.b_i, self.C_i, self.H_i, self.P_i,
            self.W_c, self.U_c, self.b_c, self.C_c, self.H_c, self.P_c,
            self.W_f, self.U_f, self.b_f, self.C_f, self.H_f, self.P_f,
            self.W_o, self.U_o, self.b_o, self.C_o, self.H_o, self.P_o,
        ]

        # attention layer
        self.att_ctx_W1 = self.init((context_dim, att_hidden_dim))
        self.att_h_W1 = self.init((output_dim, att_hidden_dim))
        self.att_b1 = shared_zeros((att_hidden_dim))

        self.att_W2 = self.init((att_hidden_dim, 1))
        self.att_b2 = shared_zeros((1))

        self.params += [
            self.att_ctx_W1, self.att_h_W1, self.att_b1,
            self.att_W2, self.att_b2
        ]

        # attention over history
        self.hatt_h_W1 = self.init((output_dim, att_hidden_dim))
        self.hatt_hist_W1 = self.init((output_dim, att_hidden_dim))
        self.hatt_b1 = shared_zeros((att_hidden_dim))

        self.hatt_W2 = self.init((att_hidden_dim, 1))
        self.hatt_b2 = shared_zeros((1))

        self.params += [
            self.hatt_h_W1, self.hatt_hist_W1, self.hatt_b1,
            self.hatt_W2, self.hatt_b2
        ]

        self.set_name(name)

    def _step(self,
              t, xi_t, xf_t, xo_t, xc_t, mask_t, parent_t,
              h_tm1, c_tm1, hist_h,
              u_i, u_f, u_o, u_c,
              c_i, c_f, c_o, c_c,
              h_i, h_f, h_o, h_c,
              p_i, p_f, p_o, p_c,
              att_h_w1, att_w2, att_b2,
              context, context_mask, context_att_trans,
              b_u):

        # context: (batch_size, context_size, context_dim)

        # (batch_size, att_layer1_dim)
        h_tm1_att_trans = T.dot(h_tm1, att_h_w1)

        # h_tm1_att_trans = theano.printing.Print('h_tm1_att_trans')(h_tm1_att_trans)

        # (batch_size, context_size, att_layer1_dim)
        att_hidden = T.tanh(context_att_trans + h_tm1_att_trans[:, None, :])
        # (batch_size, context_size, 1)
        att_raw = T.dot(att_hidden, att_w2) + att_b2
        att_raw = att_raw.reshape((att_raw.shape[0], att_raw.shape[1]))

        # (batch_size, context_size)
        ctx_att = T.exp(att_raw - T.max(att_raw, axis=-1, keepdims=True))

        if context_mask:
            ctx_att = ctx_att * context_mask

        ctx_att = ctx_att / T.sum(ctx_att, axis=-1, keepdims=True)
        # (batch_size, context_dim)
        ctx_vec = T.sum(context * ctx_att[:, :, None], axis=1)

        # t = theano.printing.Print('t')(t)

        ##### attention over history #####

        def _attention_over_history():
            hist_h_mask = T.zeros((hist_h.shape[0], hist_h.shape[1]), dtype='int8')
            hist_h_mask = T.set_subtensor(hist_h_mask[:, T.arange(t)], 1)

            hist_h_att_trans = T.dot(hist_h, self.hatt_hist_W1) + self.hatt_b1
            h_tm1_hatt_trans = T.dot(h_tm1, self.hatt_h_W1)

            hatt_hidden = T.tanh(hist_h_att_trans + h_tm1_hatt_trans[:, None, :])
            hatt_raw = T.dot(hatt_hidden, self.hatt_W2) + self.hatt_b2
            hatt_raw = hatt_raw.reshape((hist_h.shape[0], hist_h.shape[1]))
            # hatt_raw = theano.printing.Print('hatt_raw')(hatt_raw)
            hatt_exp = T.exp(hatt_raw - T.max(hatt_raw, axis=-1, keepdims=True)) * hist_h_mask
            # hatt_exp = theano.printing.Print('hatt_exp')(hatt_exp)
            # hatt_exp = hatt_exp.flatten(2)
            h_att_weights = hatt_exp / (T.sum(hatt_exp, axis=-1, keepdims=True) + 1e-7)
            # h_att_weights = theano.printing.Print('h_att_weights')(h_att_weights)

            # (batch_size, output_dim)
            _h_ctx_vec = T.sum(hist_h * h_att_weights[:, :, None], axis=1)

            return _h_ctx_vec

        h_ctx_vec = T.switch(t,
                             _attention_over_history(),
                             T.zeros_like(h_tm1))

        # h_ctx_vec = theano.printing.Print('h_ctx_vec')(h_ctx_vec)

        ##### attention over history #####

        ##### feed in parent hidden state #####

        if not config.parent_hidden_state_feed:
            t = 0

        par_h = T.switch(t,
                         hist_h[T.arange(hist_h.shape[0]), parent_t, :],
                         T.zeros_like(h_tm1))

        ##### feed in parent hidden state #####
        if config.tree_attention:
            i_t = self.inner_activation(
                xi_t + T.dot(h_tm1 * b_u[0], u_i) + T.dot(ctx_vec, c_i) + T.dot(par_h, p_i) + T.dot(h_ctx_vec, h_i))
            f_t = self.inner_activation(
                xf_t + T.dot(h_tm1 * b_u[1], u_f) + T.dot(ctx_vec, c_f) + T.dot(par_h, p_f) + T.dot(h_ctx_vec, h_f))
            c_t = f_t * c_tm1 + i_t * self.activation(
                xc_t + T.dot(h_tm1 * b_u[2], u_c) + T.dot(ctx_vec, c_c) + T.dot(par_h, p_c) + T.dot(h_ctx_vec, h_c))
            o_t = self.inner_activation(
                xo_t + T.dot(h_tm1 * b_u[3], u_o) + T.dot(ctx_vec, c_o) + T.dot(par_h, p_o) + T.dot(h_ctx_vec, h_o))
        else:
            i_t = self.inner_activation(
                xi_t + T.dot(h_tm1 * b_u[0], u_i) + T.dot(ctx_vec, c_i) + T.dot(par_h, p_i))  # + T.dot(h_ctx_vec, h_i)
            f_t = self.inner_activation(
                xf_t + T.dot(h_tm1 * b_u[1], u_f) + T.dot(ctx_vec, c_f) + T.dot(par_h, p_f))  # + T.dot(h_ctx_vec, h_f)
            c_t = f_t * c_tm1 + i_t * self.activation(
                xc_t + T.dot(h_tm1 * b_u[2], u_c) + T.dot(ctx_vec, c_c) + T.dot(par_h, p_c))  # + T.dot(h_ctx_vec, h_c)
            o_t = self.inner_activation(
                xo_t + T.dot(h_tm1 * b_u[3], u_o) + T.dot(ctx_vec, c_o) + T.dot(par_h, p_o))  # + T.dot(h_ctx_vec, h_o)
        h_t = o_t * self.activation(c_t)

        h_t = (1 - mask_t) * h_tm1 + mask_t * h_t
        c_t = (1 - mask_t) * c_tm1 + mask_t * c_t

        new_hist_h = T.set_subtensor(hist_h[:, t, :], h_t)

        return h_t, c_t, ctx_vec, new_hist_h

    def _for_step(self,
                  xi_t, xf_t, xo_t, xc_t, mask_t,
                  h_tm1, c_tm1,
                  context, context_mask, context_att_trans,
                  hist_h, hist_h_att_trans,
                  b_u):

        # context: (batch_size, context_size, context_dim)

        # (batch_size, att_layer1_dim)
        h_tm1_att_trans = T.dot(h_tm1, self.att_h_W1)

        # (batch_size, context_size, att_layer1_dim)
        att_hidden = T.tanh(context_att_trans + h_tm1_att_trans[:, None, :])

        # (batch_size, context_size, 1)
        att_raw = T.dot(att_hidden, self.att_W2) + self.att_b2

        # (batch_size, context_size)
        ctx_att = T.exp(att_raw).reshape((att_raw.shape[0], att_raw.shape[1]))

        if context_mask:
            ctx_att = ctx_att * context_mask

        ctx_att = ctx_att / T.sum(ctx_att, axis=-1, keepdims=True)

        # (batch_size, context_dim)
        ctx_vec = T.sum(context * ctx_att[:, :, None], axis=1)

        ##### attention over history #####

        if hist_h:
            hist_h = T.stack(hist_h).dimshuffle((1, 0, 2))
            hist_h_att_trans = T.stack(hist_h_att_trans).dimshuffle((1, 0, 2))
            h_tm1_hatt_trans = T.dot(h_tm1, self.hatt_h_W1)

            hatt_hidden = T.tanh(hist_h_att_trans + h_tm1_hatt_trans[:, None, :])
            hatt_raw = T.dot(hatt_hidden, self.hatt_W2) + self.hatt_b2
            hatt_raw = hatt_raw.flatten(2)
            h_att_weights = T.nnet.softmax(hatt_raw)

            # (batch_size, output_dim)
            h_ctx_vec = T.sum(hist_h * h_att_weights[:, :, None], axis=1)
        else:
            h_ctx_vec = T.zeros_like(h_tm1)

        ##### attention over history #####

        i_t = self.inner_activation(xi_t + T.dot(h_tm1 * b_u[0], self.U_i) + T.dot(ctx_vec, self.C_i) + T.dot(h_ctx_vec, self.H_i))
        f_t = self.inner_activation(xf_t + T.dot(h_tm1 * b_u[1], self.U_f) + T.dot(ctx_vec, self.C_f) + T.dot(h_ctx_vec, self.H_f))
        c_t = f_t * c_tm1 + i_t * self.activation(xc_t + T.dot(h_tm1 * b_u[2], self.U_c) + T.dot(ctx_vec, self.C_c) + T.dot(h_ctx_vec, self.H_c))
        o_t = self.inner_activation(xo_t + T.dot(h_tm1 * b_u[3], self.U_o) + T.dot(ctx_vec, self.C_o) + T.dot(h_ctx_vec, self.H_o))
        h_t = o_t * self.activation(c_t)

        h_t = (1 - mask_t) * h_tm1 + mask_t * h_t
        c_t = (1 - mask_t) * c_tm1 + mask_t * c_t

        # ctx_vec = theano.printing.Print('ctx_vec')(ctx_vec)

        return h_t, c_t, ctx_vec

    def __call__(self, X, context, parent_t_seq, init_state=None, init_cell=None, hist_h=None,
                 mask=None, context_mask=None,
                 dropout=0, train=True, srng=None,
                 time_steps=None):
        assert context_mask.dtype == 'int8', 'context_mask is not int8, got %s' % context_mask.dtype

        # (n_timestep, batch_size)
        mask = self.get_mask(mask, X)
        # (n_timestep, batch_size, input_dim)
        X = X.dimshuffle((1, 0, 2))

        retain_prob = 1. - dropout
        B_w = np.ones((4,), dtype=theano.config.floatX)
        B_u = np.ones((4,), dtype=theano.config.floatX)
        if dropout > 0:
            logging.info('applying dropout with p = %f', dropout)
            if train:
                B_w = srng.binomial((4, X.shape[1], self.input_dim), p=retain_prob,
                                    dtype=theano.config.floatX)
                B_u = srng.binomial((4, X.shape[1], self.output_dim), p=retain_prob,
                                    dtype=theano.config.floatX)
            else:
                B_w *= retain_prob
                B_u *= retain_prob

        # (n_timestep, batch_size, output_dim)
        xi = T.dot(X * B_w[0], self.W_i) + self.b_i
        xf = T.dot(X * B_w[1], self.W_f) + self.b_f
        xc = T.dot(X * B_w[2], self.W_c) + self.b_c
        xo = T.dot(X * B_w[3], self.W_o) + self.b_o

        # (batch_size, context_size, att_layer1_dim)
        context_att_trans = T.dot(context, self.att_ctx_W1) + self.att_b1

        if init_state:
            # (batch_size, output_dim)
            first_state = T.unbroadcast(init_state, 1)
        else:
            first_state = T.unbroadcast(alloc_zeros_matrix(X.shape[1], self.output_dim), 1)

        if init_cell:
            # (batch_size, output_dim)
            first_cell = T.unbroadcast(init_cell, 1)
        else:
            first_cell = T.unbroadcast(alloc_zeros_matrix(X.shape[1], self.output_dim), 1)

        if not hist_h:
            # (batch_size, n_timestep, output_dim)
            hist_h = alloc_zeros_matrix(X.shape[1], X.shape[0], self.output_dim)

        if train:
            n_timestep = X.shape[0]
            time_steps = T.arange(n_timestep, dtype='int32')

        # (n_timestep, batch_size)
        parent_t_seq = parent_t_seq.dimshuffle((1, 0))

        [outputs, cells, ctx_vectors, hist_h_outputs], updates = theano.scan(
            self._step,
            sequences=[time_steps, xi, xf, xo, xc, mask, parent_t_seq],
            outputs_info=[
                first_state,  # for h
                first_cell,  # for cell
                None, # T.unbroadcast(alloc_zeros_matrix(X.shape[1], self.context_dim), 1),  # for ctx vector
                hist_h,  # for hist_h
            ],
            non_sequences=[
                self.U_i, self.U_f, self.U_o, self.U_c,
                self.C_i, self.C_f, self.C_o, self.C_c,
                self.H_i, self.H_f, self.H_o, self.H_c,
                self.P_i, self.P_f, self.P_o, self.P_c,
                self.att_h_W1, self.att_W2, self.att_b2,
                context, context_mask, context_att_trans,
                B_u
            ])

        outputs = outputs.dimshuffle((1, 0, 2))
        ctx_vectors = ctx_vectors.dimshuffle((1, 0, 2))
        cells = cells.dimshuffle((1, 0, 2))

        return outputs, cells, ctx_vectors

    def get_mask(self, mask, X):
        if mask is None:
            mask = T.ones((X.shape[0], X.shape[1]))

        mask = T.shape_padright(mask)  # (nb_samples, time, 1)
        mask = T.addbroadcast(mask, -1)  # (time, nb_samples, 1) matrix.
        mask = mask.dimshuffle(1, 0, 2)  # (time, nb_samples, 1)
        mask = mask.astype('int8')

        return mask


import sys, csv, json, math, numpy, multiprocessing , time, random
from collections import Counter
from scipy import spatial

class Recipe:

    def __init__(self,name,id):
        self.name = name
        self.id = id
        self.ingredients = {}
        self.usersWhoRated = []
        self.clusters = {}

    def setCluster(self,cluster_name,val):
        self.clusters[cluster_name] = val

    def getMainCluster(self):
        Min = 2.0
        cluster = ''
        for c in self.clusters:
            if self.clusters[c] < Min:
                Min = self.clusters[c]
                cluster = c
        return cluster

    def getAverageRating(self):
        averageRate = 0.0
        for user_id in self.usersWhoRated:
            user = allUsers_train[user_id]
            averageRate = averageRate + user.getRate(self.id)
        averageRate = averageRate / float(len(self.usersWhoRated))
        return averageRate

    def changeIngredientValue(self,ing,val):
        self.ingredients[ing] = val

    def addIngredient(self,ing,val):
        self.ingredients[ing] = val

    def addUserRating(self,user_id):
        self.usersWhoRated.append(user_id)

class Rating:
    def __init__(self,id,rate):
        if id == '':
            print("No recipe rating id")
        elif rate == '':
            print("No recipe rate score")
        else:
            self.id = id
            self.rate = float(rate)
            if float(rate) >= 3:
                self.liked = True
            else:
                self.liked = False

class User(object):
    def __init__(self,id):
        if id == '':
            print("No User id")
        else:
            self.id = id
            self.ratings = {}
            self.rated = []
            self.averageRate = 0.0
            self.normal = True
            self.clusters = {}

    def setCluster(self,cluster_name,val):
        self.clusters[cluster_name] = val

    def getMainCluster(self):
        Max = 0.0
        cluster = ''
        for c in self.clusters:
            if self.clusters[c] > Max:
                Max = self.clusters[c]
                cluster = c
        return cluster

    def addRating(self,id,rate):
        newRating = Rating(id,rate)
        self.ratings[id] = newRating
        self.rated.append(id)

    def updateRating(self,recipe_id,val):
        newRating = Rating(recipe_id,val)
        self.ratings[recipe_id] = newRating

    def getRate(self,rec_id):
        return self.ratings[rec_id].rate

    def updateAverageRate(self):
        sum = 0.0
        for r in self.ratings:
            sum = sum + self.ratings[r].rate

        self.averageRate = sum / float(len(self.ratings))

    def normalize(self):
        self.normal = False
        for rating in self.ratings:
            self.ratings[rating].rate = self.ratings[rating].rate - self.averageRate

    def unnormalize(self):
        self.normal = True
        for rating in self.ratings:
            self.ratings[rating].rate = self.ratings[rating].rate + self.averageRate

class TestUser(User):
    def __init__(self,id):
        super(TestUser, self).__init__(id)
        self.missingRated = []
        self.missingRatings = {}

    def findmissingRated(self,allRecipes):
        for rec in allRecipes:
            if allRecipes[rec].id not in self.rated:
                self.missingRated.append(allRecipes[rec].id)

    def addMissingRating(self,rec_id,rate):
        self.missingRatings[rec_id] = rate

def parseRecipes(filePath):

    allRecipes = {}

    with open(filePath, mode ='r') as file:

        # reading the CSV file
        csvFile = csv.DictReader(file)

        # displaying the contents of the CSV file
        for lines in csvFile:
            newRecipe = Recipe(lines['dish_name'],lines['dish_id'])
            for ing in lines:
                if ing != 'dish_id' and ing != 'dish_name':
                    newRecipe.addIngredient(ing,float(lines[ing]))
                    if ing not in allingredients:
                        allingredients.append(ing)
            allRecipes[newRecipe.id] = newRecipe

    file.close()
    # print("Recipes Parsed")
    return allRecipes

def parseUsers(filePath,test):

    allUsers = {}

    f = open(filePath)

    data = json.load(f)

    for u in data:

        if test == False:
            newUser = User(u)
        else:
            newUser = TestUser(u)

        for r in data[u]:

            newUser.addRating(str(r[0]),str(r[1]))
            if test == False:
                rec = allRecipes[str(r[0])]
                rec.addUserRating(newUser.id)

        newUser.updateAverageRate()
        newUser.normalize()

        allUsers[newUser.id] = newUser

    # print("Users parsed")

    return allUsers

def cosineSim(TestUser,TrainUser,cluster):
    # print("caluculating sim score for: " + str(TestUser.id))

    # similarRatings = intersection(TestUser.rated,TrainUser.rated)
    similarRatings = set(TestUser.ratings.keys()) & set(TrainUser.ratings.keys())

    if len(similarRatings) == 0:
        return 0.0

    testData = []
    trainData = []

    for rating_id in similarRatings:
        r1 = TestUser.ratings[rating_id].rate
        r2 = TrainUser.ratings[rating_id].rate
        testData.append(r1)
        trainData.append(r2)

    result = 1 - spatial.distance.cosine(testData,trainData)

    if cluster == False:
        matchedRatio = float(len(similarRatings)) / float(len(TestUser.rated))
    else:
        matchedRatio = 1.0

    score = result * matchedRatio

    return score

def predict(rec_id, predict_user_scores, test_user):

    upper = 0.0
    lower = 0.0

    ave = test_user.averageRate

    for u in predict_user_scores:
        train_user = allUsers_train[str(u)]

        #unweight sim score
        similarRatings = set(test_user.ratings.keys()) & set(train_user.ratings.keys())
        matchedRatio = float(len(similarRatings)) / float(len(test_user.rated))

        train_user_sim_score = abs(predict_user_scores[u] / matchedRatio)
        # train_user_sim_score = predict_user_scores[u]

        # print(str(train_user_sim_score) + " vs " + str(predict_user_scores[u]))

        train_user_rating = train_user.ratings[str(rec_id)].rate

        upper = upper + float(train_user_rating) * float(train_user_sim_score)
        lower = lower + abs(float(train_user_sim_score))
    score = ave + (upper / lower)

    return score

def predict_ratings(test_user,q):

    # print("Prediction for user: " + str(test_user.id))

    for missingRate_id in test_user.missingRated:

        usersWhoRatedRecipe = allRecipes[missingRate_id].usersWhoRated

        simScores = {}

        for trainUser_id in usersWhoRatedRecipe:
            simScore = cosineSim(allUsers_test[test_user.id],allUsers_train[trainUser_id],False)
            simScores[trainUser_id] = simScore

        finalScores = {}
        c = 0
        for k, v in sorted(simScores.items(), key=lambda item: item[1]):
            c = c + 1
            if c > top:
                break
            if v == 0.0:
                continue
            finalScores[k] = v

        if len(finalScores) == 0:
            # print("could not predict score")
            continue

        s = predict(missingRate_id, finalScores, test_user)

        test_user.addMissingRating(missingRate_id,s)
        # print("Predicted: " + str(s) + " for TestUser: " + str(test_user.id) + " Recipe: " + str(missingRate_id) )

    findMAE(test_user,q)

def memoryCollabFilterPart2(q):
    if testType == "multi":
        procs = []
        for test_user in allUsers_test:
            u = allUsers_test[test_user]
            if calcMAE is True:
                if test_user in ground_truth:

                    # # multiprocessing
                    p = multiprocessing.Process(target=predict_ratings, args=(u,q))
                    procs.append(p)

                    p.start()

                    # predict_ratings(allUsers_test[test_user])
            else:
                print("NEXT TEXT USER")
                predict_ratings(allUsers_test[test_user],q)

        c = 0
        for proc in procs:
            c = c + 1
            proc.join()

        printQ(c,q)

    elif testType == "single":
        for test_user in allUsers_test:
            u = allUsers_test[test_user]
            if calcMAE is True:
                if test_user in ground_truth:
                    predict_ratings(allUsers_test[test_user],q)
            else:
                print("NEXT TEXT USER")
                predict_ratings(allUsers_test[test_user],q)

def pickseeds(nseeds,clss):
    seeds = []
    for i in range(nseeds):
        if clss == "recipe":
            ID = str(i)
            Name = "cluster_" + str(i)
            cluster = Recipe(Name,ID)

            for ing in allingredients:
                val = random.random()
                cluster.addIngredient(ing,val)

            # add = True
            # for s in seeds:
            #     if s.ingredients.values() == cluster.ingredients.values():
            #         nseeds = nseeds + 1
            #         add = False
            #         break
            # if add == True:
        elif clss == "user":
            Name = "cluster_" + str(i)
            cluster = User(Name)

            for rec_id in allRecipes:
                val = float(random.randint(2,4))
                cluster.addRating(rec_id,val)

        seeds.append(cluster)

    return seeds

def findDist(cluster, recipe):
    lst1 = []
    lst2 = []
    cings = cluster.ingredients
    rings = recipe.ingredients

    for i in allingredients:
        lst1.append(float(cings[i]))
        lst2.append(float(rings[i]))

    result = 1 - spatial.distance.cosine(lst1,lst2)

    return result

def assignClusters(centroids,clss):

    if clss == "recipe":
        for rec_id in allRecipes:
            recipe = allRecipes[rec_id]

            for cent in centroids:
                val = findDist(cent,recipe)
                val = 1 - val
                recipe.setCluster(cent.name,val)
    elif clss == "user":
        for user_id in allUsers_train:
            user = allUsers_train[user_id]

            for cent in centroids:
                val = cosineSim(user,cent,True)
                if val > 1:
                    print("this is where it is fucking up")
                user.setCluster(cent.id,val)

    pass

def pickCenteroids(oldCenteroids,clss):

    centroids = []
    for oldcent in oldCenteroids:
        if clss == "recipe":
            updated_cluster = Recipe(oldcent.name,oldcent.id)
            cluster_name = oldcent.name

            ings = {}
            for j in allingredients:
                ings[j] = 0.0

            c = 0
            for rec_id in allRecipes:
                recipe = allRecipes[rec_id]
                if recipe.getMainCluster() == cluster_name:
                    c = c + 1
                    for i in recipe.ingredients:
                        ings[i] = ings[i] + recipe.ingredients[i]

            for x in ings:
                ings[x] = ings[x] / float(c)

            updated_cluster.ingredients = ings

            centroids.append(updated_cluster)
        elif clss == "user":
            updated_cluster = User(oldcent.id)
            cluster_name = oldcent.id

            c = {}
            for rec_id in allRecipes:
                # print(rec_id)
                c[rec_id] = 0
                updated_cluster.addRating(rec_id,"0")

            for user_id in allUsers_train:

                user = allUsers_train[user_id]
                if user.getMainCluster() == cluster_name:
                    for i in user.ratings:
                        c[i] = c[i] + 1
                        ur = user.getRate(i)
                        crr = updated_cluster.getRate(i)

                        p = crr + ur
                        updated_cluster.updateRating(i,p)

            for i in allRecipes:
                x = c[i]
                if x == 0:
                    x = 1
                val = updated_cluster.getRate(i) / x
                updated_cluster.updateRating(i,val)

            centroids.append(updated_cluster)

    return centroids

def cluster(rk,rnseeds,uk,unseeds,q):

    centroidsRec = pickseeds(rnseeds,"recipe")
    # for c in centroids:
    #     print(c.name)
    for i in range(rk):
        assignClusters(centroidsRec,"recipe")
        centroidsRec = pickCenteroids(centroidsRec,"recipe")

    # print("Recipes Clustered")

    # for c in centroids:
    #     name = c.name
    #     x = 0
    #     for r in allRecipes:
    #         if allRecipes[r].getMainCluster() == name:
    #             x = x + 1
    #     print(name)
    #     print(x)

    centroidsUser = pickseeds(unseeds,"user")
    for i in range(uk):
        assignClusters(centroidsUser,"user")
        centroidsUser = pickCenteroids(centroidsUser,"user")

    # print("Users Clustered")

    # t = 0
    # res = []
    # for c in centroidsUser:
    #     name = c.id
    #     x = 0
    #     for r in allUsers_train:
    #         if allUsers_train[r].getMainCluster() == name:
    #             x = x + 1
    #             t = t + 1
    #     res.append(float(x) / 500.0)

    # res.sort()
    # print(res)
    # print("total")
    # print(t)

    # for u in allUsers_test:
    #     user = allUsers_test[u]
    #     if user.id == :
    #         clusterPredict(user,centroidsUser,centroidsRec)
    if testType == "multi":

        procs = []

        for test_user in allUsers_test:
            u = allUsers_test[test_user]
            if calcMAE is True:
                if test_user in ground_truth:
                    p = multiprocessing.Process(target=clusterPredict, args=(u,centroidsUser,centroidsRec,q))
                    procs.append(p)

                    p.start()

                    # predict_ratings(allUsers_test[test_user])
            else:
                print("NEXT TEXT USER")
                clusterPredict(u,centroidsUser,centroidsRec,q)

        c = 0
        for proc in procs:
            c = c + 1
            proc.join()

        printQ(c,q)

    elif testType == "single":
        for test_user in allUsers_test:
            u = allUsers_test[test_user]
            if calcMAE is True:
                if test_user in ground_truth:
                    clusterPredict(u,centroidsUser,centroidsRec,q)
            else:
                print("NEXT TEXT USER")
                clusterPredict(u,centroidsUser,centroidsRec,q)

    pass

def clusterPredict(user,User_centroids, Recipe_centroids,q):

    Similar_User_Centroid = ''

    for cent in User_centroids:
        if Similar_User_Centroid == '':
            Similar_User_Centroid = cent
        if cosineSim(user,cent,True) > cosineSim(user,Similar_User_Centroid,True):
            Similar_User_Centroid = cent

    if Similar_User_Centroid == '':
        print("could not find similar user centroid")
        return

    RecipePreditions = {}
    for cent in Recipe_centroids:
        x = 0
        s = 0.0
        for rec in allRecipes:
            if cent.name == allRecipes[rec].getMainCluster():
                s = s + allRecipes[rec].getAverageRating()
                x = x + 1

        s = float(s) / float(x)
        RecipePreditions[cent] = s

    finalPredictions = {}
    for rec_id in user.missingRated:

        aveRate = 0.0
        x = 0
        for u in allUsers_train:
            train_user = allUsers_train[u]
            if train_user.getMainCluster() == Similar_User_Centroid.id:
                try:
                    aveRate = aveRate + train_user.getRate(rec_id)
                    x = x + 1
                except KeyError:
                    continue

        try:
            aveRate = aveRate / float(x)
        except ZeroDivisionError:
            continue
        # print(aveRate)
        try:
            aveRec = 0.0
            recipe = allRecipes[rec_id]
            for cent in RecipePreditions:
                if cent.name == recipe.getMainCluster():
                    aveRec = RecipePreditions[cent]

            prediction = aveRate + aveRec
            prediction = prediction / 2.0

            finalPredictions[rec_id] = prediction
        except KeyError:
            continue

    user.missingRatings = finalPredictions
    if calcMAE == True:
        findMAE(user,q)
    return finalPredictions

def memoryCollabFilterPart1(q):

    rk = 2
    rnseeds = 10
    uk = 5
    unseeds = 10

    for u in allUsers_train:
        if allUsers_train[u].normal == False:
            allUsers_train[u].unnormalize()

    cluster(rk,rnseeds,uk,unseeds,q)

    for u in allUsers_train:
        if allUsers_train[u].normal == True:
            allUsers_train[u].normalize()
    # for r in allRecipes:
        # if allRecipes[r].getMainCluster() == '':
        #     print(allRecipes[r].name)
        # print(allRecipes[r].getMainCluster())

    pass

def printQ(c,q):

    mae = 0.0
    rec10 = 0.0
    rec20 = 0.0
    pre10 = 0.0
    pre20 = 0.0
    for i in range(c):
        pr = q.get()
        mae = mae + float(pr[0])
        rec10 = rec10 + float(pr[1])
        rec20 = rec20 + float(pr[2])
        pre10 = pre10 + float(pr[3])
        pre20 = pre20 + float(pr[4])
        # for s in pr:
        #     print(s)

    mae = mae / float(c)
    rec10 = rec10 / float(c)
    rec20 = rec20 / float(c)
    pre10 = pre10 / float(c)
    pre20 = pre20 / float(c)

    print("Task 1 MAE: " + str(mae))
    print("Task 2 Precision@10: " + str(pre10))
    print("Task 2 Precision@20: " + str(pre20))
    print("Task 2 Recall@10: " + str(rec10))
    print("Task 2 Recall@20: " + str(rec20))

def findMAE(user,q):
    truth_user = ground_truth[user.id]
    if truth_user.normal == False:
        truth_user.unnormalize()
    sum = 0.0
    count = 0.0
    for rating_id in user.missingRatings:
        if rating_id in truth_user.rated:
            count = count + 1.0
            x = truth_user.ratings[rating_id].rate
            y = user.missingRatings[rating_id]
            diff = abs(x - y)
            sum = sum + diff

    MAE = sum / count
    maestr = MAE # str("Test User: " + str(user.id) + " MAE: " + str(MAE) + " for " + str(count) + " matches")
    recall10 = getRecall(user,10) # str("Recall@10: " + str(getRecall(user,10)))
    recall20 = getRecall(user,20) #str("Recall@20: " + str(getRecall(user,20)))
    precision10 = getPrecision(user,10) # str("Precision@10: " + str(getPrecision(user,10)))
    precision20 = getPrecision(user,20)#str("Precision@20: " + str(getPrecision(user,20)))
    pr = [maestr,recall10,recall20,precision10,precision20]
    q.put(pr)
    pass

def recommend(user,truth):
    recommended = {}

    if user.normal == False:
        user.unnormalize()

    if truth == False:
        for rec in user.missingRatings:
            rate = user.missingRatings[rec]
            if  rate >= 3.0:
                recommended[rec] = rate
    elif truth == True:
        for rec in user.ratings:
            rate = user.getRate(rec)
            if rate >= 3.0:
                recommended[rec] = rate

    return recommended

def getRecall(user,x):

    truth_user = ground_truth[user.id]
    if truth_user.normal == False:
        truth_user.unnormalize()

    tr = recommend(truth_user,True)
    ur = recommend(user,False)

    sort_orders = sorted(tr.items(), key=lambda x: x[1], reverse=True)

    tr2 = {}
    j = 0
    for i in sort_orders:
        j = j + 1
        tr2[i[0]] = i[1]
        if j > x:
            break

    sort_orders = sorted(ur.items(), key=lambda x: x[1], reverse=True)

    ur2 = {}
    j = 0
    for i in sort_orders:
        j = j + 1
        ur2[i[0]] = i[1]
        if j > x:
            break

    relret = 0.0

    for i in tr2:
        for j in ur2:
            if i == j:
                relret = relret + 1.0
    rel = float(len(tr))
    recall = relret / rel

    return recall

def getPrecision(user,x):

    truth_user = ground_truth[user.id]
    if truth_user.normal == False:
        truth_user.unnormalize()

    tr = recommend(truth_user,True)
    ur = recommend(user,False)

    sort_orders = sorted(tr.items(), key=lambda x: x[1], reverse=True)

    tr2 = {}
    j = 0
    for i in sort_orders:
        j = j + 1
        tr2[i[0]] = i[1]
        if j > x:
            break

    sort_orders = sorted(ur.items(), key=lambda x: x[1], reverse=True)

    ur2 = {}
    j = 0
    for i in sort_orders:
        j = j + 1
        ur2[i[0]] = i[1]
        if j > x:
            break

    relret = 0.0

    for i in tr2:
        for j in ur2:
            if i == j:
                relret = relret + 1.0

    ret = float(x)

    precision = relret / ret

    return precision

def main():
    if len(sys.argv) >= 3:

        dishesFilePath = sys.argv[1]
        usersTrainFilePath = sys.argv[2]
        testfilepath = sys.argv[3]

        part = sys.argv[5]
        global top
        top = 30

        global testType
        testType = sys.argv[4]

        global calcMAE
        calcMAE = True

        global allingredients
        allingredients = []

        global allRecipes
        allRecipes = parseRecipes(dishesFilePath)

        global allUsers_train
        allUsers_train = parseUsers(usersTrainFilePath,False)
        global allUsers_test
        allUsers_test= parseUsers(usersTrainFilePath,True)

        global ground_truth
        ground_truth = parseUsers(testfilepath,True)

        for user in allUsers_test:
            allUsers_test[user].findmissingRated(allRecipes)

        q = multiprocessing.Queue()

        if str(part) == "1":
            # print("Part 1")
            memoryCollabFilterPart2(q)
        elif str(part) == "2":
            # print("Part 2")
            memoryCollabFilterPart1(q)

        # example()

    else:
        print("Not enough args")

if __name__ == "__main__":
   main()


from __future__ import unicode_literals
import yt_dlp as ytdl

from celery import Celery
import redis

from hashlib import md5

# initialize redis connection
redis = redis.Redis(host="redis", port="6379")

# define celery app
app = Celery("tasks", broker="redis://redis")

# utilities for youtube_dl
class MyLogger(object):
    def debug(self, msg):
        # debug left empty to prevent message spam
        # print(msg) to get output
        pass

    def warning(self, msg):
        print(msg)

    def error(self, msg):
        print(msg)

def progress_hook(d):
    """
    Hook to process data from download callbacks during download
    """
    video_id = d["filename"].split("/")[1]
    if d["status"] == "downloading":
        if "eta" not in d or "_percent_str" not in d:
            return
        redis.hmset(video_id,
            {
                "eta": d["_eta_str"],
                "percent": d["_percent_str"].strip(),
                "state": "downloading",
            },
        )
    elif d["status"] == "finished":
        redis.hmset(video_id, {"eta": "0", "percent": "100%", "state": "processing"})

def post_hook(d):
    """
    Hook to process data when processing (e.g. converting) during download
    """
    if d["status"] == "started":
        video_id = d["info_dict"]["_filename"].split("/")[1]
        redis.hmset(
            video_id, {"eta": "unknown", "percent": "unknown", "state": "processing"}
        )
    elif d["status"] == "finished":
        video_id = d["info_dict"]["_filename"].split("/")[1]
        redis.hmset(video_id, {"eta": "unknown", "percent": "unknown", "state": "done"})

# urls:
# test url: https://www.youtube.com/watch?v=BaW_jenozKc
# long url: https://www.youtube.com/watch?v=ddRMOKFDgos
# short ur: https://www.youtube.com/watch?v=RKmw9oS__MM

# Main task definition
@app.task
def download_request(url, dl_format):
    # config for youtube download
    ydl_opts = {
        "format": "bestvideo+bestaudio",
        "postprocessors": [],
        "logger": MyLogger(),
        "progress_hooks": [progress_hook],
        "postprocessor_hooks": [post_hook],
        #"outtmpl": "%(title)s-%(id)s.%(ext)s", # drop the title to prevent filename errors
        "outtmpl": "%(id)s.%(ext)s",
        "paths": {"home": "tmp/"},
        "verbose": True,
    }

    ydl_audio_postprocessor = {
        "key": "FFmpegExtractAudio",
        "when": "post_process",
        "preferredcodec": "mp3",
        "preferredquality": "192",
    }

    # job id used to track progress
    job_id = md5((url+"-"+dl_format).encode("ascii")).hexdigest()
    # store files by <id>/<format> (more to come later when more options are added)
    ydl_opts["paths"]["home"] = "tmp/" + job_id
    # add additional post processor if audio-only requested
    if dl_format == "audio_only":
        ydl_opts["postprocessors"].append(ydl_audio_postprocessor)
        ydl_opts["format"] = "bestaudio"
    elif dl_format == "video_only":
        ydl_opts["format"] = "bestvideo"

    # start download process
    try:
        with ytdl.YoutubeDL(ydl_opts) as ydl:
            ydl.download([url])
    except Exception as e:
        print("Error downloading")
        redis.hmset(job_id, {"state": "error", "eta": "", "percent": "", "message": str(e)})
        return


from django import forms
from .models import Comment
from users.models import UserAnimeWatchPlanned

class AddCommentForm(forms.ModelForm):
    content = forms.CharField(widget=forms.Textarea(attrs={
        'class': 'form-control',
        'rows': '4',
        'placeholder': 'Текст комментария...',
    }))

    class Meta:
        model = Comment
        fields = [
            'content',
        ]

class AddAnimeToList(forms.ModelForm):
    status = forms.ChoiceField(choices=UserAnimeWatchPlanned.Status.choices, widget=forms.Select)

    class Meta:
        model = UserAnimeWatchPlanned
        fields = [
            'status',
        ]


from collections.abc import Sequence
import dataclasses
from typing import Any, NamedTuple

import jax
from jax import core as jax_core
from jax.interpreters import ad
import jax.numpy as jnp

from jax_effects._src import core
from jax_effects._src import handler
from jax_effects._src import logging_utils

CONSOLE = logging_utils.CONSOLE
PP_SETTINGS = core.PP_SETTINGS

Loss = jax_core.ShapedArray((), jnp.float32)
loss_zero = jax_core.Literal(0.0, Loss)

print_jaxpr = logging_utils.print_jaxpr

################################################################################
# Transformation hook
################################################################################

class ValueWithLoss(NamedTuple):
  """A value with an associated loss."""

  loss: float
  value: Any

class LossTransformation(core.JaxprTransformation):
  """Loss effect transformation."""

  @classmethod
  def name(cls) -> str:
    return 'Loss-translation'

  def transform_jaxpr(self, jaxpr: jax_core.Jaxpr) -> jax_core.Jaxpr:
    return loss_translation_transform(jaxpr, g=make_zero_loss_continuation())

  def split_results(self, outvals: Sequence[Any]) -> tuple[Any, Any]:
    """Splits loss value from other result values."""
    # The loss value is the first outval.
    loss_value, *out_flat = outvals
    return loss_value, out_flat

  def combine_results(self, aux: Any, out_tree: Any) -> Any:
    """Combines loss value with other result values."""
    return ValueWithLoss(loss=aux, value=out_tree)

################################################################################
# Loss effect operation
################################################################################

# NOTE: `loss` is a special effect operation primitive.
#
# It is intentionally not registered via `@effect` for now because it should be
# skipped by the effect operation transformation because its primitive does not
# have (or need) a handler. We could extend `@effect` to support this later.
loss_p = jax_core.Primitive('loss')

LossDtype = jnp.float32
Loss = jax_core.ShapedArray((), LossDtype)

def loss(x) -> None:
  loss_p.bind(LossDtype(x))

@loss_p.def_impl
def _(x):
  return x

@loss_p.def_abstract_eval
def _(x):
  return jax_core.ShapedArray((), x.dtype)

def is_loss(primitive: jax_core.Primitive) -> bool:
  return primitive is loss_p

################################################################################
# Loss translation
################################################################################

def create_add_eqn(result, x, y):
  """Create result = x + y in jax."""
  # TODO(jax-effects-team): Create dedicated loss accumulation primitive.
  # We need special semantics for loss accumulation that effect handling
  # transform can recognize.
  return jax_core.new_jaxpr_eqn(
      invars=[x, y],
      outvars=[result],
      primitive=ad.add_jaxvals_p,
      params={},
      effects=set(),
      source_info=None,
  )

@dataclasses.dataclass
class PartialLossCont:
  """A partial loss continuation.

  captured_vars should contain a list of variables defined in the enclosing
  context.
  """

  jaxpr: jax_core.Jaxpr
  captured_vars: list[jax_core.Var]
  is_zero_loss_continuation: bool

def combine_loss_continuations(
    f: PartialLossCont, g: PartialLossCont
) -> PartialLossCont:
  """Loss continuation composition rule.

  f wthen g = lambda y: let loss, x = f(y) in loss + g(x)

  Args:
    f: The first loss continuation.
    g: The second loss continuation.

  Returns:
    A loss continuation that sequences `f` and `g` and returns their combined
    loss.
  """
  f_jaxpr = f.jaxpr
  g_jaxpr = g.jaxpr

  gensym = jax_core.gensym((f_jaxpr, g_jaxpr))
  gensym_loss = jax_core.gensym((f_jaxpr, g_jaxpr), suffix='_loss')

  loss_cont_arg = f_jaxpr.invars[len(f.captured_vars) :]
  combined_invars = f.captured_vars + g.captured_vars + loss_cont_arg
  combined_captured_vars = f.captured_vars + g.captured_vars
  f_call_invars = f.captured_vars + loss_cont_arg
  f_call_outvars = [gensym(v.aval) for v in f_jaxpr.outvars]
  f_call_eqn = jax_core.new_jaxpr_eqn(
      invars=f_call_invars,
      outvars=f_call_outvars,
      primitive=jax_core.closed_call_p,
      params=dict(call_jaxpr=jax_core.ClosedJaxpr(f_jaxpr, ())),
      effects=f_jaxpr.effects,
      source_info=None,
  )

  # If `g` is a zero loss continuation, return loss of `f` directly.
  if g.is_zero_loss_continuation:
    return PartialLossCont(
        jaxpr=jax_core.Jaxpr(
            constvars=(),
            invars=combined_invars,
            outvars=(f_call_outvars[0],),
            eqns=(f_call_eqn,),
            debug_info=f_jaxpr.debug_info,
        ),
        captured_vars=combined_captured_vars,
        is_zero_loss_continuation=False,
    )

  g_call_invars = g.captured_vars + f_call_outvars[1:]
  g_call_outvars = [gensym(v.aval) for v in g_jaxpr.outvars]
  g_call_eqn = jax_core.new_jaxpr_eqn(
      invars=g_call_invars,
      outvars=g_call_outvars,
      primitive=jax_core.closed_call_p,
      params=dict(call_jaxpr=jax_core.ClosedJaxpr(g_jaxpr, ())),
      effects=g_jaxpr.effects,
      source_info=None,
  )

  loss_add_eqn = create_add_eqn(
      gensym_loss(Loss), f_call_outvars[0], g_call_outvars[0]
  )

  combined_eqns = (f_call_eqn, g_call_eqn, loss_add_eqn)
  combined_outvars = loss_add_eqn.outvars
  combined_jaxpr = jax_core.Jaxpr(
      constvars=(),
      invars=combined_invars,
      outvars=combined_outvars,
      eqns=combined_eqns,
      debug_info=f_jaxpr.debug_info,
  )

  return PartialLossCont(
      jaxpr=combined_jaxpr,
      captured_vars=combined_captured_vars,
      is_zero_loss_continuation=False,
  )

def translate_loss_delimited_handler(
    eqn: jax_core.JaxprEqn,
    g: PartialLossCont,
    gensym_suffix,
    ctx: jax_core.JaxprPpContext,
) -> jax_core.JaxprEqn:
  """Loss translation rule for delimited handlers.

  T(with nh from p handle nf, g) = with T(nh, g) from p handle T(nf, 0)
  lk = k(arg).1 + g(k(arg).2).

  Args:
    eqn: The `delimited_handler` equation.
    g: The loss continuation.
    gensym_suffix: The gensym function.
    ctx: The pretty-printing context, for debug printing.

  Returns:
    A loss-translated `delimited_handler` equation.
  """
  if eqn.primitive is not handler.delimited_handler_p:
    raise ValueError(f'Expected delimited_handler equation, got: {eqn}')

  handler_impl = eqn.params['handler_impl']
  loss_translated_handler_impl = {}
  for effect_name, handler_impl_jaxpr in handler_impl.items():
    translated_handler_jaxpr = handler_impl_jaxpr.map_jaxpr(
        lambda jaxpr: loss_translation_transform(jaxpr, g, ctx=ctx)
    )
    loss_translated_handler_impl[effect_name] = translated_handler_jaxpr
  body_jaxpr = eqn.params['body_jaxpr']
  translated_body_jaxpr = loss_translation_transform(
      body_jaxpr, make_zero_loss_continuation()
  )
  return eqn.replace(
      outvars=[gensym_suffix('_loss')(Loss)] + eqn.outvars,
      params=dict(
          eqn.params,
          handler_impl=loss_translated_handler_impl,
          body_jaxpr=translated_body_jaxpr,
      ),
  )

@dataclasses.dataclass
class LossTranslationResult:
  new_eqns: Sequence[jax_core.JaxprEqn]
  loss_outvar: jax_core.Atom
  outvars: Sequence[jax_core.Atom]

  def pp(self, ctx: jax_core.JaxprPpContext) -> str:
    new_eqns_str = jax_core.pp_eqns(self.new_eqns, ctx, PP_SETTINGS)
    loss_outvar_str = jax_core.pp_var(self.loss_outvar, ctx)
    outvars_str = jax_core.pp_vars(self.outvars, ctx)
    return f"""LossTranslationResult(
new_eqns={new_eqns_str},
loss_outvar={loss_outvar_str},
outvars=[{outvars_str}]
)"""

def loss_transform_primitive(
    primitive: jax_core.Primitive,
) -> jax_core.Primitive:
  """Transforms primitive to return loss."""
  primitive_type = type(primitive)
  transformed_primitive = primitive_type(f'{primitive.name}_with_loss')
  transformed_primitive.multiple_results = True
  transformed_primitive.call_primitive = primitive.call_primitive
  transformed_primitive.map_primitive = primitive.map_primitive

  @transformed_primitive.def_impl
  def _(*args, **kwargs):
    del args, kwargs
    raise NotImplementedError()

  @transformed_primitive.def_effectful_abstract_eval
  def _(*args, **kwargs):
    outvals, effects = primitive.abstract_eval(*args, **kwargs)
    if isinstance(outvals, Sequence):
      return (Loss,) + tuple(outvals), effects
    else:
      return (Loss, outvals), effects

  return transformed_primitive

# TODO(jax-effects-team): Encapsulate loss transformation into a class.
# Class members: `PartialLossCont` (built), `gensym_suffix`.
def translate_loss_eqn(
    eqn: jax_core.JaxprEqn,
    g: PartialLossCont,
    gensym_suffix,
    ctx: jax_core.JaxprPpContext,
) -> LossTranslationResult:
  """Loss translation rule for a single Jaxpr equation.

  Args:
    eqn: The Jaxpr equation to translate.
    g: The loss continuation.
    gensym_suffix: The gensym function.
    ctx: The pretty-printing context, for debug printing.

  Returns:
    Loss translation result for `eqn`.
  """
  if is_loss(eqn.primitive):
    return LossTranslationResult((), eqn.invars[0], eqn.outvars)
  # TODO(jax-effects-team): Fix this rule.
  elif core.is_call_k(eqn.primitive):
    new_eqn_outvars = [gensym_suffix('_loss')(Loss)] + eqn.outvars
    new_eqn = eqn.replace(
        primitive=loss_transform_primitive(eqn.primitive),
        outvars=new_eqn_outvars,
    )
    return LossTranslationResult(
        [new_eqn],
        new_eqn.outvars[0],
        new_eqn.outvars[1:],
    )
  # Transform `call_lk` primitives.
  # Target Jaxpr should have no more `call_lk` operations.
  elif core.is_call_lk(eqn.primitive):
    CONSOLE.print('Found call_lk in loss transformation', style='bold yellow')
    print(jax_core.pp_eqn(eqn, ctx, PP_SETTINGS))
    call_k_primitive = eqn.primitive.call_k_primitive  # pytype: disable=attribute-error
    call_k_eqn_outvars = [
        gensym_suffix('')(aval) for aval in call_k_primitive.outvals
    ]
    call_k_eqn = eqn.replace(
        primitive=call_k_primitive,
        outvars=call_k_eqn_outvars,
    )

    CONSOLE.print('Converted call_lk to call_k', style='bold yellow')
    print(jax_core.pp_eqn(call_k_eqn, ctx, PP_SETTINGS))
    result = translate_loss_eqns(
        [call_k_eqn], outvars=[], g=g, gensym_suffix=gensym_suffix, ctx=ctx
    )
    CONSOLE.print('Transformed call_lk result', style='bold yellow')
    print(result.pp(ctx))
    assert len(eqn.outvars) == 1
    assert result.new_eqns[0].outvars[0] == result.loss_outvar
    # Reassign `call_k` loss outvar to `call_lk` outvar, to ensure outvar usages
    # are correct.
    result.new_eqns[0].outvars[0] = eqn.outvars[0]
    result.loss_outvar = loss_zero
    return result
  elif eqn.primitive == jax.lax.while_p:
    # Update body jaxpr.
    body_jaxpr: jax_core.ClosedJaxpr = eqn.params['body_jaxpr']
    body_num_consts = eqn.params['body_nconsts']
    new_body_jaxpr = body_jaxpr.map_jaxpr(
        lambda jaxpr: loss_translation_transform(jaxpr, g, ctx=ctx)
    )
    new_body_invars = (
        new_body_jaxpr.jaxpr.invars[:body_num_consts]
        + [gensym_suffix('_loss')(Loss)]
        + new_body_jaxpr.jaxpr.invars[body_num_consts:]
    )
    new_body_jaxpr = new_body_jaxpr.map_jaxpr(
        lambda jaxpr: jaxpr.replace(invars=new_body_invars)
    )

    # Update cond jaxpr.
    cond_jaxpr: jax_core.ClosedJaxpr = eqn.params['cond_jaxpr']
    cond_num_consts = eqn.params['cond_nconsts']
    new_cond_invars = [gensym_suffix('_loss')(Loss)] + cond_jaxpr.jaxpr.invars
    new_cond_invars = (
        cond_jaxpr.jaxpr.invars[:cond_num_consts]
        + [gensym_suffix('_loss')(Loss)]
        + cond_jaxpr.jaxpr.invars[cond_num_consts:]
    )
    new_cond_jaxpr = cond_jaxpr.map_jaxpr(
        lambda jaxpr: jaxpr.replace(invars=new_cond_invars)
    )

    # Update equation.
    new_eqn_invars = list(eqn.invars)
    new_eqn_invars.insert(body_num_consts, loss_zero)
    new_eqn_outvars = [gensym_suffix('_loss')(Loss)] + eqn.outvars
    new_eqn = eqn.replace(
        invars=new_eqn_invars,
        outvars=new_eqn_outvars,
        params=dict(
            eqn.params, body_jaxpr=new_body_jaxpr, cond_jaxpr=new_cond_jaxpr
        ),
    )
    return LossTranslationResult(
        [new_eqn],
        new_eqn.outvars[0],
        new_eqn.outvars[1:],
    )
  elif eqn.primitive is jax.lax.scan_p:
    # Update body jaxpr.
    body_jaxpr: jax_core.ClosedJaxpr = eqn.params['jaxpr']
    num_consts = eqn.params['num_consts']
    num_carry = eqn.params['num_carry']

    new_body_jaxpr = body_jaxpr.map_jaxpr(
        lambda jaxpr: loss_translation_transform(jaxpr, g, ctx=ctx)
    )
    new_body_invars = (
        new_body_jaxpr.jaxpr.invars[:num_consts]
        + [gensym_suffix('_loss')(Loss)]
        + new_body_jaxpr.jaxpr.invars[num_consts:]
    )
    new_body_jaxpr = new_body_jaxpr.map_jaxpr(
        lambda jaxpr: jaxpr.replace(
            invars=new_body_invars,
        )
    )

    # Update equation.
    new_eqn_invars = list(eqn.invars)
    new_eqn_invars.insert(num_consts, loss_zero)
    new_eqn_outvars = [gensym_suffix('_loss')(Loss)] + eqn.outvars
    new_eqn = eqn.replace(
        invars=new_eqn_invars,
        outvars=new_eqn_outvars,
        params=dict(
            eqn.params,
            jaxpr=new_body_jaxpr,
            linear=(False,) + eqn.params['linear'],
            num_carry=1 + num_carry,
        ),
    )
    return LossTranslationResult(
        [new_eqn],
        new_eqn.outvars[0],
        new_eqn.outvars[1:],
    )
  elif handler.is_handler_return(eqn.primitive):
    return LossTranslationResult([eqn], loss_zero, eqn.outvars)
  elif handler.is_delimited_handler(eqn.primitive):
    translated_eqn = translate_loss_delimited_handler(
        eqn, g, gensym_suffix, ctx=ctx
    )
    return LossTranslationResult(
        [translated_eqn], translated_eqn.outvars[0], translated_eqn.outvars[1:]
    )
  elif eqn.primitive in core.staged_transform_primitives:
    new_eqn = core.transform_subjaxprs(
        eqn, lambda jaxpr: loss_translation_transform(jaxpr, g, ctx=ctx)
    )
    return LossTranslationResult([new_eqn], loss_zero, new_eqn.outvars)
  else:
    return LossTranslationResult([eqn], loss_zero, eqn.outvars)

def translate_loss_eqns(
    eqns: Sequence[jax_core.JaxprEqn],
    outvars: Sequence[jax_core.Atom],
    g: PartialLossCont,
    gensym_suffix,
    ctx: jax_core.JaxprPpContext,
) -> LossTranslationResult:
  """Loss translation rule for Jaxpr equations.

  Definition of wlet:
    wlet x = e in e'  ==  let loss, x = e in (loss + e'.1, e'.2)

  Definition of wthen:
    e wthen lambda x: e'  ==  let loss, x = e in loss + (lambda x: e') x

  Translation rule for let
  let x = at in nf  ==  wlet x = T(at, lambda x: T(nf, g) wthen g) in T(nf, g)

  Args:
    eqns: The Jaxpr equations to translate.
    outvars: The original Jaxpr outvars.
    g: The loss continuation.
    gensym_suffix: The gensym function.
    ctx: The pretty-printing context, for debug printing.

  Returns:
    Loss translation result for `eqns`.
  """
  if not eqns:
    return LossTranslationResult((), loss_zero, outvars)

  translated_continuation = translate_loss_eqns(
      eqns[1:], outvars, g, gensym_suffix=gensym_suffix, ctx=ctx
  )

  result_eqns = []
  eqn = eqns[0]

  translated_remaining_eqns = translated_continuation.new_eqns
  continuation_loss_var = translated_continuation.loss_outvar
  continuation_outvars = translated_continuation.outvars
  continuation_outvars = [
      v if not isinstance(v, jax_core.DropVar) else jax_core.Literal(0, v.aval)
      for v in continuation_outvars
  ]
  loss_continuation_free_vars = core.free_vars_in_eqns(
      translated_remaining_eqns, set(eqn.outvars)
  )
  remaining_loss_continuation = jax_core.Jaxpr(
      constvars=(),
      invars=loss_continuation_free_vars + eqn.outvars,
      outvars=(continuation_loss_var, *continuation_outvars),
      eqns=translated_remaining_eqns,
      debug_info=g.jaxpr.debug_info,
  )
  # remaining_loss_continuation = dce_jaxpr(remaining_loss_continuation)

  # T(nf, g)
  remaining_loss_continuation_t = PartialLossCont(
      remaining_loss_continuation,
      captured_vars=loss_continuation_free_vars,
      is_zero_loss_continuation=False,
  )

  # T(at, lambda x: T(nf, g) wthen g) in T(nf, g)
  translated_eqn_loss = translate_loss_eqn(
      eqn,
      combine_loss_continuations(remaining_loss_continuation_t, g),
      gensym_suffix=gensym_suffix,
      ctx=ctx,
  )
  loss_outvar = translated_eqn_loss.loss_outvar
  result_eqns.extend(translated_eqn_loss.new_eqns)
  result_eqns.extend(translated_remaining_eqns)

  if core.is_literal_equal(loss_outvar, loss_zero):
    return LossTranslationResult(
        result_eqns, continuation_loss_var, continuation_outvars
    )
  if core.is_literal_equal(continuation_loss_var, loss_zero):
    return LossTranslationResult(result_eqns, loss_outvar, continuation_outvars)

  loss_result_var = gensym_suffix('_loss')(Loss)
  add_eqn = create_add_eqn(loss_result_var, continuation_loss_var, loss_outvar)
  result_eqns.append(add_eqn)
  return LossTranslationResult(
      result_eqns, loss_result_var, continuation_outvars
  )

def make_zero_loss_continuation() -> PartialLossCont:
  res = jax_core.Jaxpr(
      constvars=(),
      invars=(),
      outvars=(loss_zero,),
      eqns=(),
  )
  return PartialLossCont(
      jaxpr=res, captured_vars=[], is_zero_loss_continuation=True
  )

def loss_translation_transform(
    jaxpr: jax_core.Jaxpr,
    g: PartialLossCont,
    ctx: jax_core.JaxprPpContext | None = None,
) -> jax_core.Jaxpr:
  """Loss translation transform for `Jaxpr`."""
  if ctx is None:
    ctx = core.pp_ctx_factory(jaxpr)
  gensym_suffix = lambda suffix: jax_core.gensym((jaxpr,), suffix=suffix)
  translation_result = translate_loss_eqns(
      jaxpr.eqns,
      outvars=jaxpr.outvars,
      g=g,
      gensym_suffix=gensym_suffix,
      ctx=ctx,
  )
  transformed_eqns = translation_result.new_eqns
  loss_var = translation_result.loss_outvar
  return jaxpr.replace(
      eqns=transformed_eqns, outvars=(loss_var,) + tuple(jaxpr.outvars)
  )


from fastapi.testclient import TestClient
from app.main import app  # Asegúrate de importar tu aplicación FastAPI
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.schemas.card import CardBase
from app.config.db import URL, Base

# Configura una base de datos temporal para las pruebas
engine = create_engine(URL)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base.metadata.create_all(bind=engine)

# Crea un cliente de prueba
client = TestClient(app)

def test_create_card():
    card_data = {
        "name": "Test Card",
        "type": "Monster",
        "attack": 1000,
        "defense": 800,
        "description": "A test card.",
        "image_url": "https://example.com/test.jpg"
    }

    response = client.post("/cards/", json=card_data)
    assert response.status_code == 201
    created_card = response.json()
    assert created_card["name"] == card_data["name"]

    # Create a card to update
    card_data = {
        "name": "Test Card",
        "type": "Monster",
        "attack": 1000,
        "defense": 800,
        "description": "A test card.",
        "image_url": "https://example.com/test.jpg"
    }
    response = client.post("/cards/", json=card_data)
    assert response.status_code == 200
    created_card = response.json()

    # Update the card
    updated_card_data = {
        "name": "Updated Test Card",
        "type": "Spell",
        "attack": None,
        "defense": None,
        "description": "An updated test card.",
        "image_url": "https://example.com/updated_test.jpg"
    }
    response = client.put(f"/cards/{created_card['id']}", json=updated_card_data)
    assert response.status_code == 200
    updated_card = response.json()
    assert updated_card["name"] == updated_card_data["name"]
    assert updated_card["type"] == updated_card_data["type"]
    assert updated_card["attack"] == updated_card_data["attack"]
    assert updated_card["defense"] == updated_card_data["defense"]
    assert updated_card["description"] == updated_card_data["description"]
    assert updated_card["image_url"] == updated_card_data["image_url"]

def test_update_card_invalid_id():
    # Try to update a card with an invalid ID
    updated_card_data = {
        "id": 12,
        "name": "Updated Test Card",
        "type": "Spell",
        "attack": None,
        "defense": None,
        "description": "An updated test card.",
        "image_url": "https://example.com/updated_test.jpg"
    }
    response = client.put("/cards/999", json=updated_card_data)
    assert response.status_code == 404
    assert response.json()["detail"] == "Card not found"

def test_update_card_invalid_data():
    # Create a card to update
    card_data = {
        "name": "Test jjjjj",
        "type": "spell",
        "attack": 1000,
        "defense": 800,

        "image_url": "https://example.com/test.jpg"
    }
    response = client.post("/cards/", json=card_data)
    assert response.status_code == 404
    created_card = response.json()

    # Try to update the card with invalid data
    updated_card_data = {
        "name": "",
        "type": "Invalid",
        "attack": "not a number",
        "defense": "not a number",
        "description": "",
        "image_url": "not a URL"
    }
    response = client.put(f"/cards/{created_card['id']}", json=updated_card_data)
    assert response.status_code == 422
    assert response.json()["detail"][0]["msg"] == "field required"
    assert response.json()["detail"][1]["msg"] == "value is not a valid enumeration member; permitted: 'Monster', 'Spell', 'Trap'"
    assert response.json()["detail"][2]["msg"] == "value is not a valid integer"
    assert response.json()["detail"][3]["msg"] == "value is not a valid integer"
    assert response.json()["detail"][4]["msg"] == "ensure this value has at least 1 characters"
    assert response.json()["detail"][5]["msg"] == "value is not a valid URL"


import os

import pytest

from git_indexer.mirror import mirror_repo

@pytest.mark.skipif(os.environ.get("OFFLINE_MODE") == "1", reason="running in offline mode")
def test_mirror_repo(tmp_path):
    parent_path = tmp_path.as_posix()

    # 1st run should trigger a git clone using git clone --mirror
    mirror_path, is_new = mirror_repo(
        "https://github.com/sloppycoder/hello.git",
        repo_source="github",
        is_private_repo=False,
        dest_path=parent_path,
    )
    assert is_new
    assert os.path.isfile(mirror_path + "/HEAD")

    # 2nd run should just git fetch --prune. by default no output is the repo is up-to-date
    mirror_path, is_new = mirror_repo(
        "https://github.com/sloppycoder/hello.git",
        repo_source="github",
        is_private_repo=False,
        dest_path=parent_path,
    )
    assert not is_new
    assert os.path.isfile(mirror_path + "/HEAD")


#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''letv 点播工具'''
__author__ = "wenxiaoning(371032668@qq.com)"
__copyright__ = "Copyright of hopapapa (2017)."

from src.config import BaseConfig
from src.common.letv import utils as letv_utils

from elasticsearch.client import query_params

@query_params('client_ip','uploadtype','file_size')
def upload_init(video_name, params=None):
    api = 'video.upload.init'

    if params:
        params['video_name'] = video_name
    else:
        params = {'video_name': video_name}

    params['isdownload'] = 1

    return letv_utils.post(
        url=BaseConfig.LETV_VIDEO_API_URL,
        api=api,
        params=params
    )

def get_video(video_id):
    api = 'video.get'

    return letv_utils.post(
        url=BaseConfig.LETV_VIDEO_API_URL,
        api=api,
        params={"video_id": video_id}
    )


from datetime import date

start_date, start_month,start_year = 0,0,0

end_date, end_month,end_year = 0,0,0

print("Enter start year , start month , start date separated by space")
li = list(map(int,input().split()))

start_year, start_month, start_date = li[0] , li[1], li[2]

print("Enter end year , end month , end date separated by space")
li = list(map(int,input().split()))

end_year, end_month, end_date = li[0] , li[1], li[2]

initial_date = date(start_year, start_month, start_date)
final_date = date(end_year, end_month, end_date)
date_ans = final_date - initial_date

print("Nos of days are ",date_ans.days)


### Integrate HTML With Flask
### HTTP verb GET And POST
from flask import Flask,redirect,url_for,render_template,request
import pickle
import numpy as np
import pandas as pd
app=Flask(__name__)

model = pickle.load(open('model.pkl','rb'))

@app.route('/')

def welcome():
    return render_template('index.html')

# @app.route('/submit',methods=['POST','GET'])
# def submit():
#     total_score=0
#     if request.method=='POST':
#         science=float(request.form['science'])
#         maths=float(request.form['maths'])
#         c=float(request.form['c'])
#         data_science=float(request.form['datascience'])
#         total_score=(science+maths+c+data_science)/4

#         output = model.predict()
#     res=""
#     return redirect(url_for('success',score=total_score))

@app.route('/index',methods=['POST'])
def predict():
    total_score=0
    if request.method=='POST':
        ##age, job, marital, education, default,
        # balance, housing, loan, contact, day, month,
        # duration, campaign, pdays, previous, poutcome
        custmername = request.form['Name']
        age = float(request.form['Age'])
        job = float(request.form['Job'])
        marital = float(request.form['MartialStatus'])
        education = float(request.form['Education'])
        default = float(request.form['Loandefault'])
        balance = float(request.form['Balance'])
        housing = float(request.form['Housing'])
        loan = float(request.form['AnyOtherLoans'])
        contact = float(request.form['Contact'])
        day = float(request.form['Day'])
        month = float(request.form['Month'])
        duration = float(request.form['Duration'])
        campaign = float(request.form['NoOfCampaigns'])
        pdays = float(request.form['DaysAfterPreviouscampaign'])
        previous = float(request.form['NoOfPreviousContacts'])
        poutcome = float(request.form['PreviousOutcome'])

        input_variables = pd.DataFrame([[age, job, marital, education, default,
        balance, housing, loan, contact, day, month,
        duration, campaign, pdays, previous, poutcome]],
        columns=['age','job','marital','education','default',
        'balance','housing','loan','contact','day','month',
        'duration','campaign','pdays','previous','poutcome'], dtype=float, index=['input'])
        prediction = model.predict(input_variables)
        prediction = prediction[0]
        result =" "
        if prediction==1:
            result="Customer {name}  will take Term Loan!!! ".format(name = custmername)
        else:
            result="Customer {name}  will not Take Term Loan!!! ".format(name = custmername)

    return redirect(url_for('.pred',result=result))

@app.route('/index/<string:result>',methods=['GET'])
def pred(result):
    # res=""
    # if result==1:
    #     res="SUCCESS"
    # else:
    #     res="FAIL"
    return render_template('index.html',result=result)

@app.route('/result/<int:score>')
def success(score):
    res=""
    if score==1:
        res="SUCCESS"
    else:
        res="FAIL"

    return render_template('result.html',result=res)

@app.route('/fail/<int:score>')
def fail(score):
    return "The Person has failed and the marks is "+ str(score)

### Result checker
@app.route('/results/<int:marks>')
def results(marks):
    result=""
    if marks<50:
        result='fail'
    else:
        result='success'
    return redirect(url_for(result,score=marks))

### Result checker submit html page
@app.route('/submit',methods=['POST','GET'])
def submit():
    total_score=0
    if request.method=='POST':
        science=float(request.form['science'])
        maths=float(request.form['maths'])
        c=float(request.form['c'])
        data_science=float(request.form['datascience'])
        total_score=(science+maths+c+data_science)/4

        output = model.predict()
    res=""
    return redirect(url_for('success',score=total_score))

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


#!/usr/bin/env python3

# convert-lattice.py
#
# Basic lattice conversion utility, using the ConvertLattice package.
# Note: the framework is more flexible than this script, and complex
# macros can be developed for more complicated use-cases.
# M Wallbank, Fermilab <wallbank@fnal.gov>
# July 2023

import os
import argparse
from LatticeConvert import LatticeConverter

def main():
    parser = argparse.ArgumentParser(prog = "convert-lattice",
                                     description = "Simple lattice conversion between different formats.")

    parser.add_argument('-i', '--input_format', choices=['elegant','madx','6dsim'])
    parser.add_argument('-s', '--input_filename')
    parser.add_argument('-o', '--output_format', choices=['elegant','madx','6dsim'])
    parser.add_argument('-f', '--output_filename')
    parser.add_argument('-v', '--verbose', action='store_true')

    args = parser.parse_args()

    converter = LatticeConverter(verbose=args.verbose);
    if args.input_format in ['madx']:
        print("ERROR (convert-lattice.py): LatticeConvert is not yet able to input MAD-X lattices.")
        exit()
    if not args.input_filename or not os.path.isfile(args.input_filename):
        print("ERROR (convert-lattice.py): Unable to open input file {}.".format(args.input_filename))
        exit()
    if args.input_format == "elegant":
        converter.LoadElegant(inputFile=args.input_filename)
    elif args.input_format == "madx":
        converter.LoadMADX(inputFile=args.input_filename)
    elif args.input_format == "6dsim":
        converter.Load6DSim(inputFile=args.input_filename)
    if args.output_format in ['6dsim']:
        print("ERROR (convert-lattice.py): LatticeConvert is not yet able to output 6DSim lattices.")
    if args.output_format == "elegant":
        converter.WriteElegant(outputFile=args.output_filename)
    elif args.output_format == "madx":
        converter.WriteMADX(outputFile=args.output_filename)
    elif args.output_format == "6dsim":
        converter.WriteSixDSim(outputFile=args.output_filename)

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


import serial           # import the module
import struct
import time

ComPort = serial.Serial('/dev/ttyUSB1') # open COM24
ComPort.baudrate = 115200 # set Baud rate to 9600
ComPort.bytesize = 8    # Number of data bits = 8
ComPort.parity   = 'N'  # No parity
ComPort.stopbits = 1    # Number of Stop bits = 1

print("Enter 2 sixteen bit numbers.\nThe sum will be printed")
print("Press 'q' to exit infinite loop at any time")

while True:
    x=input("Enter number 1: ")
    ot= ComPort.write(struct.pack('h', int(x)))    #for sending data to FPGA
    if x == 'q':
        break

    y=input("Enter number 2: ")
    ot= ComPort.write(struct.pack('h', int(y)))    #for sending data to FPGA

    it=(ComPort.read(2))                #for receiving data from FPGA

    print(f"{x}+{y} = {int.from_bytes(it, byteorder='big')}")

ComPort.close()         # Close the Com port


import os
import glob
import sys
import math
import json
import index
from nltk import FreqDist
import text_proc

def model(json_request):

	data = json.loads(json_request)

	if data['action'] == 'build':
		request_build(data['path'])
	elif data['action'] == 'query':
		return request_query(data['query'], data['count'], data['similarity_techniques'])

# TODO: proceamiento de texto
def request_build(path):

	data = []

	if 'win' in sys.platform:
		_path = path + "\**"
	else:
		_path = path + "/**"

	if os.path.exists(path):
		# pdf_docs = glob.glob(os.path.join(_path, "*.pdf"), recursive=True)
		# docx_docs = glob.glob(os.path.join(_path, "*.docx"), recursive=True)
		txt_docs = glob.glob(os.path.join(_path, "*.txt"), recursive=True)
		docs =  txt_docs #+ docx_docs + pdf_docs

		docs_text = [(doc_path,read_doc(doc_path),0) for doc_path in docs]
		vector = get_vector(docs_text)

		#crear la estructura 'docs_text' que se utiliza para poder calcular los minterms d cada documento
		for index_doc in range(len(docs_text)):

			json_process = json.dumps({'action':'process', 'data':docs_text[index_doc][1]})
			terms = json.loads(text_proc.text_work(json_process))['terms']

			minterm_of_doc = get_minterm_of_document(vector, terms)
			docs_text[index_doc] = (docs_text[index_doc][0], terms, minterm_of_doc)

		#Creando el indice
		for doc in docs_text:
			terms = doc[1]
			doc_name = os.path.basename(doc[0])
			append_terms(doc_name, terms, data, minterm_doc=doc[2], vector= vector )

		append_idfs(data, len(docs))
		append_weights(data)

		json_process = json.dumps({'action':'create', 'data':data})
		index.start(json_process)

	else: raise Exception("The path is not correct")

# TODO: proceamiento de texto
#TODO poner las otras medidas
#TODO quitar palabras que no son de ningun doc
def request_query(query, count_doc, similarity_techniques):

	json_process = json.dumps({'action': 'process', 'data': query})
	query_terms = json.loads(text_proc.text_work(json_process))['terms']

	query_data = []
	vector = {query_terms[index_term]:index_term for index_term in range(len(query_terms)) }

	#Construir el indice de la query
	append_terms('query',query_terms,query_data,"0",vector)
	append_idfs(query_data, 1)
	append_weights(query_data)

	docs = get_docs_from_query(query_terms)
	dicc_ranking = similarity_cosine(query_data, docs) if similarity_techniques == 0 else similarity_vectorial_model(query_data, docs)

	result= list(zip(dicc_ranking.keys(), dicc_ranking.values()))
	result.sort(key=lambda x: x[1], reverse=True)
	result = result[:count_doc]

	return json.dumps({'action':'report', 'sucess':True,'results':[{'document':dc, 'match':val} for dc, val in result]})

##########################################################
#################### CONSTRUIR INDEX #####################
##########################################################

def append_terms(doc, terms, data, minterm_doc,vector):

	tf = FreqDist(terms)
	max_tf = max(tf.values()if len(tf)>0 else [0])

	for term in tf.keys():
		normalize_tf = tf[term] / max_tf
		new_doc = {'tf': normalize_tf,
				   'weight': 0,
				   'minterm':minterm_doc}
		in_data = False
		for term_data in data:
			if term == term_data['key']:

				#update
				term_data['value']['documents'][doc] = new_doc
				in_data = True
				break

		if not in_data:
			# add
			data.append({'key': term,
						 'value': {'idf':0,
									'documents': {doc:new_doc},
									'index_in_vector': vector[term]}})

def append_idfs(data, N):
	for term in data:
		term['value']['idf'] = math.log10((N / len(term['value']['documents'])) + 1)

def append_weights(data):

	for term in data:
		for doc in term['value']['documents'].keys():
			term['value']['documents'][doc]['weight'] = get_weight_term_doc(data, term, (doc,term['value']['documents'][doc]))

#TODO: Index doc[terms]
#TODO values = value
def get_weight_term_doc(data, term, doc):
	# json_get_request = index.start(json.dumps({'action':'get', 'key':term}))
	# dic_get_request = json.loads(json_get_request)

	term = term['value']

	weight = doc[1]['tf'] * term['idf']

	ter_doc = [tm for tm in data if doc[0] in tm['value']['documents'].keys()]
	list_for_sum = [(tm['value']['idf'] * tm['value']['documents'][dc]['tf']) ** 2 for tm in ter_doc for  dc in tm['value']['documents'] if
					dc == doc[0]]

	doc_norm = math.sqrt(sum(list_for_sum))

	return weight / doc_norm

#TODO: proceamiento de texto
def get_vector(docs_text):

	all_docs_together = ""
	vector_of_terms = []
	i=0
	for doc in docs_text:
		print(os.path.basename(doc[0]))
		i+=1
		json_process = json.dumps({'action': 'process', 'data': doc[1]})
		json_request =json.loads(text_proc.text_work(json_process))
		vector_of_terms = vector_of_terms + json_request['terms']
	vector_of_terms = list(set(vector_of_terms))
	return  {vector_of_terms[index_term]: index_term for index_term in range(len(vector_of_terms))}

def get_minterm_of_document(vector, doc_terms):
	minterm = ""
	for tm in vector.keys():
		if tm in doc_terms:
			minterm += "1"
		else:
			minterm += "0"

	return minterm

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

##########################################################
############### PROCESAMIENTO DOCUMENTOS #################
##########################################################

def get_docs_from_query(query_terms):

	docs = {}
	for term in query_terms:
		json_respons = json.loads(index.start(json.dumps({'action':'get', 'key':term})))
		if json_respons['success']:
			term_value = json_respons['value']
			for doc in term_value['documents'].keys():
				if doc not in docs:
					docs[doc] = {'terms': {term: 0}, 'weights': [term_value['documents'][doc]['weight']]}
				else:
					docs[doc]['terms'][term] = len(docs[doc]['weights'])
					docs[doc]['weights'].append(term_value['documents'][doc]['weight'])

	return docs

def read_doc(doc_path):
	return read_txt(doc_path) if doc_path.endswith('.txt') else read_pdf(doc_path) if doc_path.endswith('.pdf') else\
		read_html(doc_path) if doc_path.endswith('.html') else read_docx(doc_path)

def read_txt(txt_path):
	with open(txt_path, "r", encoding="utf8", errors="ignore") as f:
		document = f.read()
		f.close()
		return document

# TODO: PDF
def read_pdf(pdf_path):
	pass

#TODO: HTML
def read_html(html_path):
	pass

#TODO: DOCX
def read_docx(docx_path):
	pass

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

##########################################################
###################### FORMULAS MVG ######################
##########################################################

#TODO:EXPLICAR
def term_i_Dot_term_j(term_i, term_j):
	result = 0
	analized_minterm = []
	for doc_tm_i in term_i['documents']:
		if term_i['documents'][doc_tm_i]['minterm'] not in analized_minterm and \
				term_i['documents'][doc_tm_i]['minterm'][term_i['index_in_vector']] == '1' and term_i['documents'][doc_tm_i]['minterm'][term_j['index_in_vector']] == '1':
			result += ci_r(term_i,term_i['documents'][doc_tm_i]['minterm'])*ci_r(term_j,term_i['documents'][doc_tm_i]['minterm'])
			analized_minterm.append(term_i['documents'][doc_tm_i]['minterm'])

	for  doc_tm_j in term_j['documents']:
		if term_j['documents'][doc_tm_j]['minterm'] not in analized_minterm and \
				term_j['documents'][doc_tm_j]['minterm'][term_i['index_in_vector']] == '1' and term_j['documents'][doc_tm_j]['minterm'][term_j['index_in_vector']] == '1':
			result += ci_r(term_i,term_j['documents'][doc_tm_j]['minterm'])*ci_r(term_j,term_j['documents'][doc_tm_j]['minterm'])
			analized_minterm.append(term_j['documents'][doc_tm_j]['minterm'])

	return result

def ci_r(term_i, minterm_r):
	return sum([term_i['documents'][doc]['weight'] for  doc in term_i['documents'] if term_i['documents'][doc]['minterm'] == minterm_r ])

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

##########################################################
################### SIMILARITY METRICS ###################
##########################################################

def similarity_cosine(query_data, docs):
	dicc_ranking = {}

	for doc in docs.keys():
		numerador = 0
		sum_weight_in_doc =0
		sum_weight_in_query =0
		for tm_query_i in query_data:
			for tm_query_j in query_data:
				if tm_query_i['key'] in docs[doc]['terms'].keys():
					weight_query_i = tm_query_i['value']['documents']['query']['weight']
					index_weight_data = docs[doc]['terms'][tm_query_i['key']]
					weight_doc_j = docs[doc]['weights'][index_weight_data]
					tmI = {}
					tmJ = {}
					tm = get_from_index(tm_query_i['key'])
					if tm['success']:
						tmI = tm['value']
					tm = get_from_index(tm_query_j['key'])

					if tm['success']:
						tmJ = tm['value']
					tmI_dot_tmJ = term_i_Dot_term_j(tmI, tmJ)

					numerador += weight_query_i * weight_doc_j * tmI_dot_tmJ

			if tm_query_i['key'] in docs[doc]['terms'].keys():

				weight_query = tm_query_i['value']['documents']['query']['weight']
				sum_weight_in_query += weight_query **2

		json_request = json.dumps({'action':'terms', 'key':doc})
		json_responsive = index.start(json_request)
		result = json.loads(json_responsive)
		terms_in_doc = result['terms']

		for tm_doc in terms_in_doc:
			tm = get_from_index(tm_doc)['value']
			for d in tm['documents']:
				if d == doc:
					sum_weight_in_doc += tm['documents'][d]['weight']**2

		denominador = math.sqrt(sum_weight_in_doc*sum_weight_in_query)

		dicc_ranking[doc] = numerador/denominador

	return dicc_ranking

def similarity_vectorial_model(query_data, docs):
	dicc_ranking = {}

	for doc in docs.keys():
		for tm_query in query_data:
			if tm_query['key'] in docs[doc]['terms'].keys():

				weight_query = tm_query['value']['documents']['query']['weight']
				index_weight_data = docs[doc]['terms'][tm_query['key']]
				weight_data = docs[doc]['weights'][index_weight_data]
				if doc not in dicc_ranking:
					dicc_ranking[doc] = weight_query * weight_data
				else:
					dicc_ranking[doc] += weight_query * weight_data
	return dicc_ranking

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

def get_from_index(term):
	return json.loads(index.start(json.dumps({'action':'get', 'key':term})))