"""
This module implements a simple mobile object with
a very rudimentary AI as well as an aggressive enemy
object based on that mobile class.

"""

import random, time
from django.conf import settings

from ev import search_object, utils, Script
from contrib.tutorial_world import objects as tut_objects
from contrib.tutorial_world import scripts as tut_scripts

BASE_CHARACTER_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS

#------------------------------------------------------------
#
# Mob - mobile object
#
# This object utilizes exits and moves about randomly from
# room to room.
#
#------------------------------------------------------------

class Mob(tut_objects.TutorialObject):
    """
    This type of mobile will roam from exit to exit at
    random intervals. Simply lock exits against the is_mob attribute
    to block them from the mob (lockstring = "traverse:not attr(is_mob)").
    """
    def at_object_creation(self):
        "This is called when the object is first created."
        self.db.tutorial_info = "This is a moving object. It moves randomly from room to room."

        self.scripts.add(tut_scripts.IrregularEvent)
        # this is a good attribute for exits to look for, to block
        # a mob from entering certain exits.
        self.db.is_mob = True
        self.db.last_location = None
        # only when True will the mob move.
        self.db.roam_mode = True

    def announce_move_from(self, destination):
        "Called just before moving"
        self.location.msg_contents("With a cold breeze, %s drifts in the direction of %s." % (self.key, destination.key))

    def announce_move_to(self, source_location):
        "Called just after arriving"
        self.location.msg_contents("With a wailing sound, %s appears from the %s." % (self.key, source_location.key))

    def update_irregular(self):
        "Called at irregular intervals. Moves the mob."
        if self.roam_mode:
            exits = [ex for ex in self.location.exits
                                    if ex.access(self, "traverse")]
            if exits:
                # Try to make it so the mob doesn't backtrack.
                new_exits = [ex for ex in exits
                                     if ex.destination != self.db.last_location]
                if new_exits:
                    exits = new_exits
                self.db.last_location = self.location
                # execute_cmd() allows the mob to respect exit and
                # exit-command locks, but may pose a problem if there is more
                # than one exit with the same name.
                # - see Enemy example for another way to move
                self.execute_cmd("%s" % exits[random.randint(0, len(exits) - 1)].key)

#------------------------------------------------------------
#
# Enemy - mobile attacking object
#
# An enemy is a mobile that is aggressive against players
# in its vicinity. An enemy will try to attack characters
# in the same location. It will also pursue enemies through
# exits if possible.
#
# An enemy needs to have a Weapon object in order to
# attack.
#
# This particular tutorial enemy is a ghostly apparition that can only
# be hurt by magical weapons. It will also not truly "die", but only
# teleport to another room. Players defeated by the apparition will
# conversely just be teleported to a holding room.
#
#------------------------------------------------------------

class AttackTimer(Script):
    """
    This script is what makes an eneny "tick".
    """
    def at_script_creation(self):
        "This sets up the script"
        self.key = "AttackTimer"
        self.desc = "Drives an Enemy's combat."
        self.interval = random.randint(2, 3) # how fast the Enemy acts
        self.start_delay = True # wait self.interval before first call
        self.persistent = True

    def at_repeat(self):
        "Called every self.interval seconds."
        if self.obj.db.inactive:
            return
        #print "attack timer: at_repeat", self.dbobj.id, self.ndb.twisted_task,
        # id(self.ndb.twisted_task)
        if self.obj.db.roam_mode:
            self.obj.roam()
            #return
        elif self.obj.db.battle_mode:
            #print "attack"
            self.obj.attack()
            return
        elif self.obj.db.pursue_mode:
            #print "pursue"
            self.obj.pursue()
            #return
        else:
            #dead mode. Wait for respawn.
            if not self.obj.db.dead_at:
                self.obj.db.dead_at = time.time()
            if (time.time() - self.obj.db.dead_at) > self.obj.db.dead_timer:
                self.obj.reset()

class Enemy(Mob):
    """
    This is a ghostly enemy with health (hit points). Their chance to hit,
    damage etc is determined by the weapon they are wielding, same as
    characters.

    An enemy can be in four modes:
       roam (inherited from Mob) - where it just moves around randomly
       battle - where it stands in one place and attacks players
       pursue - where it follows a player, trying to enter combat again
       dead - passive and invisible until it is respawned

    Upon creation, the following attributes describe the enemy's actions
      desc - description
      full_health - integer number > 0
      defeat_location - unique name or #dbref to the location the player is
                        taken when defeated. If not given, will remain in room.
      defeat_text - text to show player when they are defeated (just before
                    being whisped away to defeat_location)
      defeat_text_room - text to show other players in room when a player
                         is defeated
      win_text - text to show player when defeating the enemy
      win_text_room - text to show room when a player defeates the enemy
      respawn_text - text to echo to room when the mob is reset/respawn in
                     that room.

    """
    def at_object_creation(self):
        "Called at object creation."
        super(Enemy, self).at_object_creation()

        self.db.tutorial_info = "This moving object will attack players in the same room."

        # state machine modes
        self.db.roam_mode = True
        self.db.battle_mode = False
        self.db.pursue_mode = False
        self.db.dead_mode = False
        # health (change this at creation time)
        self.db.full_health = 20
        self.db.health = 20
        self.db.dead_at = time.time()
        self.db.dead_timer = 100 # how long to stay dead
        # this is used during creation to make sure the mob doesn't move away
        self.db.inactive = True
        # store the last player to hit
        self.db.last_attacker = None
        # where to take defeated enemies
        self.db.defeat_location = "darkcell"
        self.scripts.add(AttackTimer)

    def update_irregular(self):
        "the irregular event is inherited from Mob class"
        strings = self.db.irregular_echoes
        if strings:
            self.location.msg_contents(strings[random.randint(0, len(strings) - 1)])

    def roam(self):
        "Called by Attack timer. Will move randomly as long as exits are open."

        # in this mode, the mob is healed.
        self.db.health = self.db.full_health
        players = [obj for obj in self.location.contents
                   if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:
            # we found players in the room. Attack.
            self.db.roam_mode = False
            self.db.pursue_mode = False
            self.db.battle_mode = True

        elif random.random() < 0.2:
            # no players to attack, move about randomly.
            exits = [ex.destination for ex in self.location.exits
                                                if ex.access(self, "traverse")]
            if exits:
                # Try to make it so the mob doesn't backtrack.
                new_exits = [ex for ex in exits
                                    if ex.destination != self.db.last_location]
                if new_exits:
                    exits = new_exits
                self.db.last_location = self.location
                # locks should be checked here
                self.move_to(exits[random.randint(0, len(exits) - 1)])
            else:
                # no exits - a dead end room. Respawn back to start.
                self.move_to(self.home)

    def attack(self):
        """
        This is the main mode of combat. It will try to hit players in
        the location. If players are defeated, it will whisp them off
        to the defeat location.
        """
        last_attacker = self.db.last_attacker
        players = [obj for obj in self.location.contents
                   if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:

            # find a target
            if last_attacker in players:
                # prefer to attack the player last attacking.
                target = last_attacker
            else:
                # otherwise attack a random player in location
                target = players[random.randint(0, len(players) - 1)]

            # try to use the weapon in hand
            attack_cmds = ("thrust", "pierce", "stab", "slash", "chop")
            cmd = attack_cmds[random.randint(0, len(attack_cmds) - 1)]
            self.execute_cmd("%s %s" % (cmd, target))

            # analyze result.
            if target.db.health <= 0:
                # we reduced enemy to 0 health. Whisp them off to
                # the prison room.
                tloc = search_object(self.db.defeat_location)
                tstring = self.db.defeat_text
                if not tstring:
                    tstring = "You feel your conciousness slip away ... you fall to the ground as "
                    tstring += "the misty apparition envelopes you ...\n The world goes black ...\n"
                target.msg(tstring)
                ostring = self.db.defeat_text_room
                if tloc:
                    if not ostring:
                        ostring = "\n%s envelops the fallen ... and then their body is suddenly gone!" % self.key
                        # silently move the player to defeat location
                        # (we need to call hook manually)
                    target.location = tloc[0]
                    tloc[0].at_object_receive(target, self.location)
                elif not ostring:
                    ostring = "%s falls to the ground!" % target.key
                self.location.msg_contents(ostring, exclude=[target])
                # Pursue any stragglers after the battle
                self.battle_mode = False
                self.roam_mode = False
                self.pursue_mode = True
        else:
            # no players found, this could mean they have fled.
            # Switch to pursue mode.
            self.battle_mode = False
            self.roam_mode = False
            self.pursue_mode = True

    def pursue(self):
        """
        In pursue mode, the enemy tries to find players in adjoining rooms, preferably
        those that previously attacked it.
        """
        last_attacker = self.db.last_attacker
        players = [obj for obj in self.location.contents if utils.inherits_from(obj, BASE_CHARACTER_TYPECLASS) and not obj.is_superuser]
        if players:
            # we found players in the room. Maybe we caught up with some,
            # or some walked in on us before we had time to pursue them.
            # Switch to battle mode.
            self.battle_mode = True
            self.roam_mode = False
            self.pursue_mode = False
        else:
            # find all possible destinations.
            destinations = [ex.destination for ex in self.location.exits
                                                if ex.access(self, "traverse")]
            # find all players in the possible destinations. OBS-we cannot
            # just use the player's current position to move the Enemy; this
            # might have changed when the move is performed, causing the enemy
            # to teleport out of bounds.
            players = {}
            for dest in destinations:
                for obj in [o for o in dest.contents
                           if utils.inherits_from(o, BASE_CHARACTER_TYPECLASS)]:
                    players[obj] = dest
            if players:
                # we found targets. Move to intercept.
                if last_attacker in players:
                    # preferably the one that last attacked us
                    self.move_to(players[last_attacker])
                else:
                    # otherwise randomly.
                    key = players.keys()[random.randint(0, len(players) - 1)]
                    self.move_to(players[key])
            else:
                # we found no players nearby. Return to roam mode.
                self.battle_mode = False
                self.roam_mode = True
                self.pursue_mode = False

    def at_hit(self, weapon, attacker, damage):
        """
        Called when this object is hit by an enemy's weapon
        Should return True if enemy is defeated, False otherwise.

        In the case of players attacking, we handle all the events
        and information from here, so the return value is not used.
        """

        self.db.last_attacker = attacker
        if not self.db.battle_mode:
            # we were attacked, so switch to battle mode.
            self.db.roam_mode = False
            self.db.pursue_mode = False
            self.db.battle_mode = True
            #self.scripts.add(AttackTimer)

        if not weapon.db.magic:
            # In the tutorial, the enemy is a ghostly apparition, so
            # only magical weapons can harm it.
            string = self.db.weapon_ineffective_text
            if not string:
                string = "Your weapon just passes through your enemy, causing no effect!"
            attacker.msg(string)
            return
        else:
            # an actual hit
            health = float(self.db.health)
            health -= damage
            self.db.health = health
            if health <= 0:
                string = self.db.win_text
                if not string:
                    string = "After your last hit, %s folds in on itself, it seems to fade away into nothingness. " % self.key
                    string += "In a moment there is nothing left but the echoes of its screams. But you have a "
                    string += "feeling it is only temporarily weakened. "
                    string += "You fear it's only a matter of time before it materializes somewhere again."
                attacker.msg(string)
                string = self.db.win_text_room
                if not string:
                    string = "After %s's last hit, %s folds in on itself, it seems to fade away into nothingness. " % (attacker.name, self.key)
                    string += "In a moment there is nothing left but the echoes of its screams. But you have a "
                    string += "feeling it is only temporarily weakened. "
                    string += "You fear it's only a matter of time before it materializes somewhere again."
                self.location.msg_contents(string, exclude=[attacker])

                # put mob in dead mode and hide it from view.
                # AttackTimer will bring it back later.
                self.db.dead_at = time.time()
                self.db.roam_mode = False
                self.db.pursue_mode = False
                self.db.battle_mode = False
                self.db.dead_mode = True
                self.location = None
            else:
                self.location.msg_contents("%s wails, shudders and writhes." % self.key)
        return False

    def reset(self):
        """
        If the mob was 'dead', respawn it to its home position and reset
        all modes and damage."""
        if self.db.dead_mode:
            self.db.health = self.db.full_health
            self.db.roam_mode = True
            self.db.pursue_mode = False
            self.db.battle_mode = False
            self.db.dead_mode = False
            self.location = self.home
            string = self.db.respawn_text
            if not string:
                string = "%s fades into existence from out of thin air. It's looking pissed." % self.key
                self.location.msg_contents(string)


#TBD
# PEP8 Linting
# Argparse
#/usr/bin/env python
from  __future__ import print_function
import numpy as np
import torch

# neural network class
import torch.nn as nn

# activation functions
import torch.nn.functional as F

# different optimisation techniques
import torch.optim as optim

# support for automatically getting the gradients
from torch.autograd import Variable

import  matplotlib.pyplot as plt

# Debug
debug = 0

# real data paramters
mean = 4
variance = 1.25

# generator parameters
genInputSize = 1
genHiddenSize = 50
genOutputSize = 1
genIter = 1

# discriminator parameters
discInputSize = 100
discHiddenSize = 50
discOutputSize = 1
discIter = 1

# discriminator output weather the generator is producing real/fake data
miniBatchSize = discInputSize

# optimiser parameters ( adam optimiser )
learningRate = 2e-4
optimBetas = (0.9,0.999)
epochs = 30000

# other parameters
print_stat = 200

# initial noise vector defined as a uniform distribution here
def noise_samples(M,N):
    return torch.rand(M,N)

# real samples need to be defined as gaussian
# need to convert it in a Tensor before passing on to a Pytorch Variable
def real_samples(mean, variance, N):
    return torch.Tensor(np.random.normal(mean, variance, (1,N)))

class Generator(nn.Module):
    def __init__(self):
        # do not understand super
        super(Generator, self).__init__()
        self.layer1 = nn.Linear(genInputSize, genHiddenSize)
        self.layer2 = nn.Linear(genHiddenSize, genHiddenSize)
        self.layer3 = nn.Linear(genHiddenSize, genOutputSize)

    def forward(self, inputMatrix):
        inputMatrix = inputMatrix.view(-1,genInputSize)
        x = F.elu(self.layer1(inputMatrix))
        x = F.sigmoid(self.layer2(x))
        x = self.layer3(x)
        return x

class Discriminator(nn.Module):
    # contains more non linearity to make it stronger than the generator
    def __init__(self):
        super(Discriminator, self).__init__()
        self.layer1 = nn.Linear(discInputSize, discHiddenSize)
        self.layer2 = nn.Linear(discHiddenSize, discHiddenSize)
        self.layer3 = nn.Linear(discHiddenSize, discOutputSize)

    def forward(self, inputMatrix):
        inputMatrix = inputMatrix.view(-1,discInputSize)
        x = F.elu(self.layer1(inputMatrix))
        x = F.elu(self.layer2(x))
        x = F.sigmoid(self.layer3(x))
        return x

def convertToList(tensor):
    return tensor.data.storage().tolist()

def findMetrics(tensor):
    return [np.mean(convertToList(tensor)), np.std(convertToList(tensor))]

def train():
    # create an instance of both discriminator and generator
    D = Discriminator()
    G = Generator()

    # define a optimisation method to be used
    discOptimiser = optim.Adam(D.parameters(), lr=learningRate, betas = optimBetas)
    genOptimiser = optim.Adam(G.parameters(), lr=learningRate, betas = optimBetas)

    # using the binary cross-entropy loss function
    lossFunction = nn.BCELoss()
    discRealLossList = []
    discFakeLossList = []
    genLossList = []

    for epoch in range(epochs):

        for i in range (discIter):
            # for each step, set the gradient to zero
            D.zero_grad()

            # train discriminator on real data
            discRealInput = Variable(real_samples(mean,variance,discInputSize))
            if debug:
                print (discRealInput.size())
            discRealOutput = D(discRealInput)
            if debug:
                print (discRealOutput.size())

            # loss function
            target = Variable(torch.ones(discOutputSize))
            discRealLoss = lossFunction(discRealOutput, target)
            if debug:
                print(discRealLoss.size())
                print (discRealLoss)
            discRealLoss.backward()

            # train discriminacor on fake data
            discFakeInput = Variable(noise_samples(discInputSize,genInputSize))
            if debug:
                print (discFakeInput.size())

            discFakeInput = G(discFakeInput).detach() #??
            if debug:
                print (discFakeInput.size())

            discFakeOutput = D(discFakeInput)
            if debug:
                print (discFakeOutput.size())

            # loss function
            target = Variable(torch.zeros(discOutputSize))
            discFakeLoss = lossFunction(discFakeOutput, target)
            discFakeLoss.backward()

            # backpropagation for discriminator
            # update the weights
            discOptimiser.step()

        for i in range(genIter):
            # for each step, set the gradient to zero
            G.zero_grad()

            # train generator
            # generator takes input of the form 1x1 as input
            # miniBatchSize = 1 and dimension = 1
            genInput = Variable(noise_samples(miniBatchSize, genInputSize))
            if debug:
                print (genInput.size())
            genOutput = G(genInput)
            if debug:
                print (genOutput.size())
            # generator gives the image to generator for prediction
            discOutput = D(genOutput)
            if debug:
                print (discOutput.size())

            # to fool the discriminator, all the samples are genuine
            target = Variable(torch.ones(genOutputSize))

            #loss function and backpropagation
            genLoss = lossFunction(discOutput, target)
            if debug:
                print (genLoss.size())

            genLoss.backward()
            # update the weights
            genOptimiser.step()

        listDiscRealLoss = convertToList(discRealLoss)
        listDiscFakeLoss = convertToList(discFakeLoss)
        listGenLoss = convertToList(genLoss)

        discRealLossList.append(listDiscFakeLoss[0])
        discFakeLossList.append(listDiscRealLoss[0])
        genLossList.append(listGenLoss[0])

        if epoch % print_stat == 0:
            print  ("%s D_Real:%s D_Fake:%s G:%s StatReal:%s StatFake:%s" %
                    (epoch,
                    listDiscRealLoss,
                    listDiscFakeLoss,
                    listGenLoss,
                    findMetrics(discRealInput),
                    findMetrics(genOutput)
                    ))
    plt.plot(discRealLossList, color='green', alpha=0.5, label='Discriminator Real')
    plt.plot(discFakeLossList, color='blue', alpha=0.5, label='Discriminator Fake')
    plt.plot(genLossList, color='red', alpha=0.5, label='Generator')
    plt.legend(loc = 'upper right')
    name = 'uniformtoGaussianLoss'+str(epochs)+'.png'
    plt.savefig(name)
    plt.show()
    plot_distribution(discRealInput, genInput, genOutput)

def plot_distribution(discRealInput, genInput, genOutput):

    # plot the distribution for real samples, must be Gaussian
    count, bins, ignored = plt.hist(convertToList(discRealInput), 30,
            normed=True, color = 'green', alpha=0.5, label='Real Distribution')

    # plot the distribution for noise, must be uniform
    count, bins, ignored = plt.hist(convertToList(genInput), 30, normed=True,
            color = 'blue', alpha=0.5, label='Noise')

    # the generated distribution must be similar to real distribution
    count, bins, ignored = plt.hist(convertToList(genOutput), 30, normed=True,
            color = 'red', alpha=0.5, label='Generated Distribution')

    plt.legend(loc = 'upper right')

    name = 'uniformToGaussian'+str(epochs)+'.png'
    plt.savefig(name)

    plt.show()
if __name__=="__main__":
    #plot_distribution()
    train()


# _*_coding:utf-8_*_
### 需要使用ubantu进行打开

# File: split_dataset.py
# Author:
# Date: 20220506
# Last Modified Date:
# Last Modified By:

import os
import glob
import random
from PIL import Image

if __name__ == '__main__':

    test_split_ratio = 0.05 # 测试集 验证集的划分比率
    desired_size = 128 # 图片缩放后的统一大小
    raw_path = 'raw' # 原始图片的路径

    dirs = glob.glob(os.path.join(raw_path,'*'))

    dirs = [d for d in dirs if os.path.isdir(d)]

    print(f'Totally {len(dirs)} classes: {dirs}')

    for path in dirs:

        path = path.split('/')[-1]

        os.makedirs(f'train/{path}',exist_ok=False)
        os.makedirs(f'test/{path}',exist_ok=False)

        files = glob.glob(os.path.join(raw_path,path,'*.jpg'))
        files += glob.glob(os.path.join(raw_path,path,'*.JPG'))
        files += glob.glob(os.path.join(raw_path,path,'*.png'))

        random.shuffle(files)

        boundary = int(len(files)*test_split_ratio) # 训练集和测试集的边界

        for i,file in enumerate(files):
            img = Image.open(file).convert('RGB')

            old_size = img.size   # old_size[0] is in (width,height) format

            ratio = float(desired_size)/max(old_size)

            new_size = tuple([int(x*ratio) for x in old_size])

            im = img.resize(new_size, Image.ANTIALIAS)

            new_im = Image.new("RGB",(desired_size,desired_size))

            new_im.paste(im,((desired_size-new_size[0])//2,(desired_size-new_size[1])//2))

            assert new_im.mode == 'RGB'

            if i <= boundary:
                new_im.save(os.path.join(f'test/{path}',file.split('/')[-1].split('.')[0]+'.jpg'))
            else:
                new_im.save(os.path.join(f'train/{path}',file.split('/')[-1].split('.')[0]+'.jpg'))\

    test_files = glob.glob(os.path.join('test','*','*.jpg'))
    train_files = glob.glob(os.path.join('train','*',"*.jpg"))

    print(f'Totally {len(train_files)} files for training')
    print(f'Totally {len(test_files)} files for test')


from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from flask_cors import CORS, cross_origin

from database import Database, SQLITE

dbms = Database(SQLITE, dbname='drugstore.db')

app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

api = Api(app)

class Drugstore(Resource):
    def get(self):

        local_name = request.args.get('local_nombre')
        commune_id = request.args.get('comuna_id')

        print(local_name)

        query = 'select local_nombre, local_direccion, local_telefono, local_lat, local_lng from drugstores' # Esta línea ejecuta un query y retorna un json como resultado

        if local_name or commune_id != None:
            query += ' where 1=1 '

        if local_name != None and local_name != '':
            query += " and local_nombre = '{0}'".format(local_name)

        if commune_id != None and commune_id != '':
            query += " and fk_comuna = '{0}'".format(commune_id)

        result = dbms.select_execute(query=query)

        return jsonify({'data': [dict(row) for row in result]})

class DrugstoreData(Resource):
    def get(self, drugstore_id):
        query = "select local_nombre, local_direccion, local_telefono, local_lat, local_lng from drugstores where local_id =%d " % int(drugstore_id)
        result = dbms.select_execute(query=query)

        return jsonify({'data': [dict(row) for row in result]})

api.add_resource(Drugstore, '/drugstores')
api.add_resource(DrugstoreData, '/drugstore/<drugstore_id>')

if __name__ == '__main__':
    dbms.create_db_tables()
    dbms.clean_data()
    dbms.load_sample_data()
    app.run(host='0.0.0.0')


import argparse
import os
import sys
import logging
import time
from pecos.utils import smat_util
from pecos.xmc import PostProcessor
from pecos.xmc.xlinear.model import XLinearModel
from sklearn.preprocessing import normalize

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.DEBUG)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
LOGGER.addHandler(handler)

def parse_arguments():
    """Parse prediction arguments"""

    parser = argparse.ArgumentParser()

    # Required parameters
    parser.add_argument(
        "-x",
        "--inst-path",
        type=str,
        required=True,
        metavar="PATH",
        help="path to the npz file of the feature matrix (CSR, nr_insts * nr_feats)",
    )

    parser.add_argument(
        "-m",
        "--model-folder",
        type=str,
        required=True,
        metavar="DIR",
        help="path to the model folder.",
    )

    # Optional
    parser.add_argument(
        "-k",
        "--only-topk",
        type=int,
        default=None,
        metavar="INT",
        help="override the only topk specified in the model (default None to disable overriding)",
    )

    parser.add_argument(
        "-b",
        "--beam-size",
        type=int,
        default=None,
        metavar="INT",
        help="override the beam size specified in the model (default None to disable overriding)",
    )

    parser.add_argument(
        "-pp",
        "--post-processor",
        type=str,
        choices=PostProcessor.valid_list(),
        default=None,
        metavar="STR",
        help="override the post processor specified in the model (default None to disable overriding)",
    )

    parser.add_argument(
        "-y",
        "--label-path",
        type=str,
        default=None,
        metavar="PATH",
        help="path to the npz file of the label matrix (CSR, nr_insts * nr_labels)",
    )

    parser.add_argument(
        "-o",
        "--save-pred-path",
        type=str,
        default=None,
        metavar="PATH",
        help="path to save the predictions (sorted CSR, nr_insts * nr_labels)",
    )

    parser.add_argument(
        "-B",
        "--max-pred-chunk",
        default=10 ** 7,
        metavar="INT",
        type=int,
        help="Max number of instances to predict on at once, set to avoid OOM. Set to None to predict on all instances at once. Default 10^7",
    )

    parser.add_argument(
        "-n",
        "--threads",
        type=int,
        default=-1,
        metavar="THREADS",
        help="number of threads to use (default -1 to denote all the CPUs)",
    )

    parser.add_argument(
        "-so",
        "--selected-output",
        type=str,
        default=None,
        metavar="PATH",
        help="path to the npz file of the selected output matrix (CSR, nr_insts * nr_labels), only-topk and beam-size are ignored if given",
    )
    return parser

def do_predict(args):
    """Predict and Evaluate for xlinear model

    Args:
        args (argparse.Namespace): Command line arguments parsed by `parser.parse_args()`
    """

    # Load data
    LOGGER.info("| loading data begin...")
    start_time = time.time()
    Xt = XLinearModel.load_feature_matrix(args.inst_path)
    Xt = normalize(Xt, axis=1, norm="l2")
    run_time_data = time.time() - start_time
    LOGGER.info("| loading data finsihed | time(s) {:9.4f}".format(run_time_data))

    LOGGER.info("| loading model begin...")
    start_time = time.time()
    if args.selected_output is not None:
        # Selected Output
        selected_outputs_csr = XLinearModel.load_feature_matrix(args.selected_output)
        xlinear_model = XLinearModel.load(
            args.model_folder, is_predict_only=True, weight_matrix_type="CSC"
        )
    else:
        # TopK
        selected_outputs_csr = None
        xlinear_model = XLinearModel.load(args.model_folder, is_predict_only=True)
    run_time_io = time.time() - start_time
    LOGGER.info("| loading model finsihed | time(s) {:9.4f}".format(run_time_io))

    # Model Predicting
    LOGGER.info("| inference model begin...")
    start_time = time.time()
    Yt_pred = xlinear_model.predict(
        Xt,
        selected_outputs_csr=selected_outputs_csr,
        only_topk=args.only_topk,
        beam_size=args.beam_size,
        post_processor=args.post_processor,
        threads=args.threads,
        max_pred_chunk=args.max_pred_chunk,
    )
    run_time_pred = time.time() - start_time
    LOGGER.info("| inference model finsihed | time(s) {:9.4f} latency(ms/q) {:9.4f}".format(
        run_time_pred,
        run_time_pred / Xt.shape[0] * 1000,
        )
    )

    # Save prediction
    if args.save_pred_path:
        smat_util.save_matrix(args.save_pred_path, Yt_pred)

    # Evaluate
    if args.label_path:
        Yt = XLinearModel.load_label_matrix(args.label_path)
        metric = smat_util.Metrics.generate(Yt, Yt_pred, topk=10)
        print("==== evaluation results ====")
        print(metric)

if __name__ == "__main__":
    parser = parse_arguments()
    args = parser.parse_args()
    do_predict(args)


"""Lint the code."""

import os
import colorama

def main():
    """Run linting commands."""
    print('\n=== pylint: mayax =============================================================\n')
    pylint1 = os.system('pylint src/mayax')
    print('\n=== pylint: tests =============================================================\n')
    pylint2 = os.system('pylint tests')

    print('\n=== pycodestyle: mayax ========================================================\n')
    pycodestyle1 = os.system('pycodestyle src/mayax')
    print('\n=== pycodestyle: tests ========================================================\n')
    pycodestyle2 = os.system('pycodestyle tests')

    print('\n=== pydocstyle: mayax =========================================================\n')
    pydocstyle1 = os.system('pydocstyle src/mayax')
    print('\n=== pydocstyle: tests =========================================================\n')
    pydocstyle2 = os.system('pydocstyle tests')

    print('\n===============================================================================\n')

    colorama.init()

    print(
        'pylint: {}mayax ({}){}, {}tests ({})'.format(
            colorama.Fore.RED if pylint1 else colorama.Fore.GREEN,
            'FAIL' if pylint1 else 'OK',
            colorama.Fore.RESET,
            colorama.Fore.RED if pylint2 else colorama.Fore.GREEN,
            'FAIL' if pylint2 else 'OK',
        )
    )

    print(colorama.Fore.RESET)

    print(
        'pycodestyle: {}mayax ({}){}, {}tests ({})'.format(
            colorama.Fore.RED if pycodestyle1 else colorama.Fore.GREEN,
            'FAIL' if pycodestyle1 else 'OK',
            colorama.Fore.RESET,
            colorama.Fore.RED if pycodestyle2 else colorama.Fore.GREEN,
            'FAIL' if pycodestyle2 else 'OK',
        )
    )

    print(colorama.Fore.RESET)

    print(
        'pydocstyle: {}mayax ({}){}, {}tests ({})'.format(
            colorama.Fore.RED if pydocstyle1 else colorama.Fore.GREEN,
            'FAIL' if pydocstyle1 else 'OK',
            colorama.Fore.RESET,
            colorama.Fore.RED if pydocstyle2 else colorama.Fore.GREEN,
            'FAIL' if pydocstyle2 else 'OK',
        )
    )

if __name__ == '__main__':
    main()


import logging

from django.db import transaction
from django.conf.urls import url
from django.urls import reverse
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.shortcuts import redirect, render
from django_tables2 import RequestConfig

from hub.models import CourseCode, Course, UserCourseCodeBinding, UserCourseBinding
from hub.models import Assignment, UserAssignmentBinding
from hub.models import Image

from kooplex.lib import now, translate_date

from hub.forms import FormAssignment
from hub.forms import T_BIND_ASSIGNMENT, T_COLLECT_ASSIGNMENT, T_FEEDBACK_ASSIGNMENT, T_SUBMIT_ASSIGNMENT

logger = logging.getLogger(__name__)

@login_required
def teaching(request):
    """Renders the projectlist page for courses taught."""
    logger.debug('Rendering teaching.html')
    context_dict = {
        'menu_teaching': 'active',
        'next_page': 'education:teaching',
    }
    return render(request, 'edu/teaching.html', context = context_dict)

@login_required
def courses(request):
    """Renders the projectlist page for students."""
    logger.debug('Rendering courses.html')
    context_dict = {
        'menu_teaching': 'active',
        'next_page': 'education:courses',
    }
    return render(request, 'edu/courses.html', context = context_dict)

@login_required
def conf_meta(request, course_id, next_page):
    user = request.user
    logger.debug("method: %s, course id: %s, user: %s" % (request.method, course_id, user))
    try:
        course = Course.get_usercourse(course_id = course_id, user = request.user)
    except Course.DoesNotExist as e:
        logger.error('abuse by %s course id: %s -- %s' % (user, course_id, e))
        messages.error(request, 'Course does not exist')
        return redirect(next_page)

    if request.method == 'POST' and request.POST.get('button') == 'apply':
        course.description = request.POST.get('description')
        imagename = request.POST['course_image']
        course.image = Image.objects.get(name = imagename) if imagename != 'None' else None
        course.save()
        return redirect(next_page)
    else:
        context_dict = {
            'images': Image.objects.all(),
            'course': course,
            'submenu': 'meta',
            'next_page': next_page,
        }
        return render(request, 'edu/configure.html', context = context_dict)

@login_required
def newassignment(request, course_id):
    """Renders assignment management form."""
    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    try:
        course = Course.objects.get(id = course_id)
        binding = UserCourseBinding.objects.get(user = user, course = course, is_teacher = True)
    except Course.DoesNotExist:
        logger.error("Missing course id %s and user %s" % (course_id, user))
        return redirect('education:teaching')
    except UserCourseBinding.DoesNotExist:
        logger.error("Missing course id %s and user %s" % (course_id, user))
        messages.error(request, 'You are not allowed to create an assignment for %s' % (course))
        return redirect('education:teaching')
    if request.method == 'GET':
        context_dict = {
            'course': course,
            'f_assignment': FormAssignment(user = user, course = course),
            'menu_teaching': 'active',
            'submenu': 'new',
            'next_page': 'education:teaching',
        }
        return render(request, 'edu/assignment-teacher.html', context = context_dict)
    elif request.method == 'POST':
        coursecode_ids = request.POST.getlist("coursecodes")
        name = request.POST.get("name").strip()
        description = request.POST.get("description").strip()
        folder = request.POST.get("folder")
        timenow = now()
        valid_from = translate_date(request.POST.get('valid_from')) or timenow
        expires_at = translate_date(request.POST.get('expires_at'))
        is_massassignment = bool(request.POST.get("is_massassignment"))
        can_studentsubmit = bool(request.POST.get("can_studentsubmit"))
        remove_collected = bool(request.POST.get("remove_collected"))
        try:
            assert valid_from >= timenow, "You try to shedule assignment behind time."
            assert len(name), "You need to provide a name"
            assert len(coursecode_ids), "You need to select at least one course code"
            extra = {}
            if expires_at:
                assert (expires_at - valid_from).total_seconds() >= 60, "Expiry is too close to handout. "
            for coursecodeid in coursecode_ids:
                coursecode = CourseCode.objects.get(id = coursecodeid)
                assert coursecode.course == course, "Course code mismatch"
                #UserCourseCodeBinding.objects.get(coursecode = coursecode, user = user, is_teacher = True)
                logger.debug("coursecode id %s" % coursecodeid)
                with transaction.atomic():
                    assignments = Assignment.objects.filter(
                        coursecode = coursecode,
                        name = name,
                        creator = user,
                        description = description,
                        folder = folder,
                        can_studentsubmit = can_studentsubmit,
                        remove_collected = remove_collected,
                        is_massassignment = is_massassignment,
                        expires_at = expires_at
                        )
                    if len(assignments):
                        logger.warning('Prevented from duplicating assignments for course code %s' % (coursecode))
                        messages.warning(request, 'Maybe you double clicked on assignments.')
                        continue
                    Assignment.objects.create(
                        coursecode = coursecode,
                        name = name,
                        creator = user,
                        description = description,
                        folder = folder,
                        can_studentsubmit = can_studentsubmit,
                        remove_collected = remove_collected,
                        is_massassignment = is_massassignment,
                        valid_from = valid_from,
                        expires_at = expires_at
                    )
                    logger.info('New assignments for course code %s' % (coursecode))
                    messages.info(request, 'New assignments for course code %s' % (coursecode))
        except Exception as e:
            logger.error(e)
            messages.error(request, 'Cannot fully register assignment -- %s' % e)
            return redirect('education:newassignment', course.id)
    return redirect('education:teaching')

@login_required
def bindassignment(request, course_id):
    from django.contrib.auth.models import User
    """Bind assignment and user"""
    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    try:
        course = Course.objects.get(id = course_id)
        assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = True))) > 0, "%s is not a teacher of course %s" % (user, course)
    except Exception as e:
        logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
        return redirect('indexpage')
    if request.method == 'GET':
        s_name = request.GET.get('name', '')
        s_username = request.GET.get('username', '')
        s_assignment = request.GET.get('assignment', '')
        table_bind = T_BIND_ASSIGNMENT(course.bindableassignments())
        RequestConfig(request).configure(table_bind)
        context_dict = {
            'course': course,
            't_bind': table_bind,
            'search_name': s_name,
            'search_username': s_username,
            'search_assignment': s_assignment,
            'menu_teaching': 'active',
            'submenu': 'bind',
            'next_page': 'education:teaching',
        }
        return render(request, 'edu/assignment-teacher.html', context = context_dict)
    elif request.method == 'POST':
        thetime = now()
        oopses = 0
        done = 0
        for br in request.POST.getlist('binding_representation'):
            user_id, assignment_id = br.split('_')
            try:
                assignment = Assignment.objects.get(id = assignment_id)
                student = User.objects.get(id = user_id)
                assert student in assignment.list_students_bindable(), "Cannot bind %s to %s" % (student, assignment)
                assert user.profile.is_coursecodeteacher(assignment.coursecode), "You are not a teacher of %s" % assignment.coursecode
            except Exception as e:
                raise
                logger.error("oops -- %s" % e)
                oopses += 1
                continue
            try:
                binding = UserAssignmentBinding.objects.get(user = student, assignment = assignment)
                logger.error("Will not create a duplicate %s" % binding)
                messages.error(request, "Prevented from creating a duplicate assignment %s for student %s" % (assignment.name, student))
            except UserAssignmentBinding.DoesNotExist:
                pass
            valid_from = translate_date(request.POST.get('valid_from_%s' % br))
            expires_at = translate_date(request.POST.get('expires_at_%s' % br))
            if valid_from and valid_from > thetime:
                UserAssignmentBinding.objects.create(user = student, assignment = assignment, valid_from = valid_from, expires_at = expires_at, state = UserAssignmentBinding.ST_QUEUED)
            else:
                UserAssignmentBinding.objects.create(user = student, assignment = assignment, valid_from = valid_from, expires_at = expires_at)
    #FIXME> expiry check in model init!
            messages.info(request, "Creating an assignment %s for student %s" % (assignment.name, student))
        url_next = reverse('education:bindassignment', kwargs = {'course_id': course_id})
        pager = request.POST.get('pager')
        logger.debug("next: %s %s" % (url_next, pager))
        return redirect(url_next + "?%s" % pager) if pager else redirect('education:bindassignment', course_id)
    else:
        return redirect('education:teaching')

@login_required
def collectassignment(request, course_id):
    """Handle assignment collection"""
    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    per_page=20
    try:
        course = Course.objects.get(id = course_id)
        assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = True))) > 0, "%s is not a teacher of course %s" % (user, course)
    except Exception as e:
        logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
        return redirect('indexpage')
    if request.method == 'POST' and request.POST['button'] == 'search':
        s_name = request.POST.get('name')
        per_page = request.POST.get('per_page')
        s_username = request.POST.get('username')
        s_assignment = request.POST.get('assignment')
        render_page = True
    elif request.method == 'GET':
        s_name = None
        s_username = None
        s_assignment = None
        render_page = True
    else:
        render_page = False
    if render_page:
        table_collect = T_COLLECT_ASSIGNMENT(course.userassignmentbindings(s_assignment = s_assignment, s_name = s_name, s_username = s_username, s_assignmentstate = UserAssignmentBinding.ST_WORKINPROGRESS))
        RequestConfig(request,  paginate={'per_page': per_page}).configure(table_collect)
        context_dict = {
            'course': course,
            't_collect': table_collect,
            'search_name': s_name if s_name else '',
            'search_username': s_username if s_username else '',
            'search_assignment': s_assignment if s_assignment else '',
            'per_page': per_page,
            'menu_teaching': 'active',
            'submenu': 'collect',
            'next_page': 'education:feedback',
        }
        return render(request, 'edu/assignment-teacher.html', context = context_dict)
    elif request.method == 'POST':
        userassignmentbinding_ids = request.POST.getlist('userassignmentbinding_ids')
        for binding_id in userassignmentbinding_ids:
            try:
                binding = UserAssignmentBinding.objects.get(id = binding_id)
                coursecode = binding.assignment.coursecode
                assert coursecode.course == course, "course code mismatch"
                #UserCourseCodeBinding.objects.get(user = user, coursecode = coursecode, is_teacher = True)
                UserCourseBinding.objects.get(user = user, course = binding.assignment.coursecode.course, is_teacher = True)
                binding.do_collect()
                messages.info(request, 'Assignment %s of %s for course code %s is collected' % (binding.assignment.name, binding.user, coursecode))
            except Exception as e:
                logger.error(e)
                messages.error(request, 'Cannot mark assignment collected -- %s' % e)
        url_next = reverse('education:collectassignment', kwargs = {'course_id': course_id})
        pager = request.POST.get('pager')
        return redirect(url_next + "?%s" % pager) if pager else redirect('education:collectassignment', course_id)
    else:
        return redirect('indexpage')

@login_required
def feedbackassignment(request, course_id):
    """Mark assignments to correct"""
    from hub.models.assignment import ST_LOOKUP
    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    per_page=20
    try:
        course = Course.objects.get(id = course_id)
        assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = True))) > 0, "%s is not a teacher of course %s" % (user, course)
    except Exception as e:
        logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
        return redirect('indexpage')
    if request.method == 'POST' and request.POST['button'] == 'search':
        per_page = request.POST.get('per_page')
        s_name = request.POST.get('name')
        s_username = request.POST.get('username')
        s_assignment = request.POST.get('assignment')
        s_assignmentstate = request.POST.get('assignmentstate') if request.POST.get('assignmentstate') else None
        render_page = True
    elif request.method == 'GET':
        s_name = None
        s_username = None
        s_assignment = None
        s_assignmentstate = None
        render_page = True
    else:
        render_page = False
    if render_page:
        table_feedback = T_FEEDBACK_ASSIGNMENT(course.userassignmentbindings(s_assignment = s_assignment, s_name = s_name, s_username = s_username, s_assignmentstate = s_assignmentstate))
        RequestConfig(request,  paginate={'per_page': per_page}).configure(table_feedback)
        context_dict = {
            'course': course,
            't_feedback': table_feedback,
            'search_name': s_name if s_name else '',
            'search_username': s_username if s_username else '',
            'search_assignment': s_assignment if s_assignment else '',
            'search_assignmentstate': s_assignmentstate if s_assignmentstate else '',
            'states': ST_LOOKUP,
            'menu_teaching': 'active',
            'submenu': 'feedback',
            'per_page': per_page,
            'next_page': 'education:feedback',
        }
        return render(request, 'edu/assignment-teacher.html', context = context_dict)
    elif request.method == 'POST':
        for k, v in request.POST.items():
            try:
                task, binding_id = k.split('_')
                assert task == 'task'
                assert v in [ 'correct', 'ready', 'reassign' ]
                task = v
            except:
                continue
            try:
                binding = UserAssignmentBinding.objects.get(id = binding_id)
                #UserCourseCodeBinding.objects.get(user = user, coursecode = binding.assignment.coursecode, is_teacher = True)
                UserCourseBinding.objects.get(user = user, course = binding.assignment.coursecode.course, is_teacher = True)
                score = request.POST.get('score_%s' % binding_id)
                feedback_text = request.POST.get('feedback_text_%s' % binding_id)
                if task == 'correct':
                    binding.state = UserAssignmentBinding.ST_CORRECTING
                    binding.corrector = user
                elif task == 'ready':
                    binding.state = UserAssignmentBinding.ST_FEEDBACK
                    binding.corrected_at = now()
                    binding.score = float(score)
                    if feedback_text:
                        binding.feedback_text = feedback_text.strip()
                elif task == 'reassign':
                    binding.state = UserAssignmentBinding.ST_WORKINPROGRESS
                    binding.corrected_at = now()
                binding.save()
                messages.info(request, '%s\'s assignment %s for course code %s is now %s' % (binding.user.username, binding.assignment.name, binding.assignment.coursecode.courseid, binding.assignment.state))
            except Exception as e:
                logger.error(e)
                messages.error(request, 'Cannot mark assignment corrected -- %s' % e)
        url_next = reverse('education:feedback', kwargs = {'course_id': course_id})
        pager = request.POST.get('pager')
        return redirect(url_next + "?%s" % pager) if pager else redirect('education:feedback', course_id)
    else:
        return redirect('education:teaching')

@login_required
def summaryassignment(request, course_id):
    """Summary of the grades for each assignment"""
    from hub.models.assignment import ST_LOOKUP
    from pandas import DataFrame
    from django_pandas.io import read_frame
    from hub.models import UserAssignmentBinding

    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    per_page=20
    course = Course.objects.get(id = course_id)
    qs = UserAssignmentBinding.objects.filter(assignment__coursecode__course__id=course_id)
    if len(list(qs)) == 0:
        newdf = DataFrame()
        messages.warning(request, 'Course %s has no assignments yet' % (course.name))
    else:
        try:
            df = read_frame(qs)
            df['Assignments'] = df.assignment.apply(lambda x: x.split("[")[0].strip())
            df.score.fillna(0, inplace=True)
            assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = True))) > 0, "%s is not a teacher of course %s" % (user, course)
            newdf = df.pivot_table(index='user', columns='Assignments', values='score')#, aggfunc='count')
            #qs.to_dataframe(['age', 'wage'], index='full_name'])
            #qs.filter(age__gt=20, department='IT').to_dataframe(index='full_name')
            newdf['Sum'] = df.groupby(['user']).sum().score
        except Exception as e:
            logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
            try:
                 assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = False))) > 0, "%s is not even a student of course %s" % (user, course)
                 df = df[df.user == user.username]
                 newdf = df.pivot_table(index='user', columns='Assignments', values='score')#, aggfunc='count')
                 newdf['Sum'] = df.groupby(['user']).sum().score
            except Exception as e:
                 logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
                 return redirect('indexpage')
    if request.method == 'GET':
        render_page = True
    else:
        render_page = False
    if render_page:
        context_dict = {
            'course': course,
            'pd': newdf.sort_values(by=['Sum'], ascending=True).to_html(table_id='datatable', col_space=300),
            'menu_teaching': 'active',
            'submenu': 'summary',
            'per_page': per_page,
            'next_page': 'education:summary',
        }
        return render(request, 'edu/assignment-teacher.html', context = context_dict)
    else:
        return redirect('education:teaching')

@login_required
def submitassignment(request, course_id):
    """Handle assignment submission"""
    user = request.user
    logger.debug("user %s, method: %s" % (user, request.method))
    try:
        course = Course.objects.get(id = course_id)
        assert len(list(UserCourseBinding.objects.filter(user = user, course = course, is_teacher = False))) == 1, "%s is not a student of course %s" % (user, course)
    except Exception as e:
        logger.error("Invalid request with course id %s and user %s -- %s" % (course_id, user, e))
        return redirect('indexpage')
    if request.method == 'GET':
        table_submit = T_SUBMIT_ASSIGNMENT(course.userassignmentbindings(user = user))
        RequestConfig(request).configure(table_submit)
        context_dict = {
            'course': course,
            't_submit': table_submit,
            'menu_teaching': 'active',
            'submenu': 'submit',
            'next_page': 'education:course',
        }
        return render(request, 'edu/assignment-student.html', context = context_dict)
    elif request.method == 'POST':
        userassignmentbinding_ids = request.POST.getlist('userassignmentbinding_ids')
        for binding_id in userassignmentbinding_ids:
            try:
                binding = UserAssignmentBinding.objects.get(id = binding_id, user = user)
                binding.state = UserAssignmentBinding.ST_SUBMITTED
                binding.submitted_at = now()
                binding.save()
                assignment = binding.assignment
                coursecode = assignment.coursecode
                messages.info(request, '%s assignment is submitted for course %s (%s)' % (assignment.name, coursecode.course.name, coursecode.courseid))
            except Exception as e:
                logger.error(e)
                messages.error(request, 'Cannot fully submit assignment -- %s' % e)
    return redirect('education:courses')

urlpatterns = [
    url(r'^teaching/?$', teaching, name = 'teaching'),
    url(r'^courses/?$', courses, name = 'courses'),
    url(r'^configurecourse/(?P<course_id>\d+)/meta/(?P<next_page>\w+:?\w*)$', conf_meta, name = 'conf_meta'),
    url(r'^newassignemnt/(?P<course_id>\d+)$', newassignment, name = 'newassignment'),
    url(r'^bindassignment/(?P<course_id>\d+)$', bindassignment, name = 'bindassignment'),
    url(r'^collectassignment/(?P<course_id>\d+)$', collectassignment, name = 'collectassignment'),
    url(r'^feedback/(?P<course_id>\d+)$', feedbackassignment, name = 'feedback'),
    url(r'^summary/(?P<course_id>\d+)$', summaryassignment, name = 'summary'),
    url(r'^submitassignment/(?P<course_id>\d+)$', submitassignment, name = 'submitassignment'),
]


# -*- coding: utf-8 -*-
"""
 Sylvain Bertrand, 2023

"""

import numpy as np
import matplotlib.pyplot as plt
import sys

if (len(sys.argv) == 2):
    nbOfRobots = int(sys.argv[1])
else:
    nbOfRobots = 1
    print("Warning: nb of robots set to 1")
    print("syntax: plot_from_csv.py nbOfRobots")

t = []
x = []
y = []

fig, ax = plt.subplots(2)
fig_traj, ax_traj = plt.subplots(1)

colorList = [[1,0,0], [0,1,0], [0,0,1], [1,0.91,0.31], [0.7,0.7,0.7], [0.96,0.18,0.92], [1,0.49,0.37], [0,0,0]]
while len(colorList)<nbOfRobots:
    colorList.append([0.5,0.5,0.5])

legendList = []

for robotNo in range(1,nbOfRobots+1):

    # load CSV file
    fileName = 'csv_directory/_slash_tb3_'+str(robotNo)+'_slash_odom.csv'
    f = np.loadtxt(fileName, skiprows=1, usecols=[4,5,11,12,13], delimiter=',')

    # get fields
    t.append(f[:,0]-f[0,0]+f[:,1]*0.000000001)
    x.append(f[:,2])
    y.append(f[:,3])

    legendList.append(str(robotNo))

    # time plots
    ax[0].plot(t[robotNo-1], x[robotNo-1], color=colorList[robotNo-1])
    ax[1].plot(t[robotNo-1], y[robotNo-1], color=colorList[robotNo-1])

    # trajectory plot
    ax_traj.plot(x[robotNo-1],y[robotNo-1], color=colorList[robotNo-1])

# legend and labels for time plots
ax[0].set_ylabel('x (m)')
ax[0].grid()
ax[0].legend(legendList)
ax[1].set_xlabel('t (s)')
ax[1].set_ylabel('y (m)')
ax[1].grid()
ax[1].legend(legendList)

# legend and labelds for trajectory plot
ax_traj.set_title('Trajectories')
ax_traj.set_xlabel('x (m)')
ax_traj.set_ylabel('y (m)')
ax_traj.grid()
ax_traj.legend(legendList)

# show plots
plt.show()


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('test', '0016_auto_20151127_0844'),
    ]

    operations = [
        migrations.DeleteModel(
            name='ContentTestProduct',
        ),
        migrations.AddField(
            model_name='contenttest',
            name='external_links_test',
            field=models.CharField(max_length=100, blank=True),
        ),
    ]


from django.conf.urls import patterns, include, url
from django.contrib import admin

from aggr import views

from rest_framework.urlpatterns import format_suffix_patterns

urlpatterns = patterns('',
    # Examples:

	# General urls
	url(r'^$', 'aggr.views.home', name='home'),
	url(r'^login/$', 'aggr.views.user_login', name='user_login'),
	url(r'^logout/$', 'aggr.views.user_logout', name='user_logout'),
	url(r'^profile/(?P<type>[-_\w]+)/(?P<pid>[-_\w]+)/$', 'aggr.views.profile', name='profile'),
	url(r'^profile/(?P<type>[-_\w]+)/(?P<pid>[-_\w]+)/(?P<mode>[-_\w]+)/$', 'aggr.views.profile', name='profile'),
	url(r'^register/$', 'aggr.views.register', name='register'),
	url(r'^admin/', include(admin.site.urls)),

	url(r'^json/MediaItem/', views.MediaItemList.as_view()),

	# Media item urls
	url(r'^(?P<itemtype>[-_\w]+)/$', 'aggr.views.media_list'),
	url(r'^(?P<itemtype>[-_\w]+)/(?P<name>[-_\w]+)/$', 'aggr.views.media_item'),
	url(r'^(?P<itemtype>[-_\w]+)/(?P<name>[-_\w]+)/reviews$', 'aggr.views.item_reviews'),
	url(r'^(?P<itemtype>[-_\w]+)/(?P<name>[-_\w]+)/userreviews$', 'aggr.views.item_userreviews'),

)

urlpatterns = format_suffix_patterns(urlpatterns)


from django import forms
from .models import Platillos

MUNICIPIOS = list(enumerate([
 "Apozol",
    "Apulco",
    "Atolinga",
    "Benito Juárez",
    "Calera",
    "Cañitas de Felipe Pescador",
    "Chalchihuites",
    "Concepción del Oro",
    "Cuauhtémoc",
    "El Plateado de Joaquín Amaro",
    "El Salvador",
    "Fresnillo",
    "Genaro Codina",
    "General Enrique Estrada",
    "General Francisco R. Murguía",
    "General Pánfilo Natera",
    "Guadalupe",
    "Huanusco",
    "Jalpa",
    "Jerez",
    "Jiménez del Teul",
    "Juan Aldama",
    "Juchipila",
    "Loreto",
    "Luis Moya",
    "Mazapil",
    "Melchor Ocampo",
    "Mezquital del Oro",
    "Miguel Auza",
    "Momax",
    "Monte Escobedo",
    "Morelos",
    "Moyahua de Estrada",
    "Nochistlán de Mejía",
    "Noria de Ángeles",
    "Ojocaliente",
    "Pánuco",
    "Pinos",
    "Río Grande",
    "Sain Alto",
    "Santa María de la Paz",
    "Sombrerete",
    "Susticacán",
    "Tabasco",
    "Tepechitlán",
    "Tepetongo",
    "Teúl de González Ortega",
    "Tlaltenango de Sánchez Román",
    "Trancoso",
    "Trinidad García de la Cadena",
    "Valparaíso",
    "Vetagrande",
    "Villa de Cos",
    "Villa García",
    "Villa González Ortega",
    "Villa Hidalgo",
    "Villanueva",
    "Zacatecas"
]))

class PlatilloForm(forms.ModelForm):
    class Meta:
        model = Platillos
        fields = '__all__'
        widgets = {
            "municipioPlatillo": forms.Select(attrs={'class': 'form-control'}),
            'nombrePlatillo': forms.TextInput(attrs={'class': 'form-control'}),
            'descripcionPlatillo': forms.Textarea(attrs={'class': 'form-control'}),
            'ingredientesPlatillo': forms.Textarea(attrs={'class': 'form-control'}),
        }

## Formulario para aplicar filtros a la vista de platillos.
class FiltroPlatilloForm(forms.Form):
    nombrePlatillo = forms.CharField(
        widget=forms.TextInput(
            attrs={'class': 'form-control', 'placeholder': 'Nombre del platillo'}),
        required=False
    )

    municipioPlatillo = forms.ChoiceField(
        required=False,
        choices = tuple([(u'', "Todos")] + MUNICIPIOS),
        widget = forms.Select(
            attrs={'class': 'form-control'}
        )
    )


#!/usr/bin/env python3
"""
process_data.py

Some functionality from SYSC3010 project

Notes
-----
- Docstrings follow the numpydoc style:
  https://numpydoc.readthedocs.io/en/latest/format.html
- Code follows the PEP 8 style guide:
  https://www.python.org/dev/peps/pep-0008/
"""
from thingspeak import write_to_channel
import socket as s
import constants as c
import iot_email
import iot_sender
import logging
import argparse
import json

# logging
LOG = "/tmp/logfile.log"
logging.basicConfig(filename=LOG, filemode="w", level=logging.DEBUG)

class DataPoller:
    """
    Class to poll data
    """

    def __init__(self, port, address):
        """
        Parameters
        ----------
        port : int
        address : list
        """
        self.__port = port
        self.__recipients = address

        # Flag to be reset if true after values become safe
        self.__humidity_warning = False
        self.__co2_warning = False

        # Flag to be set false after first message processing
        self.__humidity_first = True
        self.__co2_first = True

    def poll_data(self):
        """
        Waits to receive data from network
        """
        with s.socket(s.AF_INET, s.SOCK_DGRAM) as sock:
            sock.bind(('', self.__port))
            while True:
                message, address = sock.recvfrom(1024)
                self.__address = address
                logging.debug('Received: {}'.format(message))
                self.process_data(message)

    def process_data(self, message):
        """
        Processes data by uploading to cloud

        Parameters
        ----------
        message : bytes
        """
        link = 'https://api.thingspeak.com/channels/{}/feeds.json?'.format(
            c.AIR_QUALITY_FEED)
        fields = {}
        key = c.AIR_QUALITY_WRITE_KEY

        # Retrieve dict from message
        message_dict = eval(message.decode())
        type_data = message_dict.get('type', None)
        value_data = message_dict.get('value', None)
        id_data = message_dict.get('id', None)
        location_data = message_dict.get('location', None)
        ip_data = message_dict.get('src_ip', None)
        port_data = message_dict.get('src_port', None)
        address = (ip_data, port_data)

        # Unrecognized message, ignore
        if not type_data or not value_data or not id_data or not location_data:
            logging.error('Unrecognized message. Ignoring')
            return

        # Assembly humidity record
        if type_data == 'humidity':
            self.humidity_processing(value_data, address, id_data)
            fields = {c.NODE_FIELD: id_data,
                      c.LOCATION_FIELD: location_data,
                      c.HUMIDITY_FIELD: value_data}

        # Aseembly co2 record
        elif type_data == 'co2':
            self.co2_processing(value_data, address, id_data)
            fields = {c.NODE_FIELD: id_data,
                      c.LOCATION_FIELD: location_data,
                      c.CO2_FIELD: value_data}

        # Unrecognized type, ignore
        else:
            logging.error('Unrecognized message. Ignoring')
            return

        # Data received that should be recorded in the cloud
        logging.debug('Writing to cloud')
        status, reason = write_to_channel(key, fields)

        # Check status
        if status == c.GOOD_STATUS:
            logging.debug('Write to cloud was succesful')
            logging.debug('View results here {}'.format(link))
        else:
            logging.error('Write to cloud was unsuccessful: {}'.format(reason))

    def humidity_processing(self, value, address, id_data):
        """
        Further process humidity data

        Parameters
        ----------
        value : float
        address : tuple
        id_data : int
        """
        if value < c.HUMIDITY_THRESHOLD:
            logging.info('Low humidity! Alert user')

            # Send notification only upon first recognition of warning
            if not self.__humidity_warning:
                self.__humidity_warning = True

                # Send email to defined recipients
                sender = c.SENDER
                recipients = update_recipients_list(self.__recipients, id_data)
                subject = c.HUMIDITY_SUBJECT
                content = c.HUMIDITY_CONTENT.format(humidity=value)
                logging.debug('Sending Humidity warning email to user')
                iot_email.send_email(sender, recipients, subject, content)

                # If address found in message, send message back
                if address[0] and address[1]:
                    # Update LED screen
                    logging.debug('Sending Pi a WARNING humidity message')
                    iot_sender.send_humidity_update(address, 'warning')

        else:
            # Reset warning
            if self.__humidity_warning or self.__humidity_first:
                self.__humidity_warning = False
                self.__humidity_first = False

                # If address found in message, send message back
                if address[0] and address[1]:
                    logging.debug('Sending Pi a SAFE humidity message')
                    iot_sender.send_humidity_update(address, 'safe')

    def co2_processing(self, value, address, id_data):
        """
        Further process CO2 data

        Parameters
        ----------
        value : int
        address : tuple
        id_data: int
        """
        if value > c.CO2_THRESHOLD:
            logging.info('High CO2 Concentration level! Alert user')

            # Send notification only upon first recognition of warning
            if not self.__co2_warning:
                self.__co2_warning = True

                # Send email to defined recipients
                sender = c.SENDER
                recipients = update_recipients_list(self.__recipients, id_data)
                subject = c.CO2_SUBJECT
                content = c.CO2_CONTENT.format(co2=value)
                logging.debug('Sending CO2 warning email to user')
                iot_email.send_email(sender, recipients, subject, content)

                # If address found in message, send message back
                if address[0] and address[1]:
                    # Update LED screen
                    logging.debug('Sending Pi a WARNING CO2 message')
                    iot_sender.send_co2_update(address, 'warning')
                    self.__co2_warning = True

        else:
            # Reset warning
            if self.__co2_warning or self.__co2_first:
                self.__co2_warning = False
                self.__co2_first = False

                # If address found in message, send message back
                if address[0] and address[1]:
                    logging.debug('Sending Pi a SAFE CO2 message')
                    iot_sender.send_co2_update(address, 'safe')

def update_recipients_list(recipient_list, id_data):
    """
    Check whether this device has a notification email address associated
    If so, append to the list of.

    :param recipient_list: current list of recipients
    :param id_data: 48-bit mac address
    :return: updated recipients list
    """

    email_sock = s.socket(s.AF_INET, s.SOCK_DGRAM)
    email_sock.settimeout(1)
    request = json.dumps({'type': 'retrieve', 'device_id': id_data})
    email_address = ""
    try:
        email_sock.sendto(bytes(request, 'utf-8'), (c.DEVICE_MANAGER, 3210))
        reply, _ = email_sock.recvfrom(1024)
        reply_json = json.loads(reply)
        email_address = reply_json['email']
    except (s.gaierror, s.timeout) as e:
        logging.error(f'Failed to retrieve email for {id_data}. E:{str(e)}')
    finally:
        email_sock.close()

    if len(email_address) != 0:
        if not recipient_list:
            recipient_list = [email_address]
        else:
            recipient_list.append(email_address)

    return recipient_list

def parse_args():
    """
    Parses arguments for the receiver on the destination note
    """
    parser = argparse.ArgumentParser(
        description='Run the destination IoT program (CTRL-C to exit)')

    parser.add_argument('-v',
                        '--verbose',
                        default=False,
                        action='store_true',
                        help='Print all debug logs')

    parser.add_argument('-p',
                        '--port',
                        metavar='<port number>',
                        default=7777,
                        type=int,
                        help='Default: 7777')

    parser.add_argument('-a',
                        '--address',
                        metavar='<email_address>',
                        nargs='*',
                        help='Email address(es) to receive notifications')

    args = parser.parse_args()
    return args

if __name__ == '__main__':
    args = parse_args()
    logging_level = logging.DEBUG if args.verbose else logging.INFO
    logging.basicConfig(format=c.LOGGING_FORMAT, level=logging_level)
    console = logging.StreamHandler()
    console.setLevel(logging_level)
    logging.getLogger("").addHandler(console)

    recipients = []
    if args.address:
        recipients = args.address

    poller = DataPoller(args.port, args.address)
    poller.poll_data()


#!/usr/bin/env python2
import wx

from util import AddLinearSpacer

class LabeledWidget(wx.Panel):
    def __init__(self, parent, cls, label, space=5, *args, **kwargs):
        wx.Panel.__init__(self, parent)

        self.widget = cls(parent=self, *args, **kwargs)
        self.label = wx.StaticText(parent=self, label=label)

        self.sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.sizer.Add(self.label, flag=wx.CENTER)
        AddLinearSpacer(self.sizer, space)
        self.sizer.Add(self.widget, flag=wx.CENTER)

        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)

    def GetControls(self):
        return self.label, self.widget

    def SetLabel(self, value):
        self.label.SetLabel(value)

    def Enable(self, value=True):
        self.label.Enable(value)
        self.widget.Enable(value)

    def Disable(self):
        self.Enable(False)

class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        lw = LabeledWidget(self, wx.Choice, 'Choose', choices=['1', '2', '3'], space=5)
        lw.widget.SetSelection(1)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.AddStretchSpacer(prop=1)
        sizer.Add(lw, flag=wx.CENTER)
        sizer.AddStretchSpacer(prop=1)
        self.SetSizer(sizer)
        self.Show()

if __name__ == "__main__":
    app = wx.App()
    frame = MainWindow(parent=None, title='SuperConverter')
    app.MainLoop()


"""
Module to create datasets from the raw JHU data for interactive plotting
of the coronavirus-related data. These datasets will then flow into the
dashboard(s) create to illustrate the data in a better manner than simple
plots in my Jupyter notebooks.
"""
import os

import pandas as pd
from pycountry_convert import country_alpha2_to_continent_code, country_name_to_country_alpha2

from src.corona_analysis.corona_transformations import CoronaTransformations

class CoronaAnalysis:
    """
    Class containing methods to create datasets that allow for
    dashboarding of data pertaining to the Corona-virus pandemic of 2020.
    """

    def __init__(self, data_type: str, case_type: str, processing_date: str):
        self.data_type = data_type
        self.case_type = case_type
        self.processing_date = processing_date

    def load_raw_data(self) -> pd.DataFrame:
        """
        Method to load the raw data into a dataframe.
        or the USA-specific data.
        :return: A Pandas DataFrame containing the raw data.
        """

        if self.data_type == 'world' and self.case_type == 'case':
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_confirmed_global.csv')
        elif self.data_type == 'world' and self.case_type == 'death':
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_deaths_global.csv')
        elif self.data_type == 'usa' and self.case_type == 'death':
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_deaths_US.csv')
        elif self.data_type == 'usa' and self.case_type == 'case':
            raw_data = pd.read_csv(
                '~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                'csse_covid_19_time_series/time_series_covid19_confirmed_US.csv')
        elif self.data_type == 'aus' and self.case_type == 'case':
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_confirmed_global.csv')
            raw_data = raw_data.loc[raw_data['Country/Region'] == 'Australia']
        elif self.data_type == 'aus' and self.case_type == 'death':
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_deaths_global.csv')
            raw_data = raw_data.loc[raw_data['Country/Region'] == 'Australia']

        # Default to worldwide cases.
        else:
            raw_data = pd.read_csv(
                f'~/PyCharmProjects/covid-19-analysis/data/COVID-19/csse_covid_19_data/'
                f'csse_covid_19_time_series/time_series_covid19_confirmed_global.csv')

        raw_data_filtered = pd.DataFrame

        if self.data_type == 'world':
            useful_cols = [col for col in raw_data.columns if col not in ['Lat', 'Long']]
            raw_data_filtered = raw_data[useful_cols]
            countries_to_drop = ['Holy See', 'Congo (Brazzaville)', 'Congo (Kinshasa)',
                                 'Diamond Princess',
                                 'MS Zaandam', 'Kosovo', 'Timor-Leste', 'West Bank and Gaza',
                                 'Western Sahara']
            raw_data_filtered = raw_data_filtered.loc[
                ~raw_data_filtered['Country/Region'].isin(countries_to_drop)]

        elif self.data_type == 'usa' and self.case_type == 'case':
            useful_cols = [col for col in raw_data.columns if
                           col not in ['UID', 'iso2', 'iso3', 'code3',
                                       'FIPS', 'Admin2', 'Country_Region',
                                       'Long_', 'Lat', 'Combined_Key']]
            raw_data_filtered = raw_data[useful_cols]

        elif self.data_type == 'usa' and self.case_type == 'death':
            useful_cols = [col for col in raw_data.columns if
                           col not in ['UID', 'iso2', 'iso3', 'code3',
                                       'FIPS', 'Admin2', 'Country_Region',
                                       'Long_', 'Lat', 'Combined_Key', 'Population']]
            raw_data_filtered = raw_data[useful_cols]

        elif self.data_type == 'aus' and self.case_type == 'case':
            useful_cols = [col for col in raw_data.columns if
                           col not in ['Lat', 'Long', 'Country/Region']]
            raw_data_filtered = raw_data[useful_cols]

        elif self.data_type == 'aus' and self.case_type == 'death':
            useful_cols = [col for col in raw_data.columns if
                           col not in ['Lat', 'Long', 'Country/Region']]
            raw_data_filtered = raw_data[useful_cols]

        return raw_data_filtered

    @staticmethod
    def melt_df(raw_data: pd.DataFrame, id_vars: list) -> pd.DataFrame:
        """
        Method to turn a raw dataframe (with state, country, date_1, ..., date_n)
        into a dataframe with a date column and the total number of cases/deaths/etc.
        :param raw_data: The Pandas DataFrame to melt.
        :param id_vars: The columns to group by to create melted DataFrame with.
        :return: A pivoted Pandas DataFrame with a date column.
        """
        world_data = (raw_data
                      .melt(id_vars=id_vars,
                            var_name="Date",
                            value_name='total_cases')
                      )
        world_data.Date = pd.to_datetime(world_data.Date)
        return world_data

    def run_corona_analysis(self):
        """
        Main function to run analyses of World, USA, and Australian corona virus data from
        the JHU database
        """
        cts = CoronaTransformations(case_type=self.case_type, data_type=self.data_type)

        raw_data = self.load_raw_data()

        if self.data_type == 'world':
            groupby_list = ['Country/Region']
            groupby_list_cases_per_day = ['Date', 'Country/Region']
            id_vars = ["Province/State", 'Country/Region']
        elif self.data_type == 'usa' and self.case_type == 'death':
            groupby_list_cases_per_day = ['Date', 'Province_State']
            groupby_list = ['Province_State']
            id_vars = ["Province_State"]
        elif self.data_type == 'aus' and self.case_type == 'case':
            groupby_list_cases_per_day = ['Date', 'Province/State']
            groupby_list = ['Province/State']
            id_vars = ["Province/State"]
        elif self.data_type == 'aus' and self.case_type == 'death':
            groupby_list_cases_per_day = ['Date', 'Province/State']
            groupby_list = ['Province/State']
            id_vars = ["Province/State"]
        else:
            groupby_list_cases_per_day = ['Date', 'Province_State']
            groupby_list = ['Province_State']
            id_vars = ["Province_State"]

        melted_data = self.melt_df(raw_data=raw_data, id_vars=id_vars)

        print(f"Creating total number of {self.case_type}s for data type {self.data_type}...")
        total_cases_df = cts.create_cases_per_day(df_to_transform=melted_data,
                                                  groupby_list=groupby_list_cases_per_day)
        print("done.")

        print(f"Creating number of {self.case_type}s per period")
        period = 1
        cases_per_period = cts.create_cases_per_period(df_to_transform=total_cases_df,
                                                       groupby_list=groupby_list,
                                                       case_column='Total cases')
        print("done.")

        print(f"Creating rolling average of {self.case_type}s per period")
        rolling_avg = cts.create_rolling_average(df_to_transform=cases_per_period,
                                                 groupby_list=groupby_list,
                                                 period=7)
        print("done.")

        print(f"Creating power-law data products for {self.case_type}s")
        power_law = cts.calculate_power_law_columns(df_to_transform=rolling_avg,
                                                    cols_to_logify=['Total cases',
                                                                    'New cases per week'])

        pl_slope = cts.calculate_power_law_slope(df_to_transform=power_law,
                                                 period=10,
                                                 groupby_list=groupby_list,
                                                 rise='log10(New cases per week)',
                                                 run='log10(Total cases)')

        pl_acceleration = cts.calculate_power_law_acceleration(df_to_transform=pl_slope,
                                                               period=period,
                                                               groupby_list=groupby_list,
                                                               rise=f'slope of log10'
                                                                    f'(New cases per week)',
                                                               run='log10(Total cases)')
        pl_acceleration = \
            (pl_acceleration
             .rename(columns={"slope of slope of log10(New cases per week)":
                              "Acceleration of power-law",
                              "slope of log10(New cases per week)":
                                  "Slope of power-law"})
             )
        print("done.")
        print(f"Creating growth rates and doubling times for {self.case_type}s.")
        df_gr = cts.calculate_growth_rate(df_to_transform=pl_acceleration,
                                          period=5,
                                          groupby_list=groupby_list,
                                          new_cases='New cases',
                                          total_cases='Total cases')

        df_dbl_time = cts.calculate_doubling_time(df_to_transform=df_gr,
                                                  period=5,
                                                  groupby_list=groupby_list)
        print("done.")

        result_df_path = f'processed_data/'
        self.write_to_csv(df_to_write=df_dbl_time,
                          path_to_write_to=result_df_path,
                          file_name='result.csv')

        # Add continent to the data
        print(f"Adding continents to the {self.case_type}s data")
        df_dbl_time_with_cont = self.assign_continent_to_df(df_add_continent=df_dbl_time)
        print("done.")

        # Stack the DF to get data into format for the dashboard
        print(f"Preparing data for {self.case_type}s for dashboard")
        stacked = self.stack_data_for_dashboard(df_dbl_time_with_cont)

        stacked_df_path = f'stacked/'
        self.write_to_csv(df_to_write=stacked,
                          path_to_write_to=stacked_df_path,
                          file_name='result.csv')

        # pivot data for dashboard
        df_total_new_cases = stacked.loc[(stacked.indicator == 'Total cases') |
                                         (stacked.indicator == 'New cases') |
                                         (stacked.indicator == 'Growth Rate')]

        if self.data_type == 'world':
            pivoted = (df_total_new_cases
                       .set_index(['Date', 'Country/Region', 'Continent', 'Days'])
                       .pivot_table(values='value',
                                    index=['Date', 'Country/Region', 'Continent', 'Days'],
                                    columns='indicator',
                                    aggfunc='mean',
                                    fill_value=0)
                       .reset_index()
                       )
        elif self.data_type == 'aus' and self.case_type == 'case':
            pivoted = (df_total_new_cases
                       .set_index(['Date', 'Province/State', 'Days'])
                       .pivot_table(values='value',
                                    index=['Date', 'Province/State', 'Days'],
                                    columns='indicator',
                                    aggfunc='mean',
                                    fill_value=0)
                       .reset_index()
                       )
        elif self.data_type == 'aus' and self.case_type == 'death':
            pivoted = (df_total_new_cases
                       .set_index(['Date', 'Province/State', 'Days'])
                       .pivot_table(values='value',
                                    index=['Date', 'Province/State', 'Days'],
                                    columns='indicator',
                                    aggfunc='mean',
                                    fill_value=0)
                       .reset_index()
                       )
        else:
            pivoted = (df_total_new_cases
                       .set_index(['Date', 'Province_State', 'Days'])
                       .pivot_table(values='value',
                                    index=['Date', 'Province_State', 'Days'],
                                    columns='indicator',
                                    aggfunc='mean',
                                    fill_value=0)
                       .reset_index()
                       )
        print("done.")
        pivoted_data_path = f'pivoted/'
        self.write_to_csv(df_to_write=pivoted,
                          path_to_write_to=pivoted_data_path,
                          file_name='result_pivoted.csv')

    def stack_data_for_dashboard(self, df_to_stack):
        """
        Method to arrange data in a dataframe in a way that allows for representation in the
        dashboard.
        :param df_to_stack:
        :return:
        """
        if self.data_type == 'world':
            # Need to fillna to 0s
            df_to_stack = df_to_stack.fillna(0)
            stacked = (df_to_stack
                       .set_index(['Date', 'Country/Region', 'Continent'])
                       .stack()
                       .reset_index()
                       )
            stacked = stacked.rename(columns={"level_3": "indicator", 0: "value"})

            stacked['Days'] = (stacked
                               .groupby(['Country/Region', 'Continent'])['Date']
                               .transform(lambda x: (x - x.min()).dt.days)
                               )

        elif self.data_type == 'aus' and self.case_type == 'case':
            # Need to fillna to 0s
            df_to_stack.drop(columns={'Continent'}, inplace=True)
            df_to_stack = df_to_stack.fillna(0)
            stacked = (df_to_stack
                       .set_index(['Date', 'Province/State'])
                       .stack()
                       .reset_index()
                       )
            stacked = stacked.rename(columns={"level_2": "indicator", 0: "value"})

            stacked['Days'] = (stacked
                               .groupby(['Province/State'])['Date']
                               .transform(lambda x: (x - x.min()).dt.days)
                               )
        elif self.data_type == 'aus' and self.case_type == 'death':
            # Need to fillna to 0s
            df_to_stack.drop(columns={'Continent'}, inplace=True)
            df_to_stack = df_to_stack.fillna(0)
            stacked = (df_to_stack
                       .set_index(['Date', 'Province/State'])
                       .stack()
                       .reset_index()
                       )
            stacked = stacked.rename(columns={"level_2": "indicator", 0: "value"})

            stacked['Days'] = (stacked
                               .groupby(['Province/State'])['Date']
                               .transform(lambda x: (x - x.min()).dt.days)
                               )
        else:
            # Need to fillna to 0s
            df_to_stack.drop(columns={'Continent'}, inplace=True)
            df_to_stack = df_to_stack.fillna(0)
            stacked = (df_to_stack
                       .set_index(['Date', 'Province_State'])
                       .stack()
                       .reset_index()
                       )
            stacked = stacked.rename(columns={"level_2": "indicator", 0: "value"})

            stacked['Days'] = (stacked
                               .groupby(['Province_State'])['Date']
                               .transform(lambda x: (x - x.min()).dt.days)
                               )
        return stacked

    def assign_continent_to_df(self, df_add_continent: pd.DataFrame) -> pd.DataFrame:
        """
        Method to assign a continent to a country in the given dataframe. Some
        alterations have had to be made due to the absence of them in the ISO codes.
        This in NO way is a political statement from the author.
        :param df_add_continent:
        :return:
        """

        if self.data_type == 'world':
            df_add_continent.loc[
                df_add_continent['Country/Region'] == 'US', 'Country/Region'] = 'USA'
            df_add_continent.loc[
                df_add_continent['Country/Region'] == 'Burma', 'Country/Region'] = 'Myanmar'
            df_add_continent.loc[
                df_add_continent[
                    'Country/Region'] == 'Cote d\'Ivoire', 'Country/Region'] = 'Ivory Coast'
            df_add_continent.loc[df_add_continent[
                                     'Country/Region'] == 'Korea, South', 'Country/Region'] = \
                'South Korea'
            df_add_continent.loc[
                df_add_continent['Country/Region'] == 'Taiwan*', 'Country/Region'] = 'Taiwan'

            df_add_continent['Continent'] = (
                df_add_continent.apply(
                    lambda x: self.apply_continent_to_country(x['Country/Region']),
                    axis=1
                )
            )
        elif self.data_type == 'aus':
            df_add_continent['Continent'] = 'Australasia'
        else:
            df_add_continent['Continent'] = 'North America'

        return df_add_continent

    @staticmethod
    def apply_continent_to_country(country):
        continents = {
            'NA': 'North America',
            'SA': 'South America',
            'AS': 'Asia',
            'OC': 'Australasia',
            'AF': 'Africa',
            'EU': 'Europe'
        }
        return continents[country_alpha2_to_continent_code(country_name_to_country_alpha2(country))]

    def write_to_csv(self, df_to_write: pd.DataFrame, path_to_write_to: str, file_name: str):

        if self.data_type == 'world':
            if self.case_type == 'case':
                write_path_cases = f"../data/output/world/{self.processing_date}/" \
                                   f"cases/{path_to_write_to}"
                if os.path.exists(write_path_cases):
                    print(f'Writing data out to path: {write_path_cases}')
                    df_to_write.to_csv(write_path_cases + f'/{file_name}')
                else:
                    print(f"Making directory to write out to...")
                    os.makedirs(write_path_cases)
                    print(f'Writing data out to path: {write_path_cases}')
                    df_to_write.to_csv(write_path_cases + f'/{file_name}')

            if self.case_type == 'death':
                write_path_deaths = f"../data/output/world/{self.processing_date}/" \
                                    f"deaths/{path_to_write_to}"
                if os.path.exists(write_path_deaths):
                    print(f'Writing data out to path: {write_path_deaths}')
                    df_to_write.to_csv(write_path_deaths + f'/{file_name}')
                else:
                    print(f"Making directory to write out to...")
                    os.makedirs(write_path_deaths)
                    print(f'Writing data out to path: {write_path_deaths}')
                    df_to_write.to_csv(write_path_deaths + f'/{file_name}')
        elif self.data_type == 'aus' and self.case_type == 'case':
            write_path_cases = f"../data/output/aus/{self.processing_date}/cases/{path_to_write_to}"
            if os.path.exists(write_path_cases):
                print(f'Writing data out to path: {write_path_cases}')
                df_to_write.to_csv(write_path_cases + f'/{file_name}')
            else:
                print(f"Making directory to write out to...")
                os.makedirs(write_path_cases)
                print(f'Writing data out to path: {write_path_cases}')
                df_to_write.to_csv(write_path_cases + f'/{file_name}')
        elif self.data_type == 'aus' and self.case_type == 'death':
            write_path_cases = f"../data/output/aus/{self.processing_date}/" \
                               f"deaths/{path_to_write_to}"
            if os.path.exists(write_path_cases):
                print(f'Writing data out to path: {write_path_cases}')
                df_to_write.to_csv(write_path_cases + f'/{file_name}')
            else:
                print(f"Making directory to write out to...")
                os.makedirs(write_path_cases)
                print(f'Writing data out to path: {write_path_cases}')
                df_to_write.to_csv(write_path_cases + f'/{file_name}')
        else:
            if self.case_type == 'case':
                write_path_cases = f"../data/output/usa/{self.processing_date}/" \
                                   f"cases/{path_to_write_to}"
                if os.path.exists(write_path_cases):
                    print(f'Writing data out to path: {write_path_cases}')
                    df_to_write.to_csv(write_path_cases + f'/{file_name}')
                else:
                    print(f"Making directory to write out to...")
                    os.makedirs(write_path_cases)
                    print(f'Writing data out to path: {write_path_cases}')
                    df_to_write.to_csv(write_path_cases + f'/{file_name}')

            if self.case_type == 'death':
                write_path_deaths = f"../data/output/usa/{self.processing_date}/" \
                                    f"deaths/{path_to_write_to}"
                if os.path.exists(write_path_deaths):
                    print(f'Writing data out to path: {write_path_deaths}')
                    df_to_write.to_csv(write_path_deaths + f'/{file_name}')
                else:
                    print(f"Making directory to write out to...")
                    os.makedirs(write_path_deaths)
                    print(f'Writing data out to path: {write_path_deaths}')
                    df_to_write.to_csv(write_path_deaths + f'/{file_name}')

        print("done.")