import pandas as pd
import numpy as np
import random
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import VotingClassifier, RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from sklearn.model_selection import train_test_split

random.seed(42)

path = './_data/project/'
save_path = '/_save/project/'

# Load Data
dt_eng = pd.read_csv(path + 'spam_ham_dataset.csv')
dt_kor = pd.read_csv(path + 'kor_spam_ham_dataset3.csv')

# Preprocess the data
dt_eng.drop('Unnamed: 0', axis=1, inplace= True)
dt_eng.columns = ['label', 'text', 'class']
dt_eng.head()
#Eng_Text processing
#Remove stopwords from the data
stopwords = set(stopwords.words('english'))
dt_eng['text'] = dt_eng['text'].apply(lambda x: ' '.join([ word for word in word_tokenize(x)
                                                          if not word in stopwords]))

# 먼저 train 데이터와 test 데이터 인덱스 없이 배열로 만들기
kor_x = np.array([x for x in dt_kor['text']])
# print(kor_x)
kor_y = dt_kor.loc[:,'class']

eng_x = dt_eng.loc[:,'text']
eng_y = dt_eng.loc[:,'class']

y=np.array(dt_kor['class'])
_, y_indices = np.unique(y, return_inverse=True)
print(np.bincount(y_indices))
# Split the Korean data into training and testing sets
X_korean_train, X_korean_test, y_korean_train, y_korean_test = train_test_split(
    dt_kor['text'], y, test_size=0.3, random_state=42,stratify=y)

# Split the English data into training and testing sets
X_english_train, X_english_test, y_english_train, y_english_test = train_test_split(
    dt_eng['text'], dt_eng['class'], test_size=0.3, random_state=42,stratify=dt_eng['class'])

# Feature extraction
vectorizer = CountVectorizer()
# vectorizer = TfidfVectorizer()
X_korean_train_features = vectorizer.fit_transform(X_korean_train).toarray()
print(X_korean_train_features)
X_korean_test_features = vectorizer.transform(X_korean_test).toarray()
X_english_train_features = vectorizer.fit_transform(X_english_train).toarray()
X_english_test_features = vectorizer.transform(X_english_test).toarray()
print(X_korean_train_features.shape[0], X_korean_train_features.shape[1])
print(X_english_train_features.shape[0], X_english_train_features.shape[1])

# 변환된 시퀀스 번호를 이용해 단어 임베딩 벡터 생성
max_length = 230
padding_type='pre'
train_korx = pad_sequences(X_korean_train_features, padding='pre', maxlen=max_length)
test_korx = pad_sequences(X_korean_test_features, padding=padding_type, maxlen=max_length)

print(train_korx.shape, test_korx.shape) #(90, 1000) (39, 1000)
# print(train_korx)

train_engx = pad_sequences(X_english_train_features, padding='pre', maxlen=max_length)
test_engx = pad_sequences(X_english_test_features, padding=padding_type, maxlen=max_length)
print(train_engx.shape, test_engx.shape) #(3619, 1000) (1552, 1000)

# Korean model1
korean_clf = LogisticRegression(random_state=42).fit(train_korx, y_korean_train)

# English model2
english_clf = LogisticRegression(random_state=42).fit(train_engx, y_english_train)

# classifier with the two models
E_clf = RandomForestClassifier()
# E_clf = GradientBoostingClassifier()

# Fit the voting classifier on the training data
E_clf.fit(train_korx, y_korean_train)

# korean_clf.summary() # 머신러닝 모델은  summary 제공x
#훈련된 모델의 요약을 찾고 있다면 모델 coef_의 속성/ 훈련된 모델의 요약을 찾고 있다면 모델 coef_의 속성
print("Korean Model Coefficients:")
print(korean_clf.coef_)

print("English Model Coefficients:")
print(english_clf.coef_)
#훈련된 모델에서 각 기능의 중요도
print("Feature Importances of the Random Forest Classifier:")
print(E_clf.feature_importances_)

#total params전체 매개변수
#총 매개변수 수는 입력 데이터의 기능 수에 따라 결정됩니다.
# 특히 매개변수의 수는 특성 수에 1(편향 항)을 더한 것과 같습니다./ 각 입력 데이터 세트의 기능 수가 인쇄
print("Number of features in train_korx:", train_korx.shape[1])
print("Number of features in train_engx:", train_engx.shape[1])
#그런 다음 각 로지스틱 회귀 모델에 대한 총 매개변수 수를 계산하기 위해 기능 수에 1을 더할 수 있습니다.
kor_params = train_korx.shape[1] + 1
eng_params = train_engx.shape[1] + 1

print("Total parameters in Korean logistic regression model:", kor_params)
print("Total parameters in English logistic regression model:", eng_params)

#랜덤 포레스트 및 그래디언트 부스팅 분류기의 경우 매개변수의 총 수는 트리 수 또는 추정기 수와 같이 모델에 대해 선택한 하이퍼 매개변수에 따라 달라집니다.
#이렇게 하면 모델의 트리 수와 입력 데이터의 기능 수를 기반으로 랜덤 포레스트 분류기의 총 매개변수 수가 인쇄
print("Total parameters in Random Forest Classifier:", E_clf.n_estimators * (train_korx.shape[1] + 1))
#결정트리의 개수 : n_estimators

# Test the voting classifier on the testing data
y_pred = E_clf.predict(test_korx)

#lr
# Evaluate the performance of the model
accuracy = accuracy_score(y_korean_test, y_pred)
precision = precision_score(y_korean_test, y_pred)
recall = recall_score(y_korean_test, y_pred)
f1 = f1_score(y_korean_test, y_pred)

print('Accuracy:', accuracy)
print('Precision:', precision)
print('Recall:', recall)
print('F1-score:', f1)

'''
#data2
vectorizer = TfidfVectorizer()
Accuracy: 0.7692307692307693
Precision: 0.7692307692307693
Recall: 1.0
F1-score: 0.8695652173913044
'''

'''
#data3
vectorizer = TfidfVectorizer()
#max_len : 1000(임의값)
Accuracy: 0.9174311926605505
Precision: 0.9174311926605505
Recall: 1.0
F1-score: 0.9569377990430622

#max_len : 230 / 8862
Accuracy: 0.9174311926605505
Precision: 0.9174311926605505
Recall: 1.0
F1-score: 0.9569377990430622
======================================
CountVectorizer()
#max_len :  8862
Accuracy: 0.9357798165137615
Precision: 0.9345794392523364
Recall: 1.0
F1-score: 0.966183574879227

#max_len : 230
Accuracy: 0.9174311926605505
Precision: 0.9174311926605505
Recall: 1.0
F1-score: 0.9569377990430622
'''

'''
#E_clf = GradientBoostingClassifier()
Accuracy: 0.926605504587156
Precision: 0.9259259259259259
Recall: 1.0
F1-score: 0.9615384615384615
'''

# # Predict the label of a new email in English
# new_email_english = ['Buy cheap Viagra now!']
# new_email_english_features = vectorizer.transform(new_email_english)
# new_email_english_pred = voting_clf.predict(new_email_english_features)
# print('Prediction:', new_email_english_pred)

# #[실습]# 긍정인지 부정인지 맞추기
# x_predict = ['나는 성호가 정말 재미없다 너무 정말']
# token.fit_on_texts(x_predict)
# x_predict = token.texts_to_sequences(x_predict)
# x_predict = np.array(x_predict)
# x_predict = x_predict.reshape(-1,6,1)
# predict = model.predict([x_predict])
# print("긍정/부정", predict)

##############################
# #Tokenizer
# vocab_size = 2000
# tokenizer = Tokenizer(num_words = vocab_size)
# tokenizer.fit_on_texts(X_korean_train)
# X_korean_train_features = tokenizer.texts_to_sequences(X_korean_train)
# print(X_korean_train_features)
# print(X_korean_train_features.__class__)
# print(X_korean_train_features[0].__class__)
# X_korean_test_features = tokenizer.texts_to_sequences(X_korean_test)
# print(len(X_korean_train_features[0]), len(X_korean_test_features)) #69 30

# word_index = tokenizer.word_index
# max_length = 50
# padding_type='pre'
# train_korx = pad_sequences(X_korean_train_features, padding='pre', maxlen=max_length)
# test_korx = pad_sequences(X_korean_test_features, padding=padding_type, maxlen=max_length)

# print(train_korx)
# train_korx = test_korx[0]
# print(train_korx)


from discord.ext import commands
import sympy as sym
from sympy import *
import math

# コグとして用いるクラスを定義。
class MathCog(commands.Cog):

    # TestCogクラスのコンストラクタ。Botを受取り、インスタンス変数として保持。
    def __init__(self, bot):
        self.bot = bot

    # コマンドの作成。コマンドはcommandデコレータで必ず修飾する。
    @commands.command()
    async def circle(self, ctx, radius='0'): # 関数名=コマンド名
        '''半径から円の周と面積を求める''' # helpコマンドに載せる説明を書く
        try:
            radius = float(radius)
        except ValueError:
            await ctx.send('数字を入力して下さい')
        else:
            await ctx.send(f'円周：{2.0 * math.pi * radius}\n面積：{math.pi * radius * radius}')

    @commands.command()
    async def sphere(self, ctx, radius='0'):
        '''半径から球の表面積と体積を求める'''
        try:
            radius = float(radius)
        except ValueError:
            await ctx.send('数字を入力して下さい')
        else:
            await ctx.send(f'表面積：{4.0 * math.pi * radius * radius}\n体積　：{4.0 / 3.0 * math.pi * radius * radius * radius}')

    @commands.command()
    async def diff(self, ctx, y='0', n='1'):
        '''関数をxで微分する'''
        x = sym.symbols('x')
        try:
            y = eval(y)
            n = abs(int(n))
        except:
            await ctx.send('Pythonのフォーマットに対応した変数はxのみの数式を入力してください')
        else:
            y = sym.diff(y, x, n)
            prime = ['\'' for i in range(n)]
            prime = ''.join(prime)
            await ctx.send(f'y{prime} = {y}')

    @commands.command()
    async def intg(self, ctx, y='0', a='0', b='0'):
        '''関数をxで積分する'''
        x = sym.symbols('x')
        try:
            y = eval(y)
            a = int(a)
            b = int(b)
        except:
            await ctx.send('Pythonのフォーマットに対応した変数はxのみの数式を入力してください')
        else:
            if a == b:
                y = sym.integrate(y, x)
                await ctx.send(f'∫ydx = {y}')
            else:
                y = sym.integrate(y, (x, a, b))
                await ctx.send(f'∫[{a}:{b}]ydx = {y}')

# Bot本体側からコグを読み込む際に呼び出される関数。
def setup(bot):
    bot.add_cog(MathCog(bot))


import math
import boto3
import random
from typing import List
from datetime import datetime, timedelta
from elasticsearch import Elasticsearch, RequestsHttpConnection, helpers
from elasticsearch_dsl import Search, A
from requests_aws4auth import AWS4Auth
from decimal import Decimal
from ..mpc.product_types import ProductType
from ..mpc.product_visit_logs import ProductVisitLog
from ....settings import settings
from .orders import OrderAggregation
from ..mpc.Cms.UserQuestions import UserQuestionEntity as Question
from .product_entry import ProductEntry

class Product(object):
    ES_HOST = settings.AWS_ELASTICSEARCH_HOST
    ES_PORT = settings.AWS_ELASTICSEARCH_PORT
    ES_REGION = settings.AWS_ELASTICSEARCH_PRODUCTS_REGION
    INDEX_NAME = settings.AWS_ELASTICSEARCH_PRODUCTS
    DOC_TYPE = settings.AWS_ELASTICSEARCH_PRODUCTS

    def __init__(self, **kwargs):
        service = 'es'
        credentials = boto3.Session(region_name=self.ES_REGION).get_credentials()
        awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, self.ES_REGION, service)

        self.__es = Elasticsearch(
            hosts=[{'host': self.ES_HOST, 'port': self.ES_PORT}],
            # http_auth = awsauth,
            use_ssl=True,
            verify_certs=True,
            connection_class=RequestsHttpConnection
        )

    @property
    def elasticsearch(self):
        return self.__es

    def create_index(self, index, body, recreate=False, **kwargs):
        if not settings.DEBUG:
            raise Exception("Not permitted in production mode.")

        es = self.elasticsearch
        if recreate:  # Delete if recreate required.
            try:
                from warnings import warn
                warn("Are you sure that you want to remove this index?\n"\
                    "Double Check and check the next line and enable!")
                # res = es.indices.delete(index)
                # print(res)
            except Exception as e:
                print(str(e))
        return es.indices.create(index, body)

    def bulk_insert(self, index, doc_type, items, recreate=False, random_date=False, **kwargs):
        es = self.elasticsearch
        if recreate:  # Delete if recreate required.
            res = es.indices.delete(index)
        actions = []
        for item in items:
            if random_date:
                MAX_DAY = 10
                new_datetime = (datetime.now() - timedelta(days=random.randint(0, MAX_DAY))).strftime("%Y-%m-%d %H:%M:%S")
                item.update({
                    "created_at": new_datetime,
                    "updated_at": new_datetime
                })
            item.update({
                'brand_code': item.get('manufacturer', '').lower()
            })
            actions.append({
                '_index': index,
                '_type': doc_type,
                '_id': item['rs_sku'],
                '_source': item
            })
        return helpers.bulk(es, actions)

    def insert(self, index, doc_type, item, **kwargs):
        es = self.elasticsearch
        item.update({
            'brand_code': item.get('manufacturer', '').lower()
        })
        return helpers.create(es, index, item.get('rs_sku'), item, doc_type)

    def convert_item(self, item, tier: dict=None) -> dict:
        from_date = datetime.now() - timedelta(days=settings.NEW_PRODUCT_THRESHOLD)

        # @todo : refactoring
        original_price = float(item['rs_selling_price']) if item['rs_selling_price'] else 0
        discount = float(item['discount']) if item['discount'] else 0
        current_price = original_price - original_price * discount / 100

        item = {
            'id': item['portal_config_id'],
            'sku': item['rs_sku'],
            'title': item['product_name'],
            'subtitle': item['product_description'],
            'price': Decimal(0 if item['rs_selling_price'] is None else item['rs_selling_price']),
            'badge': 'NEW IN' if datetime.strptime(item['created_at'], "%Y-%m-%d %H:%M:%S") > from_date else None,
            'favorite': random.choice([True, False]),
            'product_type': item['product_size_attribute'],
            'product_sub_type': item['rs_product_sub_type'],
            'gender': item['gender'],
            'brand': item['manufacturer'],
            'sizes': [{
                'size': size['size'],
                'qty': size['qty'],
                'rs_simple_sku': size['rs_simple_sku']
            } for size in item['sizes']],
            'image': {
                'src': item['images'][0]['s3_filepath'] if len(item['images']) > 0 else 'https://placeimg.com/155/140/arch',
                'title': item['product_size_attribute'],
            },
            'original_price': original_price,
            'current_price': current_price,
        }

        if tier is not None and type(tier) == dict:
            item['fbucks'] = math.ceil(float(item['price']) * float(tier.get('discount_rate')) / 100)
        return item

    def get(self, id, **kwargs):
        return self.elasticsearch.get(self.INDEX_NAME, id, doc_type=self.DOC_TYPE)['_source']

    def convert(
            self, products, personalize=False, customer_id='BLANK',
            tier: dict=None, **kwargs) -> List[dict]:
        return [self.convert_item(item, tier=tier) for item in products]

    def get_bestsellers(self, product_type=None, gender=None, page=1, size=20, **kwargs):
        offset = (page - 1) * size
        end_date = (datetime.now() - timedelta(
            days=settings.LAST_CHANCE_END_DATE_THRESHOLD)).strftime("%Y-%m-%d %H:%M:%S")

        if product_type is None:
            query = {
                "bool": {
                    "should": [
                        {"range": {"created_at": {"lt": end_date}}},
                        {"range": {"sizes.qty": {"lte": settings.LAST_CHANCE_STOCK_THRESHOLD, "gt": 0}}}
                    ]
                }
            }
        else:
            query = {
                "bool": {
                    "should": [
                        {"range": {"created_at": {"lt": end_date}}},
                        {"range": {"sizes.qty": {"lte": settings.LAST_CHANCE_STOCK_THRESHOLD, "gt": 0}}}
                    ],
                    "must": [
                        {"term": {"product_size_attribute": product_type}}
                    ]
                }
            }

        if gender is not None and gender.lower() != 'unisex':
            if query['bool'].get('must') is None:
                query['bool']['must'] = list()
            query['bool']['must'].appen({
                "term": {"gender": gender}
            })

        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)\
            .query(query)[offset:offset + size]
        response = s.execute()
        return self.convert([item['_source'] for item in response.hits.hits])

    def get_by_price(self, max_price, min_price=0, page=1, size=20, **kwargs):
        offset = (page - 1) * size
        query = {
            "bool": {
                "must": [
                    {"range": {"rs_selling_price": {
                        "gt": min_price,
                        "lte": max_price
                    }}}
                ]
            }
        }
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)\
            .query(query)[offset:offset + size]
        response = s.execute()

        return self.convert([item['_source'] for item in response.hits.hits], personalize=True)

    def find_by_id(self, id, log=False, session_id=None, user_id=None, tier=None, **kwargs):
        es = self.elasticsearch
        item = es.get(index=self.INDEX_NAME, doc_type=self.DOC_TYPE, id=id, ignore=[404]).get('_source')
        if log and item is not None:
            # TODO: Track logs
            log_model = ProductVisitLog(session_id, customer_id=user_id)
            log_model.insert(self.convert_item(item))
        return self.convert_item(item, tier=tier)

    def get_smiliar_styles(
            self, id, page=1, size=20, customer_id='BLANK',
            tier=None, **kwargs):
        offset = (page - 1) * size
        item = self.find_by_id(id)
        if item is None:
            return []
        product_type = item.get('product_type')
        sub_type = item.get('product_sub_type')
        gender = item.get('gender')
        brand = item.get('brand')
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)\
            .query({
                "bool": {
                    "must": [
                        {
                            "range": {"sizes.qty": {"gt": 0}}
                        },
                        {
                            "term": {"gender": gender}
                        },
                        {
                            "term": {"product_size_attribute": product_type}
                        },
                        {
                            "term": {"rs_product_sub_type": sub_type}
                        },
                        {
                            "match": {"manufacturer": brand}
                        }
                    ],
                    "must_not": [
                        {
                            "match": {
                                "rs_sku": id
                            }
                        }
                    ]
                }
            }).query({
                "function_score" : {
                "query" : { "match_all": {} },
                "random_score" : {}
            }
            })[offset:offset + size]
        response = s.execute()

        return self.convert(
            [item['_source'] for item in response.hits.hits],
            tier=tier)

    def get_also_availables(self, id, tier=None, **kwargs):
        item = self.find_by_id(id)
        if item is None:
            return []

        base_sku = '_'.join(item.get('sku').split('_')[:-1])
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)\
            .query({
                "bool": {
                    "must": [
                        {
                            "range": {"sizes.qty": {"gt": 0}}
                        },
                        {
                            "prefix": {"rs_sku": {"value": base_sku}}
                        }
                    ],
                    "must_not": [
                        {
                            "match": {
                                "rs_sku": id
                            }
                        }
                    ]
                }
            }).query({
                "function_score" : {
                    "query" : { "match_all": {} },
                    "random_score" : {}
                }
            })
        response = s.execute()
        return self.convert([item['_source'] for item in response.hits.hits], tier=tier)

    def get_recently_viewed(
            self, session_id, customer_id=None,
            product_id=None, tier: dict = None, **kwargs):
        log_model = ProductVisitLog(session_id, customer_id=customer_id)
        logs = log_model.get_logs(omit=product_id)

        # for log in logs:
        #     discount = float(log.get('discount'))
        #     if log.get('original_price') is None:
        #         log['original_price'] = float(log.get('rs_selling_price'))

        #     original_price = float(log.get('original_price') or 0)
        #     current_price = original_price - original_price * discount / 100
        #     log['current_price'] = current_price
        #     if tier is not None and type(tier) == dict:
        #         log['fbucks'] = math.ceil(log['current_price'] * tier.get('discount_rate') / 100)

        # To implement discount
        skus: List[str] = [log['sku'] for log in logs]
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)\
            .query({
                "terms": {"rs_sku": skus}
            })
        response = s.execute()
        products = self.convert([item['_source'] for item in response.hits.hits], tier=tier)
        return sorted(
            products, key=lambda x: skus.index(x['sku']))

    def get_brands(
            self,
            keyword: str=None,
            prefix: List[str]=[], exclude: List[str]=[],
            page=1, size=1000, **kwargs) -> List[dict]:

        prefix = [item.lower().strip() for item in prefix]
        exclude = [item.lower().strip() for item in exclude]
        offset = (page - 1) * size
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME)
        s = s.update_from_dict({
            "query": {
                "bool": {
                    "must_not": [
                        {
                            "terms": {
                                "brand_code": [item.lower() for item in exclude if item]
                            }
                        }
                    ],
                    "must": [
                        {
                            "range": {
                                "sizes.qty": {
                                    "gte": 0
                                }
                            }
                        },
                        {
                            "bool": {
                                "should": [
                                    {
                                        "prefix": {
                                            "brand_code": item.lower()
                                        }
                                    } for item in prefix if item
                                ]
                            }
                        }
                    ]
                }
            },
            "aggs": {
                "brands": {
                    "terms": {
                        "field": "manufacturer",
                        "order": {
                            "_term": "asc"
                        },
                        "size": 1000
                    }
                }
            },
            "size": 10
        })

        response = s.execute()
        buckets = response.aggregations.brands.buckets
        return [item['key'] for item in buckets]

    def get_products_by_category(self, product_type, sub_types, gender, page=1, size=18):
        """Get products for a specific category
        """
        offset = (page - 1) * size
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME).query(
            {
                "bool": {
                    "must": [
                        {
                            "term": {
                                "product_size_attribute": {
                                    "value": product_type
                                }
                            }
                        },
                        {
                            "term": {
                                "gender": {
                                    "value": gender
                                }
                            }
                        },
                        {
                            "terms": {
                                "rs_product_sub_type": sub_types + ['BLANK']
                            }
                        },
                        {
                            "range": {
                                "sizes.qty" : {
                                    "gt": 0
                                }
                            }
                        }
                    ]
                }
            })[offset: offset + size]

        response = s.execute()
        return self.convert([item['_source'] for item in response.hits.hits])

    def filter_subtypes_in_stock(self, subtypes, gender=None, **kwargs):
        subtype_names = [item['product_type_name'] for item in subtypes]
        if gender is None or gender.lower() == 'unisex':
            query = {
                "bool": {
                    "must": [
                        {
                            "terms": {
                                "rs_product_sub_type": subtype_names
                            }
                        },
                        {
                            "range": {
                                "sizes.qty" : {
                                    "gt": 0
                                }
                            }
                        }
                    ]
                }
            }
        else:
            query = {
                "bool": {
                    "must": [
                        {
                            "term": {
                                "gender": {
                                    "value": gender.upper()
                                }
                            }
                        },
                        {
                            "terms": {
                                "rs_product_sub_type": subtype_names
                            }
                        },
                        {
                            "range": {
                                "sizes.qty" : {
                                    "gt": 0
                                }
                            }
                        }
                    ]
                }
            }
        s = Search(using=self.elasticsearch, index=self.INDEX_NAME).query(query)

        s.aggs.bucket('subtypes_in_stock', "terms", field='rs_product_sub_type')
        response = s.execute()
        buckets = response.aggregations.subtypes_in_stock.buckets
        buffer = {}
        for bucket in buckets:
            buffer[bucket['key']] = True

        return [item for item in subtypes if buffer.get(item['product_type_name'])]

    def find_by_simple_sku(self, simple_sku, **kwargs):
        config_sku = simple_sku.split('-')[0]
        item = self.get(config_sku)
        for size in item.get('sizes', []):
            if size['rs_simple_sku'] == simple_sku:
                if int(size['qty']) > 0:
                    item.update(size)
                break
        del item['sizes']
        return item


from collections import defaultdict, deque

from aoc.common.day_solver import DaySolver

class Day12Solver(DaySolver):
    year = 2021
    day = 12

    def _load_input(self, filename=None):
        lines = self.load_all_input_lines(filename)

        connections = defaultdict(lambda: [])
        can_revisit = set()
        for line in lines:
            node_1, node_2 = line.split('-')
            if node_1.isupper():
                can_revisit.add(node_1)
            if node_2.isupper():
                can_revisit.add(node_2)
            connections[node_1].append(node_2)
            connections[node_2].append(node_1)

        return connections, can_revisit

    def solve_puzzle_one(self):
        connections, can_revisit = self._load_input()

        all_paths = self._find_all_paths(connections, can_revisit, 'start', 'end')

        return len(all_paths)

    def solve_puzzle_two(self):
        connections, can_revisit = self._load_input()

        all_paths = self._find_all_paths_with_repeat(connections, can_revisit, 'start', 'end')

        return len(all_paths)

    def _find_all_paths(self, connections, can_revisit, start, end, visited=None):
        if visited is None:
            visited = set()

        visited.add(start)
        all_paths = []
        for adj in connections[start]:
            if adj == end:
                all_paths.append([start, end])
            elif adj in can_revisit or adj not in visited:
                adj_paths = self._find_all_paths(connections, can_revisit, adj, end, visited.copy())
                for path in adj_paths:
                    new_path = [start]
                    new_path.extend(path)
                    all_paths.append(new_path)

        return all_paths

    def _find_all_paths_with_repeat(self, connections, can_revisit, start, end, visited=None, has_repeat=False):
        if visited is None:
            visited = set()

        if start not in can_revisit:
            visited.add(start)
        all_paths = []
        for adj in connections[start]:
            if adj == end:
                all_paths.append([start, end])
                continue

            else:
                if adj not in visited:
                    adj_paths = self._find_all_paths_with_repeat(
                        connections,
                        can_revisit,
                        adj,
                        end,
                        visited.copy(),
                        has_repeat,
                    )
                elif not has_repeat and adj not in ('start', 'end'):
                    adj_paths = self._find_all_paths_with_repeat(
                        connections,
                        can_revisit,
                        adj,
                        end,
                        visited.copy(),
                        has_repeat=True,
                    )
                else:
                    adj_paths = []

                for path in adj_paths:
                    new_path = [start]
                    new_path.extend(path)
                    all_paths.append(new_path)

        return all_paths


from django import template
from django.template import loader
from django.utils.safestring import mark_safe
from martor.utils import markdownify
import re
register = template.Library()

re_img_tag = re.compile(r'(?P<img_tag>)')
re_attr = re.compile(r'([a-z]+)? *([a-z-]+)="([^"]+)')

img_template = template.loader.get_template('wbcore/content_image.html')

def img_match(match):
    gd = match.groupdict()
    img_tag = gd['img_tag'] if 'img_tag' in gd else ""
    img = {}

    for result in re_attr.findall(img_tag):
        img[result[1]] = result[2]

    src = img['src'] if 'src' in img else None
    if not src:
        return ""

    context = {
        'size': 'medium',
        'side': 'left',
        'src': src,
        'title': img['title'] if 'title' in img else None,
        'alt': img['alt'] if 'alt' in img else None,
    }
    return img_template.render(context)

def insert_ui_elems(field_name):
    #html = re_img_tag.sub(img_match, field_name)
    #html += '

'
    #html += '

'
    return field_name
    #return html

@register.filter
def safe_markdown(field_name):
    """
    Safe the markdown text as html ouput.

    Usage:
        {% load martortags %}
        {{ field_name|safe_markdown }}

    Example:
        {{ post.description|safe_markdown }}
    """
    return mark_safe(insert_ui_elems(markdownify(field_name)))


import random
import heapq
import json
from logging import getLogger
from time import time, sleep
from urllib import request, parse
from urllib.error import URLError
from urllib.parse import urljoin

import otree.constants
from django.conf import settings

print_function = print

in_memory_base_url = ''

logger = getLogger(__name__)
TIMEOUTWORKER_REDIS_KEY = 'otree-timeoutworker'

def post(url, data: dict):
    '''
    make the request over the network rather than in-process,
    to avoid race conditions. everything must be handled by the main
    server instance.
    '''
    data = parse.urlencode(data).encode()
    req = request.Request(url, data=data)  # this will make the method "POST"
    resp = request.urlopen(req)

def get(url):
    try:
        request.urlopen(url)
    # some users were reporting URLError but not clear what URL it was
    except URLError as exc:
        raise Exception(f'Error occurred when opening {url}: {repr(exc)}') from None

def enqueue(queue, method, kwargs, delay):
    # random.random() is the tiebreaker, so we don't resort to comparing kwargs
    heapq.heappush(queue, (time() + delay, random.random(), method, kwargs))

class Worker:
    def __init__(self, redis_conn):
        self.redis_conn = redis_conn
        # flush old stuff
        redis_conn.delete(TIMEOUTWORKER_REDIS_KEY)

    def redis_listen(self):
        print_function('timeoutworker is listening for messages through Redis')
        q = []
        while True:
            result = self.redis_conn.blpop(TIMEOUTWORKER_REDIS_KEY, timeout=3)
            if result:
                _, message_bytes = result
                message = json.loads(message_bytes.decode('utf-8'))
                enqueue(q, **message)
            while q and q[0][0] < time():
                _, _, method, kwargs = heapq.heappop(q)
                try:
                    getattr(self, method)(**kwargs)
                except Exception as exc:
                    # don't raise, because then this would crash.
                    # logger.exception() will record the full traceback
                    logger.exception(repr(exc))

    def submit_expired_url(self, participant_code, base_url, path):
        if 'testserver' in base_url:
            base_url = in_memory_base_url
            if not base_url:
                return

        from otree.models.participant import Participant

        # if the participant exists in the DB,
        # and they did not advance past the page yet

        # To reduce redundant server traffic, it's OK not to advance the page if the user already got to the next page
        # themselves, or via "advance slowest participants".
        # however, we must make sure that the user succeeded in loading the next page fully.
        # if the user made this page's POST but closed their browser before
        # the redirect to the next page's GET, then if the next page has a timeout,
        # it will not get scheduled, and then the auto-timeout chain would be broken.
        # so, instead of filtering by _index_in_pages (which is set in POST),
        # we filter by _current_form_page_url (which is set in GET,
        # AFTER the next page's timeout is scheduled.)

        if Participant.objects.filter(
            code=participant_code, _current_form_page_url=path
        ).exists():
            post(urljoin(base_url, path), data={otree.constants.timeout_happened: True})

    def ensure_pages_visited(self, base_url, participant_pks):
        """This is necessary when a wait page is followed by a timeout page.
        We can't guarantee the user's browser will properly continue to poll
        the wait page and get redirected, so after a grace period we load the page
        automatically, to kick off the expiration timer of the timeout page.
        """

        if 'testserver' in base_url:
            base_url = in_memory_base_url
            if not base_url:
                return

        from otree.models.participant import Participant

        # we used to filter by _index_in_pages, but that is not reliable,
        # because of the race condition described above.
        unvisited_participants = Participant.objects.filter(pk__in=participant_pks)
        for participant in unvisited_participants:

            # if the wait page is the first page,
            # then _current_form_page_url could be null.
            # in this case, use the start_url() instead,
            # because that will redirect to the current wait page.
            # (alternatively we could define _current_page_url or
            # current_wait_page_url)
            get(urljoin(base_url, participant._url_i_should_be_on()))

    def set_base_url(self, url):
        '''this is necessary because advance_last_place_participants uses the test client, which has a bogus url of
        http://testserver that doesn't work when we make requests over the network.
        '''
        global in_memory_base_url
        in_memory_base_url = url

_main_process_redis = None

def get_redis_client():
    '''
    (1) redis is an expensive import
    (2) we can't instantiate redis globally because REDIS_URL may not be defined'''
    global _main_process_redis
    if _main_process_redis is None:
        from redis import StrictRedis

        _main_process_redis = StrictRedis.from_url(settings.REDIS_URL)
    return _main_process_redis

def redis_enqueue(method, delay, kwargs):
    get_redis_client().rpush(
        TIMEOUTWORKER_REDIS_KEY,
        json.dumps(dict(method=method, delay=delay, kwargs=kwargs)),
    )

def set_base_url(url):
    redis_enqueue(method='ensure_pages_visited', delay=0, kwargs=dict(url=url))

def ensure_pages_visited(delay, **kwargs):
    redis_enqueue(method='ensure_pages_visited', delay=delay, kwargs=kwargs)

def submit_expired_url(delay, **kwargs):
    redis_enqueue(method='submit_expired_url', delay=delay, kwargs=kwargs)


import pandas as pd
from datetime import timedelta

""" Reading the data """

df = pd.read_excel("Assignment_Timecard.xlsx")

""" Preprocessing """

# Extracting day from datetime
df["Day"] = df["Time"].map(lambda time: time.day)

# Time difference between start of current shift and end of last shift
df["Shift Difference"] = df["Time"] - df["Time Out"].shift(1)

# Recalculating shift time as Timecard Hours aren't accurate enough
df["Shift Time"] = df["Time Out"] - df["Time"]

""" Task 1 """

df1 = df

# Removing consecutive same days (only considering one shift per day)
df1 = df1[df1["Day"] != df1["Day"].shift(-1)]

# If the difference between current day and the day exactly 7 rows above is 7, then the employee must've worked for 7 consecutive days
df1 = df1[df1["Day"] - df1["Day"].shift(7) == 7]

# Extracting only the name and the position
df1 = df1.loc[:, ["Employee Name", "Position ID"]].drop_duplicates()

print("\n\nEmployees who have worked for 7 consecutive days:\n")
print(df1)

""" Task 2 """

df2 = df

# Considering only the shifts done on the same day as valid (removing false positives)
df2 = df2[df2["Day"] == df2["Day"].shift(1)]

# Only considering and rows with shift difference more than 1 hour
df2 = df2[df2["Shift Difference"] > timedelta(hours=1)]

# Only considering and rows with shift difference less than 10 hours
df2 = df2[df2["Shift Difference"] < timedelta(hours=10)]

# Extracting only the name and the position
df2 = df2.loc[:, ["Employee Name", "Position ID"]].drop_duplicates()

print(
    "\n\nEmployees who have less than 10 hours of time between shifts but greater than 1 hour:\n"
)
print(df2)

""" Task 3 """

df3 = df

# Keeping only the rows with shift time greater than 14 hours
df3 = df3[df3["Shift Time"] > timedelta(hours=14)]

# Extracting only the name and the position
df3 = df3.loc[:, ["Employee Name", "Position ID"]].drop_duplicates()

print("\n\nEmployees who have worked for more than 14 hours in a single shift:\n")
print(df3)
print("\n\n")

# Output stored in output.txt using the command "python script.py > output.txt"


import asyncio
import os
import re
import sys

from telethon import TelegramClient

from ..core.logger import logging
from ..sql_helper.global_collection import (
    add_to_collectionlist,
    del_keyword_collectionlist,
    get_collectionlist_items,
)

package_patern = re.compile(r"([\w-]+)(?:=|<|>|!)")
github_patern = re.compile(r"(?:https?)?(?:www.)?(?:github.com/)?([\w\-.]+/[\w\-.]+)/?")
github_raw_pattern = re.compile(
    r"(?:https?)?(?:raw.)?(?:githubusercontent.com/)?([\w\-.]+/[\w\-.]+)/?"
)
trees_pattern = "https://api.github.com/repos/{}/git/trees/master"
raw_pattern = "https://raw.githubusercontent.com/{}/master/{}"

LOGS = logging.getLogger(__name__)

async def get_pip_packages(requirements):
    """Get a list of all the pacakage's names."""
    if requirements:
        packages = requirements
    else:
        cmd = await asyncio.create_subprocess_exec(
            sys.executable.replace(" ", "\\ "),
            "-m",
            "pip",
            "freeze",
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, _ = await cmd.communicate()
        packages = stdout.decode("utf-8")
    tmp = package_patern.findall(packages)
    return [package.lower() for package in tmp]

async def install_pip_packages(packages):
    """Install pip packages."""
    args = ["-m", "pip", "install", "--upgrade", "--user"]
    cmd = await asyncio.create_subprocess_exec(
        sys.executable.replace(" ", "\\ "),
        *args,
        *packages,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    await cmd.communicate()
    return cmd.returncode == 0

def run_async(func: callable):
    """Run async functions with the right event loop."""
    asyncio.get_event_loop()
    return loop.run_until_complete(func)

async def restart_script(client: TelegramClient, sandy):
    """Restart the current script."""
    try:
        ulist = get_collectionlist_items()
        for i in ulist:
            if i == "restart_update":
                del_keyword_collectionlist("restart_update")
    except Exception as e:
        LOGS.error(e)
    try:
        add_to_collectionlist("restart_update", [sandy.chat_id, sandy.id])
    except Exception as e:
        LOGS.error(e)
    executable = sys.executable.replace(" ", "\\ ")
    args = [executable, "-m", "userbot"]
    os.execle(executable, *args, os.environ)
    os._exit(143)

async def get_message_link(client, event):
    chat = await event.get_chat()
    if event.is_private:
        return f"tg://openmessage?user_id={chat.id}&message_id={event.id}"
    return f"https://t.me/c/{chat.id}/{event.id}"


from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.contrib.auth.models import User
from samples.models import WetLabStaff

class Command(BaseCommand):
	help = 'Add a wetlab user'

	def add_arguments(self, parser):
		parser.add_argument('-f', '--first_name', help='Wetlab user first name', required=True)
		parser.add_argument('-l', '--last_name', help='Wetlab user first name', required=True)
		parser.add_argument('-t', '--title', default='')
		parser.add_argument('-u', '--username', help='Django user login (already created from admin interface)', required=True)
		parser.add_argument('-c', '--creator', help='Django user login for user creating', required=True)

	def handle(self, *args, **options):
		with transaction.atomic():
			creator = User.objects.get(username=options['creator'])
			user = User.objects.get(username=options['username'])

			wetlab_user = WetLabStaff(first_name=options['first_name'], last_name=options['last_name'], title=options['title'], login_user=user)
			wetlab_user.save(save_user=creator)


import functools
import itertools

import numpy as np
import pytest
import scipy

import aesara
from aesara import function, grad
from aesara import tensor as at
from aesara.configdefaults import config
from aesara.tensor.slinalg import (
    Cholesky,
    CholeskySolve,
    Solve,
    SolveBase,
    SolveTriangular,
    cho_solve,
    cholesky,
    eigvalsh,
    expm,
    kron,
    solve,
    solve_continuous_lyapunov,
    solve_discrete_lyapunov,
    solve_triangular,
)
from aesara.tensor.type import dmatrix, matrix, tensor, vector
from tests import unittest_tools as utt

def check_lower_triangular(pd, ch_f):
    ch = ch_f(pd)
    assert ch[0, pd.shape[1] - 1] == 0
    assert ch[pd.shape[0] - 1, 0] != 0
    assert np.allclose(np.dot(ch, ch.T), pd)
    assert not np.allclose(np.dot(ch.T, ch), pd)

def check_upper_triangular(pd, ch_f):
    ch = ch_f(pd)
    assert ch[4, 0] == 0
    assert ch[0, 4] != 0
    assert np.allclose(np.dot(ch.T, ch), pd)
    assert not np.allclose(np.dot(ch, ch.T), pd)

def test_cholesky():
    rng = np.random.default_rng(utt.fetch_seed())
    r = rng.standard_normal((5, 5)).astype(config.floatX)
    pd = np.dot(r, r.T)
    x = matrix()
    chol = cholesky(x)
    # Check the default.
    ch_f = function([x], chol)
    check_lower_triangular(pd, ch_f)
    # Explicit lower-triangular.
    chol = Cholesky(lower=True)(x)
    ch_f = function([x], chol)
    check_lower_triangular(pd, ch_f)
    # Explicit upper-triangular.
    chol = Cholesky(lower=False)(x)
    ch_f = function([x], chol)
    check_upper_triangular(pd, ch_f)
    chol = Cholesky(lower=False, on_error="nan")(x)
    ch_f = function([x], chol)
    check_upper_triangular(pd, ch_f)

def test_cholesky_indef():
    x = matrix()
    mat = np.array([[1, 0.2], [0.2, -2]]).astype(config.floatX)
    cholesky = Cholesky(lower=True, on_error="raise")
    chol_f = function([x], cholesky(x))
    with pytest.raises(scipy.linalg.LinAlgError):
        chol_f(mat)
    cholesky = Cholesky(lower=True, on_error="nan")
    chol_f = function([x], cholesky(x))
    assert np.all(np.isnan(chol_f(mat)))

def test_cholesky_grad():
    rng = np.random.default_rng(utt.fetch_seed())
    r = rng.standard_normal((5, 5)).astype(config.floatX)

    # The dots are inside the graph since Cholesky needs separable matrices

    # Check the default.
    utt.verify_grad(lambda r: cholesky(r.dot(r.T)), [r], 3, rng)
    # Explicit lower-triangular.
    utt.verify_grad(
        lambda r: Cholesky(lower=True)(r.dot(r.T)),
        [r],
        3,
        rng,
        abs_tol=0.05,
        rel_tol=0.05,
    )

    # Explicit upper-triangular.
    utt.verify_grad(
        lambda r: Cholesky(lower=False)(r.dot(r.T)),
        [r],
        3,
        rng,
        abs_tol=0.05,
        rel_tol=0.05,
    )

def test_cholesky_grad_indef():
    x = matrix()
    mat = np.array([[1, 0.2], [0.2, -2]]).astype(config.floatX)
    cholesky = Cholesky(lower=True, on_error="raise")
    chol_f = function([x], grad(cholesky(x).sum(), [x]))
    with pytest.raises(scipy.linalg.LinAlgError):
        chol_f(mat)
    cholesky = Cholesky(lower=True, on_error="nan")
    chol_f = function([x], grad(cholesky(x).sum(), [x]))
    assert np.all(np.isnan(chol_f(mat)))

@pytest.mark.slow
def test_cholesky_shape():
    rng = np.random.default_rng(utt.fetch_seed())
    x = matrix()
    for l in (cholesky(x), Cholesky(lower=True)(x), Cholesky(lower=False)(x)):
        f_chol = aesara.function([x], l.shape)
        topo_chol = f_chol.maker.fgraph.toposort()
        if config.mode != "FAST_COMPILE":
            assert sum(node.op.__class__ == Cholesky for node in topo_chol) == 0
        for shp in [2, 3, 5]:
            m = np.cov(rng.standard_normal((shp, shp + 10))).astype(config.floatX)
            np.testing.assert_equal(f_chol(m), (shp, shp))

def test_eigvalsh():
    A = dmatrix("a")
    B = dmatrix("b")
    f = function([A, B], eigvalsh(A, B))

    rng = np.random.default_rng(utt.fetch_seed())
    a = rng.standard_normal((5, 5))
    a = a + a.T
    for b in [10 * np.eye(5, 5) + rng.standard_normal((5, 5))]:
        w = f(a, b)
        refw = scipy.linalg.eigvalsh(a, b)
        np.testing.assert_array_almost_equal(w, refw)

    # We need to test None separately, as otherwise DebugMode will
    # complain, as this isn't a valid ndarray.
    b = None
    B = at.NoneConst
    f = function([A], eigvalsh(A, B))
    w = f(a)
    refw = scipy.linalg.eigvalsh(a, b)
    np.testing.assert_array_almost_equal(w, refw)

def test_eigvalsh_grad():
    rng = np.random.default_rng(utt.fetch_seed())
    a = rng.standard_normal((5, 5))
    a = a + a.T
    b = 10 * np.eye(5, 5) + rng.standard_normal((5, 5))
    utt.verify_grad(
        lambda a, b: eigvalsh(a, b).dot([1, 2, 3, 4, 5]), [a, b], rng=np.random
    )

class TestSolveBase(utt.InferShapeTester):
    @pytest.mark.parametrize(
        "A_func, b_func, error_message",
        [
            (vector, matrix, "`A` must be a matrix.*"),
            (
                functools.partial(tensor, dtype="floatX", shape=(None,) * 3),
                matrix,
                "`A` must be a matrix.*",
            ),
            (
                matrix,
                functools.partial(tensor, dtype="floatX", shape=(None,) * 3),
                "`b` must be a matrix or a vector.*",
            ),
        ],
    )
    def test_make_node(self, A_func, b_func, error_message):
        np.random.default_rng(utt.fetch_seed())
        with pytest.raises(ValueError, match=error_message):
            A = A_func()
            b = b_func()
            SolveBase()(A, b)

    def test__repr__(self):
        np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b = matrix()
        y = SolveBase()(A, b)
        assert y.__repr__() == "SolveBase{lower=False, check_finite=True}.0"

class TestSolve(utt.InferShapeTester):
    def test__init__(self):
        with pytest.raises(ValueError) as excinfo:
            Solve(assume_a="test")
        assert "is not a recognized matrix structure" in str(excinfo.value)

    @pytest.mark.parametrize("b_shape", [(5, 1), (5,)])
    def test_infer_shape(self, b_shape):
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b_val = np.asarray(rng.random(b_shape), dtype=config.floatX)
        b = at.as_tensor_variable(b_val).type()
        self._compile_and_check(
            [A, b],
            [solve(A, b)],
            [
                np.asarray(rng.random((5, 5)), dtype=config.floatX),
                b_val,
            ],
            Solve,
            warn=False,
        )

    def test_correctness(self):
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b = matrix()
        y = solve(A, b)
        gen_solve_func = aesara.function([A, b], y)

        b_val = np.asarray(rng.random((5, 1)), dtype=config.floatX)

        A_val = np.asarray(rng.random((5, 5)), dtype=config.floatX)
        A_val = np.dot(A_val.transpose(), A_val)

        assert np.allclose(
            scipy.linalg.solve(A_val, b_val), gen_solve_func(A_val, b_val)
        )

        A_undef = np.array(
            [
                [1, 0, 0, 0, 0],
                [0, 1, 0, 0, 0],
                [0, 0, 1, 0, 0],
                [0, 0, 0, 1, 1],
                [0, 0, 0, 1, 0],
            ],
            dtype=config.floatX,
        )
        assert np.allclose(
            scipy.linalg.solve(A_undef, b_val), gen_solve_func(A_undef, b_val)
        )

    @pytest.mark.parametrize(
        "m, n, assume_a, lower",
        [
            (5, None, "gen", False),
            (5, None, "gen", True),
            (4, 2, "gen", False),
            (4, 2, "gen", True),
        ],
    )
    def test_solve_grad(self, m, n, assume_a, lower):
        rng = np.random.default_rng(utt.fetch_seed())

        # Ensure diagonal elements of `A` are relatively large to avoid
        # numerical precision issues
        A_val = (rng.normal(size=(m, m)) * 0.5 + np.eye(m)).astype(config.floatX)

        if n is None:
            b_val = rng.normal(size=m).astype(config.floatX)
        else:
            b_val = rng.normal(size=(m, n)).astype(config.floatX)

        eps = None
        if config.floatX == "float64":
            eps = 2e-8

        solve_op = Solve(assume_a=assume_a, lower=lower)
        utt.verify_grad(solve_op, [A_val, b_val], 3, rng, eps=eps)

class TestSolveTriangular(utt.InferShapeTester):
    @pytest.mark.parametrize("b_shape", [(5, 1), (5,)])
    def test_infer_shape(self, b_shape):
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b_val = np.asarray(rng.random(b_shape), dtype=config.floatX)
        b = at.as_tensor_variable(b_val).type()
        self._compile_and_check(
            [A, b],
            [solve_triangular(A, b)],
            [
                np.asarray(rng.random((5, 5)), dtype=config.floatX),
                b_val,
            ],
            SolveTriangular,
            warn=False,
        )

    @pytest.mark.parametrize("lower", [True, False])
    def test_correctness(self, lower):
        rng = np.random.default_rng(utt.fetch_seed())

        b_val = np.asarray(rng.random((5, 1)), dtype=config.floatX)

        A_val = np.asarray(rng.random((5, 5)), dtype=config.floatX)
        A_val = np.dot(A_val.transpose(), A_val)

        C_val = scipy.linalg.cholesky(A_val, lower=lower)

        A = matrix()
        b = matrix()

        cholesky = Cholesky(lower=lower)
        C = cholesky(A)
        y_lower = solve_triangular(C, b, lower=lower)
        lower_solve_func = aesara.function([C, b], y_lower)

        assert np.allclose(
            scipy.linalg.solve_triangular(C_val, b_val, lower=lower),
            lower_solve_func(C_val, b_val),
        )

    @pytest.mark.parametrize(
        "m, n, lower",
        [
            (5, None, False),
            (5, None, True),
            (4, 2, False),
            (4, 2, True),
        ],
    )
    def test_solve_grad(self, m, n, lower):
        rng = np.random.default_rng(utt.fetch_seed())

        # Ensure diagonal elements of `A` are relatively large to avoid
        # numerical precision issues
        A_val = (rng.normal(size=(m, m)) * 0.5 + np.eye(m)).astype(config.floatX)

        if n is None:
            b_val = rng.normal(size=m).astype(config.floatX)
        else:
            b_val = rng.normal(size=(m, n)).astype(config.floatX)

        eps = None
        if config.floatX == "float64":
            eps = 2e-8

        solve_op = SolveTriangular(lower=lower)
        utt.verify_grad(solve_op, [A_val, b_val], 3, rng, eps=eps)

class TestCholeskySolve(utt.InferShapeTester):
    def setup_method(self):
        self.op_class = CholeskySolve
        self.op = CholeskySolve()
        self.op_upper = CholeskySolve(lower=False)
        super().setup_method()

    def test_repr(self):
        assert repr(CholeskySolve()) == "CholeskySolve{(True, True)}"

    def test_infer_shape(self):
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b = matrix()
        self._compile_and_check(
            [A, b],  # aesara.function inputs
            [self.op(A, b)],  # aesara.function outputs
            # A must be square
            [
                np.asarray(rng.random((5, 5)), dtype=config.floatX),
                np.asarray(rng.random((5, 1)), dtype=config.floatX),
            ],
            self.op_class,
            warn=False,
        )
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b = vector()
        self._compile_and_check(
            [A, b],  # aesara.function inputs
            [self.op(A, b)],  # aesara.function outputs
            # A must be square
            [
                np.asarray(rng.random((5, 5)), dtype=config.floatX),
                np.asarray(rng.random(5), dtype=config.floatX),
            ],
            self.op_class,
            warn=False,
        )

    def test_solve_correctness(self):
        rng = np.random.default_rng(utt.fetch_seed())
        A = matrix()
        b = matrix()
        y = self.op(A, b)
        cho_solve_lower_func = aesara.function([A, b], y)

        y = self.op_upper(A, b)
        cho_solve_upper_func = aesara.function([A, b], y)

        b_val = np.asarray(rng.random((5, 1)), dtype=config.floatX)

        A_val = np.tril(np.asarray(rng.random((5, 5)), dtype=config.floatX))

        assert np.allclose(
            scipy.linalg.cho_solve((A_val, True), b_val),
            cho_solve_lower_func(A_val, b_val),
        )

        A_val = np.triu(np.asarray(rng.random((5, 5)), dtype=config.floatX))
        assert np.allclose(
            scipy.linalg.cho_solve((A_val, False), b_val),
            cho_solve_upper_func(A_val, b_val),
        )

    def test_solve_dtype(self):
        dtypes = [
            "uint8",
            "uint16",
            "uint32",
            "uint64",
            "int8",
            "int16",
            "int32",
            "int64",
            "float16",
            "float32",
            "float64",
        ]

        A_val = np.eye(2)
        b_val = np.ones((2, 1))

        # try all dtype combinations
        for A_dtype, b_dtype in itertools.product(dtypes, dtypes):
            A = matrix(dtype=A_dtype)
            b = matrix(dtype=b_dtype)
            x = self.op(A, b)
            fn = function([A, b], x)
            x_result = fn(A_val.astype(A_dtype), b_val.astype(b_dtype))

            assert x.dtype == x_result.dtype

def test_cho_solve():
    rng = np.random.default_rng(utt.fetch_seed())
    A = matrix()
    b = matrix()
    y = cho_solve((A, True), b)
    cho_solve_lower_func = aesara.function([A, b], y)

    b_val = np.asarray(rng.random((5, 1)), dtype=config.floatX)

    A_val = np.tril(np.asarray(rng.random((5, 5)), dtype=config.floatX))

    assert np.allclose(
        scipy.linalg.cho_solve((A_val, True), b_val),
        cho_solve_lower_func(A_val, b_val),
    )

def test_expm():
    rng = np.random.default_rng(utt.fetch_seed())
    A = rng.standard_normal((5, 5)).astype(config.floatX)

    ref = scipy.linalg.expm(A)

    x = matrix()
    m = expm(x)
    expm_f = function([x], m)

    val = expm_f(A)
    np.testing.assert_array_almost_equal(val, ref)

def test_expm_grad_1():
    # with symmetric matrix (real eigenvectors)
    rng = np.random.default_rng(utt.fetch_seed())
    # Always test in float64 for better numerical stability.
    A = rng.standard_normal((5, 5))
    A = A + A.T

    utt.verify_grad(expm, [A], rng=rng)

def test_expm_grad_2():
    # with non-symmetric matrix with real eigenspecta
    rng = np.random.default_rng(utt.fetch_seed())
    # Always test in float64 for better numerical stability.
    A = rng.standard_normal((5, 5))
    w = rng.standard_normal(5) ** 2
    A = (np.diag(w**0.5)).dot(A + A.T).dot(np.diag(w ** (-0.5)))
    assert not np.allclose(A, A.T)

    utt.verify_grad(expm, [A], rng=rng)

def test_expm_grad_3():
    # with non-symmetric matrix (complex eigenvectors)
    rng = np.random.default_rng(utt.fetch_seed())
    # Always test in float64 for better numerical stability.
    A = rng.standard_normal((5, 5))

    utt.verify_grad(expm, [A], rng=rng)

class TestKron(utt.InferShapeTester):
    rng = np.random.default_rng(43)

    def setup_method(self):
        self.op = kron
        super().setup_method()

    def test_perform(self):
        for shp0 in [(2,), (2, 3), (2, 3, 4), (2, 3, 4, 5)]:
            x = tensor(dtype="floatX", shape=(None,) * len(shp0))
            a = np.asarray(self.rng.random(shp0)).astype(config.floatX)
            for shp1 in [(6,), (6, 7), (6, 7, 8), (6, 7, 8, 9)]:
                if len(shp0) + len(shp1) == 2:
                    continue
                y = tensor(dtype="floatX", shape=(None,) * len(shp1))
                f = function([x, y], kron(x, y))
                b = self.rng.random(shp1).astype(config.floatX)
                out = f(a, b)
                # Newer versions of scipy want 4 dimensions at least,
                # so we have to add a dimension to a and flatten the result.
                if len(shp0) + len(shp1) == 3:
                    scipy_val = scipy.linalg.kron(a[np.newaxis, :], b).flatten()
                else:
                    scipy_val = scipy.linalg.kron(a, b)
                utt.assert_allclose(out, scipy_val)

    def test_numpy_2d(self):
        for shp0 in [(2, 3)]:
            x = tensor(dtype="floatX", shape=(None,) * len(shp0))
            a = np.asarray(self.rng.random(shp0)).astype(config.floatX)
            for shp1 in [(6, 7)]:
                if len(shp0) + len(shp1) == 2:
                    continue
                y = tensor(dtype="floatX", shape=(None,) * len(shp1))
                f = function([x, y], kron(x, y))
                b = self.rng.random(shp1).astype(config.floatX)
                out = f(a, b)
                assert np.allclose(out, np.kron(a, b))

def test_solve_discrete_lyapunov_via_direct_real():
    N = 5
    rng = np.random.default_rng(utt.fetch_seed())
    a = at.dmatrix()
    q = at.dmatrix()
    f = function([a, q], [solve_discrete_lyapunov(a, q, method="direct")])

    A = rng.normal(size=(N, N))
    Q = rng.normal(size=(N, N))

    X = f(A, Q)
    assert np.allclose(A @ X @ A.T - X + Q, 0.0)

    utt.verify_grad(solve_discrete_lyapunov, pt=[A, Q], rng=rng)

def test_solve_discrete_lyapunov_via_direct_complex():
    N = 5
    rng = np.random.default_rng(utt.fetch_seed())
    a = at.zmatrix()
    q = at.zmatrix()
    f = function([a, q], [solve_discrete_lyapunov(a, q, method="direct")])

    A = rng.normal(size=(N, N)) + rng.normal(size=(N, N)) * 1j
    Q = rng.normal(size=(N, N))
    X = f(A, Q)
    assert np.allclose(A @ X @ A.conj().T - X + Q, 0.0)

    # TODO: the .conj() method currently does not have a gradient; add this test when gradients are implemented.
    # utt.verify_grad(solve_discrete_lyapunov, pt=[A, Q], rng=rng)

def test_solve_discrete_lyapunov_via_bilinear():
    N = 5
    rng = np.random.default_rng(utt.fetch_seed())
    a = at.dmatrix()
    q = at.dmatrix()
    f = function([a, q], [solve_discrete_lyapunov(a, q, method="bilinear")])

    A = rng.normal(size=(N, N))
    Q = rng.normal(size=(N, N))

    X = f(A, Q)
    assert np.allclose(A @ X @ A.conj().T - X + Q, 0.0)

    utt.verify_grad(solve_discrete_lyapunov, pt=[A, Q], rng=rng)

def test_solve_continuous_lyapunov():
    N = 5
    rng = np.random.default_rng(utt.fetch_seed())
    a = at.dmatrix()
    q = at.dmatrix()
    f = function([a, q], [solve_continuous_lyapunov(a, q)])

    A = rng.normal(size=(N, N))
    Q = rng.normal(size=(N, N))
    X = f(A, Q)

    assert np.allclose(A @ X + X @ A.conj().T, Q)

    utt.verify_grad(solve_continuous_lyapunov, pt=[A, Q], rng=rng)


import unittest2

class TestIterator(unittest2.TestCase):

    def _getTargetClass(self):
        from gcloud.iterator import Iterator
        return Iterator

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_ctor(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        iterator = self._makeOne(client, PATH)
        self.assertTrue(iterator.client is client)
        self.assertEqual(iterator.path, PATH)
        self.assertEqual(iterator.page_number, 0)
        self.assertEqual(iterator.next_page_token, None)

    def test___iter__(self):
        PATH = '/foo'
        KEY1 = 'key1'
        KEY2 = 'key2'
        ITEM1, ITEM2 = object(), object()
        ITEMS = {KEY1: ITEM1, KEY2: ITEM2}

        def _get_items(response):
            for item in response.get('items', []):
                yield ITEMS[item['name']]
        connection = _Connection({'items': [{'name': KEY1}, {'name': KEY2}]})
        client = _Client(connection)
        iterator = self._makeOne(client, PATH)
        iterator.get_items_from_response = _get_items
        self.assertEqual(list(iterator), [ITEM1, ITEM2])
        kw, = connection._requested
        self.assertEqual(kw['method'], 'GET')
        self.assertEqual(kw['path'], PATH)
        self.assertEqual(kw['query_params'], {})

    def test_has_next_page_new(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        iterator = self._makeOne(client, PATH)
        self.assertTrue(iterator.has_next_page())

    def test_has_next_page_w_number_no_token(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        iterator = self._makeOne(client, PATH)
        iterator.page_number = 1
        self.assertFalse(iterator.has_next_page())

    def test_has_next_page_w_number_w_token(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        TOKEN = 'token'
        iterator = self._makeOne(client, PATH)
        iterator.page_number = 1
        iterator.next_page_token = TOKEN
        self.assertTrue(iterator.has_next_page())

    def test_get_query_params_no_token(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        iterator = self._makeOne(client, PATH)
        self.assertEqual(iterator.get_query_params(), {})

    def test_get_query_params_w_token(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        TOKEN = 'token'
        iterator = self._makeOne(client, PATH)
        iterator.next_page_token = TOKEN
        self.assertEqual(iterator.get_query_params(),
                         {'pageToken': TOKEN})

    def test_get_query_params_extra_params(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        extra_params = {'key': 'val'}
        iterator = self._makeOne(client, PATH, extra_params=extra_params)
        self.assertEqual(iterator.get_query_params(), extra_params)

    def test_get_query_params_w_token_and_extra_params(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        TOKEN = 'token'
        extra_params = {'key': 'val'}
        iterator = self._makeOne(client, PATH, extra_params=extra_params)
        iterator.next_page_token = TOKEN

        expected_query = extra_params.copy()
        expected_query.update({'pageToken': TOKEN})
        self.assertEqual(iterator.get_query_params(), expected_query)

    def test_get_query_params_w_token_collision(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        extra_params = {'pageToken': 'val'}
        self.assertRaises(ValueError, self._makeOne, client, PATH,
                          extra_params=extra_params)

    def test_get_next_page_response_new_no_token_in_response(self):
        PATH = '/foo'
        TOKEN = 'token'
        KEY1 = 'key1'
        KEY2 = 'key2'
        connection = _Connection({'items': [{'name': KEY1}, {'name': KEY2}],
                                  'nextPageToken': TOKEN})
        client = _Client(connection)
        iterator = self._makeOne(client, PATH)
        response = iterator.get_next_page_response()
        self.assertEqual(response['items'], [{'name': KEY1}, {'name': KEY2}])
        self.assertEqual(iterator.page_number, 1)
        self.assertEqual(iterator.next_page_token, TOKEN)
        kw, = connection._requested
        self.assertEqual(kw['method'], 'GET')
        self.assertEqual(kw['path'], PATH)
        self.assertEqual(kw['query_params'], {})

    def test_get_next_page_response_no_token(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        iterator = self._makeOne(client, PATH)
        iterator.page_number = 1
        self.assertRaises(RuntimeError, iterator.get_next_page_response)

    def test_reset(self):
        connection = _Connection()
        client = _Client(connection)
        PATH = '/foo'
        TOKEN = 'token'
        iterator = self._makeOne(client, PATH)
        iterator.page_number = 1
        iterator.next_page_token = TOKEN
        iterator.reset()
        self.assertEqual(iterator.page_number, 0)
        self.assertEqual(iterator.next_page_token, None)

    def test_get_items_from_response_raises_NotImplementedError(self):
        PATH = '/foo'
        connection = _Connection()
        client = _Client(connection)
        iterator = self._makeOne(client, PATH)
        self.assertRaises(NotImplementedError,
                          iterator.get_items_from_response, object())

class TestMethodIterator(unittest2.TestCase):

    def _getTargetClass(self):
        from gcloud.iterator import MethodIterator
        return MethodIterator

    def _makeOne(self, *args, **kw):
        return self._getTargetClass()(*args, **kw)

    def test_ctor_defaults(self):
        wlm = _WithListMethod()
        iterator = self._makeOne(wlm.list_foo)
        self.assertEqual(iterator._method, wlm.list_foo)
        self.assertEqual(iterator._token, None)
        self.assertEqual(iterator._page_size, None)
        self.assertEqual(iterator._kw, {})
        self.assertEqual(iterator._max_calls, None)
        self.assertEqual(iterator._page_num, 0)

    def test_ctor_explicit(self):
        wlm = _WithListMethod()
        TOKEN = wlm._letters
        SIZE = 4
        CALLS = 2
        iterator = self._makeOne(wlm.list_foo, TOKEN, SIZE, CALLS,
                                 foo_type='Bar')
        self.assertEqual(iterator._method, wlm.list_foo)
        self.assertEqual(iterator._token, TOKEN)
        self.assertEqual(iterator._page_size, SIZE)
        self.assertEqual(iterator._kw, {'foo_type': 'Bar'})
        self.assertEqual(iterator._max_calls, CALLS)
        self.assertEqual(iterator._page_num, 0)

    def test___iter___defaults(self):
        import string
        wlm = _WithListMethod()
        iterator = self._makeOne(wlm.list_foo)
        found = []
        for char in iterator:
            found.append(char)
        self.assertEqual(found, list(string.printable))
        self.assertEqual(len(wlm._called_with), len(found) // 10)
        for i, (token, size, kw) in enumerate(wlm._called_with):
            if i == 0:
                self.assertEqual(token, None)
            else:
                self.assertEqual(token, string.printable[i * 10:])
            self.assertEqual(size, None)
            self.assertEqual(kw, {})

    def test___iter___explicit_size_and_maxcalls_and_kw(self):
        import string
        wlm = _WithListMethod()
        iterator = self._makeOne(wlm.list_foo, page_size=2, max_calls=3,
                                 foo_type='Bar')
        found = []
        for char in iterator:
            found.append(char)
        self.assertEqual(found, list(string.printable[:2 * 3]))
        self.assertEqual(len(wlm._called_with), len(found) // 2)
        for i, (token, size, kw) in enumerate(wlm._called_with):
            if i == 0:
                self.assertEqual(token, None)
            else:
                self.assertEqual(token, string.printable[i * 2:])
            self.assertEqual(size, 2)
            self.assertEqual(kw, {'foo_type': 'Bar'})

class _WithListMethod(object):

    def __init__(self):
        import string
        self._called_with = []
        self._letters = string.printable

    def list_foo(self, page_token, page_size, **kw):
        if page_token is not None:
            assert page_token == self._letters
        self._called_with.append((page_token, page_size, kw))
        if page_size is None:
            page_size = 10
        page, self._letters = (
            self._letters[:page_size], self._letters[page_size:])
        token = self._letters or None
        return page, token

class _Connection(object):

    def __init__(self, *responses):
        self._responses = responses
        self._requested = []

    def api_request(self, **kw):
        self._requested.append(kw)
        response, self._responses = self._responses[0], self._responses[1:]
        return response

class _Client(object):

    def __init__(self, connection):
        self.connection = connection


import logging
import random
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponseRedirect

from django.db.models import Q
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from rest_framework.response import Response
from rest_framework import viewsets, permissions, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from rest_framework.authentication import TokenAuthentication, SessionAuthentication, BasicAuthentication
from rest_framework.decorators import action

from users.models import User
from .models import Album, Picture, Game, Player
from .utils import check_players
from control.models import Control
from . import serializers

logger = logging.getLogger(__file__)

@api_view(['GET','POST',],)
@permission_classes((IsAuthenticated,))
def pictures(request):
    data = {}

    if request.method == 'POST':
        album = request.data['album_id']
    elif request.method == 'GET':
        album = request.GET.get('album_id')
    else:
        return Response(status.HTTP_400_BAD_REQUEST)

    pictures = Picture.objects.filter(album=album).values()
    pictures_list = []
    for picture in pictures:
        print(f'PICTURE: {picture}')
        serializer = serializers.PicturesSerializer(picture, data=request.data)
        if serializer.is_valid():
            pictures_list.append(picture)
        else:
            print(f'Failed picture serializing. ERROR: {serializer.errors}')
    data['pictures'] = pictures_list
    data['response'] = f'Picture for album: {album}'

    if len(pictures_list) == 0:
        return Response(data='No Pictures', status=status.HTTP_200_OK)
    else:
        return Response(data)

@api_view(['GET',],)
def public_albums(request):
    """Access to public albums
    """
    data = {}
    if request.method == 'GET':
        _public_albums = Album.objects.filter(is_public=True)
        albums_list = []
        for album in _public_albums:
            serializer = serializers.AlbumSerializer(album, data=request.data)
            if serializer.is_valid():
                albums_list.append(serializer.data)
            else:
                print(f'Failed picture serializing. ERROR: {serializer.errors}')
                logger.error(f'Failed picture serializing. ERROR: {serializer.errors}')

        data['response'] = 'Public albums'
        data['albums'] = albums_list
        return Response(data)

@api_view(['POST', 'GET', 'PUT'],)
@permission_classes((IsAuthenticated,))
def albums(request):
    '''
    Retreive albums: either public ones or per user
    API endpoints:
    /api/game/albums/album_id=<ALBUM ID> for particular user album (the one that is logged in)
    /api/game/albums/ for all user albums
    '''
    data = {}
    if request.method == 'GET':
        query = request.GET.get('album_id')

        if query is not None:
            user_album = Album.objects.get(pk=query)
            serializer = serializers.AlbumSerializer(user_album, data=request.data)
            if serializer.is_valid():
                data['response'] = f'User album id {query}'
                data['album'] = serializer.data
                return Response(data)
            else:
                print(f'Failed picture serializing. ERROR: {serializer.errors}')
                data['response'] = f'No album found for id {query}'
                return Response(data)
        else:
            print('>> Getting all user albums')
            user_albums = Album.objects.filter(Q(user=request.user))
            albums_list = []
            for album in user_albums:
                serializer = serializers.AlbumSerializer(album, data=request.data)
                if serializer.is_valid():
                    albums_list.append(serializer.data)
            data['response'] = f'All albums for user: {request.user}'

            # print(f'ERROR GETTNIG ALL ALBUMS')
            # data = serializer.errors
            data['albums'] = albums_list
            return Response(data)

    # Update album
    elif request.method == 'PUT':

        try:
            album = Album.objects.get(album_id=request.GET.get('album_id'))
        except Album.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)

        serializer = serializers.AlbumSerializer(album, data=request.data)
        data = {}
        if serializer.is_valid():
            updated_album = serializer.save()
            data = serializer.data
            data['response'] = 'Update successful'
        else:
            data['response'] = "Update failed."

        return Response(data)

    # Create new album
    elif request.method == 'POST':
        data = {}
        print(f'New Album request: {request.data}')
        album_name = request.data['name']
        user = request.user
        pictures = request.data['pictures']
        board = request.data['board']

        album = Album()
        album.name = album_name
        album.user = user
        album.pictures = pictures
        album.board = board
        album.number_of_pictures = len(pictures[0])
        album.save()

        for pic in pictures[0]:
            image = Picture()
            image.name = pic
            image.album = album
            image.url = pictures[0][f'{pic}']['url']
            image.remove_id = pictures[0][f'{pic}']['remote_id']
            image.save()

        serializer = serializers.AlbumSerializer(album, data=request.data)
        if serializer.is_valid():
            data = serializer.data
        else:
            return Response('ERROR SERIALIZING NEW ALBUM')

        return Response(data)

    return Response(status.HTTP_400_BAD_REQUEST)

    # return Response(data='Images loaded', status=status.HTTP_200_OK)

################ NOT USED ###################
class UserAlbumsView(viewsets.ModelViewSet):
    serializer_class = serializers.AlbumSerializer
    authentication_classes = [TokenAuthentication,]
    permission_classes = (IsAuthenticated,)

    # queryset = Order.objects.all()
    @action(methods=['POST', ], detail=False)
    def get_queryset(self, *args, **kwargs):
        # queryset_list = Album.objects.all()
        user = self.request.user
        queryset_list = queryset_list.filter(Q(user=user))
        print(f'USER {user}  Albums: {queryset_list}')

        return queryset_list

@api_view(['POST',])
@permission_classes((IsAuthenticated,))
def game_create(request):
    data = {}
    # info = {}
    if request.method == 'POST':
        user = request.user
        # album_id = Album.objects.get(user=request.user, name=request.data['album_name'])

        # info['winning_conditions'] = request.data['winning_conditions']
        # info['is_public'] = request.data['is_public']
        # info['prizes'] = request.data['prizes']

        album_id = request.data['album_id']
        album = Album.objects.get(pk=album_id)
        # print(f'ALBUM_SER: {album_ser}')

        serializer = serializers.GameSerializer(data=request.data)

        if serializer.is_valid():
            game = serializer.save()

            game.album = album
            game.pictures_pool = album.pictures
            game.user = user
            game.save()

            data['response'] = "Game is ready"
            data['game_id'] = game.game_id
            data['album_id'] = game.album.pk
            data['winning_conditions'] = game.winning_conditions
            data['is_public'] = game.is_public
            data['prizes'] = game.prizes
            data['user_id'] = user.pk

            return Response(data)
        else:
            data['response'] = "Failed preping the game."
            data['errors'] = serializer.errors
            return Response(data)
    else:
        return Response(status.HTTP_400_BAD_REQUEST)

@api_view(['POST',])
@permission_classes((IsAuthenticated,))
def game_request(request):
    '''
    Calculating the price for the game according to the number of players joined.
    '''
    data = {}
    if request.method == 'POST':

        game = Game.objects.get(user=request.user, game_id=request.data['game_id'])
        game.game_requested = True

        # Collecting the Players
        try:
            players = Player.objects.filter(player_game_id=request.data["game_id"])
            players_list = []
            for player in players:
                player_data = {}
                player_data['nickname'] = player.nickname
                player_data['player_id'] = str(player.player_id)
                players_list.append(player_data)

            game.players_list = players_list

            game.number_of_players = len(players)

            #Setting up the price for the game
            # TODO: Define pricing accurately

            try:
                base_price = Control.objects.get(name='base_price').value_float
            except:
                base_price = 0.1

            if players:
                if len(players) < 21:
                    game_cost = round(len(players) * base_price,2)
                elif len(players) < 41:
                    game_cost = round(len(players) * base_price*0.80,2)
                else:
                    game_cost = round(len(players) * base_price*0.66,2)
            else:
                game_cost = 0

            data['game_cost'] = game_cost
            data['num_of_players'] = len(players)
            game.game_cost = game_cost
            game.save()
            return Response(data)
        except Exception as e:
            print(f'GAME: There are no players. ERROR: {e}')
            logger.error(f'GAME: No players found for this game. ERROR: {e}')
            return Response(status.HTTP_503_SERVICE_UNAVAILABLE)
    else:
        return Response('Bad request', status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET',])
@permission_classes((IsAuthenticated,))
def game_info(request):
    data = {}
    if request.method == 'GET':
        game_id = request.GET.get('game_id')
        user = request.user
        game = Game.objects.get(user=user, game_id=game_id)
        serializer = serializers.GameSerializer(game)
        data = serializer.data
        return Response(data)
    else:
        return Response(status.HTTP_400_BAD_REQUEST)

@api_view(['POST',])
@permission_classes((IsAuthenticated,))
def game_confirm(request):
    '''
    - Host to confirm the game
    - Checking that the user's balanace can cover the cost of the game
    '''
    data = {}
    if request.method == 'POST':
        user = User.objects.get(pk=request.user.pk)
        game = Game.objects.get(user=request.user.pk, game_id=request.data['game_id'])

        # Check user's balance and deduct the amount
        current_balance = user.balance
        game_cost = game.game_cost
        new_balance = current_balance - game_cost
        if new_balance > 0:
            # Updating user new balance
            user.balance = new_balance
            user.save()

            # Start the game
            game.started = True
            game.save()
            data['response'] = 'Game Started'
            return Response(data, status=status.HTTP_200_OK)
        else:
            data['response'] = 'Not enough balance'
            return Response(data)
    else:
        return Response(status.HTTP_400_BAD_REQUEST)

@api_view(['GET',])
@permission_classes((IsAuthenticated,))
def game_play(request):
    '''
    Handles the process for every click on the NEXT button
    1) Get the pictures from the pictures_pool
    2) Get a random picture from the pool
    3) Pop that picture from the pool and write it back to the DB
    4) Check which players met the winning condition
    5) Continue until "BINGO" or untill all pictures were drawn

    Request:
    - game id (error if there is no such game for the user)

    Return:
    - Picture ID for display
    - number of pictures pooled
    - number of pictures left
    - player IDs those who have winning conditions

    '''
    data = {}
    if request.method == 'GET':
        try:
            game_id = request.GET.get('game_id')
            game = Game.objects.get(user=request.user.pk, game_id=game_id)
        except Exception as e:
            print('No such game for this player')
            logger.error(f'No such game for this player. ERROR: {e}')
            return Response('Error game request', status=status.HTTP_400_BAD_REQUEST)

        pictures_pool_dict = game.pictures_pool[0]
        pictures_pool_list = []
        items_list = []

        # Creating a list of all keys in the dict
        if len(pictures_pool_dict) > 0:
            for pic in pictures_pool_dict:
                items_list.append(pic)

            # Drawing a random key from the dict
            picture_draw_index = random.randint(0,len(items_list)-1)
            rand_item = items_list[picture_draw_index]
            picture_draw = pictures_pool_dict.pop(rand_item)

            # Updating the DB with the reduced list of pictures
            pictures_pool_list.append(pictures_pool_dict)
            game.pictures_pool = pictures_pool_list

            current_shown_pictures = game.shown_pictures
            current_shown_pictures.append({rand_item:picture_draw})
            game.shown_pictures = current_shown_pictures

            game.save()

            data['remaining_pictures'] = len(pictures_pool_dict)
            data['picture'] = picture_draw
            data['pic_index'] = rand_item

            # Check the players' boards:
            active_boards = check_players(picture_id=picture_draw['remote_id'], game_id=game_id)
            print(f'ACTIVE BOARDS: {active_boards}')

            picture_draw_id = picture_draw["remote_id"]

            for board in active_boards:
                print(f'>> Pic: {picture_draw_id}')
                print(f'>> B: {board.pictures}')
                x_count_full = 0
                for i in range(board.size):
                    x_count_row = 0

                    # 1) Replace the hits with an X
                    board.pictures[i] = [pic if pic != picture_draw_id else 'X' for pic in board.pictures[i]]

                    # Count the 'X's are on the row
                    x_count_row += board.pictures[i].count('X')
                    if x_count_row == board.size:
                        player = Player.objects.get(board_id=board.pk)
                        player.winnings.append(f'row_{i}')
                        player.save()
                        print(f'BINGO: Player {player.nickname} Row {i} board {board}!!!')

                    # Count the 'X's are on the board
                    x_count_full += board.pictures[i].count('X')

                    # Update the board with the hits
                    board.save()

                    # Reset the row count for the next row
                    x_count_row = 0

                # Columns and diagnals winning condition check
                x_count_diag_left_to_right = 0
                x_count_diag_right_to_left = 0

                for column in range(board.size):
                    player = Player.objects.get(board_id=board.pk)
                    x_count_column = 0

                    for row in range(board.size):
                        if board.pictures[row][column] == 'X':
                            x_count_column += 1
                            player.winnings.append(f'row_{row}_col_{column}')

                    if x_count_column == board.size:
                        player.winnings.append(f'col_{column}')
                        print(f'BINGO: Player {player.nickname} Column {i} board {board}!!!')

                    if board.pictures[column][column] == 'X':
                        x_count_diag_left_to_right += 1

                    if x_count_diag_left_to_right == board.size:
                        player.winnings.append('diag_l2r')
                        print(f'BINGO: Player {player.nickname} Diagonal L2R board {board}!!!')

                    if board.pictures[-column-1][-column-1] == 'X':
                        x_count_diag_right_to_left += 1

                    if x_count_diag_right_to_left == board.size:
                        player.winnings.append('diag_r2l')
                        print(f'BINGO: Player {player.nickname} Diagonal R2L board {board}!!!')

                    player.save()

                # print(f'>> UPDATED B: {board.pictures}')
                # print(f'>> X-FULL Count: {x_count_full}')
                if x_count_full == board.size ** 2:
                    player = Player.objects.get(board_id=board.pk)
                    player.winnings.append('FULL')
                    player.save()
                    print(f'BINGO FULLLLL!!! Player {player.nickname} Board: {board}')

            return Response(data)
        else:
            game.ended = True
            game.is_finished = True
            game.save()

            return Response('Game Finished. No more pictures')
    else:
        print('Bad request at game play')
        logger.error('Bad request at game play')
        return Response(status.HTTP_400_BAD_REQUEST)

@api_view(['GET',])
@permission_classes((IsAuthenticated,))
def game_winnings(request):
    """Checks the winnings for particular player

    Args:
        player_id (str):

    Returns:
        list: List of all the winnings that the player currently has
    """
    if request.method == 'GET':
        winnings_set = set()
        board_id = request.GET.get('board_id')
        game_id = request.GET.get('game_id')
        try:
            player = Player.objects.get(board_id=board_id)
            for win in player.winnings:
                winnings_set.add(win)

            if len(winnings_set) == 0:
                return Response('No winnings')
            else:
                return Response(winnings_set)
        except Exception as e:
            print(f'There is no such player for that game. ERROR: {e}')
            logger.error(f'There is no such player for that game. ERROR: {e}')
            return Response('Player not found.', status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET',])
@permission_classes((IsAuthenticated,))
def get_players(request):
    ''' Getting the current players listed for a game
    '''
    data = {}
    if request.method == 'GET':
        game_id = request.GET.get('game_id')
        print(f'>> API: Accessing players list on game {game_id}')
        game_players = []
        _players = Player.objects.filter(player_game_id=game_id)
        for player in _players:
            player_obj = {}
            player_obj['id'] = player.pk
            player_obj['nickname'] = player.nickname
            player_obj['approved'] = player.approved
            game_players.append(player_obj)
        data['players'] = game_players
        return Response(data)
    else:
        print(f'>> API: Failed Accessing players list')
        return Response(status.HTTP_400_BAD_REQUEST)

@api_view(['GET','PUT',])
@permission_classes((IsAuthenticated,))
def player(request):
    """Get information about a particular player, and update his approved status

    Args:
        player_id (str):
        game_id (str):

    Returns:
        approved: bool
        board_id: str
    """
    if request.method == 'GET':
        player_id = request.GET.get('player_id')
        game_id = request.GET.get('game_id')
        print(f"Getting Player: {player_id}")

        data = {}
        player = Player.objects.get(player_game_id=game_id, pk=player_id)
        data['approved'] = player.approved
        data['board_id'] = player.board_id
        return Response(data)
    elif request.method == 'PUT':
        print(f'PU: {request.data}')
        player_id = request.data['player_id']
        game_id = request.data['game_id']
        player = Player.objects.get(player_game_id=game_id, pk=player_id)
        if request.data['approved'] == 'True':
            player.approved = True
        elif request.data['approved'] == 'False':
            player.approved = False
        else:
            return Response('Bad data formating', status.HTTP_400_BAD_REQUEST)
        player.save()

        return Response(f'Player approved status: {player.approved}', status=status.HTTP_200_OK)
    else:
        return Response(status.HTTP_400_BAD_REQUEST)

#################
# DEMO ONLY
#################

@login_required
def game_control(request):
    context = {}
    if request.method == 'POST':
        if 'gamePrep' in request.POST:
            album = Album.objects.filter(user=request.user).first()
            new_game = Game.objects.create(
                album_id=album,
                user = request.user,
                winning_conditions='ALL',
                is_public=True
            )

            try:
                context['game'] = new_game
                context['album'] = album.name
                context['winning_cond'] = 'ALL'
                context['is_public'] = 'Public Game'
            except Exception as e:
                context['game'] = new_game
                context['album'] = 'There are no albums for this user'
                context['winning_cond'] = 'ALL'
                context['is_public'] = 'Public Game'

            # return redirect('game:game-room', new_game.game_id)

        elif 'startGame' in request.POST:
            # TODO: open a WS on start of game listening to the joining of players
            game = Game.objects.filter(user=request.user).last
            context['game'] = game

        else:
            context['next'] = "NEXT IMAGE"

    #TODO: What happend on game complete?, e.g. is_finished is set to true

    return render(request, 'game/game-control.html', context)

def home(request):
    context = {}
    return render(request, 'game/home.html', context)

def game_room(request, game_id):
    ''' For testing purposes. Room game demo
    '''
    context = {}
    game = Game.objects.get(game_id=game_id)

    if request.method == 'POST':
        if 'requestGame' in request.POST:
            # TODO: Delete game data to oavoid future collisions

            # Setting game to started
            game = Game.objects.get(user=request.user.pk, game_id=game_id)
            game.game_requested = True

            # Collecting the Players
            _players = Player.objects.filter(player_game_id=game_id)
            game.player_list = _players

            game.number_of_players = len(_players)

            #Setting up the price for the game
            # TODO: Define pricing accurately
            if _players:
                if len(_players) < 21:
                    game.game_cost = round(len(_players) * 1/20,2)
                elif len(_players < 41):
                    game.game_cost = round(len(_players) * 1/25,2)
                else:
                    game.game_cost = round(len(_players) * 1/30,2)

            print(f'COST: {game.game_cost}')
            game.save()
        elif 'conirmed' in request.POST:
            user = User.objects.get(pk=request.user.pk)
            game = Game.objects.get(user=request.user.pk, game_id=game_id)
            game.game_requested = False

            # Check user's balance and deduct the amount
            current_balance = user.balance
            game_cost = game.game_cost
            new_balance = current_balance - game_cost
            if new_balance > 0:
                # Updating user new balance
                user.balance = new_balance
                user.save()

                # Start the game
                game.started = True
                game.save()
                print(f'GAME STARTED')

    context['game'] = game

    return render(request, 'game/game_room.html',context)

def players(request, user_id, game_id):
    ''' For generating the dynamic players table created with websocket
    '''
    context = {}
    _players = Player.objects.filter(player_game_id=game_id)
    context['players'] = _players
    return render(request, 'game/players.html', context)


import argparse

# Getting the scripts
parser = argparse.ArgumentParser()
parser.add_argument('--regularization', type = float, dest = 'reg_rate', default = 0.01, help = 'Enter regularization rate')
parser.add_argument('--input-data', type = str, dest = 'training_dataset_id', help = 'training dataset')
args = parser.parse_args()

# Set regularization rate
reg = args.reg_rate

# Get the experiment run context
from azureml.core import Run

run = Run.get_context()

# Load the diabetes dataset
diabetes = run.input_datasets['training_data'].to_pandas_dataframe()

# Seperate features and labels
X, y = diabetes[['Pregnancies','PlasmaGlucose','DiastolicBloodPressure','TricepsThickness','SerumInsulin','BMI','DiabetesPedigree','Age']].values, diabetes['Diabetic'].values

# Split data into training set and test set
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0)

# Train a logistic regression model
from sklearn.linear_model import LogisticRegression
import numpy as np

print('Training a logistic regression model with regularization rate of', reg)
run.log('Regularization Rate',  np.float(reg))
model = LogisticRegression(C=1/reg, solver="liblinear").fit(X_train, y_train)

# calculate accuracy
y_hat = model.predict(X_test)
acc = np.average(y_hat == y_test)
print('Accuracy:', acc)
run.log('Accuracy', np.float(acc))

# calculate AUC
from sklearn.metrics import roc_auc_score

y_scores = model.predict_proba(X_test)
auc = roc_auc_score(y_test,y_scores[:,1])
print('AUC: ' + str(auc))
run.log('AUC', np.float(auc))

# plot ROC curve
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_test, y_scores[:,1])
fig = plt.figure(figsize=(6, 4))

# Plot the diagonal 50% line
plt.plot([0, 1], [0, 1], 'k--')

# Plot the FPR and TPR achieved by our model
plt.plot(fpr, tpr)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')

run.log_image(name = "ROC", plot = fig)
plt.show()

# Creating output folder
os.makedirs('practice-arena/outputs', exist_ok = True)

# Save the model
import joblib

joblib.dump(value = model, filename = 'practice-arena/outputs/diabetes_model.pkl')

run.complete()

Work with Compute in Azure Machine Learning/diabetes-training-logistic/diabetes_training.py


# counting_sort 代码实现

from typing import List

def counting_sort(arr: List[int]):
    ma = max(arr)
    mi = min(arr)
    count = [0] * (ma - mi + 1)
    for j in range(ma - mi + 1):
        count[j] = 0
    for index in arr:
        count[index - mi] += 1
    for index in range(len(count) - 1):
        count[index + 1] += count[index]
    sortedArr = [0] * len(arr)
    for i in range(len(arr)):
        sortedArr[count[arr[i] - mi] - 1] = arr[i]
        count[arr[i] - mi] -= 1
    return sortedArr

if __name__ == '__main__':
    # import random
    # random.seed(54)
    # arr = [random.randint(0, 100) for _ in range(10)]
    arr = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
    print("原始数据：  ", arr)
    res = counting_sort(arr)
    print("计数排序结果", res)