# Attribute Detector

from __future__ import division, print_function

import util.io as io
from util.math import compute_mean_ap
import dataset as dset
import misc
import opt_parser as op

import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as F

import os
import numpy as np
from collections import OrderedDict
import time
import h5py

# Todo: add attribute format transformer
class AttributeFormatTransformer():
    def __init__(self, dataset = 'vqa1.9'):
        ans_list = io.load_json('data/dataset_supp/%s/answer_list.json'%dataset)
        self.ans2idx = {a['answer']:idx for idx, a in enumerate(ans_list[0:1000])}
        self.ans = [a['answer'] for a in ans_list[0:1000]]

        with h5py.File('data/dataset_supp/%s/ans_catagory.h5'%dataset) as h5_in:
            self.ans_word2vec = h5_in['ans_cat_glove'][:]
            self.ans_glove = h5_in['ans_cat_glove'][:]

        # add zero vector for attributes not in the list
        self.ans.append('UNKOWN')
        self.ans_word2vec = np.vstack((self.ans_word2vec, np.zeros((1,300), dtype = np.float32)))
        self.ans_glove = np.vstack((self.ans_glove, np.zeros((1,300), dtype = np.float32)))

    def transform(self, att_input, input_fmt, output_fmt):
        '''
        input_fmt: 'name' or 'label'
        output_fmt: 'name', 'label', 'word2vec' or 'glove'
        '''

        if input_fmt == 'label':
            # convert the att_input into 1-d list
            if isinstance(att_input, (torch.FloatTensor, torch.cuda.FloatTensor, torch.LongTensor, torch.cuda.LongTensor)):
                index = att_input.view(-1).cpu().numpy().tolist()
            elif isinstance(att_input, Variable):
                index = att_input.data.view(-1).cpu().numpy().tolist()
            elif isinstance(att_input, (list, np.ndarray)):
                index = att_input
            elif isinstance(att_input, (int, float)):
                index=  [att_input]
        elif input_fmt == 'name':
            # att_input: 1-d list of str
            index = [self.ans2idx.get(att, 1000) for att in att_input]
        else:
            raise Exception('Unsupported input attribute format "%s"'%input_fmt)

        if output_fmt == 'name':
            return [self.ans[idx] for idx in index]
        elif output_fmt == 'label':
            return index
        elif output_fmt == 'word2vec':
            return self.ans_word2vec[index]
        elif output_fmt == 'glove':
            return self.ans_glove[index]
        else:
            raise Exception('Unsupported output attribute format "%s"'%output_fmt)

class AttributeDetector(nn.Module):
    '''
    Attribute Detector Class
    '''

    def __init__(self, opts = None, fn = None, gpu_id = 0):
        '''
        "opts" is attribute detector options, see opt_parser.py
        '''

        assert (opts or fn), 'either "opts" or "fn" should be provided to create attribute detector'

        super(AttributeDetector, self).__init__()

        # set model options
        if fn:
            # load opts from file
            self.opts = torch.load(fn)['opts']
            self.opts.gpu_id = gpu_id
            opts = self.opts
        else:
            self.opts = opts

        # load attribute list
        att_list = io.load_json(opts.att_list_fn)
        assert len(att_list) >= opts.num_att
        self.att_list = [a['answer'] for a in att_list[0:opts.num_att]]

        # create MLP
        layers = OrderedDict()
        layers['mlp_fc1'] = nn.Linear(opts.feat_size, opts.num_att)
        for n in xrange(1, opts.num_mlp_layer):
            layers['mlp_relu%d'%n] = nn.ReLU()
            layers['mlp_drop%d'%n] = nn.Dropout(p = 0.2)
            layers['mlp_fc%d'%(n+1)] = nn.Linear(opts.num_att, opts.num_att)

        self.mlp = nn.Sequential(layers)

        # Todo: add cnn if needed

        if fn:
            model_info = torch.load(fn)
            state_dicts = model_info['state_dicts']
            self.load_state_dict(state_dicts)
        else:
            self._init_weight()

        self.cuda(opts.gpu_id)

        # format transformer
        self.transformer = AttributeFormatTransformer()

        # Todo: attribute similarity embedding

        self.qs_w = 1           # question similarity weight (alpha)
        self.qs_embed = None    # question similarity embedding (num_att * n, nparray)
        self.vs_w = 1           # visual similarity weight (beta)
        self.vs_embed = None    # visual similarity embedding (num_att * m, nparray)
        self.embed_norm = True  # normalize embedding
        self.A = None           # affinity matrix

    def _init_weight(self):
        '''
        initialize MLP weight
        '''

        init_range = 0.08

        for layer_name, layer in self.mlp._modules.iteritems():
            if layer_name.startswith('mlp_fc'):
                layer._parameters['weight'].data.uniform_(-init_range, init_range)
                layer._parameters['bias'].data.fill_(0)

    def learnable_parameters(self):
        '''
        return generator of learnable parameters
        '''
        return self.mlp.parameters()

    def save_model(self, fn):

        model_info = {\
            'opts': self.opts,\
            'state_dicts': self.state_dict()\
            }

        torch.save(model_info, fn)

    def forward(self, feat):
        '''
        forward process
            feat: image feature
        '''

        mlp_output = self.mlp(feat)
        output = F.sigmoid(mlp_output)
        return output

    def detect(self, feat, num = 1):
        '''
        input: image feature (single image)
        '''
        score = self.forward(feat)
        # score[540] = 0 # the answer 'a'
        att_score, att_index = score[0].sort(dim = 0, descending = True)
        return [(att_index.data[idx], att_score.data[idx]) for idx in xrange(num)], score

    def detect_pair(self, feat_pair, num = 1):
        assert feat_pair.size(0) == 2
        score = self.forward(feat_pair)

        score_np = score.data.cpu().numpy()
        score_1, score_2 = score_np[0:1,:], score_np[1:2,:]

        pair_score = np.dot((score_1 * (1 - score_2)).T, score_2 * (1 - score_1)) * self.A

        att_pair = []
        for idx in xrange(num):
            i, j = np.unravel_index(pair_score.argmax(), pair_score.shape)
            att_pair.append(([i, j], float(pair_score[i,j])))
            pair_score[i,:] = 0
            pair_score[j,:] = 0
            pair_score[:,i] = 0
            pair_score[:,j] = 0

        return att_pair, score

    def set_attribute_similarity(self, question_answerer_instance = None, alpha = None, beta = None, norm = None):
        '''
        set up attribute similairity for attribute pair selection
        '''

        # set question similarity embedding useing pretrained QA model
        if question_answerer_instance is not None:

            # use the last fc layer of the question channel in QA model as the question similarity embedding
            self.qs_embed = question_answerer_instance.ques_cls.weight.data.cpu().numpy()
            assert self.qs_embed.shape[0] == self.opts.num_att

        # set visual similarity
        if self.vs_embed is None:
            self.vs_embed = self.mlp._modules.values()[-1].weight.data.cpu().numpy()
            assert self.vs_embed.shape[0] == self.opts.num_att

        # set embedding hyper parameters
        if alpha is not None:
            self.qs_w = alpha
        if beta is not None:
            self.vs_w = beta
        if norm is not None:
            self.embed_norm = norm

        # compute affinity matrix
        qs_embed = self.qs_embed.copy()
        vs_embed = self.vs_embed.copy()

        # if self.embed_norm == 1:
        #     qs_embed /= np.linalg.norm(qs_embed, ord = 2, axis = 1, keepdims = True)
        #     vs_embed /= np.linalg.norm(vs_embed, ord = 2, axis = 1, keepdims = True)

        qs_embed /= np.linalg.norm(qs_embed, ord = 2, axis = 1, keepdims = True)
        vs_embed /= np.linalg.norm(vs_embed, ord = 2, axis = 1, keepdims = True)

        A = np.exp(self.qs_w * qs_embed.dot(qs_embed.T) - self.vs_w * vs_embed.dot(vs_embed.T))
        for i in xrange(self.opts.num_att):
            A[i,i] = 0

        if self.embed_norm == 2:
            # compute the square root of inf-norm of each raw in A
            a = np.sqrt(A.max(axis = 0).reshape(1,-1))
            A = A / a / a.T

        self.A = A

    def att_format_trans(self, att_input, input_fmt, output_fmt):
        '''
        attribute format transform. see AttributeFormatTransformer class
        '''
        return self.transformer.transform(att_input, input_fmt, output_fmt)

    def detect_att_pair(self, feat, num = 1):
        '''
        for new version question generator
        input:
            feat(bsz, 2, C)
        output:
            att_pair(bsz, num, 2)
        '''
        assert feat.size(1) == 2 # input should be image feature pair
        bsz = feat.size(0)

        # compute attribute probablity
        prob = self.forward(feat.view(bsz * 2, -1)).view(bsz, 2, -1)
        prob_np = prob.data.cpu().numpy()

        att_pair = np.zeros((bsz, num, 2), dtype = np.int)
        att_score = np.zeros((bsz, num), dtype = np.float32)

        for b_idx in xrange(bsz):
            prob_1, prob_2 = prob_np[b_idx, 0:1, :], prob_np[b_idx, 1:2, :]
            pair_score = np.dot((prob_1 * (1 - prob_2)).T, prob_2 * (1 - prob_1)) * self.A

            for a_idx in xrange(num):
                i, j = np.unravel_index(pair_score.argmax(), pair_score.shape)
                att_pair[b_idx, a_idx] = i, j
                att_score[b_idx, a_idx] = pair_score[i, j]
                pair_score[i, :] = 0
                pair_score[j, :] = 0
                pair_score[:, i] = 0
                pair_score[:, j] = 0

        return att_pair, att_score

    def detect_att_score(self, feat):

        score = self.forward(feat.view(bsz * 2, -1)).view(bsz, 2, -1)
        return score

def train_model(model, opts):

    print('Training Attribute Detector...')

    model_opts = model.opts
    gpu_id = model_opts.gpu_id

    if not opts.id.startswith('att_'):
        opts.id = 'att_' + opts.id

    # create data loader
    feat_type = model_opts.cnn_type + '_' + model_opts.cnn_feat_layer

    if opts.dataset == 'vqa1.0' or opts.dataset == 'vqa1.9':
        train_dset = dset.Dataset_VQA_Attribute(subset = 'train', version = opts.dataset[-3::], feat_type = feat_type)
        test_dset = dset.Dataset_VQA_Attribute(subset = 'val', version = opts.dataset[-3::], feat_type = feat_type)
    else:
        raise Exception('invalid dataset for training: "%s"'%opts.dataset)

    if opts.debug:
        train_dset.len = opts.batch_size * 10
        # test_dset.len = opts.batch_size
        test_dset = train_dset

    train_loader = torch.utils.data.DataLoader(train_dset, batch_size = opts.batch_size, shuffle = True, num_workers = 2)
    test_loader = torch.utils.data.DataLoader(test_dset, batch_size = opts.batch_size, num_workers = 2)

    # create optimizer
    if opts.optim == 'sgd':
        optimizer = torch.optim.SGD(model.learnable_parameters(), lr = opts.lr, weight_decay = opts.weight_decay, momentum = opts.momentum)
    elif opts.optim == 'adam':
        optimizer = torch.optim.Adam(model.learnable_parameters(), lr = opts.lr, betas = (opts.optim_alpha, opts.optim_beta), eps = opts.optim_epsilon, weight_decay = opts.weight_decay)

    # create loss function
    # crit = nn.NLLLoss(weight = opts.loss_weight)
    crit = nn.BCELoss()

    # def snapshot information and function
    info = {'opts': vars(model_opts),\
            'train_opts': vars(opts),\
            'loss_history': [],\
            'test_loss_history': [],\
            'lr_history': []\
            }

    log_buffer = []

    # remove previous log file
    output_dir = os.path.join('models', opts.id)
    io.mkdir_if_missing(output_dir)
    log_fn = os.path.join('models', opts.id, 'log.txt')
    info_fn = os.path.join(output_dir, 'info.json')

    if os.path.isfile(log_fn):
        os.remove(log_fn)

    def _snapshot(epoch):

        print('saving... checkpoint to %s\t' % output_dir)

        # save model weights
        weight_fn = os.path.join(output_dir, '%s.pth'%epoch)
        model.save_model(weight_fn)

        # save info
        io.save_json(info, info_fn)

        # save log
        io.save_str_list(log_buffer, log_fn, mode = 'a')
        log_buffer[:] = []

    # create loss buffer
    loss_buffer = misc.Loss_Buffer(opts.average_loss)

    # main training loop
    epoch = 0

    while epoch != opts.max_epochs:

        # set model mode
        model.train()

        # update lr
        lr = opts.lr * (opts.lr_decay_rate ** (epoch//opts.lr_decay))
        for group in optimizer.param_groups:
            group['lr'] = lr

        # train one eopch
        for batch_idx, (data, label) in enumerate(train_loader):

            optimizer.zero_grad()

            # forward and backward
            data, label = Variable(data.cuda(gpu_id)), Variable(label.cuda(gpu_id))
            output = model(data)
            loss = crit(output, label) * opts.loss_weight

            loss.backward()

            # optimize
            optimizer.step()

            # display loss
            loss_smt = loss_buffer(loss.data[0])

            if batch_idx % opts.display_interval == 0:

                log = '[%s] [%s] Train Epoch %d [%d/%d  (%.2f%%)]  LR: %.3e  Loss: %.6f' % (time.ctime(), opts.id,\
                        epoch, batch_idx * train_loader.batch_size, len(train_loader.dataset),\
                        100.* batch_idx / len(train_loader), lr, loss_smt)
                print(log)
                log_buffer.append(log)

                batch_idx_glb = batch_idx + epoch * len(train_loader) # global batch_idx
                info['loss_history'].append({'iteration': batch_idx_glb,'epoch': epoch, 'loss': loss_smt})
                info['lr_history'].append({'iteration': batch_idx_glb, 'epoch': epoch, 'lr': lr})

        # update epoch index
        epoch += 1

        # test
        if opts.test_interval > 0 and epoch % opts.test_interval == 0:

            # set model mode
            model.eval()

            # set test iteration
            test_iter = opts.test_iter if opts.test_iter > 0 else len(test_loader)

            # cross-batch variables
            num_test_sample = len(test_loader.dataset)

            all_score = [] # save all predictions
            all_label = [] # save all ground truth labels
            test_loss = 0

            # test
            for batch_idx, (data, label) in enumerate(test_loader):

                # forward
                data, label = Variable(data.cuda(gpu_id)), Variable(label.cuda(gpu_id))
                output = model(data)
                loss = crit(output, label) * opts.loss_weight

                all_score.append(output.data.exp().cpu().numpy())
                all_label.append(label.data.cpu().numpy())
                test_loss += loss.data[0]

                print('\rTesting %d/%d (%.2f%%)' % (batch_idx, test_iter, 100.*batch_idx/test_iter), end = '')
                if (batch_idx + 1) == test_iter:
                    break

            # compute average loss and MAP
            test_loss /= test_iter

            all_score = np.vstack(all_score)
            all_label = np.vstack(all_label)
            mean_ap, ap = compute_mean_ap(all_score, all_label)

            # display test information
            log = '[%s] [%s] Test Epoch: %d  Loss: %.6f  meanAP: %.2f%%' % (time.ctime(), opts.id, epoch, test_loss, mean_ap * 100)
            print('\n' + log)

            log_buffer.append(log)
            ap = zip(model.att_list, ap)
            info['test_loss_history'].append({\
                'iteration': epoch*len(train_loader), \
                'epoch': epoch, \
                'loss': test_loss, \
                'mean_ap': mean_ap, \
                'ap': ap\
                })

        # snapshot
        if opts.snapshot_interval > 0 and epoch % opts.snapshot_interval == 0:
            _snapshot(epoch)

    # final snapshot
    _snapshot(epoch = 'final')

if __name__ == '__main__':

    command = op.parse_command().command

    if command == 'train':
        model_opts = op.parse_attribute_opt()
        train_opts = op.parse_train_opt()

        model = AttributeDetector(opts = model_opts)
        train_model(model, train_opts)
    else:
        raise Exception('invalid command "%s" for attribute_detector'%command)


# -*- coding: utf-8 -*-
from django.dispatch.dispatcher import receiver
from django.db.models.signals import post_save
from sevenapps.manage.actions.models import Log
from sevenapps.manage.traffic.models import PromotionPreference

PROMOTION_CHANGED_LOG_MSG = \
    'Changed promotion settings. ' \
    'New values: ' \
    'Self Promotion {} positions, Cross Promotion {} positions'

@receiver(post_save, sender=PromotionPreference)
def log_promotion_changed(sender, **kwargs):
    instance = kwargs['instance']
    is_created = kwargs['created']
    owner_id = instance.owner_id
    if not is_created:
        Log.objects.create(
            owner_id=owner_id,
            message=PROMOTION_CHANGED_LOG_MSG.format(instance.self_promotion,
                                                     instance.cross_promotion))


import os
from decouple import config

BASE_DIR = os.path.dirname(os.path.dirname(
    os.path.dirname(os.path.abspath(__file__))))

SECRET_KEY = config('SECRET_KEY')

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'django_filters',
    'widget_tweaks',
    'crispy_forms',
    'django_countries',

    'core',
    'users'
]

CRISPY_TEMPLATE_PACK = 'bootstrap4'

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'demo.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'demo.wsgi.application'

LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = False

# where employees actually works
OFFSHORE = 'CZ'

AUTH_USER_MODEL = 'users.User'

# Static files (CSS, JavaScript, Images)

STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static_in_env')]
VENV_PATH = os.path.dirname(BASE_DIR)
STATIC_ROOT = os.path.join(VENV_PATH, 'static_root')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(VENV_PATH, 'media')

SAML2_AUTH = {
    # Metadata is required, choose either remote url or local file path
    'METADATA_AUTO_CONF_URL': 'https://login.teradata.com/adfs/ls/'
}

LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'


# untility functions
from random import shuffle
from datetime import datetime, date
import itertools
import secrets

def get_2days_timeslots():
    two_days_timeslots = {
        "1": "Day 1 (Saturday Feb 27th, 2021) 10am - 2pm",
        "2": "Day 1 (Saturday Feb 27th, 2021) 2pm - 6pm",
        "3": "Day 2 (Saturday Mar 6th, 2021) 10am - 3pm",
        "4": "Day 2 (Saturday Mar 6th, 2021) 3pm - 7pm"
    }
    return two_days_timeslots

def count_by_status(team_counts, team_status):
    count_result = 0
    for item in team_counts:
        if item[1].lower() == team_status.lower():
            count_result = item[0]
            break
    return count_result

def check_ranking2(team_score_list):
    i = 0
    refined_team_scores = list()
    min_score = min(team_score_list)
    max_score = max(team_score_list)
    if max_score == min_score:
        # all two are tier, all are ranking #1
        for i in range(2):
            tmp_dict = {
                "original_team_sequence": i + 1,
                "ranking": 1,
                "fw": 1,
                "score": max_score
            }
            refined_team_scores.append(tmp_dict)
    else:
        rank_1_index = team_score_list.index(max_score)
        rank_2_index = team_score_list.index(min_score)
        # add rank #1
        tmp_dict = {
            "original_team_sequence": rank_1_index + 1,
            "ranking": 1,
            "fw": 1,
            "score": max_score
        }
        refined_team_scores.append(tmp_dict)

        # add rank #2
        tmp_dict = {
            "original_team_sequence": rank_2_index + 1,
            "ranking": 2,
            "fw": 0,
            "score": min_score
        }
        refined_team_scores.append(tmp_dict)
    return refined_team_scores

def check_ranking3(team_score_list):
    i = 0
    refined_team_scores = list()
    min_score = min(team_score_list)
    mid_score = 0
    max_score = max(team_score_list)
    if max_score == min_score:
        # all three are tier, all are ranking #1
        for i in range(3):
            tmp_dict = {
                "original_team_sequence": i + 1,
                "ranking": 1,
                "fw": 1,
                "score": max_score
            }
            refined_team_scores.append(tmp_dict)
    else:
        rank_1_index = team_score_list.index(max_score)
        rank_3_index = team_score_list.index(min_score)
        rank_2_index = 0
        for i in range(3):
            if (i+1) != rank_1_index and (i+1) != rank_3_index:
                rank_2_index = i+1
                mid_score = team_score_list[rank_2_index]
                break
        # add rank #1
        tmp_dict = {
            "original_team_sequence": rank_1_index + 1,
            "ranking": 1,
            "fw": 1,
            "score": max_score
        }
        refined_team_scores.append(tmp_dict)

        if mid_score == min_score:
            for i in range(2):
                tmp_dict = {
                    "original_team_sequence": i + 2,
                    "ranking": 2,
                    "fw": 0,
                    "score": mid_score
                }
                refined_team_scores.append(tmp_dict)
        elif mid_score == max_score:
            # add rank #2 -> should be tie with rank #1
            tmp_dict = {
                "original_team_sequence": rank_2_index + 1,
                "ranking": 1,
                "fw": 1,
                "score": mid_score
            }
            refined_team_scores.append(tmp_dict)

            # add rank #3
            tmp_dict = {
                "original_team_sequence": rank_3_index + 1,
                "ranking": 3,
                "fw": 0,
                "score": min_score
            }
            refined_team_scores.append(tmp_dict)
        else:
            # add rank #2
            tmp_dict = {
                "original_team_sequence": rank_2_index + 1,
                "ranking": 2,
                "fw": 0,
                "score": mid_score
            }
            refined_team_scores.append(tmp_dict)

            # add rank #3
            tmp_dict = {
                "original_team_sequence": rank_3_index + 1,
                "ranking": 3,
                "fw": 0,
                "score": min_score
            }
            refined_team_scores.append(tmp_dict)
    return refined_team_scores

def get_sm_performance_order():
    sm_steps = [
        {
            "step_number": 1,
            "step_text": "The Opponent challenges the Reporter for the problem",
            "step_mins": 1,
            "reporter": "off",
            "opponent": "on"
        },
        {
            "step_number": 2,
            "step_text": "The Reporter accepts or rejects the challenge",
            "step_mins": 1,
            "reporter": "on",
            "opponent": "off"
        },
        {
            "step_number": 3,
            "step_text": "Preparation of the Reporter",
            "step_mins": 5,
            "reporter": "on",
            "opponent": "off"
        },
        {
            "step_number": 4,
            "step_text": "Presentation of the Reporter",
            "step_mins": 12,
            "reporter": "on",
            "opponent": "off"
        },
        {
            "step_number": 5,
            "step_text": "Questions of the Opponent to the Reporter and answers of the Reporter",
            "step_mins": 2,
            "reporter": "on",
            "opponent": "on"
        },
        {
            "step_number": 6,
            "step_text": "Preparation of the Opponent",
            "step_mins": 3,
            "reporter": "off",
            "opponent": "on"
        },
        {
            "step_number": 7,
            "step_text": "The Opponent takes the floor (maximum 4 min)",
            "step_mins": 4,
            "reporter": "off",
            "opponent": "on"
        },
        {
            "step_number": 8,
            "step_text": "Discussion between the Reporter and the Opponent",
            "step_mins": 14,
            "reporter": "on",
            "opponent": "on"
        },
        {
            "step_number": 9,
            "step_text": "The Opponent summarizes the discussion",
            "step_mins": 1,
            "reporter": "off",
            "opponent": "on"
        },
        {
            "step_number": 10,
            "step_text": "Concluding remarks of the Reporter",
            "step_mins": 2,
            "reporter": "on",
            "opponent": "off"
        },
        {
            "step_number": 11,
            "step_text": "Questions of the Jury",
            "step_mins": 5,
            "reporter": "on",
            "opponent": "on"
        }
    ]
    return sm_steps

def get_fm_performance_order():
    fm_steps = [
        {
            "step_number": 1,
            "step_text": "The Opponent challenges the Reporter for the problem",
            "step_mins": 1,
            "reporter": "off",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 2,
            "step_text": "The Reporter accepts or rejects the challenge",
            "step_mins": 1,
            "reporter": "on",
            "opponent": "off",
            "reviewer": "off"
        },
        {
            "step_number": 3,
            "step_text": "Preparation of the Reporter",
            "step_mins": 5,
            "reporter": "on",
            "opponent": "off",
            "reviewer": "off"
        },
        {
            "step_number": 4,
            "step_text": "Presentation of the Reporter",
            "step_mins": 12,
            "reporter": "on",
            "opponent": "off",
            "reviewer": "off"
        },
        {
            "step_number": 5,
            "step_text": "Questions of the Opponent to the Reporter and answers of the Reporter",
            "step_mins": 2,
            "reporter": "on",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 6,
            "step_text": "Preparation of the Opponent",
            "step_mins": 3,
            "reporter": "off",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 7,
            "step_text": "The Opponent takes the floor (maximum 4 min)",
            "step_mins": 4,
            "reporter": "off",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 8,
            "step_text": "Discussion between the Reporter and the Opponent",
            "step_mins": 14,
            "reporter": "on",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 9,
            "step_text": "The Opponent summarizes the discussion",
            "step_mins": 1,
            "reporter": "off",
            "opponent": "on",
            "reviewer": "off"
        },
        {
            "step_number": 10,
            "step_text": "Concluding remarks of the Reporter",
            "step_mins": 2,
            "reporter": "on",
            "opponent": "off",
            "reviewer": "off"
        },
        {
            "step_number": 11,
            "step_text": "Questions of the Reviewer to the Reporter and the Opponent and answers to the questions",
            "step_mins": 3,
            "reporter": "on",
            "opponent": "on",
            "reviewer": "on"
        },
        {
            "step_number": 12,
            "step_text": "Preparation of the Reviewer",
            "step_mins": 2,
            "reporter": "off",
            "opponent": "off",
            "reviewer": "on"
        },
        {
            "step_number": 13,
            "step_text": "The Reviewer takes the floor",
            "step_mins": 4,
            "reporter": "off",
            "opponent": "off",
            "reviewer": "on"
        },
        {
            "step_number": 14,
            "step_text": "Concluding remarks of the Reporter",
            "step_mins": 2,
            "reporter": "on",
            "opponent": "off",
            "reviewer": "off"
        },
        {
            "step_number": 15,
            "step_text": "Questions of the Jury",
            "step_mins": 5,
            "reporter": "on",
            "opponent": "on",
            "reviewer": "on"
        }
    ]
    return fm_steps

def convert_background_text(background_code):
    background_choices = [('10', 'Middle School'), ('12', 'High School'), ('20', 'Under Graduate or above')]
    background_text = None
    for item in background_choices:
        if item[0] == background_code:
            background_text = item[1]
            break
    return background_text

def calculate_age_from_datetime(dob_datetime):
    days_in_year = 365.25
    today = datetime.now()
    age = float((today - dob_datetime).days / days_in_year)
    return age

def calculate_age_from_string(dob_string):
    days_in_year = 365.25
    today = date.today()
    dob_date = datetime.strptime(dob_string, "%Y-%m-%d").date()
    age = float((today - dob_date).days / days_in_year)
    return age

"""
# this function is to randomly matching two teams for each round
# the rules are:
# - each team should not meet the same team more than once
# - each pair has two teams (this doesn't work for the final round)
# - each team has assigned team code from 1 to N (N=total number of teams)
# - after this team matching function, the system will assign real team to each team code randomly in another function
# the return type of this function is a list of team_matches
# each element in the team_matches is a dictionary initiated as following:
        {
            "team_code": index + 1,
            "opponents": [], # paired team for each round
            "team_name": 'unassigned',
            "team_id": 0,
            "school_id": 0,
            "school_name": '',
            "room_codes": [] # assigned room code for each round
        }
"""
def team_matching_2(team_count, round_count):
    # fixed number of teams in each pair
    num_in_each_pair = 2  # set the number of teams in each pair
    # initial team list
    team_list_original = []
    for i in range(team_count):
        team_list_original.append(i + 1)

    # initial team matches for each round
    team_matches = []
    for i in range(team_count):
        tmp_dict = {
            "team_code": i + 1,
            "opponents": [],
            "team_name": 'unassigned',
            "team_id": 0,
            "school_id": 0,
            "school_name": '',
            "room_codes": []
        }
        team_matches.append(tmp_dict)

    # start loop for all rounds
    warning_max_try = 3000 * team_count * round_count
    warning_try = 0
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    accepted_pairs = []
    accepted_pairs_by_round = []
    for round in range(round_count):
        warning_try += 1
        if warning_try > warning_max_try:
            return None
        # initialize at the beginning of each round
        tmp_selected_pairs = []
        # initial team list for current round
        tmp_team_list = []
        for i in range(team_count):
            tmp_team_list.append(i + 1)
        # loop at current round
        while tmp_team_list is not None and len(tmp_team_list) > 0:
            warning_try += 1
            if warning_try > warning_max_try:
                return None
            # randomly select one pair
            tmp_random_pair = secure_random.sample(tmp_team_list, num_in_each_pair)
            tmp_random_pair_accepted = True
            # get all the possible combinations of the tmp_random_pair
            tmp_iter_result = itertools.permutations(tmp_random_pair)
            for each in tmp_iter_result:
                warning_try += 1
                if warning_try > warning_max_try:
                    return None
                if each in accepted_pairs or each in tmp_selected_pairs:
                    tmp_random_pair_accepted = False
                    break
                else:
                    tmp_selected_pairs.append(each)

            if tmp_random_pair_accepted:
                for item in tmp_random_pair:
                    # remove form tmp team list of the current round
                    tmp_team_list.remove(item)
            else:
                # initial team list
                tmp_team_list = []
                for i in range(team_count):
                    tmp_team_list.append(i + 1)
                tmp_selected_pairs = []

        # add current round selected pairs to accepted pairs
        accepted_pairs_by_round.append(tmp_selected_pairs)
        for pair in tmp_selected_pairs:
            accepted_pairs.append(pair)

        # add current round selected to team matches
        for pair in tmp_selected_pairs:
            team_matches[pair[0] - 1]["opponents"].append(pair[1])

    return team_matches

"""
# this function is to assign the team code to the real team randomly
# rules:
# - get all the teams for the event
# - if the total team number is bigger than the real total, then use vacancy to replace the remaining
# the return type of this function is a list of teams
# Each team in the list is initiated as following:
# tmp_tuple = (team[2], team[3], team[5], team[6]) # team_id, team_name, team_school_id, team_school_name
"""
def assign_team_code_randomly(current_event_teams, teams_total_number):
    # random generate team sequence
    current_event_teams_name = list()
    for team in current_event_teams:
        # team_name = team[3]
        tmp_tuple = (team[2], team[3], team[5], team[6])
        current_event_teams_name.append(tmp_tuple)
    current_event_teams_number = len(current_event_teams_name)
    if current_event_teams_number < teams_total_number:
        i = current_event_teams_number
        while i < teams_total_number:
            i = i+1
            tmp_tuple_vacancy = (0, "Vacancy", 0, "Unknown")
            current_event_teams_name.append(tmp_tuple_vacancy) # originally append "Vacancy"
    shuffle(current_event_teams_name)
    current_teams_number_assignment = list()
    i = 0
    for team in current_event_teams_name:
        i = i+1
        tmp_tuple_team = (i, team)
        current_teams_number_assignment.append(tmp_tuple_team)
    return current_teams_number_assignment

"""
# this function is to assign the team code to the real team randomly
# rules:
# - get all the teams for the event
# - if the total team number is bigger than the real total, then use vacancy to replace the remaining
# the return type of this function is a list of teams
# Each team in the list is initiated as following:
# tmp_tuple = (team[2], team[3], team[5], team[6]) # team_id, team_name, team_school_id, team_school_name
"""
def assign_team_code_inorder(current_event_teams, teams_total_number):
    # random generate team sequence
    current_event_teams_name = list()
    for team in current_event_teams:
        # team_name = team[3]
        tmp_tuple = (team[2], team[3], team[5], team[6])
        current_event_teams_name.append(tmp_tuple)
    current_event_teams_number = len(current_event_teams_name)
    if current_event_teams_number < teams_total_number:
        i = current_event_teams_number
        while i < teams_total_number:
            i = i+1
            tmp_tuple_vacancy = (0, "Vacancy", 0, "Unknown")
            current_event_teams_name.append(tmp_tuple_vacancy) # originally append "Vacancy"
    current_teams_number_assignment = list()
    i = 0
    for team in current_event_teams_name:
        i = i+1
        tmp_tuple_team = (i, team)
        current_teams_number_assignment.append(tmp_tuple_team)
    return current_teams_number_assignment

"""
# This function is to randomly assign juror to each room
# rules:
# - Each room has a room code from 1 to N (N = number of team pairs for each round + 1) ideally
# - the total number of rooms N should not be less than the total number of teams / 2
# - the jurors that are available for all the four timeslots will be assigned to each room AS BALANCED AS POSSIBLE
# - the jurors that are available for three timeslots will be assigned to each room evenly but without checking the exact timeslots
# - the jurors that are available for one or two timeslots will be assigned to each room randomly
# - the jurors that are not available at all should not be approved by the administrator
# the return type of this function is a list with two elements: [room_plan, juror_plan]
# - room_plan is a list of rooms, the element in the room is a dictionary that is initiated as the following:
            {
                "room_code": index + 1,
                "jurors": [], # a list of juror codes that assigned to the room
                "room_name": 'unassigned',
                "room_id": 0,
                "room_number": '',
                "room_cois": [], # a list of school ids that includes all the coi schools of all the jurors in the room
                "room_team_matches_codes": [] # a list that includes the pair of the teams for each round, again each team is represented by team_code
                "room_team_matches_school_ids": [],
                "room_telec": '',
                "room_type": '',
                "room_details": ''
            }
# - juror_plan is a list of jurors, the element in the juror is a dictionary that is initiated as the following:
            {
                "juror_code": index + 1,
                "juror_info": juror, # it is a list of juror attributes like ppant_id, cois, is_tl, etc.
                "room_code": 0,
                "room_id": 0,
                "room_number": ''
            }
"""
def assign_room_juror_code_randomly(juror_list, teams_total_number, rooms_for_each_round, rounds_total_number):
    # plan room and juror
    # room_juror_plan = assign_room_juror_code_randomly(juror_list, teams_total_number, rooms_for_each_round)
    juror_count = len(juror_list)
    pair_count_per_round = int(teams_total_number / 2)
    room_count = pair_count_per_round
    if rooms_for_each_round > room_count:
        room_count = rooms_for_each_round
    if juror_list is None or juror_count < room_count:
        return [None, 'A', juror_count]
    else:
        secure_random = secrets.SystemRandom()  # creates a secure random object.
        # initial juror code list
        juror_code_list_original = []
        for j in range(juror_count):
            juror_code_list_original.append(j + 1)
        # initial juror_plan
        juror_plan = []
        i = 0
        for juror in juror_list:
            tmp_dict = {
                "juror_code": i + 1,
                "juror_info": juror,
                "room_code": 0,
                "room_id": 0,
                "room_number": ''
            }
            juror_plan.append(tmp_dict)
            i += 1
        # initial room_plan for each room
        room_plan = []
        for i in range(room_count):
            tmp_dict = {
                "room_code": i + 1,
                "jurors": [],
                "room_name": 'unassigned',
                "room_id": 0,
                "room_number": '',
                "room_cois": [],
                "room_team_matches_codes": [],
                "room_team_matches_school_ids": [],
                "room_telec": '',
                "room_type": '',
                "room_details": ''
            }
            room_plan.append(tmp_dict)
        # split all the juror codes based on the availability
        jurors_available_4slots = list()
        jurors_available_3slots = list()
        jurors_available_2slots = list()
        jurors_available_1slots = list()
        i = 0
        for juror in juror_list:
            i += 1
            if juror[11] == "Yes" and juror[12] == "Yes" and juror[13] == "Yes" and juror[14] == "Yes":
                jurors_available_4slots.append(i)
            elif juror[11] == "Yes" and juror[12] == "Yes" and juror[13] == "Yes":
                jurors_available_3slots.append(i)
            elif juror[11] == "Yes" and juror[12] == "Yes" and juror[14] == "Yes":
                jurors_available_3slots.append(i)
            elif juror[11] == "Yes" and juror[13] == "Yes" and juror[14] == "Yes":
                jurors_available_3slots.append(i)
            elif juror[12] == "Yes" and juror[13] == "Yes" and juror[14] == "Yes":
                jurors_available_3slots.append(i)
            elif juror[11] == "Yes" and juror[12] == "Yes":
                jurors_available_2slots.append(i)
            elif juror[11] == "Yes" and juror[13] == "Yes":
                jurors_available_2slots.append(i)
            elif juror[11] == "Yes" and juror[14] == "Yes":
                jurors_available_2slots.append(i)
            elif juror[12] == "Yes" and juror[13] == "Yes":
                jurors_available_2slots.append(i)
            elif juror[12] == "Yes" and juror[14] == "Yes":
                jurors_available_2slots.append(i)
            elif juror[13] == "Yes" and juror[14] == "Yes":
                jurors_available_2slots.append(i)
            else:
                jurors_available_1slots.append(i)
        if len(jurors_available_4slots) < room_count:
            return [None, 'B', juror_count, len(jurors_available_4slots)]
        else:
            # add first juror to each room
            # randomly select from jurors_available_4slots
            tmp_random_jurors = secure_random.sample(jurors_available_4slots, room_count)
            i = 0
            for juror_code in tmp_random_jurors:
                room_plan[i]['jurors'].append(juror_code)
                i += 1
                jurors_available_4slots.remove(juror_code)
                juror_code_list_original.remove(juror_code)
            while len(jurors_available_4slots) >= room_count:
                # randomly select
                tmp_random_jurors = secure_random.sample(jurors_available_4slots, room_count)
                i = 0
                for juror_code in tmp_random_jurors:
                    room_plan[i]['jurors'].append(juror_code)
                    i += 1
                    jurors_available_4slots.remove(juror_code)
                    juror_code_list_original.remove(juror_code)
            # if jurors_available_4slots has left over
            if len(jurors_available_4slots) > 0:
                missed_4slots_juror_count = room_count - len(jurors_available_4slots)
                # get missed from jurors_available_3slots
                if len(jurors_available_3slots) > missed_4slots_juror_count:
                    # randomly select from jurors_available_3slots
                    tmp_random_jurors = secure_random.sample(jurors_available_3slots, missed_4slots_juror_count)
                    for juror_code in tmp_random_jurors:
                        jurors_available_4slots.append(juror_code)
                        jurors_available_3slots.remove(juror_code)
                    # randomly select from jurors_available_4slots
                    tmp_random_jurors = secure_random.sample(jurors_available_4slots, room_count)
                    i = 0
                    for juror_code in tmp_random_jurors:
                        room_plan[i]['jurors'].append(juror_code)
                        i += 1
                        jurors_available_4slots.remove(juror_code)
                        juror_code_list_original.remove(juror_code)
                else:
                    # give the jurors_available_4slots to jurors_available_3slots
                    for juror_code in jurors_available_4slots:
                        jurors_available_3slots.append(juror_code)

            while len(jurors_available_3slots) >= room_count:
                # randomly select
                tmp_random_jurors = secure_random.sample(jurors_available_3slots, room_count)
                i = 0
                for juror_code in tmp_random_jurors:
                    room_plan[i]['jurors'].append(juror_code)
                    i += 1
                    jurors_available_3slots.remove(juror_code)
                    juror_code_list_original.remove(juror_code)
            # if jurors_available_3slots has left over
            if len(jurors_available_3slots) > 0:
                missed_3slots_juror_count = room_count - len(jurors_available_3slots)
                # get missed from jurors_available_2slots
                if len(jurors_available_2slots) > missed_3slots_juror_count:
                    # randomly select from jurors_available_2slots
                    tmp_random_jurors = secure_random.sample(jurors_available_2slots, missed_3slots_juror_count)
                    for juror_code in tmp_random_jurors:
                        jurors_available_3slots.append(juror_code)
                        jurors_available_2slots.remove(juror_code)
                    # randomly select from jurors_available_3slots
                    tmp_random_jurors = secure_random.sample(jurors_available_3slots, room_count)
                    i = 0
                    for juror_code in tmp_random_jurors:
                        room_plan[i]['jurors'].append(juror_code)
                        i += 1
                        jurors_available_3slots.remove(juror_code)
                        juror_code_list_original.remove(juror_code)
                else:
                    # give the jurors_available_3slots to jurors_available_2slots
                    for juror_code in jurors_available_3slots:
                        jurors_available_2slots.append(juror_code)
            # deal with the rest of the juror codes
            while len(juror_code_list_original) >= room_count:
                # randomly select
                tmp_random_jurors = secure_random.sample(juror_code_list_original, room_count)
                i = 0
                for juror_code in tmp_random_jurors:
                    room_plan[i]['jurors'].append(juror_code)
                    i += 1
                    juror_code_list_original.remove(juror_code)
            # if juror_code_list_original has left over
            if len(juror_code_list_original) > 0:
                while len(juror_code_list_original) < room_count:
                    juror_code_list_original.append(0)
                # randomly select
                tmp_random_jurors = secure_random.sample(juror_code_list_original, room_count)
                i = 0
                for juror_code in tmp_random_jurors:
                    if juror_code > 0:
                        room_plan[i]['jurors'].append(juror_code)
                        juror_code_list_original.remove(juror_code)
                    i += 1

        # end of assigning jurors to rooms
        for room in room_plan:
            # update juror_plan
            for juror_code in room['jurors']:
                juror_plan[juror_code - 1]['room_code'] = room['room_code']
                for coi_school in juror_plan[juror_code -1]['juror_info'][3]:
                    room['room_cois'].append(coi_school[1])
        return [room_plan, juror_plan]

# holds jurors[]: juror_code only, volunteers[]: volunteer_code only, room_sids: all the teams' school id from all rounds
def assign_chair_room(chair_list, rooms_for_each_round):
    # initial room_plan for each room
    room_plan = []
    for i in range(rooms_for_each_round):
        tmp_dict = {
            "room_code": i + 1,
            "jurors": [],
            "room_name": 'unassigned',
            "room_id": 0,
            "room_number": '',
            "room_cois": [],
            "room_team_matches_codes": [],
            "room_team_matches_school_ids": [],
            "room_telec": '',
            "room_type": '',
            "room_details": '',
            "room_sids": [],
            "volunteers": [],
            "is_chair": 'Yes'
        }
        room_plan.append(tmp_dict)

    chair_room_assignment = list()
    for chair in chair_list:
        temp = {
            "juror_code": 0,
            "is_chair": "Yes",
            "juror_info": chair,
            "juror_cois": [],
            "room_code": 0,
            "room_id": 0,
            "room_number": ''
        }

        for coi_school in chair[3]:
            temp['juror_cois'].append(coi_school[1])
        chair_room_assignment.append(temp)
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    temp_chair_room_assignment = secure_random.sample(chair_room_assignment, rooms_for_each_round)
    juror_plan = list()
    i = 0
    for chair in temp_chair_room_assignment:
        i = i + 1
        chair['juror_code'] = i
        chair['room_code'] = i
        # add chair to juror_plan
        juror_plan.append(chair)
        # add juror_code to the assigned room
        room_plan[i - 1]['jurors'].append(i)
        # add juror's cois to the assigned room
        for coi_school in chair['juror_info'][3]:
            room_plan[i - 1]['room_cois'].append(coi_school[1])

    return [juror_plan, room_plan]

"""
# This function is to randomly assign each team pair at each round to each room
# rules:
# - There must not have conflict of interest for each room for each team pair at each round
# - There might be one team assigned to the same room at different round, but this function tried best to
#   minimize the likelyhood, but still can't avoid 100%
# - it is allowed and encourage to have more rooms than the total number of team pairs at each round, which helps to meet the above requirements
# the return type of this function is a list with two elements: [round_plan, room_plan]
# - room_plan is also the input parameter and was initiated in function:
# - round_plan is a list of rounds, the element in the round is a dictionary that is initiated as the following:
        {
            "round_code": index + 1,
            "round_team_matches_code": [], # a list of team pair codes for each round
            "round_team_matches_school_ids": [], # a list of school ids of the team pair for each round
            "round_team_matches_room_codes": [], # a list of room codes for each round
        }
"""
def assign_room_to_team(rounds_total_number, team_plan, teams_total_number , rooms_for_each_round, room_plan):
    # plan round
    # initial each round
    round_plan = []
    for i in range(rounds_total_number):
        tmp_dict = {
            "round_code": i + 1,
            "round_team_matches_code": [],
            "round_team_matches_school_ids": [],
            "round_team_matches_room_codes": []
        }
        round_plan.append(tmp_dict)
    # add team to each round
    for round in round_plan:
        tmp_current_round_code = round['round_code']
        for team in team_plan:
            tmp_team_a = team['team_code']
            tmp_team_b = team['opponents'][tmp_current_round_code - 1]
            tmp_random_pair = [tmp_team_a, tmp_team_b]

            # get all the possible combinations of the team a and team b
            tmp_iter_result = itertools.permutations(tmp_random_pair)
            tmp_random_pair_accepted = False
            for each in tmp_iter_result:
                if each in round['round_team_matches_code']:
                    tmp_random_pair_accepted = False
                    break
                else:
                    tmp_random_pair_accepted = True
            if tmp_random_pair_accepted:
                round['round_team_matches_code'].append((tmp_team_a, tmp_team_b))
                tmp_cois = [team_plan[tmp_team_a - 1]['school_id'], team_plan[tmp_team_b - 1]['school_id']]
                round['round_team_matches_school_ids'].append(tmp_cois)

    # assign room_code to each team_matches of each round in round_plan
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    give_up = False
    retry_round_times = 0
    max_retry_round_times = 10
    show_stopper = False
    max_try_times = 100 * teams_total_number * rooms_for_each_round
    for round in round_plan:
        tmp_try_times = 0
        tmp_random_room_code = 0
        matched_rooms_available = False
        current_round_room_code_assignment = []
        while tmp_try_times < max_try_times:
            tmp_round_team_matches_room_code = []
            tmp_random_room_code = 0
            tmp_try_times += 1
            # initial current_round_room_assignment
            current_round_room_code_assignment = []
            i = 0
            for school_ids_at_team_pair in round['round_team_matches_school_ids']:
                # reset all the original room_codes to matched_rooms
                matched_rooms = []
                for r in range(rooms_for_each_round):
                    if r + 1 not in current_round_room_code_assignment:
                        matched_rooms.append(r + 1)
                # check if there are room(s) fits current team_pair (two school ids)
                matched_rooms_available = False
                for team_school_id in school_ids_at_team_pair:
                    for room in room_plan:
                        if room['room_code'] not in current_round_room_code_assignment:
                            if team_school_id in room['room_cois']:
                                if len(matched_rooms) > 0:
                                    if room['room_code'] in matched_rooms:
                                        matched_rooms.remove(room['room_code'])
                                else:
                                    # no matched room for current team pair
                                    show_stopper = True
                                    break
                    if show_stopper:
                        break
                i += 1
                if not show_stopper and len(matched_rooms) > 0:
                    tmp_random_room_code = secure_random.sample(matched_rooms, 1)
                    selected_room_codes_for_current_pair = []
                    if round['round_code'] > 1:
                        try:
                            for round_done in range(round['round_code'] - 1):
                                tmp_round_done_room_code = round_plan[round_done]['round_team_matches_room_codes'][
                                    i - 1]
                                selected_room_codes_for_current_pair.append(tmp_round_done_room_code)
                            tmp_rooms_for_selection = []
                            for room_code in matched_rooms:
                                if room_code not in selected_room_codes_for_current_pair:
                                    tmp_rooms_for_selection.append(room_code)
                            if len(tmp_rooms_for_selection) > 0:
                                tmp_random_room_code = secure_random.sample(tmp_rooms_for_selection, 1)
                            else:
                                if tmp_try_times == max_try_times:
                                    tmp_random_room_code = secure_random.sample(matched_rooms, 1)
                                else:
                                    show_stopper = True
                                    break
                        except:
                            if tmp_try_times == max_try_times:
                                tmp_random_room_code = secure_random.sample(matched_rooms, 1)
                            else:
                                show_stopper = True
                                break
                    # tmp_round_team_matches_room_code.append(tmp_random_room_code)
                    current_round_room_code_assignment.append(tmp_random_room_code[0])
                else:
                    # can't find matched room for current team_pair
                    # re-do
                    break
            if not show_stopper and len(current_round_room_code_assignment) > 0:
                break
            else:
                current_round_room_code_assignment = []
                if tmp_try_times < max_try_times:
                    show_stopper = False
        if not show_stopper and len(current_round_room_code_assignment) > 0:
            for room_code in current_round_room_code_assignment:
                round['round_team_matches_room_codes'].append(room_code)
        else:
            break
    return [round_plan, room_plan]

"""
# This function is to randomly assign each team pair at each round to each room with chair already assigned
# rules:
# - There must not have conflict of interest for each room for each team pair at each round, including chair
# - There might be one team assigned to the same room at different round, but this function tried best to
#   minimize the likelyhood, but still can't avoid 100%
# - it is not allowed to have more rooms than the total number of team pairs at each round
# the return type of this function is a list with two elements: [round_plan, room_plan]
# - room_plan is also the input parameter and was initiated in function: assign_chair_room
# - round_plan is a list of rounds, the element in the round is a dictionary that is initiated as the following:
        {
            "round_code": index + 1,
            "round_team_matches_code": [], # a list of team pair codes for each round
            "round_team_matches_school_ids": [], # a list of school ids of the team pair for each round
            "round_team_matches_room_codes": [], # a list of room codes for each round
        }
"""
def assign_room_to_team_wt_chair(rounds_total_number, team_plan, teams_total_number, rooms_for_each_round, chairs_room_assignment, max_repeated_rooms, open_last_round):
    juror_plan = chairs_room_assignment[0]
    room_plan = chairs_room_assignment[1]
    # plan round
    # initial each round
    round_plan = []
    for i in range(rounds_total_number):
        tmp_dict = {
            "round_code": i + 1,
            "round_team_matches_code": [],
            "round_team_matches_school_ids": [],
            "round_team_matches_room_codes": []
        }
        round_plan.append(tmp_dict)
    # add team to each round => round_plan
    for round in round_plan:
        tmp_current_round_code = round['round_code']
        for team in team_plan:
            tmp_team_a = team['team_code']
            tmp_team_b = team['opponents'][tmp_current_round_code - 1]
            tmp_random_pair = [tmp_team_a, tmp_team_b]

            # get all the possible combinations of the team a and team b
            tmp_iter_result = itertools.permutations(tmp_random_pair)
            tmp_random_pair_accepted = False
            for each in tmp_iter_result:
                if each in round['round_team_matches_code']:
                    tmp_random_pair_accepted = False
                    break
                else:
                    tmp_random_pair_accepted = True
            if tmp_random_pair_accepted:
                round['round_team_matches_code'].append((tmp_team_a, tmp_team_b))
                tmp_cois = [team_plan[tmp_team_a - 1]['school_id'], team_plan[tmp_team_b - 1]['school_id']]
                round['round_team_matches_school_ids'].append(tmp_cois)

    # assign room_code to each team_matches of each round in round_plan
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    finding_room_tried_times = 0
    finding_room_max_try_times = 10 * teams_total_number * rooms_for_each_round
    finding_room = False
    team_round_room_codes = []
    while finding_room_tried_times < finding_room_max_try_times:
        finding_room_tried_times += 1
        # start of one while loop here
        team_round_room_codes = []
        for team in team_plan:
            tmp_item = [team['team_code'], []]
            team_round_room_codes.append(tmp_item)
        # reset room_code in round_plan to 0
        for round in round_plan:
            round['round_team_matches_room_codes'] = []
        # start to loop in the round_plan
        for round in round_plan:
            current_round_code = round['round_code']
            current_round_tried_times = 0
            current_round_max_try_times = 10
            team_current_round_room_code = []
            for team in team_plan:
                tmp_item2 = [team['team_code'], 0]
                team_current_round_room_code.append(tmp_item2)
            finding_room = False
            while current_round_tried_times < current_round_max_try_times:
                current_round_tried_times += 1
                # find room for current round - start one loop
                current_round_assigned_room_codes = []
                pair_index = -1
                for pair in round['round_team_matches_code']:
                    pair_index += 1
                    # find room for current pair
                    tmp_available_room_codes = []
                    for i in range(rooms_for_each_round):
                        tmp_available_room_codes.append(i + 1)
                    # remove the room has been assigned to other teams at current round
                    for room_code in current_round_assigned_room_codes:
                        tmp_available_room_codes.remove(room_code)
                    current_pair_school_ids = round['round_team_matches_school_ids'][pair_index]
                    # check if there is a room that has not conflict wiht current chair
                    tmp_ok_room_for_current_pair = []
                    for room_code in tmp_available_room_codes:
                        if (current_pair_school_ids[0] not in room_plan[room_code-1]['room_cois']) and (current_pair_school_ids[1] not in room_plan[room_code-1]['room_cois']):
                            # this room is ok for current pair so far, continue to look at next room
                            tmp_ok_room_for_current_pair.append(room_code)
                            #tmp_available_room_codes.remove(room['room_code'])
                        else:
                            # has conflict with the room, look for next available room
                            pass
                    # check current pair finding room results
                    if len(tmp_ok_room_for_current_pair) > 0:
                        # there are room(s) at current round is not conflict with current pair
                        # continue to check if the teams in the current pair have been the ok room(s) in previous round, if yes, remove it
                        if current_round_code > (rounds_total_number - max_repeated_rooms) or pair_index > (teams_total_number/2 -open_last_round):
                            # too hard to find not-repeated room, give up
                            pass
                        else:
                            # check current pair - 1st team's previous assigned rooms
                            for room_code in team_round_room_codes[pair[0]-1][1]:  #team_round_room_codes[i][1]
                                if room_code in tmp_ok_room_for_current_pair:
                                    tmp_ok_room_for_current_pair.remove(room_code)
                            for room_code in team_round_room_codes[pair[1]-1][1]:
                                if room_code in tmp_ok_room_for_current_pair:
                                    tmp_ok_room_for_current_pair.remove(room_code)
                        # end of check, select one of room for current pair current round if success otherwise re-do this round
                        if len(tmp_ok_room_for_current_pair) > 0:
                            # final good ending for current pair
                            #current_round_tried_times += 1
                            finding_room = True
                            # found room for current pair, randomly select one to the the current pair
                            tmp_random_pair_room_code = secure_random.sample(tmp_ok_room_for_current_pair, 1)
                            current_pair_room_code = 0
                            for item in tmp_random_pair_room_code:
                                current_pair_room_code = item
                                current_round_assigned_room_codes.append(current_pair_room_code)
                                team_current_round_room_code[pair[0]-1][1]=current_pair_room_code
                                team_current_round_room_code[pair[1]-1][1]=current_pair_room_code
                                break
                        else:
                            # can't find room for current pair, this round is failed, re-do this round
                            finding_room = False
                            break
                    else:
                        # there is NOT room at current round is not conflict with current pair
                        # current round failed, should re-do this round
                        finding_room = False
                        break
                # find room for current round - end one loop
                if finding_room:
                    # final good ending for the current round
                    # add curent round's room assignment to round plan
                    round['round_team_matches_room_codes'] = current_round_assigned_room_codes
                    i = -1
                    for team in team_current_round_room_code:
                        i += 1
                        team_round_room_codes[i][1].append(team[1])
                    break
                else:
                    #current_round_tried_times += 1
                    # failed in this round, continue to try
                    finding_room = False

            if finding_room:
                # continue next round
                pass
            else:
                # current round is failed, stop the this while loop, try another while loop
                break
        # end of one while loop here
        if finding_room:
            break
        else:
            finding_room_tried_times += 1
    return [round_plan, room_plan, finding_room, team_round_room_codes]

"""
# This function is to do final cleanup for the planning
# rules:
# - fill the team pair codes to room_plan
# --- use the team pair codes in the round_plan
# --- the sequence of the team_pair_codes are the number of the teams at each round
# --- use the team_code - 1 as the team index to find the according team record in the team_plan
# --- use append to add data, replace function will cause the mess up
# - fill the room code to each team at each round in the team_plan
# --- the same as above
# - assign real room number to room_code in room_plan
# --- get the list of active PM rooms and randomly select the required number of rooms
# --- if available active PM room is less than required rooms, then add vacancy as placeholder
# --- else: randomly select the required number of active PM rooms
# --- shuffle the selected room list and assign to room_code
"""
def tournament_plan_cleanup(round_plan, room_plan, team_plan, current_event_rooms_usage1):
    # clean the data
    for round in round_plan:
        for i in range(len(round['round_team_matches_code'])):
            tmp_team_pair_codes_me = round['round_team_matches_code'][i]
            tmp_team_pair_school_ids = round['round_team_matches_school_ids'][i]
            tmp_team_pair_room_code = round['round_team_matches_room_codes'][i]
            room_plan[tmp_team_pair_room_code - 1]['room_team_matches_codes'].append(tmp_team_pair_codes_me)
            room_plan[tmp_team_pair_room_code - 1]['room_team_matches_school_ids'].append(tmp_team_pair_school_ids)
            # fill room_sids
            if tmp_team_pair_school_ids[0] not in room_plan[tmp_team_pair_room_code - 1]['room_sids']:
                room_plan[tmp_team_pair_room_code - 1]['room_sids'].append(tmp_team_pair_school_ids[0])
            if tmp_team_pair_school_ids[1] not in room_plan[tmp_team_pair_room_code - 1]['room_sids']:
                room_plan[tmp_team_pair_room_code - 1]['room_sids'].append(tmp_team_pair_school_ids[1])
        # add None to remaining room for the current round
        for room in room_plan:
            if len(room['room_team_matches_codes']) < round['round_code']:
                room['room_team_matches_codes'].append(None)
                room['room_team_matches_school_ids'].append(None)
    for round in round_plan:
        round_code = round['round_code']
        round_index = round_code - 1
        team_pair_index = -1
        for team_pair in round['round_team_matches_code']:
            team_pair_index += 1
            current_room_code = round['round_team_matches_room_codes'][team_pair_index]
            if team_pair is not None:
                current_team_a_code = team_pair[0]
                current_team_b_code = team_pair[1]
                team_plan[current_team_a_code-1]['room_codes'].append(current_room_code)
                team_plan[current_team_b_code - 1]['room_codes'].append(current_room_code)

    # initial event_room_list
    current_event_room_list = []
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    for room in current_event_rooms_usage1:
        current_event_room_list.append(room)

    current_event_rooms_total = len(current_event_room_list)
    current_event_rooms_required = len(room_plan)
    current_event_room_list_usg1 = []
    if current_event_rooms_total < current_event_rooms_required:
        i = current_event_rooms_total
        while i < current_event_rooms_required:
            i = i+1
            current_event_room_list.append(None)
        current_event_room_list_usg1 = current_event_room_list
    else:
        tmp_random_rooms = secure_random.sample(current_event_room_list, current_event_rooms_required)
        for room in tmp_random_rooms:
            current_event_room_list_usg1.append(room)

    shuffle(current_event_room_list_usg1)
    for room in room_plan:
        room_index = room['room_code'] - 1
        if current_event_room_list_usg1[room_index] is not None:
            room['room_number'] = current_event_room_list_usg1[room_index][2]
            room['room_id'] = current_event_room_list_usg1[room_index][0]
            room['room_telec'] = current_event_room_list_usg1[room_index][6]
            room['room_type'] = current_event_room_list_usg1[room_index][8]
            room['room_details'] = current_event_room_list_usg1[room_index][9]
    return [round_plan, room_plan, team_plan]

def assign_volunteer(data_entrier_options, volunteer_list, room_plan):
    # randomly select qualified volunteer and assign to each room for data entry
    room_count = len(room_plan)
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    data_entrier_options_list = list()
    for row in data_entrier_options:
        data_entrier_options_list.append(row)
    tmp_random_data_entriers = secure_random.sample(data_entrier_options_list, room_count)
    volunteer_plan = list()
    volunteer_selected_ppant_id_list = list()
    i = 0
    for data_entrier in tmp_random_data_entriers:
        tmp_dict = {
            "mvolunteer_code": i + 1,
            "cois": [],
            "volunteer_name": data_entrier[5] + " " + data_entrier[6],
            "ppant_id": data_entrier[1],
            "ppant_subtype": 9,
            "timeslots": data_entrier[9] + " " + data_entrier[10] + " " + data_entrier[11] + " " + data_entrier[12],
            "mroom_code": i + 1,
            "data_entry": 'Yes'
        }
        i += 1
        volunteer_selected_ppant_id_list.append(data_entrier[1])
        volunteer_plan.append(tmp_dict)

    # assign the rest of the volunteers to the room that without conflict of interests
    tmp_available_mroom_codes = list()
    current_mvolunteer_code = len(volunteer_plan)
    for volunteer in volunteer_list:
        current_ppant_id = volunteer[0]
        if current_ppant_id not in volunteer_selected_ppant_id_list:
            current_mvolunteer_code += 1
            current_timeslots = volunteer[11] + volunteer[12] + volunteer[13] + volunteer[14]
            current_volunteer_name = volunteer[5]
            current_cois = list()
            if volunteer[1] == 1:
                # add cois
                for coi_school in volunteer[3]:
                    tmp_coi_school_id = coi_school[1]
                    current_cois.append(tmp_coi_school_id)

            # generate the mroom codes for selection
            if tmp_available_mroom_codes is None or len(tmp_available_mroom_codes) < 1:
                tmp_available_mroom_codes = list()
                for i in range(room_count):
                    tmp_available_mroom_codes.append(i + 1)

            # find mroom for current volunteer
            current_mroom_code = 0
            if current_cois is not None and len(current_cois)>0:
                # has cois
                tmp_coi_mroom_codes = list()
                # find the mrooms that has conflict
                for coi_school_id in current_cois:
                    # check the mroom that without conflict of interest with current volunteer
                    for room in room_plan:
                        if coi_school_id in room['room_cois']:
                            tmp_coi_mroom_codes.append(coi_school_id)
                # filter the mrooms that has conflict
                filter_result = itertools.filterfalse(lambda x: x in tmp_coi_mroom_codes, tmp_available_mroom_codes)
                if filter_result is not None:
                    tmp_filter_result_list = list()
                    for row in filter_result:
                        tmp_filter_result_list.append(row)
                    # has room available, randomly select on mroom for current volunteer
                    tmp_random_mroom_codes = secure_random.sample(tmp_filter_result_list, 1)
                    for item in tmp_random_mroom_codes:
                        current_mroom_code = item
                        tmp_available_mroom_codes.remove(current_mroom_code)
                        break
                else:
                    # no room available, leave it 0 and assign mroom later
                    current_mroom_code = 0
            else:
                # this volunteer has no coi, can random select one room from available mroom codes
                tmp_random_mroom_codes = secure_random.sample(tmp_available_mroom_codes, 1)
                for item in tmp_random_mroom_codes:
                    current_mroom_code = item
                    tmp_available_mroom_codes.remove(current_mroom_code)
                    break

            # assemble the current mvolunteer
            tmp_dict2 = {
                "mvolunteer_code": current_mvolunteer_code,
                "cois": current_cois,
                "volunteer_name": current_volunteer_name,
                "ppant_id": current_ppant_id,
                "ppant_subtype": 8,
                "timeslots": current_timeslots,
                "mroom_code": current_mroom_code,
                "data_entry": 'No'
            }
            # add current volunteer to volunteer plan
            volunteer_plan.append(tmp_dict2)
        else:
            # no need to add this volunteer
            pass

    # assign mroom to those didn't get assigned in the previous loop
    for volunteer in volunteer_plan:
        if volunteer['mroom_code'] == 0:
            # assign whatever room number that fits this volunteer
            # generate the mroom codes for selection
            if tmp_available_mroom_codes is None or len(tmp_available_mroom_codes) < 1:
                tmp_available_mroom_codes = list()
                for i in range(room_count):
                    tmp_available_mroom_codes.append(i + 1)
            # find the mrooms that has conflict
            tmp_mroom_codes_has_conflict = list()
            for coi_school_id in volunteer['cois']:
                for room in room_plan:
                    if coi_school_id in room['room_cois']:
                        tmp_mroom_codes_has_conflict.append(room['room_code'])
                        break
            # filter the mrooms with conflict
            filter_result = itertools.filterfalse(lambda x: x in tmp_mroom_codes_has_conflict, tmp_available_mroom_codes)
            tmp_filter_result_list = list()
            for row in filter_result:
                tmp_filter_result_list.append(row)

            # randomly select one mroom to the current volunteer
            tmp_random_mroom_codes = secure_random.sample(tmp_filter_result_list, 1)
            for item in tmp_random_mroom_codes:
                # assigne the mroom code to the current volunteer
                volunteer['mroom_code'] = item
                break
    return volunteer_plan

# this is new
def assign_volunteers(data_entrier_options, volunteer_list, room_plan):
    # randomly select qualified volunteer and assign to each room for data entry
    room_count = len(room_plan)
    secure_random = secrets.SystemRandom()  # creates a secure random object.
    data_entrier_options_list = list()
    for row in data_entrier_options:
        data_entrier_options_list.append(row)
    tmp_random_data_entriers = secure_random.sample(data_entrier_options_list, room_count)
    volunteer_plan = list()
    volunteer_selected_ppant_id_list = list()
    i = 0
    for data_entrier in tmp_random_data_entriers:
        tmp_dict = {
            "mvolunteer_code": i + 1,
            "cois": [],
            "volunteer_name": data_entrier[5] + " " + data_entrier[6],
            "ppant_id": data_entrier[1],
            "ppant_subtype": 9,
            "timeslots": data_entrier[9] + " " + data_entrier[10] + " " + data_entrier[11] + " " + data_entrier[12],
            "mroom_code": i + 1,
            "data_entry": 'Yes'
        }
        i += 1
        volunteer_selected_ppant_id_list.append(data_entrier[1])
        volunteer_plan.append(tmp_dict)

    # assign the rest of the volunteers to each room
    tried_times = 0
    max_try_times = len(volunteer_list)*room_count*len(volunteer_list)
    while tried_times <= max_try_times:
        for i in range(room_count):
            room_code = i + 1
            # get a volunteer who has not conflict with current room
            for volunteer in volunteer_list:
                tried_times += 1
                current_ppant_id = volunteer[0]
                current_mvolunteer_code = len(volunteer_plan) + 1
                current_cois = []
                current_volunteer_name =volunteer[5]
                current_timeslots = volunteer[11] + volunteer[12] + volunteer[13] + volunteer[14]
                current_mroom_code = room_code
                if current_ppant_id not in volunteer_selected_ppant_id_list:
                    # check cois
                    if volunteer[1] == 1:
                        # has cois
                        for coi_school in volunteer[3]:
                            tmp_coi_school_id = coi_school[1]
                            current_cois.append(tmp_coi_school_id)

                        # check if fit this room
                        current_volunteer_fit_current_room = True
                        for school_id in room_plan[room_code-1]['room_sids']:
                            if school_id in current_cois:
                                # not fit
                                current_volunteer_fit_current_room = False
                                break
                        if current_volunteer_fit_current_room:
                            # fit this room
                            # assemble the current mvolunteer
                            tmp_dict2 = {
                                "mvolunteer_code": current_mvolunteer_code,
                                "cois": current_cois,
                                "volunteer_name": current_volunteer_name,
                                "ppant_id": current_ppant_id,
                                "ppant_subtype": 8,
                                "timeslots": current_timeslots,
                                "mroom_code": current_mroom_code,
                                "data_entry": 'No'
                            }
                            # add current volunteer to volunteer plan
                            volunteer_plan.append(tmp_dict2)
                            volunteer_selected_ppant_id_list.append(current_ppant_id)
                            break
                    else:
                        # no cois - assign this volunteer to this room,
                        # assemble the current mvolunteer
                        tmp_dict2 = {
                            "mvolunteer_code": current_mvolunteer_code,
                            "cois": current_cois,
                            "volunteer_name": current_volunteer_name,
                            "ppant_id": current_ppant_id,
                            "ppant_subtype": 8,
                            "timeslots": current_timeslots,
                            "mroom_code": current_mroom_code,
                            "data_entry": 'No'
                        }
                        # add current volunteer to volunteer plan
                        volunteer_plan.append(tmp_dict2)
                        volunteer_selected_ppant_id_list.append(current_ppant_id)
                        break
        if len(volunteer_plan) == len(volunteer_list):
            break

    not_assigned_volunteers = []
    if len(volunteer_selected_ppant_id_list) < len(volunteer_list):
        for volunteer in volunteer_list:
            if volunteer[0] in volunteer_selected_ppant_id_list:
                pass
            else:
                tmp_n_volunteer = (volunteer[0], volunteer[5], volunteer[3])
                not_assigned_volunteers.append(tmp_n_volunteer)

    return (volunteer_plan, not_assigned_volunteers)

def assign_moved_jurors(moved_juror_list, round_plan, rooms_for_each_round):
    # initial moved_juror_plan
    moved_juror_room_assignment = list()
    juror_code_index = rooms_for_each_round - 1
    for juror in moved_juror_list:
        juror_code_index += 1
        tmp = {
            "juror_code": juror_code_index + 1,
            "is_chair": "No",
            "juror_info": juror,
            "juror_cois": [],
            "round_room_codes": [],
            "room_id": 0,
            "room_number": ''
        }
        for i in range(len(round_plan)):
            tmp['round_room_codes'].append(0)
        for coi_school in juror[3]:
            tmp['juror_cois'].append(coi_school[1])
        moved_juror_room_assignment.append(tmp)

    moved_juror_plan = []
    for round in round_plan:
        current_round_code = round['round_code']

        current_round_assigned_juror_codes = []
        while True:
            pair_index = -1
            for room_code in round['round_team_matches_room_codes']:
                pair_index += 1
                # found one moved juror for current round and current room
                current_round_room_sids = round['round_team_matches_school_ids'][pair_index]
                for juror in moved_juror_room_assignment:
                    # each round: each available juror only can be assgined once
                    if juror['juror_code'] not in current_round_assigned_juror_codes:
                        # check if this guy is available for current round index 11 - round 1, 12 - round 2&3, 13 - round 4&5, 14 - final round
                        juror_available = False
                        if current_round_code == 1:
                            if juror['juror_info'][11] == "Yes":
                                juror_available = True
                        elif current_round_code == 2 or current_round_code == 3:
                            if juror['juror_info'][12] == "Yes":
                                juror_available = True
                        elif current_round_code == 4 or current_round_code == 5:
                            if juror['juror_info'][13] == "Yes":
                                juror_available = True

                        if juror_available:
                            if juror['juror_code'] not in current_round_assigned_juror_codes:
                                if (current_round_room_sids[0] not in juror['juror_cois']) and (current_round_room_sids[1] not in juror['juror_cois']):
                                    # this juror is ok to stay at this room,
                                    tmp_tuple = (current_round_code, room_code, juror['juror_code'])
                                    moved_juror_plan.append(tmp_tuple)
                                    current_round_assigned_juror_codes.append(juror['juror_code'])
                                    break
                                else:
                                    pass
                        else:
                            current_round_assigned_juror_codes.append(juror['juror_code'])

            if len(current_round_assigned_juror_codes) >= len(moved_juror_list):
                break

        # finished the assignment to the moved jurors, now to fill the plan
        for item in moved_juror_plan:
            round_code = item[0]
            room_code = item[1]
            juror_code = item[2]
            moved_juror_room_assignment[juror_code-1-rooms_for_each_round]['round_room_codes'][round_code-1] = room_code
    return moved_juror_room_assignment


# -*- coding: utf-8 -*-

import logging

from ..actions import action
from ...connector.nuvlabox import NuvlaBox

@action('restart-stream', True)
class NBRestartStreamJob(object):

    def __init__(self, _, job):
        self.job = job
        self.api = job.api

    def restart_stream(self):
        nuvlabox_peripheral_id = self.job['target-resource']['href']

        peripheral = self.api.get(nuvlabox_peripheral_id).data
        nuvlabox_id = peripheral['parent']

        data = {"id": nuvlabox_peripheral_id,
                "video-device": peripheral['video-device']}

        logging.info('Restarting data stream for {} in NuvlaBox {}'.format(nuvlabox_peripheral_id,
                                                                         nuvlabox_id))
        connector = NuvlaBox(api=self.api, nuvlabox_id=nuvlabox_id, job=self.job)

        # IMPORTANT BIT THAT MUST CHANGE FOR EVERY NUVLABOX API ACTION
        api_action_name = 'data-source-mjpg/restart'
        r = connector.start(api_action_name=api_action_name, method='post', payload=data)

        msg = 'Call /api/{} for NuvlaBox {}. Output: {}'.format(api_action_name, nuvlabox_id, r)
        self.job.set_status_message(msg)

        return 0

    def do_work(self):
        return self.restart_stream()


import sys
import aubio
from aubio import source, pitch, midi2note, freq2note, note2freq
from os import path
import numpy as np
import pyaudio
import wave
import queue
from time import sleep
from math import log
from threading import Thread
import kivy
kivy.require('1.11.1')
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, NumericProperty, BooleanProperty
from kivy.clock import Clock
from playsound import playsound

# Manager for the screens
class WindowManager(ScreenManager):
	pass

# Wrapper for the screens so that they are all similar
class ScreenWrapper(Screen):
	pass

class Comparison(ScreenWrapper, Screen):

	compare_text = StringProperty()
	scale = StringProperty()
	scale_text = StringProperty()
	is_recording = BooleanProperty()
	is_sharp = BooleanProperty()

	def __init__(self, **kwargs):
		super(Comparison, self).__init__(**kwargs)
		self.compare_text = "Comparison"
		self.scale_text = "None"
		self.scale = ""
		self.is_recording = False
		self.is_sharp = True
		self.scale_dict = {
		"A Scale": ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#', 'A', 'G#', 'F#', 'E', 'D', 'C#', 'B', 'A'],
		"A# Scale": ['A#', 'C', 'D', 'D#', 'F', 'G', 'A', 'A#', 'A' 'G', 'F', 'D#', 'D', 'C', 'A#'],
		"B Scale": ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#', 'B', 'A#', 'G#', 'F#', 'E', 'D#', 'C#', 'B'],
		"C Scale": ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C', 'B', 'A', 'G', 'F', 'E', 'D', 'C'],
		"C# Scale": ['C#', 'D#', 'F', 'F#', 'G#', 'A#', 'C' 'C#', 'C', 'A#', 'G#', 'F#', 'F', 'D#', 'C#'],
		"D Scale": ['D', 'E', 'F#', 'G', 'A', 'B', 'C#', 'D', 'C#', 'B', 'A', 'G', 'F#', 'E', 'D'],
		"D# Scale": ['D#', 'F', 'G', 'G#', 'A#', 'C', 'D', 'D#', 'D', 'C', 'A#', 'G#', 'G', 'F', 'D#'],
		"E Scale": ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#', 'E', 'D#', 'C#', 'B', 'A', 'G#', 'F#', 'E'],
		"F Scale": ['F', 'G', 'A', 'A#', 'C', 'D', 'E', 'F', 'E', 'D', 'C', 'A#', 'A', 'G', 'F'],
		"F# Scale": ['F#', 'G#', 'A#', 'B', 'C#', 'D#', 'F', 'F#', 'F', 'D#', 'C#', 'B', 'A#', 'G#', 'F#'],
		"G Scale": ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G', 'F#', 'E', 'D', 'C', 'B', 'A', 'G'],
		"G# Scale": ['G#', 'A#', 'C', 'C#', 'D#', 'F', 'G', 'G#', 'G', 'F', 'D#', 'C#',  'C', 'A#', 'G#'],
		"A Scale": ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#', 'A', 'G#', 'F#', 'E', 'D', 'C#', 'B', 'A'],
		}

	# Compares pitches of 2 files using the get pitch method
	def compare_pitch(self, file1, file2):
		file1_pitch = self.a.get_pitch(file1)
		file2_pitch = self.a.get_pitch(file2)

		if file1_pitch == file2_pitch:
			self.compare_text = "You are correct"
			print("\nYou are correct.")
		else:
			self.compare_text = "You are incorrect"
			print("\nYou are incorrect.")

	def record_scale(self, state):
		if self.scale == "":
			self.scale_text = "Select a Scale"
		else:
			self.state = state
			if self.state == 'down':
				self.is_recording = True
				self.q = queue.Queue()
				self.t = Thread(target=Comparison.record_compare, args=(self,))
				self.t.daemon = True
				self.t.start()
			else:
				self.is_recording = False

	def record_compare(self):
		self.gate = True
		while self.state == 'down':
			if self.gate == True:
				Analysis.record_init(self, self.state)
				self.gate = False
		Analysis.get_pitch_init(self, 'output.wav')
		self.notes = self.q.get()

		if self.notes == self.scale_dict[self.scale]:
			playsound('music/victory.wav')
		else:
			print('FUCK NOOOOOOOOOOOO')

	def on_leave(self):
		self.is_recording = False

class Analysis(ScreenWrapper, Screen):

	pitch_text = StringProperty()
	is_recording = BooleanProperty()

	def __init__(self, **kwargs):
		super(Analysis, self).__init__(**kwargs)
		self.pitch_text	= "Analysis"
		self.is_recording = False

	def get_pitch_init(self, filename):
		self.filename = filename
		self.t = Thread(target=Analysis.get_pitch, args=(self,))
		self.t.daemon = True
		self.t.start()

	# Gets the musical notes from a file
	def get_pitch(self):
		if path.exists(self.filename) == False:
			raise Exception(f"File Path to {self.filename} does not exist")

		else:
			downsample = 1
			samplerate = 44100 // downsample
			if len( sys.argv ) > 2: samplerate = int(sys.argv[2])

			win_s = 4096 // downsample # fft size
			hop_s = 512  // downsample # hop size

			s = source(self.filename, samplerate, hop_s)
			samplerate = s.samplerate

			tolerance = 0.8

			pitch_o = pitch("yin", win_s, hop_s, samplerate)
			pitch_o.set_unit("midi")
			pitch_o.set_tolerance(tolerance)

			pitches = []
			confidences = []

			# Total number of frames read
			total_frames = 0
			while True:
			    samples, read = s()
			    pitch_midi = pitch_o(samples)[0]
			    pitch_midi = int(round(pitch_midi))
			    confidence = pitch_o.get_confidence()
			    if confidence < 0.9: pitch_midi = 0.
			    #print("%f %f %f" % (total_frames / float(samplerate), pitch, confidence))
			    if len(pitches) == 0 and pitch_midi != 0:
			    	pitches.append(pitch_midi)
			    elif len(pitches) > 0:
			    	if pitch_midi != pitches[-1] and pitch_midi != 0:
			    		pitches.append(pitch_midi)
			    else:
			    	pass

			    #print(pitches)
			    confidences += [confidence]
			    total_frames += read
			    if read < hop_s: break

			if 0: sys.exit(0)
			notes = []
			for midi in pitches:
				note = midi2note(midi)
				notes.append(note.strip("0123456789"))

			print(notes)
			self.pitch_text = str(notes)
			self.q.put(notes)
			raise Exception("Thread Terminated")

	# Starts the thread to record from mic
	def record_init(self, state):
		self.state = state
		if self.state == 'down': # Checks if the button is in a pressed state
			self.is_recording = True
			self.t = Thread(target=Analysis.record, args=(self, self.state))
			self.t.daemon = True
			self.t.start()
		else:
			self.is_recording = False

	# Records input from the default mic
	def record(self, state, chunk=1024, wavformat=pyaudio.paInt16, channels=1, rate=44100, wave_output_filename="output.wav"):
		self.state = state

		p = pyaudio.PyAudio()

		stream = p.open(format=wavformat,
						channels=channels,
						rate=rate,
						input=True,
						frames_per_buffer=chunk)

		print("Recording...")

		frames = []

		while self.state == 'down': # Reads data while button is pressed
				data = stream.read(chunk)
				frames.append(data)

		print("Done Recording")

		stream.close()
		p.terminate()

		wf = wave.open(wave_output_filename, 'wb')
		wf.setnchannels(channels)
		wf.setsampwidth(p.get_sample_size(wavformat))
		wf.setframerate(rate)
		wf.writeframes(b''.join(frames))
		wf.close()
		raise Exception("Thread Terminated")

	def on_leave(self):
		self.state = 'up'

class Tuner(ScreenWrapper, Screen):

	note_text = StringProperty()
	cent_text = StringProperty()
	posy = NumericProperty()
	color_red = NumericProperty()
	color_green = NumericProperty()
	color_blue = NumericProperty()

	def __init__(self, **kwargs):
		super(Tuner, self).__init__(**kwargs)
		self.note_text = " "
		self.cent_text = " "
		self.posy = .65
		self.color_red = 0
		self.color_green = 0
		self.color_blue = 0

	# Starts the thread to tune from the mic when the screen is entered
	def on_enter(self):
		print("Tuning..")
		self.t = Thread(target=Tuner.tune, args=(self,))
		self.is_running = True
		self.t.daemon = True
		self.t.start()

	# Closes thread on leave
	def on_leave(self):
		self.is_running = False
		print("Done Tuning")

	# Tunes from the mic
	def tune(self):
		pDetection = aubio.pitch("default", 2048, 1024, 44100)
		pDetection.set_unit('Hz')
		pDetection.set_silence(-40)

		p = pyaudio.PyAudio()

		stream = p.open(format=pyaudio.paFloat32,
    					channels=1,
    					rate=44100,
    					input=True,
    					frames_per_buffer=1024)

		while self.is_running == True:

			data = stream.read(1024)

			samples = np.frombuffer(data, dtype=aubio.float_type)
			pitch = pDetection(samples)[0]

			if pitch != 0:
				pitch_cent = round(1200 * log(pitch / note2freq(freq2note(pitch)), 2), 2)
				self.note_text = freq2note(pitch)
				self.cent_text = str(pitch_cent)
				if pitch_cent < 0:
					self.posy = .45
				else:
					self.posy = .65
				self.color_red = round((abs(pitch_cent) * .051), 3)
				self.color_green = round(((abs(pitch_cent) * -.051) + 2.55), 3)
				print(f"Green: {self.color_green} Red: {self.color_red}")
			#print(freq2note(pitch))

		raise Exception("Thread Terminated") # Terminates the thread

class Metronome(ScreenWrapper, Screen):

	met_text = StringProperty()
	format_text = StringProperty()
	met_format = NumericProperty()

	def __init__(self, **kwargs):
		super(Metronome, self).__init__(**kwargs)
		self.met_text = "Metronome OFF"
		self.format_text = 'None'
		self.met_format = 4
		self.clock = None

	# Schedules the met to be played
	def met(self, state, bpm):
		self.state = state
		self.bpm = bpm
		if self.state == 'down':
			if self.bpm.isdigit() == False :
				self.met_text = "Input was not a natural number"
			else:
				self.bpm = int(self.bpm)
				if self.bpm < 1:
					self.met_text = "Input was not a natural number"
				else:
					self.bpm = 60/self.bpm
					self.met_text = "Metronome ON"
					self.t = Thread(target=Metronome.playmet, args=(self, self.bpm))
					self.is_running = True
					self.t.daemon = True
					self.t.start()

		# Kills the thread running the Metronome
		else:
			self.met_text = "Metronome OFF"
			self.is_running = False

	# Plays the metronome sound
	def playmet(self, bpm):
		counter = 0
		while self.is_running == True:
			if self.format_text == '2/4':
				if counter == 0 or counter%2 == 0:
					playsound('music/highmet.wav')
				else:
					playsound('music/lowmet.wav')
				sleep(bpm)
			elif self.format_text == '3/4':
				if counter == 0 or counter%3 == 0:
					playsound('music/highmet.wav')
				else:
					playsound('music/lowmet.wav')
				sleep(bpm)
			elif self.format_text == '4/4':
				if counter == 0 or counter%4 == 0:
					playsound('music/highmet.wav')
				else:
					playsound('music/lowmet.wav')
				sleep(bpm)
			elif self.format_text == '6/8':
				if counter == 0 or counter%6 == 0:
					playsound('music/highmet.wav')
				elif counter%3 == 0:
					playsound('music/met.wav')
				else:
					playsound('music/lowmet.wav')
				sleep(bpm/3)
			else:
				pass

			counter += 1
		raise Exception("Thread Terminated")

class BandApp(App):

	def build(self):
		pass

if __name__ == '__main__':
	BandApp().run()