# -*- coding: utf-8 -*-
from datetime import timedelta
from odoo import models, fields, api, exceptions, _

class OvSalaryPackage(models.Model):
    _name = 'ov.salarypackage'
    _inherit = ['mail.thread','mail.activity.mixin']

    #name = fields.Char("Name")
    name = fields.Selection([
        ('SCDSGSO18','SCDSGSO18'),
        ('SCDSGSO213','SCDSGSO213'),
        ('SCDSGSO323','SCDSGSO323'),
        ('SCDSGSS1','SCDSGSS1'),
        ('SCDSGSSO1','SCDSGSSO1'),
        ('SCMMYSO2145','SCMMYSO2145'),
        ('SCMMYSO2145P','SCMMYSO2145P'),
        ('SCMMYSS2365','SCMMYSS2365'),
        ('SCMMYSS2365P','SCMMYSS2365P'),
        ('SCMMYSSO2255','SCMMYSSO2255'),
        ('SCMMYSSO2255P','SCMMYSSO2255P'),
        ('SCMMYSO145','SCMMYSO145'),
        ('SCMMYSO145P','SCMMYSO145P'),
        ('SCMMYSS1265','SCMMYSS1265'),
        ('SCMMYSS1265P','SCMMYSS1265P'),
        ('SCMMYSSO1155','SCMMYSSO1155'),
        ('SCMMYSSO1155P','SCMMYSSO1155P'),
        ('SCMSGSO2100','SCMSGSO2100'),
        ('SCMSGSO2100P','SCMSGSO2100P'),
        ('SCMSGSS1','SCMSGSS1'),
        ('SCMSGSS1P','SCMSGSS1P'),
        ('SCMSGSSO1','SCMSGSSO1'),
        ('SCMSGSSO1P','SCMSGSSO1P'),
        ('SCMSGSSS1','SCMSGSSS1'),
        ('SCMSGSSS1P','SCMSGSSS1P'),
        ], "Salary Code", required=True,track_visibility='always')
    #job_title = fields.Many2one('hr.job','Job Title',)
    #country_id = fields.Many2one("res.country", "Country")
    type_of_package = fields.Selection([
        ('daily','Daily'),
        ('monthly','Monthly'),
        ], "Salary Scheme", help='Type of appoinment i.e: Full-Time/Part-Time')
    payment_schedule = fields.Selection([
        ('daily','Daily'),
        ('monthly','Monthly'),
        ], "Salary Scheme", help='Type of appoinment i.e: Full-Time/Part-Time')
    time = fields.Selection([
        ('730am730pm','7.30 am – 7.30 pm'),
        ('800am800pm','8.00 am – 8.00 pm'),
        ('730pm730am','7.30 pm – 7.30 am'),
        ('800pm800am','8.00 pm – 8.00 am'),
        ], "Shift")
    basic_salary = fields.Float("Basic Salary")
    ot = fields.Float("Overtime")
    mvc = fields.Float("Monthly Variable Component")
    pis = fields.Float("Productivity Incentive")
    awbr = fields.Float("Attendance & Bonus Reward")
    #gross_salary = fields.Float("Gross Salary",compute='_gross_amount',store=True)

    # @api.one
    # def _get_total_gross(self):
    #     total = self.gross_salary
    #     try:
    #         total = sum(self.basic_salary+self.ot+self.mvc+self.pis+self.awbr)
    #     except:
    #         raise "Calculation Error"
    #         #salary.gross_salary = sum(salary.basic_salary+salary.ot+salary.mvc+salary.pis+salary.awbr)
    #     return total

    # @api.one
    # def _gross_amount(self):
    #     for gross in self:
    #         comm_total = self.basic_salary+self.ot+self.mvc+self.pis+self.awbr
    #         gross.update({'gross_salary': comm_total })

    @api.multi
    @api.onchange('name')
    def onchange_name(self):
        if self.name:
            if ((self.name == 'SCDSGSO18') or (self.name == 'SCDSGSO213') or (self.name == 'SCDSGSO323') or (self.name == 'SCDSGSS1') or (self.name == 'SCDSGSSO1')):
                self.type_of_package = 'daily'
            else:
                self.type_of_package = 'monthly'


import time
import copy
import torch
import random
import argparse
import multiprocessing

import numpy             as np

# Custom imports
from data_loader             import data_loader
from utils                   import save_checkpoint, load_checkpoint, accuracy, activations, mi, mi_edge

# Model imports
from vgg16.model.vgg16         import VGG16    as vgg
from resnet56.model.resnet56   import RESNET56 as resnet56
from resnet50.model.resnet50   import RESNET50 as resnet50

### Fixed Backend To Force Dataloader To Be Consistent ###
torch.backends.cudnn.deterministic = True
random.seed(1)
torch.manual_seed(1)
torch.cuda.manual_seed(1)
np.random.seed(1)

def idxs(labels, samples_per_class, dataset):
    unq_lab = np.unique(labels)
    op_idxs = None
    for lab in unq_lab:
        if op_idxs is None:
            op_idxs = np.random.choice(np.where(labels == lab)[0], samples_per_class)
        else:
            op_idxs = np.concatenate((op_idxs, np.random.choice(np.where(labels == lab)[0], samples_per_class)))

    # Verify
    for idx in np.arange(0, op_idxs.shape[0], samples_per_class):
        assert(np.unique(labels[op_idxs[idx:idx+samples_per_class]]).shape[0] == 1)

    np.save(dataset+'_idxs_'+str(samples_per_class)+'.npy', op_idxs)

def terp(min_value, max_value, value):
    return ((value - min_value)/(max_value - min_value))

#### Conditional Mutual Information Computation ####
def cmi(data):
    clusters, c1_op, child, p1_op, num_layers, labels, labels_children, phi_type, est_type = data
    I_value = np.zeros((clusters,))

    for group_1 in range(clusters):
        if est_type == 'mst':
            I_value[group_1] += mi(X=c1_op[str(num_layers)][:, np.where(labels_children[str(num_layers)]==child)[0]], Y=p1_op[str(num_layers)][:, np.where(labels[str(num_layers)]==group_1)[0]], Z=p1_op[str(num_layers)][:, np.where(labels[str(num_layers)]!=group_1)[0]])

        else:
            I_value[group_1] += mi_edge(X=c1_op[str(num_layers)][:, np.where(labels_children[str(num_layers)]==child)[0]], Y=p1_op[str(num_layers)][:, np.where(labels[str(num_layers)]==group_1)[0]], Z=p1_op[str(num_layers)][:, np.where(labels[str(num_layers)]!=group_1)[0]], phi_type=phi_type)

    # END FOR

    return I_value

#### CMI group-based computation ####
def cmi_group(nlayers, I_parent, p1_op, c1_op, labels, labels_children, clusters, clusters_children, cores, phi_type, init_weights, children_key, max_value, min_value, est_type):

    print("----------------------------------")
    print("Begin Execution of Group-based CMI estimation")
    print("Estimator Type: %s, Phi Type: %s"%(est_type, phi_type))

    pool = multiprocessing.Pool(cores)

    for num_layers in range(nlayers):
        data = []

        for child in range(clusters_children[num_layers]):
            data.append([clusters[num_layers], c1_op, child, p1_op, num_layers, labels, labels_children, phi_type, est_type])

        # END FOR

        data = tuple(data)
        ret_values = pool.map(cmi, data)

        for child in range(clusters_children[num_layers]):

            if 'weight' in phi_type:
                network_weights = terp(min_value, max_value, init_weights[children_key[num_layers]][child, :].detach().cpu().numpy())
                assert(np.max(network_weights) <= 1 and np.min(network_weights)>=0)

                if 'conv' in children_key[num_layers]:
                    network_weights = np.mean(network_weights, (1,2))

                mean_step_size = int(network_weights.shape[0]/clusters[num_layers])

                for elements in  np.arange(0,network_weights.shape[0], mean_step_size):
                    if phi_type == 'weight' or phi_type == 'activation_weight':
                        I_parent[str(num_layers)][child, int(elements/mean_step_size)] = np.multiply(np.mean(network_weights[elements:elements + mean_step_size]), ret_values[child][int(elements/mean_step_size)])

                    elif phi_type == 'exp_weight' or phi_type == 'exp_activation_weight':
                        I_parent[str(num_layers)][child, int(elements/mean_step_size)] = np.multiply(np.exp(-np.mean(network_weights[elements:elements + mean_step_size])**2/2.), ret_values[child][int(elements/mean_step_size)])

                    elif phi_type == 'weight_sq' or phi_type == 'activation_weight_sq':
                        I_parent[str(num_layers)][child, int(elements/mean_step_size)] = np.multiply(np.mean(network_weights[elements:elements + mean_step_size])**2, ret_values[child][int(elements/mean_step_size)])

            else:
                I_parent[str(num_layers)][child,:] = ret_values[child]

        # END FOR

    # END FOR

#### Main Code Executor
def calc_cmi(model, dataset, parent_key, children_key, clusters, clusters_children, weights_dir, cores, name_postfix, samples_per_class, dims, phi_type, est_type, device_ids):

    #### Load Model ####
    init_weights   = load_checkpoint(weights_dir+'logits_best.pkl')

    #### Compute the min and max range of weight values ####
    min_value = 1000000
    max_value = 0
    for key in init_weights.keys():
        if np.max(init_weights[key].detach().reshape(-1).cpu().numpy()) > max_value:
            max_value = np.max(init_weights[key].detach().reshape(-1).cpu().numpy())

        if np.min(init_weights[key].detach().reshape(-1).cpu().numpy()) < min_value:
            min_value = np.min(init_weights[key].detach().reshape(-1).cpu().numpy())

    # Check if GPU is available (CUDA)
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    #### Load Network ####
    if model == 'vgg':
        model = vgg(num_classes=dims).to(device)

    elif model == 'resnet56':
        model = resnet56(num_classes=dims).to(device)

    elif model == 'resnet50':
        model = resnet50(num_classes=dims).to(device)

    else:
        print('Invalid model selected')

    # END IF

    ## Extend model to multi-gpu setup
    #model = torch.nn.DataParallel(model, device_ids=device_ids)

    model.load_state_dict(init_weights)
    model.eval()

    #### Load Data ####
    trainloader, testloader, extraloader, train_data, test_data = data_loader(dataset, 64, samples_per_class)

    # Original Accuracy
    acc = 100.*accuracy(model, testloader, device)
    print('Accuracy of the original network: %f %%\n' %(acc))

    nlayers         = 1
    labels          = {}
    labels_children = {}
    children        = {}
    I_parent        = {}

    # Obtain Activations
    print("----------------------------------")
    print("Collecting activations from layers")

    act_start_time = time.time()
    p1_op = {}
    c1_op = {}

    unique_keys = np.unique(np.union1d(parent_key, children_key)).tolist()
    act         = {}
    lab         = {}

    # Since we collect activations from extraloader, we already use only a small subset of training data
    for item_key in unique_keys:
        act[item_key], lab[item_key] = activations(extraloader, model, device, item_key)

    for item_idx in range(len(parent_key)):
        p1_op[str(item_idx)] = copy.deepcopy(act[parent_key[item_idx]])
        c1_op[str(item_idx)] = copy.deepcopy(act[children_key[item_idx]])

    act_end_time   = time.time()

    print("Time taken to collect subset of activations is : %f seconds\n"%(act_end_time - act_start_time))

    #idxs(lab['conv2.weight'], samples_per_class, 'IMAGENET')

    del act, lab

    labels[str(0)]          = np.zeros((init_weights[children_key[0]].shape[1],))
    labels_children[str(0)] = np.zeros((init_weights[children_key[0]].shape[0],))
    I_parent[str(0)]        = np.zeros((clusters_children[0], clusters[0]))

    #### Compute Clusters/Groups ####
    print("----------------------------------")
    print("Begin Clustering Layers: %s and %s\n"%(parent_key[0], children_key[0]))

    # Parents
    if p1_op[str(0)].shape[1] == clusters[0]:
        labels[str(0)] = np.arange(clusters[0])

    else:
        if labels[str(0)].shape[0]%clusters[0] > 0:
            print('Cluster assignments are not balanced. Please retry with a different cluster size.')
            exit(1)
        else:
            labels[str(0)] = np.repeat(np.arange(clusters[0]), labels[str(0)].shape[0]/clusters[0])

    # END IF

    # Children
    if c1_op[str(0)].shape[1] == clusters_children[0]:
        labels_children[str(0)] = np.arange(clusters_children[0])

    else:
        if labels_children[str(0)].shape[0]%clusters_children[0] > 0:
            print('Cluster assignments are not balanced. Please retry with a different cluster size.')
            exit(1)
        else:
            labels_children[str(0)] = np.repeat(np.arange(clusters_children[0]), labels_children[str(0)].shape[0]/clusters_children[0])

    # END IF

    ### Compute CMI ####
    cmi_start_time = time.time()
    cmi_group(nlayers, I_parent, p1_op, c1_op, labels, labels_children, clusters, clusters_children, cores, phi_type, init_weights, children_key, max_value, min_value, est_type)
    cmi_end_time = time.time()

    print('Time taken to compute CMI: %s, Phi: %s is %f'%(est_type, phi_type, cmi_end_time - cmi_start_time))

    #### Save results ####
    np.save('results/'+weights_dir+'I_parent_'+name_postfix+'.npy', I_parent)
    np.save('results/'+weights_dir+'Labels_'+name_postfix+'.npy', labels)
    np.save('results/'+weights_dir+'Labels_children_'+name_postfix+'.npy', labels_children)

    return cmi_end_time - cmi_start_time

if __name__=='__main__':

    """"
    Sample Input values

    parent_key        = ['conv1.weight','conv2.weight','conv3.weight','conv4.weight','conv5.weight','conv6.weight','conv7.weight','conv8.weight','conv9.weight', 'conv10.weight','conv11.weight','conv12.weight','conv13.weight', 'linear1.weight']
    children_key      = ['conv2.weight','conv3.weight','conv4.weight','conv5.weight','conv6.weight','conv7.weight','conv8.weight','conv9.weight','conv10.weight','conv11.weight','conv12.weight','conv13.weight','linear1.weight', 'linear3.weight']
    alg               = '1a_group'
    clusters          = [8,8,8,8,8,8,8,8,8,8,8,8,8,8]
    clusters_children = [8,8,8,8,8,8,8,8,8,8,8,8,8,8]

    load_weights  = '/z/home/madantrg/Pruning/results/CIFAR10_VGG16_BN_BATCH/0/logits_best.pkl'
    save_data_dir = '/z/home/madantrg/Pruning/results/CIFAR10_VGG16_BN_BATCH/0/'
    """

    parser = argparse.ArgumentParser()

    parser.add_argument('--model',                type=str)
    parser.add_argument('--dataset',              type=str)
    parser.add_argument('--weights_dir',          type=str)
    parser.add_argument('--cores',                type=int)
    parser.add_argument('--dims',                 type=int, default=10)
    parser.add_argument('--key_id',               type=int)
    parser.add_argument('--samples_per_class',    type=int,      default=250)
    parser.add_argument('--parent_clusters',      nargs='+',     type=int,       default=[8])
    parser.add_argument('--children_clusters',    nargs='+',     type=int,       default=[8])
    parser.add_argument('--name_postfix',         type=str)
    parser.add_argument('--phi_type',             type=str)
    parser.add_argument('--est_type',             type=str, default='edge')
    parser.add_argument('--device_ids',           nargs='+',     type=int,       default=[0])
    parser.add_argument('--expt_rerun',           type=int, default=1)

    args = parser.parse_args()

    print('Selected key id is %d'%(args.key_id))

    if args.model == 'vgg':
        layer_names = np.load('vgg16_dict.npy', allow_pickle=True).item()
        parents     = layer_names['parents']
        children    = layer_names['children']

    elif args.model == 'resnet56':
        layer_names = np.load('resnet56_dict.npy', allow_pickle=True).item()
        parents     = layer_names['parents']
        children    = layer_names['children']

        if args.key_id <= 20:
            args.parent_clusters[0] = 16
        if args.key_id <= 19:
            args.children_clusters[0] = 16
        if args.key_id > 20 and args.key_id <= 38:
            args.parent_clusters[0] = 32
        if args.key_id > 19 and args.key_id <= 37:
            args.children_clusters[0] = 32
        if args.key_id > 38:
            args.parent_clusters[0] = 64
        if args.key_id > 37:
            args.children_clusters[0] = 64

    elif args.model == 'resnet50':
        layer_names = np.load('resnet50_dict.npy', allow_pickle=True).item()
        parents     = layer_names['parents']
        children    = layer_names['children']

    if args.key_id == len(parents):
        args.children_clusters = [args.dims]

    cmi_time = []

    for rerun in range(args.expt_rerun):
        cmi_time.append(calc_cmi(args.model, args.dataset, [parents[args.key_id-1]], [children[args.key_id-1]], args.parent_clusters, args.children_clusters, args.weights_dir, args.cores, args.name_postfix +'_'+parents[args.key_id-1]+'_'+children[args.key_id-1], args.samples_per_class, args.dims, args.phi_type, args.est_type, args.device_ids))

    print('Average CMI runtime over %d trials is %f'%(args.expt_rerun, np.mean(cmi_time)))
    print('Std. of CMI runtime over %d trials is %f'%(args.expt_rerun, np.std(cmi_time)))
    print('CMI run times ', np.array(cmi_time))

    print('Code Execution Complete')


# -*- coding: utf-8 -*-
"""
Created on Mon Apr 27 16:49:13 2020

@author: alexi
"""

#http://codeforces.com/contest/1342/problem/A --- Alexis Galvan

def road_zero():

    cases = int(input())
    numbers = []
    prices = []

    for i in range(cases):
        numbers.append(list(map(int, input().split())))
        prices.append(list(map(int, input().split())))

    for i in range(len(numbers)):
        if prices[i][0] == 0:
            print(0)
            continue

        output = 0
        minimum = min(abs(numbers[i][0]),abs(numbers[i][1]))
        best = min((minimum*prices[i][0])*2,minimum*prices[i][1])

        if abs(numbers[i][0]) == minimum:
            numbers[i][0] = 0
        else:
            numbers[i][0] -= minimum

        if abs(numbers[i][1]) == minimum:
            numbers[i][1] = 0
        else:
            numbers[i][1] -= minimum

        output += best

        if numbers[i][0] == 0 and numbers[i][1] == 0:
            print(output)
            continue

        maximum = max(numbers[i][0],numbers[i][1])
        output += (maximum*(prices[i][0]))

        print(output)

road_zero()


"""
Constructs Hedge Based Cryptocurrency Uncertainty Index
"""

import pandas as pd
import numpy as np
from tqdm import tqdm
from pathlib import Path
from typing import Optional, Union
from datetime import datetime
from transformers import (
    AutoTokenizer,
    AutoModelForSequenceClassification,
)
from transformers.pipelines import pipeline
from nlp.hedge_classifier.huggingface.inference import (
    RedditInferenceDataset,
)
from utils.logger import log
from utils import gen_date_chunks

# Config
DATE_FMT = "%Y-%m-%d"
TOKENIZER_KWARGS = {
    "padding": True,
    "truncation": True,
}

def construct_hedge_index(
    data_source: Union[str, Path],
    start_date: Union[str, datetime],
    end_date: Union[str, datetime],
    hf_model_name: str,
    hf_model_ckpt: Optional[str] = None,
    name: str = "hedge",
    granularity: str = "week",
) -> pd.DataFrame:
    # Load All Data
    log.info(f"Constructing Dataset from: {data_source}")
    red_df = RedditInferenceDataset(data_source=data_source)
    # Set up HF pipeline
    log.info(
        f"Setting up Hugging Face Pipeline using: {hf_model_ckpt if hf_model_ckpt else hf_model_name}"
    )
    tokenizer = AutoTokenizer.from_pretrained(
        hf_model_name,
        normalization=True,
    )
    model = AutoModelForSequenceClassification.from_pretrained(
        hf_model_ckpt if hf_model_ckpt else hf_model_name
    )
    pipe = pipeline(
        task="text-classification",
        model=model,
        tokenizer=tokenizer,
    )
    # Get weekly batches
    if isinstance(start_date, str):
        start_date = datetime.strptime(start_date, DATE_FMT)
        end_date = datetime.strptime(end_date, DATE_FMT)

    date_chunks = gen_date_chunks(
        start_date=start_date, end_date=end_date, granularity=granularity
    )
    # Store All weekly counts
    ucry_hedge_raw = []
    # Subset out relevant data from each weekly date chunk
    log.info(
        f"Constructing Hedge based UCRY index from start={start_date} to end={end_date} ..."
    )
    for start, end in tqdm(date_chunks):
        start_, end_ = (
            datetime.strftime(start, DATE_FMT),
            datetime.strftime(end, DATE_FMT),
        )
        weekly_data = red_df.date_subset(start_, end_)
        # Perform inference using HF pipeline
        hedge_pipe = pipe(weekly_data, **TOKENIZER_KWARGS)
        res = [
            1 if res.get("label", None) == "LABEL_1" else 0
            for res in tqdm(iter(hedge_pipe), leave=True)
        ]
        # Store results for this week
        ucry_hedge_raw.append(
            {
                "type": name,
                "start_date": start_,
                "end_date": end_,
                "doc_count": np.sum(res),
            }
        )
    log.info("Computing Index Values ..")
    ucry_hedge_df = pd.DataFrame(ucry_hedge_raw)
    mu_1 = ucry_hedge_df["doc_count"].mean()
    sig_1 = ucry_hedge_df["doc_count"].std()
    ucry_hedge_df["index_value"] = ((ucry_hedge_df["doc_count"] - mu_1) / sig_1) + 100
    return ucry_hedge_df

# Test
# START_DATE = "2014-01-01"
# END_DATE = "2014-06-30"
# MODEL_CHECKPOINT = "nlp/hedge_classifier/models/best_model"
# MODEL_NAME = "vinai/bertweet-base"

# test_res = construct_hedge_index(
#     data_source="nlp/topic_models/data/processed_reddit",
#     start_date=START_DATE,
#     end_date=END_DATE,
#     hf_model_name=MODEL_NAME,
#     hf_model_ckpt=MODEL_CHECKPOINT,
#     name='bertweet-hedge'
# )


import os
import sys
import requests
import logging
import time

import json
import pandas as pd
import numpy as np

from concurrent.futures import ProcessPoolExecutor

from git import Git

class FPL_Gameweek:
    """ Get the Gameweek state """

    def __init__(self, logger, season_data):
        """
        Args:
            logger (logging.Logger): Logging pacakge
            season_data (int): Season year
        """
        self.season = season_data['season']

        self.root = f'data/fpl_official/{self.season}-{self.season % 2000 + 1}/gameweek/'
        if not os.path.exists(self.root):
            os.makedirs(self.root)

        self.current_gw, self.players = self.get_fpl_metadata()
        self.players.to_csv(os.path.join(self.root, 'player_ids.csv'))

        self.logger = logger

    def get_fpl_metadata(self):
        """ Request the FPL API

        Returns:
            (tuple): Next GW and player ids
        """
        url = 'https://fantasy.premierleague.com/api/bootstrap-static/'
        res = requests.get(url).json()

        # Get current gameweek
        current_gw = self.get_current_gw(res['events'])
        if not os.path.exists(os.path.join(self.root, f'{current_gw}')):
            os.mkdir(os.path.join(self.root, f'{current_gw}'))

        # Get player ids
        cols = ["id", "first_name", "second_name", "team"]
        players = pd.DataFrame(res['elements'])[cols]
        players = players.set_index("id")

        return current_gw, players

    def get_current_gw(self, events):
        """ Get the next gameweek to be played in the EPL

        Args:
            events (json): FPL API response

        Returns:
            (int): Next gameweek
        """
        for idx, gw in enumerate(events):
            if gw['is_current']:
                return idx + 1

    def sample_ranks(self):
        """Sample every rank to get metadata"""
        # Data management
        transfer_strategy = self.players.copy()
        transfer_strategy.loc[:, [
            'Top_100_in', 'Top_1K_in', 'Top_10K_in', 'Top_50K_in',
            'Top_100K_in', 'Top_250K_in', 'Top_500K_in', 'Top_100_out',
            'Top_1K_out', 'Top_10K_out', 'Top_50K_out', 'Top_100K_out',
            'Top_250K_out', 'Top_500K_out']] = 0
        self.players.loc[:, [
            'Top_100', 'Top_1K', 'Top_10K', 'Top_50K', 'Top_100K',
            'Top_250K', 'Top_500K']] = 0
        captain = self.players.copy()
        chip_strategy = pd.DataFrame(index=[
            'wildcard', 'freehit', 'bboost', '3xc'])
        chip_strategy.loc[:, [
            'Top_100', 'Top_1K', 'Top_10K', 'Top_50K',
            'Top_100K', 'Top_250K', 'Top_500K']] = 0
        hit_strategy = pd.DataFrame(index=['transfers'])
        hit_strategy.loc[:, [
            'Top_100', 'Top_1K', 'Top_10K', 'Top_50K',
            'Top_100K', 'Top_250K', 'Top_500K']] = 0

        # Sample ~10% of players teams
        range_limits = [
            ('Top_100', 0, 100, 75),
            ('Top_1K', 100, 1000, 500),
            ('Top_10K', 1000, 10000, 2000),
            ('Top_50K', 10000, 50000, 4000),
            ('Top_100K', 50000, 100000, 5000),
            ('Top_250K', 100000, 250000, 15000),
            ('Top_500K', 250000, 500000, 25000)
            ]

        for col, min_rank, max_rank, n_samples in range_limits:
            self.logger.info(f"Starting to scrape {col} ranks")
            fpl_ranks = np.random.randint(min_rank, max_rank, n_samples)

            # Concurrent API Requests
            with ProcessPoolExecutor(max_workers=8) as executor:
                team_data = list(
                    executor.map(self.get_fpl_strategy, fpl_ranks))

            for team, cap, chip, transfer in team_data:
                # Ownership
                for p in team:
                    self.players.loc[p, col] += 1
                if cap is not None and len(cap):
                    captain.loc[cap[0], col] += 1
                # Chip strategy
                if chip is not None:
                    chip_strategy.loc[chip, col] += 1
                # Transfer strategy
                if transfer is not None and len(transfer):
                    transfer_in, transfer_out = transfer
                    for p_in, p_out in zip(transfer_in, transfer_out):
                        transfer_strategy.loc[p_in, col+'_in'] += 1
                        transfer_strategy.loc[p_out, col+'_out'] += 1
                    hit_strategy.loc['transfers', col] += len(transfer_in)

            self.players.loc[:, col] = (
                self.players.loc[:, col] / n_samples * 100)
            captain.loc[:, col] = captain.loc[:, col] / n_samples * 100
            chip_strategy.loc[:, col] = (
                chip_strategy.loc[:, col] / n_samples * 100)
            hit_strategy.loc[:, col] = hit_strategy.loc[:, col] / n_samples
            transfer_strategy.loc[:, col+'_in'] = (
                transfer_strategy.loc[:, col + '_in'] / n_samples * 100)
            transfer_strategy.loc[:, col+'_out'] = (
                transfer_strategy.loc[:, col + '_out'] / n_samples * 100)

        self.players.to_csv(
            os.path.join(self.root, f"{self.current_gw}/player_ownership.csv"))
        captain.to_csv(
            os.path.join(self.root, f"{self.current_gw}/captain.csv"))
        chip_strategy.to_csv(
            os.path.join(self.root, f"{self.current_gw}/chip_strategy.csv"))
        hit_strategy.to_csv(
            os.path.join(self.root, f"{self.current_gw}/hit_strategy.csv"))
        transfer_strategy.to_csv(
            os.path.join(self.root, f"{self.current_gw}/transfer_strategy.csv"))

    def get_fpl_teamid(self, rank):
        """ Get the FPL Team ID based on the rank

        Args:
            rank (int): Manager rank

        Returns:
            int: FPL Team ID
        """
        # Scrape the correct page
        page = rank // 50 + 1
        place = rank % 50
        url = f'https://fantasy.premierleague.com/api/leagues-classic/314/standings/?page_standings={page}'
        res = requests.get(url)
        return res.json()['standings']['results'][place]['entry']

    def get_fpl_team(self, team_id):
        """ Get the ids of player in a team

        Args:
            team_id (int): FPL Team ID

        Returns:
            (list): FPL Player IDs of the players selected
        """
        res = requests.get(
            f'https://fantasy.premierleague.com/api/entry/{team_id}/event/{self.current_gw}/picks/'
            ).json()
        return (
            [i['element'] for i in res['picks']],
            [i['element'] for i in res['picks'] if i['multiplier'] > 1])

    def get_fpl_strategy(self, rank):
        """ Scrape FPL manager metadata

        Args:
            rank (int): FPL rank

        Returns:
            (tuple): strategy
        """
        attempts = 3
        while attempts:
            try:
                team_id = self.get_fpl_teamid(rank)
                fpl_team, fpl_cap = self.get_fpl_team(team_id)
                fpl_chips = self.get_fpl_chips(team_id)
                fpl_transfers = self.get_fpl_transfers(team_id)
                return fpl_team, fpl_cap, fpl_chips, fpl_transfers
            except:
                attempts -= 1
                if not attempts:
                    self.logger.warning(
                        f"API Call to rank {rank} failed after 3 attempts.")
                    return [], [], [], []

                self.logger.warning(
                    f'API Call failed, retrying in 3 seconds! Rank: {rank}')
                time.sleep(3)

    def get_fpl_chips(self, team_id):
        """ Get the GW when a manager used a chip

        Args:
            team_id (int): Manager id

        Returns:
            (int): Gameweek
        """
        res = requests.get(
            f'https://fantasy.premierleague.com/api/entry/{team_id}/history/'
            ).json()['chips']
        if res == []:
            return None

        if res[-1]['event'] == self.current_gw:
            return res[-1]['name']
        else:
            return None

    def get_fpl_transfers(self, team_id):
        """ Get the transfer managers did

        Args:
            team_id (int): Manager id

        Returns:
            (tuple): FPL player ids
        """
        res = requests.get(
            f'https://fantasy.premierleague.com/api/entry/{team_id}/transfers'
            ).json()

        transfer_in = []
        transfer_out = []

        for transfers in res:
            if transfers['event'] == self.current_gw:
                transfer_in.append(transfers['element_in'])
                transfer_out.append(transfers['element_out'])
            else:
                return transfer_in, transfer_out

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(message)s")
    logger: logging.Logger = logging.getLogger(__name__)

    with open('info.json') as f:
        season_data = json.load(f)

    fplg = FPL_Gameweek(logger, season_data)
    fplg.sample_ranks()

    if len(sys.argv) > 1:
        logger.info("Saving data ...")
        Git()
    else:
        print("Local")


##############
# Setup Django

import django
django.setup()

#############
# Test proper

import threading
import time
import pytest
from django.db import DatabaseError, connection, transaction
from django.db.models import F, Subquery
from app.models import Sock

@pytest.mark.django_db
def test_serializable_count_can_fail():
    def create():
        Sock.objects.all().delete()
        Sock.objects.create(id_a=1, id_b=1, colour='black', hits=1)
        Sock.objects.create(id_a=2, id_b=2, colour='white', hits=1)

    create_thread = threading.Thread(target=create)
    create_thread.start()
    create_thread.join()

    barrier_1 = threading.Barrier(2)
    barrier_2 = threading.Barrier(2)
    barrier_3 = threading.Barrier(2)

    def serializable_a():
        with transaction.atomic():
            cursor = connection.cursor()
            cursor.execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')
            Sock.objects.all().count()
            barrier_1.wait()
            Sock.objects.get(id_a=2).save()
        barrier_2.wait()

    caught = None
    def serializable_b():
        nonlocal caught
        try:
            with transaction.atomic():
                cursor = connection.cursor()
                cursor.execute('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE')
                Sock.objects.all().count()
                barrier_1.wait()
                Sock.objects.get(id_a=1).save()
                barrier_2.wait()
        except Exception as exception:
            caught = exception

    serializable_a_thread = threading.Thread(target=serializable_a)
    serializable_a_thread.start()
    serializable_b_thread = threading.Thread(target=serializable_b)
    serializable_b_thread.start()

    serializable_a_thread.join()
    serializable_b_thread.join()

    assert isinstance(caught, DatabaseError)
    assert 'could not serialize access due to read/write dependencies among transactions' in caught.args[0]


import datetime
x = datetime.datetime.now()
print(x)

def my_function(fname):
    print(fname + " Refsnes")

my_function("Emil")
my_function("Tobias")
my_function("Linus")

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def myfunc(self):
        print("Hello my name is " + self.name)

p1 = Person("John", 36)
p1.myfunc()

thisdict = {
    "brand": "Ford",
    "model": "Mustang",
    "year": 1964
}
thislist = ["monkagiga", 123]
thistuple = ([1, 2], "haha")
tuplea = ({
    "brand": "Ford",
    "model": "Mustang",
    "year": 1964
}, 5)


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

def plotTriangleMesh(mesh, **kwargs):
    '''
    It requires a mesh with a connectivity in it and the coordinates.
    If singular values are needed to be plotted that those must be specified in the
    dictionary as 'spatial' those will than be added to the plot as well

    Input: mesh['crd'] should be 3xn where n > = 3
    '''

    if len(kwargs) == 0:
        scalar = np.ones((mesh['crd'].shape[1], 1))
    else:
        scalar = kwargs['spatial']

    fig = plt.figure()
    ax = fig.gca(projection='3d')

    triang = mtri.Triangulation(x=mesh['crd'][0, :].T, y=mesh['crd'][1, :].T, triangles=mesh['con'].T)

    ax.plot_trisurf(triang, mesh['crd'][2, :])

    plt.show()

def plotSurface(mesh, **kwargs):
    '''
    It will plot the surface of any polygon that has been meshed up. Plotting of spatial data is also
    allowed and this is accomplished by providing a set of singular data points for each being plotted of
    the surface of the polygon. If spatial data is not provided than the surface will all be plotted the
    same color 'blue'

    Input: mesh a dictionary containing a set of coordinates/nodal value and a set of connectivity of
           each face

           mesh['crd'] should be a 3xn where n > = 3. The x, y, and z coords are located at
           mesh['crd'][0,:], mesh['crd'][1,:], and mesh['crd'][2,:] respectively. If a 2d surface is to
           be plotted other matplotlib functions might be more appropriate, but one can do it here by
           setting all of the out of plane coordinates to be equal to a singular constant.

           mesh['con'] should be a a 3xn where n >= 1. The connectivity should follow a standard clockwise
           or counter-clockwise order around the face of the surface polygon. If it is not then no
           insurance can be made that junk won't be plotted that doesn't represent what one was hoping to
           get out.

           kwargs input: "scalar" - the spatial data corresponding to what one wants to plot on each fac
                             of the surface.
                         "colorMap" - the color map that one wants to use with the scalar values, default
                             value is jet
                         Other inputs are the same as those used in the Poly3DCollection object, so
                             "facecolors", "edgecolors", "alpha", "zsort".
                                 The facecolors arg is replaced with the scalar data mapping if that is
                                 provided.

    Output: a poly3dcollection is outputted
    '''

    if mesh['crd'].shape[0] != 3 or mesh['crd'].shape[1] < 3:
        print('The inputted mesh[''crd''] is not correct. mesh[''crd''] needs to have dim of 3xn where n>=3')
        raise ValueError('The inputted mesh[''crd''] is not correct. mesh[''crd''] needs to have dim of 3xn where n>=3')

    if mesh['con'].shape[0] is not 3:
        print('The inputted mesh[''con''] is not correct. mesh[''con''] needs to have dim of 3x1 where n>=3')
        raise ValueError('The inputted mesh[''con''] is not correct. mesh[''con''] needs to have dim of 3xn where n>=1')

    condim = np.atleast_2d(mesh['con']).shape

    # Check to see if connectivity was originally one dimension
    # and fix it if it to be 2d and in the right order
    if condim[0] == 1 and condim[1] == 3:
        condim = condim([1,0])
        mesh['con'] = np.atleast_2d(mesh['con']).T

    scalars = kwargs.pop('scalar', None)
    colormap = kwargs.pop('colorMap', None)
    facecolors = kwargs.pop('facecolors', None)

    # set up the color options

    if colormap is None:
        colormap = cm.jet

    if scalars is None and facecolors is None:
        facecolors = colormap(np.zeros(condim[1]))
    elif scalars is not None and facecolors is None:
        N = scalars / scalars.max()
        facecolors = colormap(N)
    elif scalars is None and facecolors is not None:
        N = np.random.rand(condim[1])
        print(N.shape)
        facecolors = colormap(N)

    print(facecolors)

    '''
    Creating the polygon/surface vertices
    poly_verts is initially initiallized to be a zeros 3d matrix
    '''

    poly_verts = np.zeros((condim[1], 3, 3))

    ind = 0

    minx = np.max(mesh['crd'][0, :])
    maxx = np.min(mesh['crd'][0, :])
    miny = np.max(mesh['crd'][1, :])
    maxy = np.min(mesh['crd'][1, :])
    minz = np.max(mesh['crd'][2, :])
    maxz = np.min(mesh['crd'][2, :])

    for con in mesh['con'].T:

        x = mesh['crd'][0, np.int_(con)]
        y = mesh['crd'][1, np.int_(con)]
        z = mesh['crd'][2, np.int_(con)]

        tminx = np.min(x)
        tmaxx = np.max(x)
        tminy = np.min(y)
        tmaxy = np.max(y)
        tminz = np.min(z)
        tmaxz = np.max(z)

        if tminx < minx:
            minx = tminx
        if tminy < miny:
            miny = tminy
        if tminz < minz:
            minz = tminz

        if tmaxx > minx:
            maxx = tmaxx
        if tmaxy > maxy:
            maxy = tmaxy
        if tmaxz > maxz:
            maxz = tmaxz

        vertices = np.asarray(list(zip(x, y, z)))

        poly_verts[ind, :, :] = vertices

        ind += 1

    coll = Poly3DCollection(poly_verts, facecolors=facecolors, **kwargs)

    fig = plt.figure()
    ax = fig.gca(projection='3d')
    ax.add_collection(coll)

    xlim = [minx * 1.1, maxx * 1.1]
    ylim = [miny * 1.1, maxy * 1.1]
    zlim = [minz * 1.1, maxz * 1.1]

    ax.set_xlim(xlim[0], xlim[1])
    ax.set_ylim(ylim[0], ylim[1])
    ax.set_zlim(zlim[0], zlim[1])
    ax.elev = 50

    plt.show()

    return coll

def plotPolygon(mesh, **kwargs):
    '''
    It will plot the surface of any polygon that has been meshed up. Plotting of spatial data is also
    allowed and this is accomplished by providing a set of singular data points for each being plotted of
    the surface of the polygon. If spatial data is not provided than the surface will all be plotted the
    same color 'blue'. Currently, only the following element types are taken in: standard linear
    tetrahedral, standard quadratic tetrahedral, and FePX quadratic tetrahedral element order. In the
    future, the following element types will be added standard 8 node brick element and 20 node brick
    element order.

    Input: mesh a dictionary containing a set of coordinates/nodal value and a set of connectivity of
           each face

           mesh['crd'] should be a 3xn where n > = 3. The x, y, and z coords are located at
           mesh['crd'][0,:], mesh['crd'][1,:], and mesh['crd'][2,:] respectively. If a 2d surface is to
           be plotted other matplotlib functions might be more appropriate, but one can do it here by
           setting all of the out of plane coordinates to be equal to a singular constant.

           mesh['con'] should be a a 4xn where n >= 1. The connectivity should follow a standard clockwise
           or counter-clockwise order around the face of the surface polygon. If it is not then no
           insurance can be made that junk won't be plotted that doesn't represent what one was hoping to
           get out.

           kwargs input: "scalar" - the spatial data corresponding to what one wants to plot on each
                             element
                         "colorMap" - the color map that one wants to use with the scalar values, default
                             value is jet
                         "fepx" - the element type used is fepx type
                         Other inputs are the same as those used in the Poly3DCollection object, so
                             "facecolors", "edgecolors", "alpha", "zsort".
                                 The facecolors arg is replaced with the scalar data mapping if that is
                                 provided.

    Output: a poly3dcollection is outputted
    '''

    condim = np.atleast_2d(mesh['con']).shape

    # Check to see if connectivity was originally one dimension
    # and fix it if it to be 2d and in the right order
    if condim[0] == 1 and condim[1] > 1:
        mesh['con'] = np.atleast_2d(mesh['con']).T

    numnode = mesh['con'].shape[0]

    print(numnode)

    if numnode == 4:
        elem = 'ltet'
    elif numnode == 10:
        etype = kwargs.pop('fepx', None)
        if etype is None:
            elem = 'qtet'
        else:
            elem = 'fepx'

#     scalar = kwargs.pop('scalars', None)

    mesh['con'] = getelemface(mesh['con'], elem)

    coll = plotSurface(mesh, **kwargs)

    return coll

def getelemface(con, eltype):
    '''
    It takes in the connectivity of the nodes of the element and returns the appropriate surface
    connectivity of the element. So if a tetrahedral element is taken in then the surface connectivity
    now is a [3 x 4n] array where n is the number of elements.

    Input: con - a [m x n] array where m is atleast 4 and corresponds to the number of nodes in the
                 element. Then n is the number of elements
           eltype - a string that describes the element type and is one of the following:
                    'ltet' - a standard linear tetrahedral element
                     'qtet' - a standard quadratic tetrahedral element
                     'fepx' - a quadratic tetrahedral element that corresponds to fepx propram input
    '''

    nelem = con.shape[1]

    surfcon = np.zeros((3, nelem*4))

    print(eltype)

    j = 0
    ind = 0

    for i in con.T:

        if eltype == 'ltet' or eltype == 'qtet':
            surfcon[:, j] = i[[0, 1, 2]]
            surfcon[:, j+1] = i[[0, 1, 3]]
            surfcon[:, j+2] = i[[1, 2, 3]]
            surfcon[:, j+3] = i[[2, 0, 3]]
        elif eltype == 'fepx':
            surfcon[:, j] = i[[0, 2, 4]]
            surfcon[:, j+1] = i[[0, 2, 9]]
            surfcon[:, j+2] = i[[2, 4, 9]]
            surfcon[:, j+3] = i[[4, 0, 9]]

        ind +=1
        j = ind*4

    return surfcon

'''
Example of the above function:

import Graphics
import Sphere
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.tri as mtri
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

plt.close('All')
mesh = Sphere.SphBaseMesh(2)

G=np.ones((8,))*10
G[3]=5
G[2]=0.1
N=G/G.max()

coll = Graphics.plotSurface(mesh, **{'scalar':N,'colorMap':cm.Blues,'edgecolors':'none'})

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(coll)

xlim=[mesh['crd'][0,:].min()*1.5,mesh['crd'][0,:].max()*1.5]
ylim=[mesh['crd'][1,:].min()*1.5,mesh['crd'][1,:].max()*1.5]
zlim=[mesh['crd'][2,:].min()*1.5,mesh['crd'][2,:].max()*1.5]

ax.set_xlim(xlim[0], xlim[1])
ax.set_ylim(ylim[0], ylim[1])
ax.set_zlim(zlim[0], zlim[1])
ax.elev = 50

plt.show()

'''


from odoo import api, fields, models

class Picking(models.Model):
    _inherit = "stock.picking"

    lorry_no = fields.Char('Lorry No.')
    customer_ship_id = fields.Many2one('res.partner', 'Shipping Address')
    origin_so = fields.Many2one('sale.order', 'Sales Order')
    origin_so_po = fields.Char('PO Number', related="origin_so.po_number")
    seq_num = fields.Char('No', store=False, compute="_get_seq_num")
    shipping_date = fields.Date('Shipping Date')

    def _get_seq_num(self):
        for rec in self:
            if rec.name:
                if rec.name != '/':
                    rec.seq_num = rec.name.split('/')[2]
                else:
                    rec.seq_num = '/'

    @api.onchange('origin_so')
    def get_products_from_so(self):
        for rec in self:
            if not rec.backorder_id and rec.origin_so:
                rtn_list = []
                for line in rec.origin_so.order_line:
                    rtn_list.append((0, 0, {
                        'name': (line.product_id.name
                                 + line.product_id.description_sale),
                        'product_id': line.product_id.id,
                        'product_uom_qty': line.product_uom_qty,
                        'product_uom': line.product_uom,
                    }))
                rec.move_ids_without_package = [(6, 0, [])]
                if rtn_list:
                    rec.move_ids_without_package = rtn_list

    def name_get(self):
        res = []
        for spick in self:
            if spick.seq_num != '/':
                name = spick.seq_num
            else:
                name = spick.name
            res.append((spick.id, name))
        return res

    def action_generate_backorder_wizard(self):
        wiz = self.env['stock.backorder.confirmation'].\
            create({'pick_ids': [(4, p.id) for p in self]})
        wiz.process()
        return True