from six.moves import zip
import math
from absl import flags
import numpy as np
import tensorflow as tf
import sonnet as snt
from tensorflow.contrib.layers import layer_norm
import data.classification as cls_data

FLAGS = flags.FLAGS
# Model Specification
flags.DEFINE_integer("num_iters", 1, "Number of iterations (T).")
flags.DEFINE_integer("dim_reprs", 64, "Dimension of the functional representation outputs (dim(r(x))).")
flags.DEFINE_integer("nn_size", 64, "Size of hidden layers in neural modules.")
flags.DEFINE_integer("nn_layers", 3, "Number of MLP layers in neural modules.")
flags.DEFINE_integer("embedding_layers", 1, "Num of embedding mlp layers.")
flags.DEFINE_float(
    "initial_inner_lr", 1.0, "The initial learning rate for functional updates.")
flags.DEFINE_boolean("use_kernel", False, "If True, use kernel; If False, use attention.")
flags.DEFINE_boolean("use_gradient", False, "If True, use gradient-based local updater; "
                                        "If False, use neural local updater.")
flags.DEFINE_boolean("no_decoder", False, "Whether to remove decoder and directly use the functional "
                                            "representation as the predictor .")
flags.DEFINE_string("initial_state_type", "zero", "Type of initial state (zero/constant/parametric)")
flags.DEFINE_string("attention_type", "dot_product", "Type of attention (only dot_product is supported now)")
flags.DEFINE_string("kernel_type", "se", "Type of kernel functions (se/deep_se)")
flags.DEFINE_boolean("repr_as_inputs", False, "If true, use reprs as inputs to the decoder; "
                                        "If false, use reprs to generate weights of the predictor.")
# Regularisation
flags.DEFINE_float("dropout_rate", 0.0, "Rate of dropout.")
flags.DEFINE_float("l2_penalty_weight", 1e-8, "The weight measuring the "
                   "importance of the l2 regularization in the final loss. See λ₁ "
                   "in LEO paper.")
flags.DEFINE_float("orthogonality_penalty_weight", 1e-3, "The weight measuring "
                   "the importance of the decoder orthogonality regularization "
                   "in the final loss. See λ₂ in LEO paper.")
flags.DEFINE_float("label_smoothing", 0.0, "Label smoothing for classification tasks.")

class MetaFunClassifier(snt.AbstractModule):

    def __init__(self, name="MetaFunClassifier"):
        super(MetaFunClassifier, self).__init__(name=name)
        self._float_dtype = tf.float32
        self._int_dtype = tf.int32
        # Components configurations
        self._use_kernel = FLAGS.use_kernel
        self._use_gradient = FLAGS.use_gradient
        self._attention_type = FLAGS.attention_type
        self._kernel_type = FLAGS.kernel_type
        self._no_decoder = FLAGS.no_decoder
        if self._no_decoder:
            self._dim_reprs = 1
        self._initial_state_type = FLAGS.initial_state_type
        # Architecture configurations
        self._nn_size = FLAGS.nn_size
        self._nn_layers = FLAGS.nn_layers
        self._dim_reprs = FLAGS.dim_reprs
        self._num_iters = FLAGS.num_iters
        self._embedding_layers = FLAGS.embedding_layers
        # Regularisation configurations
        self._l2_penalty_weight = FLAGS.l2_penalty_weight
        self._dropout_rate = FLAGS.dropout_rate
        self._label_smoothing = FLAGS.label_smoothing
        self._orthogonality_penalty_weight = FLAGS.orthogonality_penalty_weight
        # Data configurations
        self._num_classes = FLAGS.num_classes
        self._num_tr_examples_per_class = FLAGS.num_tr_examples_per_class
        self._num_val_examples_per_class = FLAGS.num_val_examples_per_class
        # Other configurations
        self._initial_inner_lr = FLAGS.initial_inner_lr
        self._nonlinearity = tf.nn.relu

    def _build(self, data, is_training=True):
        data = cls_data.ClassificationDescription(*data)
        self.is_training = is_training
        self.embedding_dim = data.tr_input.get_shape()[-1].value
        # initial states
        tr_reprs = self.forward_initialiser(data.tr_input)
        val_reprs = self.forward_initialiser(data.val_input)
        # inner learning rate
        alpha = tf.compat.v1.get_variable("alpha", [1, 1], dtype=self._float_dtype,
                initializer=tf.constant_initializer(self._initial_inner_lr), trainable=True)
        # iterative functional updating
        for k in range(self._num_iters):
            updates = self.forward_local_updater(tr_reprs, data.tr_output, data.tr_input)
            tr_updates = alpha * self.forward_kernel_or_attention(querys=data.tr_input, keys=data.tr_input, values=updates)
            val_updates = alpha * self.forward_kernel_or_attention(querys=data.val_input, keys=data.tr_input, values=updates)
            tr_reprs += tr_updates
            val_reprs += val_updates
        # decode functional representation
        classifier_weights = self.forward_decoder(tr_reprs)
        tr_loss, tr_metric = self.calculate_loss_and_acc(
                data.tr_input, data.tr_output, classifier_weights)
        classifier_weights = self.forward_decoder(val_reprs)
        val_loss, val_metric = self.calculate_loss_and_acc(
                data.val_input, data.val_output, classifier_weights)
        # aggregate loss and metrics in a batch
        batch_tr_loss = tf.reduce_mean(val_loss)
        batch_tr_metric = tf.reduce_mean(tr_metric)
        batch_val_loss = tf.reduce_mean(val_loss)
        batch_val_metric = tf.reduce_mean(val_metric)
        #
        regularization_penalty = (
           self._l2_regularization + self._decoder_orthogonality_reg)
        return batch_val_loss + regularization_penalty, batch_tr_metric, batch_val_metric

    ### Initialiser r_0(x) ###
    @snt.reuse_variables
    def forward_initialiser(self, x):
        num_points = tf.shape(x)[0]
        if self._initial_state_type == "parametric":
            reprs = self.parametric_initialiser(x)
        elif self._initial_state_type == "constant":
            reprs = self.constant_initialiser(num_points, trainable=True)
        elif self._initial_state_type == 'zero':
            reprs = self.constant_initialiser(num_points, trainable=False)
        else:
            raise NameError("Unknown initial state type")
        tf.compat.v1.logging.info("forwarded {0} initialiser".format(self._initial_state_type))
        return reprs
    # r_0(x) = c
    @snt.reuse_variables
    def constant_initialiser(self, num_points, trainable=False):
        with tf.compat.v1.variable_scope("constant_initialiser"):
            if trainable:
                init = tf.compat.v1.get_variable(
                    "initial_state", [1, self._dim_reprs],
                    dtype=self._float_dtype,
                    initializer=tf.constant_initializer(0.0), trainable=True)
            else:
                init = tf.zeros([1, self._dim_reprs])
            init = tf.tile(init, [num_points, 1])
            init = tf.concat([init for c in range(self._num_classes)], axis=-1)
            return init
    # r_0(x) = MLP(x)
    @snt.reuse_variables
    def parametric_initialiser(self, x):
        with tf.compat.v1.variable_scope("parametric_initialiser"):
            after_dropout = tf.nn.dropout(x, rate=self.dropout_rate)
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            module = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [self._dim_reprs],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(module, n_dims=1)(after_dropout)
            outputs = tf.concat([outputs for c in range(self._num_classes)], axis=-1)
            return outputs

    ### Local Updater u ###
    @snt.reuse_variables
    def forward_local_updater(self, r, y, x=None, iter=""):
        if self._use_gradient:
            updates = self.gradient_local_updater(r=r, y=y, x=x, iter=iter)
            tf.compat.v1.logging.info("forwarded gradient local updater")
        else:
            r_shape = r.shape.as_list()
            r = tf.reshape(r, r_shape[:-1] +[self._num_classes, r_shape[-1]//self._num_classes])
            updates = self.neural_local_updater(r=r, y=y, x=x, iter=iter)
            updates = tf.reshape(updates, shape=r_shape)
            tf.compat.v1.logging.info("forwarded neural local updater")
        return updates
    #
    @snt.reuse_variables
    def neural_local_updater(self, r, y, x=None, iter=""):
        with tf.compat.v1.variable_scope("neural_local_updater{}".format(iter), reuse=tf.compat.v1.AUTO_REUSE):
            y = tf.one_hot(y, self._num_classes)
            y = tf.transpose(y, perm=[0, 2, 1])
            # reprs = tf.nn.dropout(reprs, rate=self.dropout_rate)
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            # MLP m
            module1 = snt.nets.MLP(
                [self._nn_size] * self._nn_layers,
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(module1, n_dims=2)(r)
            agg_outputs = tf.reduce_mean(outputs, axis=-2, keepdims=True)
            outputs = tf.concat([outputs, tf.tile(agg_outputs, [1,self._num_classes,1])], axis=-1)
            # MLP u+
            module2 = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [self._dim_reprs],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
                name="true",
            )
            outputs_t = snt.BatchApply(module2, n_dims=2)(outputs)
            # MLP u-
            module3 = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [self._dim_reprs],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
                name="false",
            )
            outputs_f = snt.BatchApply(module3, n_dims=2)(outputs)
            outputs = outputs_t * y + outputs_f * (1-y)
            return outputs
    # gradient-based local updater, used in ablation study
    @snt.reuse_variables
    def gradient_local_updater(self, r, y, x=None, iter=""):
        with tf.compat.v1.variable_scope("gradient_local_updater{}".format(iter), reuse=tf.compat.v1.AUTO_REUSE):
            lr = tf.compat.v1.get_variable(
                "lr", [1, self._num_classes * self._dim_reprs],
                dtype=self._float_dtype,
                initializer=tf.constant_initializer(1.0), trainable=True)
        classifier_weights = self.forward_decoder(r)
        tr_loss, _= self.calculate_loss_and_acc(
                x, y, classifier_weights)
        batch_tr_loss = tf.reduce_mean(tr_loss)
        loss_grad = tf.gradients(batch_tr_loss, r)[0]
        updates = - lr * loss_grad
        return updates

    ### Kernel and Attention ###
    @snt.reuse_variables
    def forward_kernel_or_attention(self, querys, keys, values, iter=""):
        if self._use_kernel:
            if self._kernel_type == "se":
                rtn_values = self.squared_exponential_kernel(querys, keys, values, iter=iter)
            elif self._kernel_type == 'deep_se':
                rtn_values = self.deep_se_kernel(querys, keys, values, iter=iter)
            else:
                raise NameError("Unknown kernel type")
            tf.compat.v1.logging.info("forwarded {0} kernel".format(self._kernel_type))
        else:
            rtn_values = self.attention_block(querys, keys, values, iter=iter)
            tf.compat.v1.logging.info("forwarded {0} attention".format(self._attention_type))
        return rtn_values

    @snt.reuse_variables
    def squared_exponential_kernel(self, querys, keys, values, iter=""):
        num_keys = tf.shape(keys)[0]
        num_querys = tf.shape(querys)[0]
        with tf.compat.v1.variable_scope("squared_exponential_kernel{}".format(iter), reuse=tf.compat.v1.AUTO_REUSE):
            sigma = tf.compat.v1.get_variable("sigma", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
            lengthscale = tf.compat.v1.get_variable("lengthscale", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
        _keys = tf.tile(tf.expand_dims(keys, axis=1), [1, num_querys, 1])
        _querys = tf.tile(tf.expand_dims(querys, axis=0), [num_keys, 1, 1])
        sq_norm = tf.reduce_sum((_keys - _querys)**2, axis=-1)
        kernel_qk = sigma**2 * tf.exp(- sq_norm / (2.*lengthscale**2))
        k = kernel_qk
        v = tf.einsum('kq,kv->qv', k, values)
        return v

    @snt.reuse_variables
    def deep_se_kernel(self, querys, keys, values, iter=""):
        with tf.compat.v1.variable_scope("deep_se_kernel{}".format(iter), reuse=tf.compat.v1.AUTO_REUSE):
            # deep embedding of keys and querys
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            module = snt.nets.MLP(
                [self.embedding_dim] * self._embedding_layers,
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            keys = snt.BatchApply(module, n_dims=1)(keys)
            querys = snt.BatchApply(module, n_dims=1)(querys)
        num_keys = tf.shape(keys)[0]
        num_querys = tf.shape(querys)[0]
        with tf.compat.v1.variable_scope("deep_se_kernel"):
            sigma = tf.compat.v1.get_variable("sigma", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
            lengthscale = tf.compat.v1.get_variable("lengthscale", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
        # compute \sum_i k(x, x_i)u_i
        _keys = tf.tile(tf.expand_dims(keys, axis=1), [1, num_querys, 1])
        _querys = tf.tile(tf.expand_dims(querys, axis=0), [num_keys, 1, 1])
        sq_norm = tf.reduce_sum((_keys - _querys)**2, axis=-1)
        kernel_qk = sigma**2 * tf.exp(- sq_norm / (2.*lengthscale**2))
        k = kernel_qk
        v = tf.einsum('kq,kv->qv', k, values)
        return v

    @snt.reuse_variables
    def attention_block(self, querys, keys, values, iter=""):
        config = {
            "rep": "mlp",
            "output_sizes": [self.embedding_dim] * self._embedding_layers,
            "att_type": self._attention_type,
            "normalise": True,
            "scale": 1.0,
            "l2_penalty_weight": self._l2_penalty_weight,
            "nonlinearity": self._nonlinearity,
        }
        with tf.compat.v1.variable_scope("attention_block{}".format(iter), reuse=tf.compat.v1.AUTO_REUSE):
            attention = Attention(config=config)
            v = attention(keys, querys, values)
        return v

    ### Decoder ###
    @snt.reuse_variables
    def forward_decoder(self, cls_reprs):
        if self._no_decoder:
            # use functional representation directly as the predictor, used in ablation study
            tf.compat.v1.logging.info("no decoder used")
            return cls_reprs
        s = cls_reprs.shape.as_list()
        cls_reprs = tf.reshape(cls_reprs, s[:-1]+[self._num_classes, self._dim_reprs])
        weights_dist_params = self.decoder(cls_reprs)
        fan_in = self.embedding_dim
        fan_out = self._num_classes
        stddev_offset = np.sqrt(2. / (fan_out + fan_in))
        classifier_weights = self.sample(weights_dist_params,
                                                  stddev_offset=stddev_offset)
        return classifier_weights
    # this decoder generates weights of softmax
    @snt.reuse_variables
    def decoder(self, inputs):
        with tf.compat.v1.variable_scope("decoder"):
            l2_regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            orthogonality_reg = get_orthogonality_regularizer(
                self._orthogonality_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            # 2 * embedding_dim, because we are returning means and variances
            decoder_module = snt.Linear(
                self.embedding_dim * 2,
                use_bias=True,
                regularizers={"w": l2_regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(decoder_module, n_dims=2)(inputs)
            self._orthogonality_reg = orthogonality_reg(decoder_module.w)
            return outputs

    ### Other ###
    @property
    def dropout_rate(self):
        return self._dropout_rate if self.is_training else 0.0

    @property
    def _l2_regularization(self):
        return tf.cast(tf.reduce_sum(tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)),
                                dtype=self._float_dtype)

    def loss_fn(self, model_outputs, original_classes):
        original_classes = tf.squeeze(original_classes, axis=-1)
        one_hot_outputs = tf.one_hot(original_classes, depth=self._num_classes)
        return tf.compat.v1.losses.softmax_cross_entropy(onehot_labels=one_hot_outputs, logits=model_outputs, \
                label_smoothing=self._label_smoothing, reduction=tf.compat.v1.losses.Reduction.NONE)

    def predict(self, inputs, weights):
        if self._no_decoder:
            return weights
        after_dropout = tf.nn.dropout(inputs, rate=self.dropout_rate)
        preds = tf.einsum("ik,imk->im", after_dropout, weights)
        return preds

    def calculate_loss_and_acc(self, inputs, true_outputs, classifier_weights):
        model_outputs = self.predict(inputs, classifier_weights)
        model_predictions = tf.argmax(
                    model_outputs, -1, output_type=self._int_dtype)
        accuracy = tf.contrib.metrics.accuracy(model_predictions,
                    tf.squeeze(true_outputs, axis=-1))
        return self.loss_fn(model_outputs, true_outputs), accuracy

    def sample(self, distribution_params, stddev_offset=0.):
        means, unnormalized_stddev = tf.split(distribution_params, 2, axis=-1)
        stddev = tf.exp(unnormalized_stddev)
        stddev -= (1. - stddev_offset)
        stddev = tf.maximum(stddev, 1e-10)
        distribution = tf.distributions.Normal(loc=means, scale=stddev)
        if not self.is_training:
            return means
        samples = distribution.sample()
        return samples

    @property
    def _decoder_orthogonality_reg(self):
        return self._orthogonality_reg

class MetaFunRegressor(snt.AbstractModule):

    def __init__(self, name="MetaFunRegressor"):
        super(MetaFunRegressor, self).__init__(name=name)
        self._float_dtype = tf.float32
        self._int_dtype = tf.int32
        # components configurations
        self._use_kernel = FLAGS.use_kernel
        self._use_gradient = FLAGS.use_gradient
        self._attention_type = FLAGS.attention_type
        self._kernel_type = FLAGS.kernel_type
        self._no_decoder = FLAGS.no_decoder
        if self._no_decoder:
            self._dim_reprs = 1
        self._initial_state_type = FLAGS.initial_state_type
        # neural module configurations
        self._nn_size = FLAGS.nn_size
        self._nn_layers = FLAGS.nn_layers
        self._dim_reprs = FLAGS.dim_reprs
        self._num_iters = FLAGS.num_iters
        self._embedding_layers = FLAGS.embedding_layers
        # regularisation configurations
        self._l2_penalty_weight = FLAGS.l2_penalty_weight
        self._dropout_rate = FLAGS.dropout_rate
        self._orthogonality_penalty_weight = FLAGS.orthogonality_penalty_weight
        #
        self._initial_inner_lr = FLAGS.initial_inner_lr
        self._orthogonality_reg = 0
        self._loss_type = "mse" # mse | log_prob
        self._nonlinearity = tf.nn.relu
        self._repr_as_inputs = FLAGS.repr_as_inputs

    def _build(self, data, is_training=True):
        self.is_training = is_training
        self.embedding_dim = data.tr_input.get_shape()[-1].value
        tr_input = data.tr_input
        val_input = data.val_input
        tr_output = data.tr_output
        val_output = data.val_output
        # initial states
        tr_reprs = self.forward_initialiser(tr_input)
        val_reprs = self.forward_initialiser(val_input)
        all_tr_reprs = [tr_reprs]
        all_val_reprs = [val_reprs]
        # inner learning rate
        alpha = tf.compat.v1.get_variable("alpha", [1, 1], dtype=self._float_dtype,
                initializer=tf.constant_initializer(self._initial_inner_lr), trainable=True)
        # iterative functional updating
        for k in range(self._num_iters):
            updates = self.forward_local_updater(r=tr_reprs, y=tr_output, x=tr_input)
            tr_updates = alpha * self.forward_kernel_or_attention(querys=tr_input, keys=tr_input, values=updates)
            val_updates = alpha * self.forward_kernel_or_attention(querys=val_input, keys=tr_input, values=updates)
            tr_reprs += tr_updates
            val_reprs += val_updates
            all_tr_reprs.append(tr_reprs)
            all_val_reprs.append(val_reprs)

        # record predictions at each iteration for visualisation
        all_val_mu = []
        all_val_sigma = []
        output_sizes = [self._nn_size] * (self._nn_layers-1) + [2] # architecture of the predictor
        # decoder r_t(x) at each iteration into the predictor for visualisation
        for k in range(self._num_iters+1):
            weights = self.forward_decoder(all_tr_reprs[k], output_sizes=output_sizes) # generate weights of the predictor
            tr_mu, tr_sigma = self.predict(tr_input, weights, output_sizes=output_sizes) # forward the predictor
            weights = self.forward_decoder(all_val_reprs[k], output_sizes=output_sizes)
            val_mu, val_sigma = self.predict(val_input, weights, output_sizes=output_sizes)
            all_val_mu.append(val_mu)
            all_val_sigma.append(val_sigma)
        #
        tr_loss, tr_metric = self.calculate_loss_and_metrics(
                tr_output, tr_mu, tr_sigma)
        val_loss, val_metric = self.calculate_loss_and_metrics(
                val_output, val_mu, val_sigma)
        batch_tr_loss = tf.reduce_mean(tr_loss)
        batch_tr_metric = tf.reduce_mean(tr_metric)
        batch_val_loss = tf.reduce_mean(val_loss)
        batch_val_metric = tf.reduce_mean(val_metric)
        #
        all_val_mu = tf.stack(all_val_mu, axis=0)
        all_val_sigma = tf.stack(all_val_sigma, axis=0)
        #
        regularization_penalty = (
           self._l2_regularization + self._decoder_orthogonality_reg)
        return batch_tr_loss + batch_val_loss + regularization_penalty, batch_tr_metric, batch_val_metric, \
                data.tr_input, data.tr_output, data.tr_func, data.val_input, data.val_output, \
                data.val_func, all_val_mu, all_val_sigma

    ### Initialiser r_0(x) ###
    @snt.reuse_variables
    def forward_initialiser(self, x):
        num_points = tf.shape(x)[0]
        if self._initial_state_type == 'zero':
            reprs = self.constant_initialiser(num_points, trainable=False)
        elif self._initial_state_type == "constant":
            reprs = self.constant_initialiser(num_points, trainable=True)
        elif self._initial_state_type == "parametric":
            reprs = self.parametric_initialiser(x)
        else:
            raise NameError("Unknown initial state type")
        tf.compat.v1.logging.info("forwarded {0} initialiser".format(self._initial_state_type))
        return reprs
    # r_0(x) = c
    @snt.reuse_variables
    def constant_initialiser(self, num_points, trainable=False):
        with tf.compat.v1.variable_scope("constant_initialiser"):
            if trainable:
                init = tf.compat.v1.get_variable(
                    "initial_state", [1, self._dim_reprs],
                    dtype=self._float_dtype,
                    initializer=tf.constant_initializer(0.0), trainable=True)
            else:
                init = tf.zeros([1, self._dim_reprs])
            init = tf.tile(init, [num_points, 1])
            return init
    # r_0(x) = MLP(x)
    @snt.reuse_variables
    def parametric_initialiser(self, x):
        with tf.compat.v1.variable_scope("parametric_initialiser"):
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            module = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [self._dim_reprs],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(module, n_dims=1)(x)
            return outputs

    ### Local Updater u ###
    @snt.reuse_variables
    def forward_local_updater(self, r, y, x):
        if self._use_gradient:
            updates = self.gradient_local_updater(r=r, y=y, x=x)
            tf.compat.v1.logging.info("forwarded gradient local updater")
        else:
            updates = self.neural_local_updater(r=r, y=y, x=x)
            tf.compat.v1.logging.info("forwarded neural local updater")
        return updates
    # neural local updater, for regression, we simply concatenate [r, y, x]
    @snt.reuse_variables
    def neural_local_updater(self, r, y, x=None):
        with tf.compat.v1.variable_scope("neural_local_updater"):
            if x is not None:
                reprs = tf.concat([r, y, x], axis=-1)
            else:
                reprs = tf.concat([r, y], axis=-1)
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            module = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [self._dim_reprs],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(module, n_dims=1)(reprs)
            return outputs
    # gradient-based local updater, used in ablation study
    @snt.reuse_variables
    def gradient_local_updater(self, r, y, x=None):
        with tf.compat.v1.variable_scope("gradient_local_updater"):
            lr = tf.compat.v1.get_variable(
                "lr", [1, self._dim_reprs],
                dtype=self._float_dtype,
                initializer=tf.constant_initializer(1.0), trainable=True)

        r = tf.stop_gradient(r)
        weights = self.forward_decoder(r)
        tr_mu, tr_sigma = self.predict(x, weights)
        tr_loss, tr_mse = self.calculate_loss_and_metrics(
                tr_mu, tr_sigma, y)

        # self.debug_op = tf.gradients(weights, reprs)[0]
        batch_tr_loss = tf.reduce_mean(tr_loss)
        loss_grad = tf.gradients(batch_tr_loss, r)[0]
        updates = - lr * loss_grad
        return updates

    ### Kernel and Attention ###
    @snt.reuse_variables
    def forward_kernel_or_attention(self, querys, keys, values):
        if self._use_kernel:
            if self._kernel_type == "se":
                rtn_values = self.squared_exponential_kernel(querys, keys, values)
            elif self._kernel_type == 'deep_se':
                rtn_values = self.deep_se_kernel(querys, keys, values)
            else:
                raise NameError("Unknown kernel type")
            tf.compat.v1.logging.info("forwarded {0} kernel".format(self._kernel_type))
        else:
            rtn_values = self.attention_block(querys, keys, values)
            tf.compat.v1.logging.info("forwarded {0} attention".format(self._attention_type))

        return rtn_values

    @snt.reuse_variables
    def squared_exponential_kernel(self, querys, keys, values):
        num_keys = tf.shape(keys)[0]
        num_querys = tf.shape(querys)[0]
        with tf.compat.v1.variable_scope("squared_exponential_kernel"):
            sigma = tf.compat.v1.get_variable("sigma", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
            lengthscale = tf.compat.v1.get_variable("lengthscale", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
        _keys = tf.tile(tf.expand_dims(keys, axis=1), [1, num_querys, 1])
        _querys = tf.tile(tf.expand_dims(querys, axis=0), [num_keys, 1, 1])
        sq_norm = tf.reduce_sum((_keys - _querys)**2, axis=-1)
        kernel_qk = sigma**2 * tf.exp(- sq_norm / (2.*lengthscale**2))
        k = kernel_qk
        v = tf.einsum('kq,kv->qv', k, values)
        return v

    @snt.reuse_variables
    def deep_se_kernel(self, querys, keys, values):
        with tf.compat.v1.variable_scope("deep_se_kernel"):
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            module = snt.nets.MLP(
                [self._nn_size] * self._embedding_layers,
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            keys = snt.BatchApply(module, n_dims=1)(keys)
            querys = snt.BatchApply(module, n_dims=1)(querys)
        num_keys = tf.shape(keys)[0]
        num_querys = tf.shape(querys)[0]
        with tf.compat.v1.variable_scope("deep_se_kernel"):
            sigma = tf.compat.v1.get_variable("sigma", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
            lengthscale = tf.compat.v1.get_variable("lengthscale", shape=(), dtype=self._float_dtype,
                    initializer=tf.constant_initializer(1.0), trainable=True)
        # compute \sum_i k(x, x_i)u_i
        _keys = tf.tile(tf.expand_dims(keys, axis=1), [1, num_querys, 1])
        _querys = tf.tile(tf.expand_dims(querys, axis=0), [num_keys, 1, 1])
        sq_norm = tf.reduce_sum((_keys - _querys)**2, axis=-1)
        kernel_qk = sigma**2 * tf.exp(- sq_norm / (2.*lengthscale**2))
        k = kernel_qk
        v = tf.einsum('kq,kv->qv', k, values)
        return v

    @snt.reuse_variables
    def attention_block(self, querys, keys, values):
        config = {
            "rep": "mlp",
            "output_sizes": [self._nn_size] * self._embedding_layers,
            "att_type": self._attention_type,
            "normalise": True,
            "scale": 1.0,
            "l2_penalty_weight": self._l2_penalty_weight,
            "nonlinearity": self._nonlinearity,
        }
        with tf.compat.v1.variable_scope("attention_block"):
            attention = Attention(config=config)
            v = attention(keys, querys, values)
        return v

    @snt.reuse_variables
    def forward_decoder(self, reprs, output_sizes=[40,40,2]):
        if self._no_decoder:
            tf.compat.v1.logging.info("no decoder used")
            return reprs
        else:
            weights_dist_params = self.decoder(reprs, output_sizes=output_sizes)
            stddev_offset = np.sqrt(1. / self._nn_size)
            weights = self.sample(weights_dist_params, stddev_offset=stddev_offset)
            tf.compat.v1.logging.info("forwarded decoder")
            return weights

    @snt.reuse_variables
    def decoder(self, reprs, output_sizes=[40,40,2]):
        with tf.compat.v1.variable_scope("decoder"):
            # _repr_as_inputs = True: Representation is used as inputs to the predictor
            # _repr_as_inputs = False: Representation is used to generate weights of the predictor
            if self._repr_as_inputs:
                return reprs
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            num_layers = len(output_sizes)
            output_sizes = [self.embedding_dim] + output_sizes
            # count number of parameters in the predictor
            num_params = 0
            for i in range(num_layers):
                num_params += (output_sizes[i]+1) * output_sizes[i+1]
            # decode the representation into the weights of the predictor
            module = snt.nets.MLP(
                [self._nn_size] * self._nn_layers + [2 * num_params],
                activation=self._nonlinearity,
                use_bias=True,
                regularizers={"w": regularizer},
                initializers={"w": initializer},
            )
            outputs = snt.BatchApply(module, n_dims=1)(reprs)
            return outputs

    @snt.reuse_variables
    def predict(self, inputs, weights, output_sizes=[40,40,2]):
        # no_decoder = True: functional representation is the predictor itself (unused for regression problems)
        if self._no_decoder:
            if self._dim_reprs == 1: # predictive mean
                return weights, tf.ones_like(weights) * 0.5
            elif self._dim_reprs == 2: # predictive mean and std
                return tf.split(weights, 2, axis=-1)
            else:
                raise Exception("num_reprs must <=2 if no_decoder")
        if self._repr_as_inputs:
            with tf.compat.v1.variable_scope("predict"):
                regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
                initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
                outputs = tf.concat([weights, inputs], axis=-1) # weights is actually repr for repr_as_inputs=True
                # construct the predictor conditioned on the repr (weights).
                # repr is feeded into the network at each layer.
                for i, s in enumerate(output_sizes):
                    module = snt.nets.MLP(
                        [s],
                        use_bias=True,
                        regularizers={"w": regularizer},
                        initializers={"w": initializer},
                    )
                    outputs = snt.BatchApply(module, n_dims=1)(outputs)
                    if i < len(output_sizes)-1:
                        outputs = self._nonlinearity(outputs)
                        outputs = tf.concat([outputs, weights], axis=-1)
                preds = outputs
        else:
            # use the generated weights to construct the predictor
            num_layers = len(output_sizes)
            output_sizes = [self.embedding_dim] + output_sizes
            begin = 0
            preds = inputs
            for i in range(num_layers):
                in_size = output_sizes[i]
                out_size = output_sizes[i+1]
                end = begin + in_size * out_size
                w = tf.reshape(weights[:, begin:end], [-1, in_size, out_size])
                b = tf.reshape(weights[:, end:end+out_size], [-1, out_size])
                begin = end + out_size
                preds = tf.einsum("ik,ikm->im", preds, w) + b
                if i < num_layers - 1:
                    preds = self._nonlinearity(preds)
        # return preds
        mu, log_sigma = tf.split(preds, 2, axis=-1)
        sigma = 0.1 + 0.9 * tf.nn.softplus(log_sigma)
        return mu, sigma

    def sample(self, distribution_params, stddev_offset=0.):
        # here we consider a deterministic case, but one can try the probabilstic version
        means, unnormalized_stddev = tf.split(distribution_params, 2, axis=-1)
        return means
        ## probabilstic version:
        # stddev = tf.exp(unnormalized_stddev)
        # stddev -= (1. - stddev_offset)
        # stddev = tf.maximum(stddev, 1e-10)
        # distribution = tf.distributions.Normal(loc=means, scale=stddev)
        # if not self.is_training:
        #     return means
        # samples = distribution.sample()
        # return samples

    def loss_fn(self, model_outputs, labels):
        return tf.losses.mean_squared_error(labels=labels, predictions=model_outputs,
                reduction=tf.compat.v1.losses.Reduction.NONE)

    def calculate_loss_and_metrics(self, target_y, mus, sigmas, coeffs=None):
        if self._loss_type == "mse":
            mu, sigma = mus, sigmas
            mse = self.loss_fn(mu, target_y)
            return mse, mse
        elif self._loss_type == "log_prob":
            mu, sigma = mus, sigmas
            dist = tf.contrib.distributions.MultivariateNormalDiag(loc=mu, scale_diag=sigma)
            loss = - dist.log_prob(target_y)
            mse = self.loss_fn(mu, target_y)
            return loss, loss
        else:
            raise NameError("unknown output_dist_type")

    @property
    def _l2_regularization(self):
        return tf.cast(
            tf.reduce_sum(tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)),
            dtype=self._float_dtype)

    @property
    def _decoder_orthogonality_reg(self):
        return self._orthogonality_reg

# (Copy from https://github.com/deepmind/leo, see copyright and original license in our LICENSE file.)
def get_orthogonality_regularizer(orthogonality_penalty_weight):
  """Returns the orthogonality regularizer."""
  def orthogonality(weight):
    """Calculates the layer-wise penalty encouraging orthogonality."""
    with tf.name_scope(None, "orthogonality", [weight]) as name:
      w2 = tf.matmul(weight, weight, transpose_b=True)
      wn = tf.norm(weight, ord=2, axis=1, keepdims=True) + 1e-32
      correlation_matrix = w2 / tf.matmul(wn, wn, transpose_b=True)
      matrix_size = correlation_matrix.get_shape().as_list()[0]
      base_dtype = weight.dtype.base_dtype
      identity = tf.eye(matrix_size, dtype=base_dtype)
      weight_corr = tf.reduce_mean(
          tf.math.squared_difference(correlation_matrix, identity))
      return tf.multiply(
          tf.cast(orthogonality_penalty_weight, base_dtype),
          weight_corr,
          name=name)
  return orthogonality

# Attention modules
# (Adapted from https://github.com/deepmind/neural-processes, see copyright and original license in our LICENSE file.)
def dot_product_attention(q, k, v, normalise):
    """Computes dot product attention.

    Args:
        q: queries. tensor of  shape [B,m,d_k].
        k: keys. tensor of shape [B,n,d_k].
        v: values. tensor of shape [B,n,d_v].
        normalise: Boolean that determines whether weights sum to 1.
    Returns:
        tensor of shape [B,m,d_v].
    """
    d_k = tf.shape(q)[-1]
    scale = tf.sqrt(tf.cast(d_k, tf.float32))
    unnorm_weights = tf.einsum('jk,ik->ij', k, q) / scale # [B,m,n]
    if normalise:
        weight_fn = tf.nn.softmax
    else:
        weight_fn = tf.sigmoid
    weights = weight_fn(unnorm_weights)
    rep = tf.einsum('ik,kj->ij', weights, v)
    return rep

class Attention(snt.AbstractModule):

    def __init__(self, config=None, name="attention"):
        super(Attention, self).__init__(name=name)
        self._float_dtype = tf.float32
        self._int_dtype = tf.int32
        self._rep = config['rep']
        self._output_sizes = config['output_sizes']
        self._att_type = config['att_type']
        self._normalise = config['normalise']
        self._scale = config['scale']
        self._l2_penalty_weight = config['l2_penalty_weight']
        self._nonlinearity = config['nonlinearity']

    def _build(self, x1, x2, r):
        if self._rep == 'identity':
            k, q = (x1, x2)
        elif self._rep == 'mlp':
            # Pass through MLP
            initializer = tf.initializers.glorot_uniform(dtype=self._float_dtype)
            regularizer = tf.contrib.layers.l2_regularizer(self._l2_penalty_weight)
            module = snt.nets.MLP(
                self._output_sizes,
                activation=self._nonlinearity,
                use_bias=True,
                initializers={"w": initializer},
            )
            k = snt.BatchApply(module, n_dims=1)(x1)
            q = snt.BatchApply(module, n_dims=1)(x2)
        else:
            raise NameError("'rep' not among ['identity','mlp']")

        if self._att_type == 'dot_product':
            rep = dot_product_attention(q, k, r, self._normalise)
        else:
            raise NameError(("'att_type' not among ['dot_product']"))
        return rep


class Dialogue:
	""" This is a directed graph. Or at least trying to be.
	Each set of dialogue is its own graph. You must create the nodes and edges
	using the appropriate functions when building the game.
	"""
	def __init__(self, data=None):
		if not data:
			self.data = {}
		self.start_node = None
		self.current_node = None

	def add_node(self, node):
		self.data[node.name] = node
		if node.entry:
			self.start_node = node
			self.current_node = node

	def add_nodes(self, *nodes):
		for node in nodes:
			self.add_node(node)

	def add_edge(self, response_text, from_node, to_node):
		edge = Edge(response_text, self.data[to_node])
		self.data[from_node].edges.append(edge)

	def add_edges(self, *edges):
		for edge in edges:
			(response_text, from_node, to_node) = edge
			self.add_edge(response_text, from_node, to_node)

	def start(self):
		print(self.current_node.text)
		if self.current_node.edges:
			for i, edge in enumerate(self.current_node.edges, 1):
				print("{0}: {1}".format(i, edge.text))
			print("{0}: Leave.".format(i+1))
			response = ""
			while not (response == str(i+1)) :
				response = input("[Enter a numbered response] >> ")
				end_dialogue = self.check_response(response)
				if end_dialogue:
					self.current_node = self.start_node
					return

	def check_response(self, response):
		end = False
		try:
			i = int(response) - 1
			if (i+1 == len(self.current_node.edges)+1):
				end = True
			elif i >= 0 and i <= len(self.current_node.edges):
				edge = self.current_node.edges[i]
				print("YOU: {0}".format(edge.text))
				if edge.next_node:
					self.current_node = edge.next_node
					print(self.current_node.text)
					if self.current_node.edges:
						for i, edge in enumerate(self.current_node.edges, 1):
							print("{0}: {1}".format(i, edge.text))
					else:
						end = True
			else:
				print("You try to say something completely out of context. It isn't well received... Want to try that again?")
		except:
			print("You utter some completely random jibberish and get a weird look for it. Want to try that again?")
		return end

class Node:
	"""Contains NPC Dialogue."""
	def __init__(self, name, text, entry=False):
		self.name = name
		self.text = text
		self.entry = entry
		self.edges = []

class Edge:
	"""The direction that connects Nodes"""
	def __init__(self, text, next_node=""):
		self.text = text
		self.next_node = next_node


from django.shortcuts import render
from django.http import HttpResponseRedirect, Http404
from django.urls import reverse

# Create your views here.
from .models import Topic, Entry
from .forms import TopicForm, EntryForm
from django.contrib.auth.decorators import login_required
from .views_functions import check_topic_owner

def index(request):
    """学习笔记的主页"""
    return render(request, 'learning_logs/index.html')

@login_required
def topics(request):
    ts = Topic.objects.filter(owner=request.user).order_by('-date_added')
    context = {
        'topics': ts
    }
    return render(request, 'learning_logs/topics.html', context)

@login_required
def topic(request, topic_id):
    t = Topic.objects.get(id=topic_id)
    if not check_topic_owner(request, t):
        raise Http404
    entries = t.entry_set.order_by('-date_added')
    context = {
        'topic': t,
        'entries': entries,
        'topic_id': topic_id
    }
    return render(request, 'learning_logs/topic.html', context)

@login_required
def new_topic(request):
    if request.method != 'POST':
        form = TopicForm()
    else:
        form = TopicForm(request.POST)
        if form.is_valid():
            tp = form.save(commit=False)
            tp.owner = request.user
            tp.save()
            return HttpResponseRedirect(reverse('learning_logs:topics'))

    context = {'form': form}
    return render(request, 'learning_logs/new_topic.html', context)

@login_required
def new_entry(request, topic_id):
    tp = Topic.objects.get(id=topic_id)
    if not check_topic_owner(request, tp):
        raise Http404
    topic_text = tp.text
    if request.method != 'POST':
        form = EntryForm()
    else:
        form = EntryForm(request.POST)
        if form.is_valid():
            new_en = form.save(commit=False)
            new_en.topic = tp
            new_en.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=(topic_id,)))

    context = {
        'form': form,
        'topic_id': topic_id,
        'topic_text': topic_text
    }
    return render(request, 'learning_logs/new_entry.html', context)

@login_required
def edit_entry(request, entry_id):
    entry = Entry.objects.get(id=entry_id)
    tp = entry.topic
    if not check_topic_owner(request, tp):
        raise Http404

    if request.method != 'POST':
        form = EntryForm(instance=entry)
    else:
        form = EntryForm(instance=entry, data=request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('learning_logs:topic', args=(tp.id,)))
    context = {
        'entry': entry,
        'topic': tp,
        'form': form
    }
    return render(request, 'learning_logs/edit_entry.html', context)


from amazon.base.selenium_driver import SeleniumDriver
from amazon.utilities import custom_logger as cl
import logging
import time

class AmazonHome(SeleniumDriver):

    log = cl.custom_logger(logging.DEBUG)

    def __init__(self, driver):
        super().__init__(driver)
        self.driver = driver

    #Locators
    _search_box = "twotabsearchtextbox"
    _item_link = '//*[text()= "%s"]'
    _add_to_cart = "add-to-cart-button"
    _cart = "nav-cart-count"
    _new_item_accordion = "//div[@id='newOfferAccordionRow']//div[@class='a-accordion-row-a11y']"
    _paper_back_section = "mediaTab_heading_1"
    _swatch = "//*[@id='tmmSwatches']//li[2]"

    def enter_search_text(self, search_text):
        """
        for searching for item in search box
        :param search_text: search item string
        :return: null
        """
        self.send_keys(search_text, self._search_box)

    def select_an_item(self, search_text):
        self.select_searched_item(search_text)

    def select_searched_item(self, search_text):
        """
        select a specific item in cart view
        :param search_text: string for locating item
        :return:
        """
        search_text = search_text.strip()
        item_elem = self._item_link % search_text
        try:
            element = self.get_element(item_elem, "xpath")
            element.click()
            self.log.info("Send data on element with locator: " + item_elem )
        except:
            self.log.info("Cannot send data on element with locator: " + item_elem )
            self.print_stack()

    def add_to_cart(self):
        """
        Add item to cart based on media type
        :return: null
        """
        swatch_elem = self.check_swatch_elem_displayed()
        if swatch_elem:
            _swatch = self.get_element(self._swatch, "xpath")
            _swatch_attr = _swatch.get_attribute("class")
            if "selected" in _swatch_attr:
                element = self.get_element(self._add_to_cart)
                element.click()
        else:
            self.select_media_type(self._paper_back_section)
            new_item_accordion = self.get_element(self._new_item_accordion, "xpath")
            accordion_aria_attr = new_item_accordion.get_attribute("aria-checked")
            # cart_count = int(cart_elem.text)
            print(accordion_aria_attr)
            try:
                if accordion_aria_attr == 'true':
                    element = self.get_element(self._add_to_cart)
                    element.click()
                    time.sleep(5)
                    self.log.info("Send data on element with locator: " + self._add_to_cart)
                else:
                    new_item_accordion = self.get_element(self._new_item_accordion, "xpath")
                    new_item_accordion.click()
                    time.sleep(5)
                    element = self.get_element(self._add_to_cart)
                    element.click()
                    time.sleep(3)

            except:
                self.log.info("Cannot send data on element with locator: " + self._add_to_cart)
                self.print_stack()

    def select_media_type(self, locator):
        """
        selects type of media e.g paperback
        :param locator: id for locating element
        :return: null
        """
        class_name = "a-active"
        media_section = self.get_element(locator)
        target_attr= media_section.get_attribute("class")
        if class_name in target_attr:
            #self.log.info("media section: " + media_section + " is active")
            pass
        else:
            media_section.click()

    def check_swatch_elem_displayed(self):
        _swatch_parent = "tmmSwatches"
        _swatch_parent = self.get_element(_swatch_parent)
        if _swatch_parent == None:
            pass
        else:
            return _swatch_parent.is_displayed()

    def get_number_of_cart_items(self):
        """

        :return: item count in cart
        """
        count_elem = self.get_element(self._cart)
        cart_items_count = int(count_elem.text)
        return cart_items_count

    def view_cart_items(self):
        """
        for viewing cart items
        :return: null
        """
        cart = self.get_element(self._cart)
        cart.click()


#Write a python program to guess number game
import random

guess_word = []
guess_count=0
guess_limit=3
while guess_count<guess_limit:
    guess = int(input('Guess a number: '))
    guess_count += 1
    if guess == correct_guess:
        print('Congratulations! You won!')
        break
else:
    print('sorry you lost')


#!/usr/bin/env python
"""
8888
8www 8   8 8d8b.d8b. .d88b   88b. Yb  dP
8    8b d8 8P Y8P Y8 8.dP'   8  8  YbdP
8    `Y8P8 8   8   8 `Y88P w 88P'   dP
                             8     dP
"""
import re
import time
import redis
import logging
from fume_exceptions import FumeIsBroken

class Dispatch:
    def __init__(self, redis_srv='127.0.0.1', redis_port=6379,
            redis_db=0, redis_use_socket=False, redis_socket='/tmp/redis.sock',
            redis_ch_alerts='alerts', redis_ch_acks='acks', alert_sep='/',
            debug=False):
        # Lumberjack
        if debug:
            logging.basicConfig(level = logging.DEBUG)
        else:
            logging.basicConfig(level = logging.ERROR)

        # Redis connect
        if redis_use_socket:
            self.r = redis.StrictRedis(unix_socket_path=redis_socket, db=redis_db)
        else:
            self.r = redis.StrictRedis(host=redis_srv, port=redis_port, db=redis_db)
        self.r.flushall() # Remove any stale data
        self.ch_alerts = redis_ch_alerts
        self.ch_acks = redis_ch_acks
        self.alert_sep = alert_sep

    """For shipping alerts to moxxy"""
    def publish(self, host, triggerid):
        self.r.publish(self.ch_alerts, host + self.alert_sep + triggerid)

    """For ACKs and junk"""
    def subscribe(self, handler):
        pass

    """ return boolean based on whether or not we've seen $alert
    before"""
    def seen(self, alert):
        key = alert['host'] + self.alert_sep + alert['triggerid']
        # Have we stored this trigger?
        if not self.r.exists(key):
            logging.debug('never seen this alert for this host before')
            return False
        # Has this trigger been updated since we stored it?
        if int(alert['lastchange']) != int(self.r.hget(key, 'lastchange')):
            logging.debug('the alert we have is stale. %s != %s' % (alert['lastchange'], self.r.hget(key, 'lastchange')))
            return False
        return True

    def store(self, alert):
        """ This is an example of an alert:
                {
            "status": "0",
            "hostname": "dc-6642c37-000",
            "description": "image.uploaded.success",
            "state": "0",
            "url": "",
            "type": "1",
            "templateid": "0",
            "value_flags": "0",
            "lastchange": "1428605426",
            "value": "1",
            "priority": "1",
            "triggerid": "184099",
            "hostid": "12757",
            "flags": "0",
            "comments": "\"description\": \"cb44829f-a803-472e-9...\", \"event_type\": \"image.uploaded\", \"status\": \"success\"\n",
            "groups": [
                {
                    "groupid": "19"
                }
            ],
            "error": "",
            "host": "dc-6642c37-000",
            "expression": "{dc-6642c37-000:image.uploaded.success.regexp(\\w+)}=1",
            "groupid": "19"
        }"""
        # Get rid of the pesky groups array
        alert.pop('groups')
        # We're using a composite key for a lot of reasons. If you have a
        # better idea, implement it and submit a pull request.
        # Reasons:
        # 0. redis doesn't support complex data structures
        # 0. we want r.hexists calls to be fast.
        # 0. pickle, while neat, is kind of gross.
        # 0. So much json (de/en)coding
        self.r.hmset(alert['host'] + self.alert_sep + alert['triggerid'], alert)

    def screen(self, trigs):
        for trig in trigs:
            if not self.seen(trig):
                self.store(trig)
                self.publish(trig['host'], trig['triggerid'])


"""
Makes all speech sounds

- Reads in from a file in .config/smartbot/speech.json
- reads in "say" and replaces %T with name of track, %N with name
- writes out to .config/smartbot/speech/[voice]
- saves in speech.json

Designed to be run either on a cron or after get_iplayer crontab
"""
import personality
import config_io
import voice_library as voice

def make_speech():
    g_config = config_io.Configuration.get()
    g_personality = personality.Personality(g_config)
    v = g_personality.voice_library
    v.update()

if __name__ == "__main__":
    make_speech()


import os
import numpy as np
import time
import sys
import copy
import cv2

from data import Data
from objects import Annotations, Detections, TrackingResults
from utilities import ImageSequenceCapture, draw_box, linux_path, CustomLogger, BaseParams

class Input:
    """
     read images, annotations, detections and tracking results for a sequence

    :type params: Input.Params
    :type _logger: CustomLogger
    :type all_frames: list[np.ndarray]
    :type curr_frame: np.ndarray
    :type frame_id: int
    :type frame_size: tuple
    :type _cap: cv2.VideoCapture
    :type annotations: Annotations
    :type track_res: TrackingResults
    :type detections: Detections
    """

    class Params(BaseParams):
        """
        :type annotations: Annotations.Params
        :type track_res: TrackingResults.Params
        :type detections: Detections.Params

         :ivar path: path of the directory, video file or binary file from where
        images are to be read; if this is not specified then it is computed from
        db_root_path, sequence set/name (in Params.py), source_type and img_fmt/vid_fmt

        :ivar frame_ids: two element tuple specifying the IDs of the first and
        last frames in the sub sequence; if either is less than 0, it is computed
        from ratios and offsets

        :ivar db_root_path: path of the directory that contains all datasets

        :ivar source_type: 0: image sequence 1: video file

        :ivar img_fmt: (naming scheme, extension) of each image file in the sequence

        :ivar vid_fmt: file extension of the video file

        :ivar resize_factor: multiplicative factor by which to resize the input
        images

        :ivar convert_to_gs: convert all input images to greyscale (if they are
        RGB) before they are used

        :ivar read_from_bin: write all image data to a binary file for quick and
        lossless reading

        :ivar write_to_bin: write all image data to a binary file for quick and
        lossless reading

        :ivar batch_mode: read all frames in the input sequence at once to avoid
        repeated disk accesses

        :ivar roi: four element tuple specifying the region of interest (ROI)
        as (xmin, ymin, xmax, ymax) of the corresponding bounding box; ROI is
        only enabled if xmx > xmin and ymax > ymin

        :ivar annotations: parameters for Annotations in Objects module

        :ivar track_res: parameters for tracking results (same as Annotations
        in Objects module)

        :ivar detections: parameters for Detections in Objects module

        """

        def __init__(self):
            self.path = ''
            self.frame_ids = (-1, -1)

            self.db_root_path = '/data'
            self.source_type = 0
            self.img_fmt = ('image%06d', 'jpg')
            self.vid_fmt = 'mp4'

            self.resize_factor = 1
            self.convert_to_gs = 0
            self.read_from_bin = 0
            self.write_to_bin = 0
            self.batch_mode = 1
            self.roi = (0, 0, 0, 0)

            self.annotations = Annotations.Params()
            self.track_res = TrackingResults.Params()
            self.detections = Detections.Params()

    def __init__(self, params, logger):
        """
        :type params: Input.Params
        :type logger: CustomLogger
        :rtype: None
        """

        self.params = copy.deepcopy(params)
        self._logger = logger

        self.curr_frame = None
        self.all_frames = None
        self.frame_id = 0
        self.n_pix = 0
        self.frame_size = None
        self.bin_fid = None
        self.source_path = None
        self._cap = None

        self.roi = self.params.roi
        if self.roi[0] >= self.roi[2] or self.roi[1] >= self.roi[3]:
            self.roi = None

        self.annotations = None
        self.detections = None
        self.track_res = None

        self.read_from_bin = self.params.read_from_bin

        self.start_frame_id, self.end_frame_id = self.params.frame_ids
        self.seq_name = None
        self.seq_set = None
        self.seq_n_frames = 0
        self.n_frames = 0

        self.is_initialized = False

    def initialize(self, data, read_img=True, logger=None):
        """
        :type data: Data
        :type read_img: bool | int
        :type logger: CustomLogger | logging.RootLogger
        :rtype: bool
        """

        if logger is not None:
            self._logger = logger

        self.annotations = None
        self.detections = None
        self.track_res = None

        self.frame_id = 0
        self.start_frame_id, self.end_frame_id = self.params.frame_ids

        curr_path = self.params.path

        if curr_path:
            self.seq_name = os.path.splitext(os.path.basename(curr_path))[0]
            self.seq_set = "custom"
            self.seq_n_frames = self.get_n_frames(curr_path)
            if self.seq_n_frames <= 0:
                raise AssertionError('No. of frames could not be obtained from: {}'.format(curr_path))

            if self.start_frame_id < 0:
                self.start_frame_id = 0
            if self.end_frame_id < 0:
                self.end_frame_id = self.seq_n_frames - 1
        else:
            if not data.is_initialized:
                raise AssertionError('Source path must be provided with uninitialized data module')
            self.seq_name, self.seq_set, self.seq_n_frames = data.seq_name, data.seq_set, data.seq_n_frames

            if self.start_frame_id < 0:
                self.start_frame_id = data.start_frame_id
            if self.end_frame_id < 0:
                self.end_frame_id = data.end_frame_id
        self.n_frames = self.end_frame_id - self.start_frame_id + 1

        self._logger.info('seq_set: {:s}'.format(self.seq_set))
        self._logger.info('seq_name: {:s}'.format(self.seq_name))
        self._logger.info('seq_n_frames: {:d}'.format(self.seq_n_frames))
        self._logger.info('start_frame_id: {:d}'.format(self.start_frame_id))
        self._logger.info('end_frame_id: {:d}'.format(self.end_frame_id))
        self._logger.info('n_frames: {:d}'.format(self.n_frames))

        if not read_img:
            # input pipeline is needed only for reading objects
            self._logger.info('Skipping image pipeline initialization')
            return True

        self.read_from_bin = self.params.read_from_bin

        if self.read_from_bin:
            if curr_path:
                bin_path = curr_path
            else:
                bin_fname = '{:s}_{:d}_{:d}'.format(self.seq_name, self.start_frame_id + 1,
                                                    self.end_frame_id + 1)
                bin_path = linux_path(self.params.db_root_path, self.seq_set,
                                      'Images', bin_fname) + '.bin'

            if not os.path.isfile(bin_path):
                self._logger.info('Binary data file {:s} does not exist'.format(bin_path))
                if curr_path:
                    return False
                self.read_from_bin = 0
            else:
                self._logger.info('Reading image data from binary file: {:s}'.format(bin_path))
                self.bin_fid = open(bin_path, 'rb')
                self.frame_size = tuple(np.fromfile(self.bin_fid, dtype=np.uint32, count=2))
                self.n_pix = self.frame_size[0] * self.frame_size[1]
                actual_file_size = os.path.getsize(bin_path)
                expected_file_size = self.n_frames * self.n_pix + 8
                if expected_file_size != actual_file_size:
                    self._logger.info('Size of binary file: {:d} does not match the expected size: {:d}'.format(
                        actual_file_size, expected_file_size))
                    self.read_from_bin = 0
                    self.bin_fid.close()
                else:
                    self.curr_frame = np.fromfile(self.bin_fid, dtype=np.uint8, count=self.n_pix).reshape(
                        (self.frame_size[1], self.frame_size[0]))
                    self.params.write_to_bin = 0

        if not self.read_from_bin:
            if not curr_path:
                curr_path = linux_path(self.params.db_root_path, self.seq_set,
                                       'Images', self.seq_name)
            if self.params.source_type == 1:
                self.source_path = curr_path + '.' + self.params.vid_fmt
            else:
                self.source_path = curr_path
                if self.params.img_fmt:
                    self.source_path = linux_path(self.source_path, self.params.img_fmt[0]
                                                  + '.' + self.params.img_fmt[1])

            self._logger.info('Getting images from: {:s}'.format(self.source_path))

            if self.params.source_type == 1:
                self._cap = cv2.VideoCapture()
                if not self._cap.open(self.source_path):
                    err_txt = 'Video file ' + self.source_path + ' could not be opened'
                    raise AssertionError(err_txt)
            else:
                self._cap = ImageSequenceCapture()  # type: ImageSequenceCapture
                if not self._cap.open(self.source_path):
                    err_txt = 'Image sequence ' + self.source_path + ' could not be read'
                    raise AssertionError(err_txt)
                assert self.seq_n_frames == self._cap.n_src_files, \
                    f"Mismatch between seq_n_frames: {self.seq_n_frames} and n_src_files: {self._cap.n_src_files}"

            if self.start_frame_id > 0:
                if cv2.__version__.startswith('2'):
                    cv_prop = cv2.cv.CV_CAP_PROP_POS_FRAMES
                else:
                    cv_prop = cv2.CAP_PROP_POS_FRAMES

                self._cap.set(cv_prop, self.start_frame_id)
                next_frame_id = self._cap.get(cv_prop)
                if next_frame_id != self.start_frame_id:
                    if self.params.source_type == 1:
                        self._logger.info('OpenCV VideoCapture set property functionality is not available so '
                                          'manually skipping {:d} frames'.format(self.start_frame_id))
                        for i in range(self.start_frame_id):
                            ret, _ = self._cap.read()
                            if not ret:
                                raise AssertionError('Frame {:d} could not be read'.format(self.frame_id + 1))
                    else:
                        raise AssertionError(
                            'Something weird going on in ImageSequenceCapture'.format(self.start_frame_id))
            # curr_frame_id = self._cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
            # self.logger.debug('curr_frame_id before initialize: {:f}'.format(curr_frame_id))
            ret, frame = self._cap.read()
            if not ret:
                raise AssertionError('Frame {:d} could not be read'.format(self.frame_id + 1))
            # curr_frame_id = self._cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
            # self.logger.debug('curr_frame_id after initialize: {:f}'.format(curr_frame_id))
            # cv2.imshow('Frame',frame)
            # if cv2.waitKey(0) == 27:
            # exit()
            self._logger.info('Input pipeline initialized successfully with frames of size: {:d} x {:d}'.format(
                frame.shape[1], frame.shape[0]))
            if self.params.convert_to_gs:
                self.curr_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            else:
                self.curr_frame = frame

            if self.params.resize_factor != 1:
                self.curr_frame = cv2.resize(self.curr_frame, dsize=(0, 0), fx=self.params.resize_factor,
                                             fy=self.params.resize_factor)
                self._logger.info('Resizing input frames to : {:d} x {:d}'.format(
                    self.curr_frame.shape[1], self.curr_frame.shape[0]))

            # (width, height) format rather than the (n_rows, n_cols) of shape
            self.frame_size = (self.curr_frame.shape[1], self.curr_frame.shape[0])
            self.n_pix = self.frame_size[0] * self.frame_size[1]

            if self.params.write_to_bin:
                if self.params.source_type == 1:
                    bin_path = linux_path(os.path.dirname(self.source_path),
                                          os.path.splitext(os.path.basename(self.source_path))[0] + ".bin")
                else:
                    bin_path = curr_path + '.bin'
                self.bin_fid = open(bin_path, 'wb')
                self._logger.info('Writing image data to binary file: {:s}'.format(bin_path))
                np.array(self.frame_size, dtype=np.uint32).tofile(self.bin_fid)
                self.curr_frame.astype(np.uint8).tofile(self.bin_fid)

        if self.params.batch_mode:
            if not self._read_all_frames():
                return False

        self.is_initialized = True
        return True

    def update(self):
        """
        :rtype: bool
        """
        if self.frame_id >= self.n_frames - 1:
            return False

        if self.read_from_bin:
            self.curr_frame = np.fromfile(self.bin_fid, dtype=np.uint8, count=self.n_pix).reshape(
                (self.frame_size[1], self.frame_size[0]))
            self.frame_id += 1
            if self.frame_id == self.n_frames - 1:
                self.bin_fid.close()
        else:
            ret, curr_frame_rgb = self._cap.read()
            if not ret:
                raise AssertionError('Frame {:d} could not be read'.format(self.frame_id + 1))
                # raise IOError('End of sequence reached unexpectedly')
            if self.params.convert_to_gs:
                self.curr_frame = cv2.cvtColor(curr_frame_rgb, cv2.COLOR_BGR2GRAY)
                # self.curr_frame = cv2.cvtColor(curr_frame_rgb.astype(np.float32), cv2.COLOR_BGR2GRAY)
                # self.curr_frame = np.ceil(self.curr_frame).astype(np.uint8)
            else:
                self.curr_frame = curr_frame_rgb
            if self.params.resize_factor != 1:
                self.curr_frame = cv2.resize(self.curr_frame, dsize=(0, 0), fx=self.params.resize_factor,
                                             fy=self.params.resize_factor)
            self.frame_id += 1
            if self.params.write_to_bin:
                self.curr_frame.astype(np.uint8).tofile(self.bin_fid)
                if self.frame_id == self.n_frames - 1:
                    self.bin_fid.close()
        return True

    def _read_all_frames(self):
        """
        :rtype: bool
        """
        self._logger.info('Reading all frames...')
        start_t = time.time()
        self.all_frames = [None] * self.n_frames
        # print 'len(self.all_frames): ', len(self.all_frames)
        self.all_frames[self.frame_id] = self.curr_frame
        print_diff = max(1, int(5 * round(self.n_frames * 0.04 / 5)))
        while self.frame_id < self.n_frames - 1:
            if not self.update():
                sys.stdout.write('\n')
                return False
            # print 'self.frame_id: ', self.frame_id
            self.all_frames[self.frame_id] = self.curr_frame
            if (self.frame_id + 1) % print_diff == 0:
                sys.stdout.write('\rDone {:d}/{:d} frames'.format(
                    self.frame_id + 1, self.n_frames))
                sys.stdout.flush()
        end_t = time.time()
        sys.stdout.write('\rDone {:d}/{:d} frames\n'
                         'Time taken: {:f} secs\n'.format(
            self.n_frames, self.n_frames, end_t - start_t))
        sys.stdout.flush()
        return True

    def get_n_frames(self, path):
        return cv2.VideoCapture(path).get(cv2.CAP_PROP_FRAME_COUNT)

    # def clearDetections(self, build_index):
    #     self.detections = None

    def read_detections(self):
        """
        :rtype: bool
        """
        detections_params = copy.deepcopy(self.params.detections)
        if not detections_params.path:
            detections_params.path = linux_path(self.params.db_root_path, self.seq_set,
                                                detections_params.src_dir, self.seq_name + '.txt')

        self.detections = Detections(detections_params, self._logger)
        self.detections.initialize(self.seq_n_frames,
                                   self.start_frame_id, self.end_frame_id)

        ignored_regions = None
        if self.annotations is not None:
            ignored_regions = self.annotations.ignored_regions

        if not self.detections.read(
                frame_size=self.frame_size,
                ignored_regions=ignored_regions,
                resize_factor=self.params.resize_factor):
            self._logger.error('Failed to read detections')
            self.detections = None
            return False

        return True

    # def clearAnnotations(self, build_index):
    #     self.annotations = None

    def read_annotations(self):
        """
        :rtype: bool
        """
        annotations_params = copy.deepcopy(self.params.annotations)
        if not annotations_params.path:
            annotations_params.path = linux_path(self.params.db_root_path, self.seq_set,
                                                 annotations_params.src_dir, self.seq_name + '.txt')

        self.annotations = Annotations(annotations_params, self._logger)
        self.annotations.initialize(self.seq_n_frames, self.start_frame_id, self.end_frame_id)

        if not self.annotations.read(resize_factor=self.params.resize_factor, frame_size=self.frame_size):
            self._logger.error('Failed to read annotations')
            self.annotations = None
            return False

        return True

    # def clearTrackingResults(self, build_index):
    #     self.tracking_res = None

    def read_tracking_results(self, res_path):
        """
        :type res_path: str
        :type build_index: bool
        :rtype: bool
        """
        self.params.track_res.path = res_path
        self.track_res = TrackingResults(self.params.track_res, self._logger)
        self.track_res.initialize(self.seq_n_frames,
                                  self.start_frame_id, self.end_frame_id)
        if not self.track_res.read(self.frame_size, self.params.resize_factor):
            self._logger.error('Failed to read tracking results')
            self.track_res = None
            return False
        return True

    def get_frame(self):
        return self.curr_frame

    def get_all_frames(self):
        return self.all_frames

    def show_detections(self):
        for frame_id in range(self.n_frames):
            frame_disp = cv2.cvtColor(self.all_frames[frame_id], cv2.COLOR_GRAY2BGR)
            det_ids = self.detections.idx[frame_id]
            if det_ids is None:
                continue
            for det_id in det_ids:
                draw_box(frame_disp, self.detections.data[det_id, 2:6])
            cv2.imshow('Detections', frame_disp)
            if cv2.waitKey(1) == 27:
                exit()

    def show_annotations(self):
        for frame_id in range(self.n_frames):
            if self.all_frames[frame_id] is None:
                raise SystemError('frame is None')
            frame_disp = np.copy(self.all_frames[frame_id]).astype(np.uint8)
            # frame_disp = cv2.cvtColor(self.all_frames[frame_id], cv2.COLOR_GRAY2BGR)
            ann_ids = self.annotations.idx[frame_id]
            if ann_ids is None:
                continue
            for ann_id in ann_ids:
                draw_box(frame_disp, self.annotations.data[ann_id, 2:6])
            cv2.imshow('Annotations', frame_disp)
            if cv2.waitKey(1) == 27:
                exit()


from django.shortcuts import render,redirect
import json
from django.shortcuts import HttpResponse
from django.http import HttpResponseRedirect
from django.template.context_processors import csrf
from .models import *
from django.core.paginator import PageNotAnInteger
from .tables import StudentCourseTable, MyCourseTable
import xlrd
import re
import hashlib
from django_tables2 import SingleTableView
from django.views.generic import CreateView, UpdateView, DeleteView
from . import global_variable

def change_info(request):
    c = {}
    c['title'] = 'Student'
    student = Student.objects.get(email=global_variable.global_user)
    c['username'] = student.email

    if request.method == "POST":
        street = request.POST.get("street", "")
        phone = request.POST.get("phone", "")
        if street:
            student.street = street
        if phone:
            student.phone = phone
        student.save()
        return HttpResponse(json.dumps('success'))

    c.update(csrf(request))
    return render(request, 'app/student_change_info.html', c)

def student_courses(request):
    c = {}
    c['title'] = 'Student'
    course_name = request.GET.get('course_name', "")
    courses = Course.objects.all()
    c['course_name'] = course_name
    if course_name:
        courses = courses.filter(course_name__contains=course_name)
    # sort
    sort = request.GET.get("sort", "")
    if sort:
        try:
            courses = courses.order_by(sort)
            c["sort"] = sort
        except Exception as e:
            pass
    if courses:
        pass
    else:
        c['is_data'] = "false"
    # page turning
    try:
        page = request.GET.get("page", 1)
    except PageNotAnInteger:
        page = 1
    c["page"] = page
    try:
        table = StudentCourseTable(courses)
        table.paginate(page=page, per_page=10)
        c['course_table'] = table
    except Exception as e:
        pass

    c.update(csrf(request))
    return render(request, 'app/student_choose_course.html', c)

def choose_course(request, id):
    print(global_variable.global_user)
    student = Student.objects.get(email=global_variable.global_user)
    if id:
        success = False
        sections = Sections.objects.filter(course_id=id)
        for section in sections:
            if Enrolls.objects.filter(section=section, student=student).exists():
                return HttpResponse('you have already chose this course')
            enroll = Enrolls.objects.filter(section=section)
            if len(enroll) >= section.limit:
                pass
            else:
                new_enroll = Enrolls(section=section, student=student)
                new_enroll.save()
                success = True
                break
        if success is False:
            return HttpResponse('There is no remaining amount for this course')
        else:
            return HttpResponse('success')
    return HttpResponse('failed')

def my_courses(request):
    c = {}
    c['title'] = 'Student'
    student = Student.objects.get(email=global_variable.global_user)
    enroll = Enrolls.objects.filter(student=student)
    sections = []
    for e1 in enroll:
        sections.append(e1.section)

    if sections:
        pass
    else:
        c['is_data'] = "false"
    # page turning
    try:
        page = request.GET.get("page", 1)
    except PageNotAnInteger:
        page = 1
    c["page"] = page
    try:
        table = MyCourseTable(sections)
        table.paginate(page=page, per_page=10)
        c['section_table'] = table
    except Exception as e:
        pass

    c.update(csrf(request))
    return render(request, 'app/student_my_courses.html', c)

def reg_detail(request, id):
    c = {}
    c['title'] = 'Student'
    section = Sections.objects.get(pk=id)
    student = Student.objects.get(email=global_variable.global_user)
    final_score = FinalScore.objects.filter(section=section, student=student)
    if final_score:
        final_score = final_score.first()
        c['final_score'] = final_score.grade
    course = section.course_id
    c['description'] = course.course_description
    c['course_name'] = course.course_name
    professors = Prof_team_members.objects.filter(teaching_team_id=section.teaching_team_id)
    c['professors'] = professors
    # homeworks
    homeworks = Homework.objects.filter(section=section)
    c['homeworks'] = homeworks
    homework_dict = []
    for h in homeworks:
        tmp = {}
        grade = Homework_grades.objects.get(homework=h, student=student).grades
        tmp['grade'] = grade
        tmp['no'] = h.pk
        tmp['detail'] = h.hw_details
        homework_dict.append(tmp)
    c['homeworks'] = homework_dict
    # exams
    exams = Exams.objects.filter(section=section)
    c['exams'] = exams
    exam_dict = []
    for e in exams:
        tmp = {}
        grade = Exam_grades.objects.get(exam=e, student=student).grades
        tmp['no'] = e.pk
        tmp['grade'] = grade
        tmp['detail'] = e.exam_details
        exam_dict.append(tmp)
    c['exams'] = exam_dict
    c.update(csrf(request))
    return render(request, 'app/student_reg_detail.html', c)

def cap_detail(request, id):
    c = {}
    c['title'] = 'Student'
    student = Student.objects.get(email=global_variable.global_user)
    section = Sections.objects.get(pk=id)
    final_score = FinalScore.objects.filter(section=section, student=student)
    if final_score:
        final_score = final_score.first()
        c['final_score'] = final_score.grade
    course = section.course_id
    c['description'] = course.course_description
    c['course_name'] = course.course_name
    # cap_group
    cap_team = Capstone_Team_Members.objects.filter(student=student)
    if cap_team:
        cap_team = cap_team.first()
        project = cap_team.team.project_no
        c['professor'] = project.sponsor_id.email
        members = []
        mems = Capstone_Team_Members.objects.filter(team=cap_team.team)
        c['members'] = members
        for m in mems:
            members.append(m.student.name)
        score = Capstone_grades.objects.get(team=cap_team.team).grade
        c['c_grade'] = score
        # exams
        exams = Exams.objects.filter(section=section)
        c['exams'] = exams
        exam_dict = []
        for e in exams:
            tmp = {}
            grade = Exam_grades.objects.get(exam=e, student=student).grades
            tmp['no'] = e.pk
            tmp['grade'] = grade
            tmp['detail'] = e.exam_details
            exam_dict.append(tmp)
        c['exams'] = exam_dict
    c.update(csrf(request))
    return render(request, 'app/student_cap_detail.html', c)


import random
from class_Monk import Monk
from copy import deepcopy

def load_rocks(garden_plan):
    rocks = []
    for line in garden_plan[2:]:
        rock_info = line.split()
        rock = (int(rock_info[0])+1, int(rock_info[1])+1)
        rocks.append(rock)
    return rocks

# generate first-random population DNAs:
def create_DNA(DNA_size, all_start_positions):
    random_DNA = []
    starting_poz = [1]
    turn_choices = [1]

    for j in range(DNA_size//2):
        turn_choices.append(random.choice(['l', 'r']))
        random_start_poz = random.choice(all_start_positions)
        while random_start_poz in starting_poz:
            random_start_poz = random.choice(all_start_positions)
        starting_poz.append(random_start_poz)

    random_DNA.append(starting_poz)
    random_DNA.append(turn_choices)
    return random_DNA

# ->Generation raking simulation:
def simulate_generation(old_monk_population, size_of_population, DNA_size, mutation_probability, original_garden, num_of_generation, parent_selection):

    new_monk_population = []
    whole_garden_raked = False

    for i in range(size_of_population):

        # ak je to prva generacia
        if num_of_generation == 1:
            garden_copy = deepcopy(original_garden)
            random_DNA = create_DNA(DNA_size, garden_copy.free_start_positions)
            monk = Monk(garden_copy, random_DNA, i)

        else:
            if parent_selection == 't':
                # vyber 2 rodicov turnajom:
                parent1 = tournament_selection(old_monk_population, random.randint(2, 5))
                parent2 = tournament_selection(old_monk_population, random.randint(2, 5))
            else:
                # vyber 2 rodicov ruletov:
                parent1, parent2 = roulette_selection(old_monk_population)

            # vytvor potomka a pridaj do listu potomkov = novej generacie:
            monk = create_descendant(parent1, parent2, deepcopy(original_garden), i, mutation_probability)

        monk.rake_garden()
        new_monk_population.append(monk)

        # ak sa podarilo pohrabat celu zahradu
        if monk.num_of_raked_places == original_garden.num_of_sand_places:
            whole_garden_raked = True
            break

    return new_monk_population, whole_garden_raked

# --- REPRODUCTION --- #

# ->Selections:
def tournament_selection(monk_population, n):

    tournament_members = []
    # vyber nahodnych mnichov do turnaja
    for i in range(n):
        # vyber nahodneho mnicha
        tournament_members.append(random.choice(monk_population))

    best_monk = None
    for monk_competitor in tournament_members:
        if (best_monk == None) or (monk_competitor.num_of_raked_places > best_monk.num_of_raked_places):
            best_monk = monk_competitor

    return best_monk

def roulette_selection(monk_population):

    total_fintess = 0
    # celkova fitness celej populacie mnichov
    for monk in monk_population:
        total_fintess += monk.num_of_raked_places

    cummulative_probabilities = []
    # vypocet pravdepodobnosti vyberu jednotlivych mnichov
    for monk in monk_population:
        monk_probability = monk.num_of_raked_places / total_fintess

        if len(cummulative_probabilities) == 0:
            cummulative_probabilities.append(monk_probability)
        else:
            cummulative_probabilities.append(cummulative_probabilities[-1] + monk_probability)

    # vyber n(2?) mnichov ruletov
    chosen_monks = []
    for n in range(2):
        r = random.uniform(0, 1)
        for i in range(len(cummulative_probabilities)):
            if r <= cummulative_probabilities[i]:
                chosen_monks.append(monk_population[i])
                break

    return chosen_monks[0], chosen_monks[1]

# ->Child creation:
def create_descendant(parent1, parent2, childs_garden, index, mutation_probability):
    # krizenie:
    child_DNA = crossover(parent1.DNA, parent2.DNA)
    # mutacia:
    child_DNA = mutation(child_DNA, mutation_probability, childs_garden)

    child = Monk(childs_garden, child_DNA, index)
    return child

# ->Krizenie:
def crossover(DNA1, DNA2):
    child_DNA = []

    # random cut-point:
    cut_point = random.randrange(1, len(DNA1[0])-1)
    child_starting_poz = ([1] + DNA1[0][1:cut_point] + DNA2[0][cut_point:])
    # cut_point = random.randrange(1, len(DNA2[1]) - 1)
    child_turn_choices = ([1] + DNA1[1][1:cut_point] + DNA2[1][cut_point:])

    child_DNA.append(child_starting_poz)
    child_DNA.append(child_turn_choices)

    return child_DNA

# ->Mutacia:
def mutation(childs_DNA, mutation_probability, childs_garden):

    if random.random() <= mutation_probability:

        random_gene_to_change = random.randrange(1, len(childs_DNA[0]))
        random_start_poz = random.choice(childs_garden.free_start_positions)
        while random_start_poz in childs_DNA[0]:
            random_start_poz = random.choice(childs_garden.free_start_positions)
        childs_DNA[0][random_gene_to_change] = random_start_poz

        random_gene_to_change = random.randrange(1, len(childs_DNA[1]))
        if childs_DNA[1][random_gene_to_change] == 'l':
            childs_DNA[1][random_gene_to_change] = 'r'
        elif childs_DNA[1][random_gene_to_change] == 'r':
            childs_DNA[1][random_gene_to_change] = 'l'

    return childs_DNA

# New blood:
def new_blood(monk_population, original_garden, DNA_size):
    kill_num = int(input("what percentage of the population should be killed ?\n "))
    kill_num = int((kill_num * len(monk_population)) / 100.0)

    for i in range(kill_num):
        random_index = random.randrange(1, len(monk_population))
        del(monk_population[random_index])

        garden_copy = deepcopy(original_garden)
        random_DNA = create_DNA(DNA_size, garden_copy.free_start_positions)
        monk = Monk(garden_copy, random_DNA, i)
        monk.rake_garden()
        monk_population.append(monk)

    return monk_population

# -Najlepsi pokus- #
def get_best_try(monk_population, num_of_generation, best_try):
    curr_best_fitness = None
    curr_best_monk = None

    # najde nejlepsieho mnicha tejto generacie:
    for monk in monk_population:
        fitness = monk.num_of_raked_places
        if curr_best_fitness == None or fitness > curr_best_fitness:
            curr_best_fitness = fitness
            curr_best_monk = monk

    # ak je najlepsi z tejto lepsi jak celkovy:
    if best_try[0] == None or curr_best_fitness >= best_try[0].num_of_raked_places:
        best_try = (curr_best_monk, num_of_generation)
        print("gen. num: " + str(best_try[1]) + "; fitness-" + str(best_try[0].num_of_raked_places))

    return best_try

def get_best_monk(monk_population):
    best_fitness = None
    best_monk = None

    for monk in monk_population:
        fitness = monk.num_of_raked_places
        if best_fitness == None or fitness > best_fitness:
            best_fitness = fitness
            best_monk = monk

    return best_monk

def get_average_fitness(monk_population):

    fitness_sum = 0
    for monk in monk_population:
        fitness_sum += monk.num_of_raked_places

    return fitness_sum//len(monk_population)

def append_fitnesses(all_fitnesses_arr, monk_population):
    for monk in monk_population:
        all_fitnesses_arr.append(monk.num_of_raked_places)

    return all_fitnesses_arr


import pdb
import torch
import numpy as np
import torch.nn as nn

class PointLSTMCell(nn.Module):
    def __init__(self, pts_num, in_channels, hidden_dim, offset_dim, bias):
        super(PointLSTMCell, self).__init__()
        self.bias = bias
        self.pts_num = pts_num
        self.in_channels = in_channels
        self.hidden_dim = hidden_dim
        self.offset_dim = offset_dim
        self.pool = nn.Sequential(
            nn.AdaptiveMaxPool2d((None, 1))
        )
        self.conv = nn.Conv2d(in_channels=self.in_channels + self.offset_dim + self.hidden_dim,
                              out_channels=4 * self.hidden_dim,
                              kernel_size=(1, 1),
                              bias=self.bias)

    def forward(self, input_tensor, hidden_state, cell_state):
        hidden_state[:, :4] -= input_tensor[:, :4]
        combined = torch.cat([input_tensor, hidden_state], dim=1)
        combined_conv = self.conv(combined)
        cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.hidden_dim, dim=1)
        i = torch.sigmoid(cc_i)
        f = torch.sigmoid(cc_f)
        o = torch.sigmoid(cc_o)
        g = torch.tanh(cc_g)
        c_next = f * cell_state + i * g
        h_next = o * torch.tanh(c_next)
        return self.pool(h_next), self.pool(c_next)

    def init_hidden(self, batch_size):
        return (torch.zeros(batch_size, self.hidden_dim, self.pts_num, 1).cuda(),
                torch.zeros(batch_size, self.hidden_dim, self.pts_num, 1).cuda())

class PointLSTM(nn.Module):
    def __init__(self, pts_num, in_channels, hidden_dim, offset_dim, num_layers, topk=16, offsets=False,
                 batch_first=True, bias=True, return_all_layers=False):
        super(PointLSTM, self).__init__()
        # Make sure that both `kernel_size` and `hidden_dim` are lists having len == num_layers
        hidden_dim = self._extend_for_multilayer(hidden_dim, num_layers)
        if not len(hidden_dim) == num_layers:
            raise ValueError('Inconsistent list length.')

        self.bias = bias
        self.topk = topk
        self.offsets = offsets
        self.pts_num = pts_num
        self.in_channels = in_channels
        self.offset_dim = offset_dim
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        self.batch_first = batch_first
        self.return_all_layers = return_all_layers

        cell_list = []
        for i in range(0, self.num_layers):
            cur_in_channels = self.in_channels if i == 0 else self.hidden_dim[i - 1] + 4
            cell_list.append(PointLSTMCell(pts_num=self.pts_num,
                                           in_channels=cur_in_channels,
                                           hidden_dim=self.hidden_dim[i],
                                           offset_dim=self.offset_dim,
                                           bias=self.bias))
        self.cell_list = nn.ModuleList(cell_list)

    def forward(self, input_tensor, hidden_state=None):
        # batch, timestep, c, n (N points, M neighbor)
        if not self.batch_first:
            # (t, b, c, n) -> (b, t, c, n)
            input_tensor = input_tensor.permute(1, 0, 2, 3)
        if hidden_state is not None:
            raise NotImplementedError()
        else:
            hidden_state = self._init_hidden(batch_size=input_tensor.size(0))

        layer_output_list = []
        last_state_list = []
        position = input_tensor[:, :, :4]
        if self.offsets:
            centroids = torch.mean(position[:, :, :3], dim=3)
            group_offsets = (centroids[:, :-1] - centroids[:, 1:])[:, :, :, None]
            group_ind = torch.cat(
                (
                    self.group_points(position[:, 0, :3], position[:, 0, :3], dim=2,
                                      topk=self.topk).unsqueeze(1),
                    self.group_points(position[:, 1:, :3] + group_offsets, position[:, :-1, :3],
                                      dim=3, topk=self.topk),
                ),
                dim=1
            )
        else:
            group_ind = torch.cat(
                (
                    self.group_points(position[:, 0, :3], position[:, 0, :3], dim=2,
                                      topk=self.topk).unsqueeze(1),
                    self.group_points(position[:, 1:, :3], position[:, :-1, :3],
                                      dim=3, topk=self.topk),
                ),
                dim=1
            )
        seq_len = input_tensor.shape[1]

        cur_layer_input = input_tensor.unsqueeze(-1)
        for layer_idx in range(self.num_layers):
            h, c = hidden_state[layer_idx]
            output_inner = []
            for t in range(seq_len):
                past = 0 if t == 0 else t - 1
                center_pts = cur_layer_input[:, t].expand(-1, -1, -1, self.topk)
                h_with_pos = torch.cat((position[:, past].unsqueeze(-1), h), dim=1)
                h_grouped = h_with_pos.squeeze(-1).unsqueeze(1).expand(-1, self.pts_num, -1, -1). \
                    gather(3, group_ind[:, t].unsqueeze(2).
                           expand(-1, -1, self.hidden_dim[layer_idx] + self.offset_dim, -1)) \
                    .permute(0, 2, 1, 3)
                c_grouped = c.squeeze(-1).unsqueeze(1).expand(-1, self.pts_num, -1, -1). \
                    gather(3, group_ind[:, t].unsqueeze(2).expand(-1, -1, self.hidden_dim[layer_idx], -1)) \
                    .permute(0, 2, 1, 3)
                h, c = self.cell_list[layer_idx](
                    input_tensor=center_pts.clone(),
                    hidden_state=h_grouped.clone(),
                    cell_state=c_grouped.clone()
                )
                output_inner.append(h)
            layer_output = torch.cat((position.unsqueeze(-1), torch.stack(output_inner, dim=1)), dim=2)
            cur_layer_input = layer_output
            layer_output_list.append(layer_output)
            last_state_list.append([h, c])

        if not self.return_all_layers:
            layer_output_list = layer_output_list[-1:]
            last_state_list = last_state_list[-1:]

        return layer_output_list, last_state_list, group_ind

    def _init_hidden(self, batch_size):
        init_states = []
        for i in range(self.num_layers):
            init_states.append(self.cell_list[i].init_hidden(batch_size))
        return init_states

    def group_points(self, array1, array2, dim, topk):
        dist, _, _ = self.array_distance(array1, array2, dim)
        dists, idx = torch.topk(dist, topk, -1, largest=False, sorted=False)
        return idx

    @staticmethod
    def array_distance(array1, array2, dim):
        # return array1.shape[-1] * array2.shape[-1] matrix
        distance_mat = array1.unsqueeze(dim + 1) - array2.unsqueeze(dim)
        mat_shape = distance_mat.shape
        mat_shape = mat_shape[:1] + (array1.shape[1],) + mat_shape[2:]
        array1 = array1.unsqueeze(dim + 1).expand(mat_shape)
        array2 = array2.unsqueeze(dim).expand(mat_shape)
        distance_mat = torch.sqrt((distance_mat ** 2).sum(dim - 1))
        return distance_mat, array1, array2

    @staticmethod
    def _extend_for_multilayer(param, num_layers):
        if not isinstance(param, list):
            param = [param] * num_layers
        return param

    @staticmethod
    def tensor2numpy(tensor, name="test"):
        np.save(name, tensor.cpu().detach().numpy())