import numpy as np
import pandas as pd
import csv
from tqdm import tqdm
import math
import seaborn as sns
import matplotlib.pyplot as plt

# useful global variables
wines_header, tumors_header, wines_global,tumors_global = [],[],[],[]

# def normalize(x):
#     if max(x) == min(x):
#         return x
#     return x / (max(x) - min(x))

def norm(x):
    x_normed = (x - x.min(0)) / x.ptp(0)
    return x_normed

def process_wines():
    with open("winequality-red.csv", 'r') as f:
        wines = list(csv.reader(f, delimiter=";"))
    global wines_header
    wines_header = np.array(wines[0])  # with label header
    wines = np.array(wines[1:], dtype=np.float)  # with label

    # clean malinformed values by deleting the rows they inhabit
    invalid_index = []
    for i in range(len(wines)):
        for number in wines[i]:
            if math.isnan(number):
                np.delete(wines, i, 0)

    # differentiate labels
    for i in tqdm(range(len(wines[:, -1]))):
        if wines[:, -1][i] > 5:
            wines[:, -1][i] = 1
        else:
            wines[:, -1][i] = 0

    global wines_global
    wines_global = wines
    wines_x = wines[:, :-1]
    # for i in range(len(wines_x)):
    #     for j in range(len(wines_x[0])):
    #         wines_x[i][j] -= 5

    # # wines_x = np.delete(wines_x,10,axis=1)
    # # wines_x = np.delete(wines_x,9,axis=1)
    # wines_x = np.delete(wines_x,8,axis=1)
    # wines_x = np.delete(wines_x,7,axis=1)
    # wines_x = np.delete(wines_x,6,axis=1)
    # wines_x = np.delete(wines_x,5,axis=1)
    # wines_x = np.delete(wines_x,4,axis=1)
    # wines_x = np.delete(wines_x,3,axis=1)
    # # wines_x = np.delete(wines_x,2,axis=1)
    # # wines_x = np.delete(wines_x,1,axis=1)
    # wines_x = np.delete(wines_x, 0, axis=1)
    wines_y = wines[:, -1]
    #
    # for i in range(len(wines_x)):
    #     wines_x[i] = normalize(wines_x[i])
    # wines_x = norm(wines_x)

    return wines_x, wines_y

def process_tumors():
    with open("breast-cancer-wisconsin.data", 'r') as f:
        tumors = list(csv.reader(f, delimiter=";"))

    global tumors_header
    tumors_header = ["clump thickness", "cell size", "cell shape", "marginal adhesion", \
                     "single epithelial cell size", "number of bare nuclei", "bland chromatin", \
                     "number of normal nuclei", "mitosis", "label"]  # with label header but no IDs

    # highlight malinformed values
    invalid_index = []
    for i in tqdm(range(len(tumors))):
        tumors[i] = tumors[i][0].split(",")
        for j in range(len(tumors[i])):
            if tumors[i][j].isnumeric() == False:
                invalid_index.append(i)  # the whole row
        # differentiate labels
        if int(tumors[i][-1]) <= 2:
            tumors[i][-1] = '0'
        else:
            tumors[i][-1] = '1'

    # clean malinformed values by deleting the rows they inhabit
    invalid_index.sort(reverse=True)
    for i in invalid_index:
        tumors.remove(tumors[i])

    tumors = np.array(tumors[0:], dtype=np.float)
    tumors = tumors[:, 1:]

    global tumors_global
    tumors_global = tumors
    tumors_x = tumors[:,:-1]
    # tumors_x = norm(tumors_x) normalization
    #tumors_x = np.delete(tumors_x,3,axis=1) #
    tumors_y = tumors[:,-1]

    # for i in range(len(tumors_x)):
    #     tumors_x[i] = normalize(tumors_x[i])

    return tumors_x, tumors_y

def stats_wines():
    global wines_global
    wines = wines_global
    pos_index = []
    neg_index = []
    # separate two classes
    for index in range(len(wines)):
        if wines[index][-1] == 1:
            pos_index.append(index)
        else:
            neg_index.append(index)
    pos_wines = wines[pos_index]
    neg_wines = wines[neg_index]
    # sns.boxplot(data=pos_wines)
    # sns.boxplot(data=neg_wines)
    df = pd.DataFrame(wines, columns=wines_header)
    df.describe()
    # sns.pairplot(df) # density looks very suspicious
    # sns_plot = sns.pairplot(df)
    # sns_plot.savefig("wines_pairplot.png")
    df_pos = pd.DataFrame(pos_wines, columns=wines_header)
    df_pos.describe()
    df_neg = pd.DataFrame(neg_wines, columns=wines_header)
    df_neg.describe()

def stats_tumors():
    global tumors_global
    tumors = tumors_global
    pos_index = []
    neg_index = []
    # separate two classes
    for index in range(len(tumors)):
        if tumors[index][-1] == 1:
            pos_index.append(index)
        else:
            neg_index.append(index)
    pos_tumors = tumors[pos_index]
    neg_tumors = tumors[neg_index]

    df = pd.DataFrame(tumors, columns=tumors_header)
    df.describe()
    # sns.pairplot(df) # density looks very suspicious
    df_pos = pd.DataFrame(pos_tumors, columns=tumors_header)
    df_pos.describe()
    df_neg = pd.DataFrame(neg_tumors, columns=tumors_header)
    df_neg.describe()

# x,y = process_tumors()
# print(wines_global)
# print(x)
# print(y)
# for i in range(len(x)):
#     for j in range(len(x[0])):
#         if math.isnan(x[i][j]):
#             print(i,j)


import json

class DestinoCulinario:
    def __init__(self, id_destino, nombre, tipo_cocina, ingredientes, precio_minimo, precio_maximo, popularidad, disponibilidad, id_ubicacion, imagen):
        self.id = id_destino
        self.nombre = nombre
        self.tipo_cocina = tipo_cocina
        self.ingredientes = ingredientes
        self.precio_minimo = precio_minimo
        self.precio_maximo = precio_maximo
        self.popularidad = popularidad
        self.disponibilidad = disponibilidad
        self.id_ubicacion = id_ubicacion
        self.imagen = imagen

    #def __str__(self):
    #    return f"Destino Culinario: {self.nombre}, Tipo de cocina: {self.tipo_cocina}, \nPopularidad: {self.popularidad}, Disponibilidad: {self.disponibilidad}"

    def cambiar_disponibilidad(self, disponibilidad_nueva):
        self.disponibilidad = disponibilidad_nueva
    def a_json(self):
        return json.dumps(self.__dict__)

    @classmethod
    def de_json(cls, datos_json):
        datos = json.loads(datos_json)
        return cls(**datos)

    @staticmethod
    def cargar_destinos(archivo_json):
        with open(archivo_json, "r", encoding="utf-8") as archivo:
            datos = json.load(archivo)
        return [DestinoCulinario.de_json(json.dumps(dato)) for dato in datos]


# import library
import pandas as pd
import matplotlib.pyplot as plot

# read data
data_public = pd.read_csv("survey_results_public.csv",
                        usecols=["Respondent", "Age", "WorkWeekHrs", "YearsCodePro", "Age1stCode", "CompTotal"],
                        index_col="Respondent")

# drop null
data_public.dropna(inplace=True)
# Converting text to numeric data
data_public = data_public.replace(to_replace={"Younger than 5 years":"0"})
data_public = data_public.replace(to_replace={"Older than 85":"90"})
data_public = data_public.replace(to_replace={"Less than 1 year":"0"})
data_public = data_public.replace(to_replace={"More than 50 years":"55"})
# Converting work week hrs
data_public["WorkWeekHrs"] = data_public["WorkWeekHrs"] / 40

# change data type to float
data_public["Age1stCode"] = data_public["Age1stCode"].astype("float64")
data_public["YearsCodePro"] = data_public["YearsCodePro"].astype("float64")
# print data type
print(data_public.dtypes)
# show correlation between data
print(data_public.corr(method='pearson'))

# Creating a plots

plot.scatter(data_public["Age"], data_public["YearsCodePro"])
plot.title("Age depending on Years code pro")
plot.xlabel("Age")
plot.ylabel("Years Code Pro")
plot.show()

plot.scatter(data_public["YearsCodePro"], data_public["CompTotal"])
plot.title("Years code pro depending on CompTotal")
plot.xlabel("Years Code Pro")
plot.ylabel("CompTotal")
plot.show()

plot.scatter(data_public["Age"], data_public["CompTotal"])
plot.title("Age depending on CompTotal")
plot.xlabel("Age")
plot.ylabel("CompTotal")
plot.show()

plot.scatter(data_public["Age1stCode"], data_public["Age"])
plot.title("Age 1st code depending on Age")
plot.xlabel("Age 1st Code")
plot.ylabel("Age")
plot.show()

plot.scatter(data_public["Age"], data_public["WorkWeekHrs"])
plot.title("Work Week Hrs depending on Age")
plot.xlabel("Age")
plot.ylabel("WorkWeekHrs")
plot.show()
# dependent variable: "YearsCodePro"
# independent variables: "Age" & "Age1stCode"


from django.urls import path, include
from .views import PostsList, PostsSearch, PostDetail, PostCreate, PostUpdate, PostDelete, PostsListInCategory, \
    SubscribeCategoryView, Testtt
from django.views.generic import RedirectView
from django.views.decorators.cache import cache_page

urlpatterns = [
    # path('', RedirectView.as_view(url='accounts/')),  # instead of error page, it will redirect to the needed page
    path('posts/', cache_page(60)(PostsList.as_view()), name='posts_list'),
    path('posts/<int:pk>', PostDetail.as_view(), name='post_detail'),
    path('posts/search/', PostsSearch.as_view(), name='posts_search_list'),

    path('article/create/', PostCreate.as_view(), name='article_create'),
    path('article/<int:pk>/update/', PostUpdate.as_view(), name='article_update'),
    path('article/<int:pk>/delete/', PostDelete.as_view(), name='article_delete'),

    path('news/create/', PostCreate.as_view(), name='news_create'),
    path('news/<int:pk>/update/', PostUpdate.as_view(), name='news_update'),
    path('news/<int:pk>/delete/', PostDelete.as_view(), name='news_delete'),

    path('category/<int:category_id>/', PostsListInCategory.as_view(), name='posts_in_category'),
    path('category/<int:category_id>/subscribe/', SubscribeCategoryView.as_view(), name='subscribe_category'),

    path('testtt/', Testtt.as_view(), name='posts_testtt_list'),
]


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

import time
import datetime
import os
from app.common import definitions, values
from shutil import copyfile

prev_trace_func = None

def create():
    log_file_name = "log-" + str(time.time())
    log_file_path = definitions.DIRECTORY_LOG_BASE + "/" + log_file_name
    definitions.FILE_MAIN_LOG = log_file_path
    with open(definitions.FILE_MAIN_LOG, 'w+') as log_file:
        log_file.write("[Start] " + values.TOOL_NAME + " started at " + str(datetime.datetime.now()) + "\n")
    if os.path.exists(definitions.FILE_LAST_LOG):
        os.remove(definitions.FILE_LAST_LOG)
    if os.path.exists(definitions.FILE_ERROR_LOG):
        os.remove(definitions.FILE_ERROR_LOG)
    if os.path.exists(definitions.FILE_COMMAND_LOG):
        os.remove(definitions.FILE_COMMAND_LOG)
    with open(definitions.FILE_ERROR_LOG, 'w') as fp:
        pass
    with open(definitions.FILE_COMMAND_LOG, 'w') as fp:
        pass
    with open(definitions.FILE_LAST_LOG, 'w+') as last_log:
        last_log.write("[Start] " + values.TOOL_NAME + " started at " + str(datetime.datetime.now()) + "\n")

def log(log_message):
    log_message = "[" + str(time.asctime()) + "]" + log_message

    if "COMMAND" in log_message:
        with open(definitions.FILE_COMMAND_LOG, 'a') as log_file:
            log_file.write(log_message)
    with open(definitions.FILE_MAIN_LOG, 'a') as log_file:
        log_file.write(log_message)
    with open(definitions.FILE_LAST_LOG, 'a') as log_file:
        log_file.write(log_message)

def configuration(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = str(message).strip().lower().replace("[config]", "")
    message = "[CONFIGURATION]: " + str(message) + "\n"
    log(message)

def information(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[INFO]: " + str(message) + "\n"
    log(message)

def trace(function_name, arguments):
    global prev_trace_func
    if function_name != prev_trace_func:
        prev_trace_func = function_name
        message = "[TRACE]: " + function_name + "\n"
        log(message)

def note(message):
    message = str(message).strip().lower().replace("[note]", "")
    message = "[NOTE]: " + str(message) + "\n"
    log(message)

def command(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[COMMAND]: " + str(message) + "\n"
    log(message)

def error(message):
    with open(definitions.FILE_ERROR_LOG, 'a') as last_log:
        last_log.write(str(message) + "\n")
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[ERROR]: " + str(message) + "\n"
    log(message)

def output(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[OUTPUT]: " + str(message) + "\n"
    log(message)

def warning(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[WARNING]: " + str(message) + "\n"
    log(message)

def data(message, data=None, is_patch=False):
    if values.DEBUG_DATA or is_patch:
        message = str(message).strip()
        message = "[DATA]: " + str(message) + "\n"
        log(message)
        if data:
            data = "[DATA]: " + str(data) + "\n"
            log(data)

def debug(message):
    message = str(message).strip().replace("\t", "").replace("\n", "")
    message = "[DEBUG]: " + str(message) + "\n"
    log(message)

def store():
    copyfile(definitions.FILE_MAIN_LOG, definitions.DIRECTORY_LOG + "/log-latest")
    copyfile(definitions.FILE_COMMAND_LOG, definitions.DIRECTORY_LOG + "/log-command")
    copyfile(definitions.FILE_ERROR_LOG, definitions.DIRECTORY_LOG + "/log-error")
    copyfile(definitions.FILE_MAKE_LOG, definitions.DIRECTORY_LOG + "/log-make")

def end(time_duration, is_error=False):
    output("[END] " + values.TOOL_NAME + " ended at " + str(datetime.datetime.now()))
    output("Initialization: " + time_duration[definitions.KEY_DURATION_INITIALIZATION] + " minutes")
    output("Build: " + time_duration[definitions.KEY_DURATION_BUILD_ANALYSIS] + " minutes")
    output("Diff Analysis: " + time_duration[definitions.KEY_DURATION_DIFF_ANALYSIS] + " minutes")
    output("Slicing: " + time_duration[definitions.KEY_DURATION_SLICE] + " minutes")
    output("Clone Analysis: " + time_duration[definitions.KEY_DURATION_CLONE_ANALYSIS] + " minutes")
    output("AST Analysis: " + time_duration[definitions.KEY_DURATION_EXTRACTION] + " minutes")
    output("Map Generation: " + time_duration[definitions.KEY_DURATION_MAP_GENERATION] + " minutes")
    output("Translation: " + time_duration[definitions.KEY_DURATION_TRANSLATION] + " minutes")
    output("Evolution: " + time_duration[definitions.KEY_DURATION_EVOLUTION] + " minutes")
    output("Transplantation: " + time_duration[definitions.KEY_DURATION_TRANSPLANTATION] + " minutes")
    output("Verification: " + time_duration[definitions.KEY_DURATION_VERIFICATION] + " minutes")
    # output("Reverse Transplantation: " + time_duration[Definitions.KEY_DURATION_REVERSE] + " minutes")
    # output("Evaluation: " + time_duration[Definitions.KEY_DURATION_EVALUATION] + " minutes")
    output("Comparison: " + time_duration[definitions.KEY_DURATION_COMPARISON] + " minutes")
    output("Summarizing: " + time_duration[definitions.KEY_DURATION_SUMMARIZATION] + " minutes")
    if is_error:
        output(values.TOOL_NAME + " exited with an error after " + time_duration[
            definitions.KEY_DURATION_TOTAL] + " minutes")
    else:
        output(values.TOOL_NAME + " finished successfully after " + time_duration[
            definitions.KEY_DURATION_TOTAL] + " minutes")


from django import forms

from core.models.auxiliary_tables import Region, CompanyDomain, CompanyIndustry, CLevel
from core.models.startup import StartupUser, ADMIN_EVENTS

class ClearableFileInputCustom(forms.ClearableFileInput):
    def get_context(self, name, value, attrs):
        context = super().get_context(name, value, attrs)
        context['widget']['is_initial'] = False
        return context

class StartupForm(forms.ModelForm):
    admins_events = forms.TypedChoiceField(
        required=True,
        choices=ADMIN_EVENTS,
        widget=forms.RadioSelect(
            attrs={"class": "form-check form-check-inline"}
        )
    )

    class Meta:
        model = StartupUser
        exclude = ["user_id"]
        widgets = {
            "who_am_I": forms.TextInput(attrs={"class": "form-control"}),
            "status": forms.Select(attrs={"class": "form-control"}),
            "position": forms.TextInput(attrs={"class": "form-control"}),
            "company_name": forms.TextInput(attrs={"class": "form-control"}),
            "fintech": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "commerce_tech": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "gtm_strategy": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "fundraising": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "tech": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "human_capital": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "international": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "domestic": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "company_description": forms.Textarea(attrs={"class": "form-control", "cols": 80, "rows": 4}),
            "describe_help": forms.Textarea(attrs={"class": "form-control", "cols": 80, "rows": 4}),
            "terms_conditions": forms.CheckboxInput(attrs={"class": "form-check form-check-inline"}),
            "problem": forms.Textarea(attrs={"class": "form-control", "cols": 80, "rows": 4}),
            "solution": forms.Textarea(attrs={"class": "form-control", "cols": 80, "rows": 4}),
            "funding_id": forms.Select(attrs={"class": "form-control"}),
            "annual_profit": forms.Select(attrs={"class": "form-control"}),
            "goals": forms.Select(attrs={"class": "form-control"}),
            "upload_deck": forms.FileInput(attrs={
                "class": "form-control",
                "accept": "application/pdf"
            }),
            "upload_profile_picture": ClearableFileInputCustom(attrs={"class": "form-control"}),
            "enable_match": forms.CheckboxInput(attrs={"class": "form-check"}),
            "functions_events": forms.CheckboxInput(attrs={"class": "form-check"})
        }

    def __init__(self, *args, **kwargs):
        super(StartupForm, self).__init__(*args, **kwargs)

        checkbox_fields = {
            "regions_user": Region,
            "regions_international": Region,
            "c_level_user": CLevel,
            "fintech_user": CompanyIndustry,
            "commerce_tech_user": CompanyIndustry
        }

        for item in checkbox_fields:
            self.fields[item].widget = forms.CheckboxSelectMultiple(
                attrs={"class": "form-check form-check-inline"}
            )
            self.fields[item].queryset = checkbox_fields[item].objects.all()

        fintech = CompanyDomain.objects.get(name__iexact="fintech")
        self.fields["fintech_user"].queryset = CompanyIndustry.objects.filter(domain=fintech)

        commerce_tech = CompanyDomain.objects.get(name__iexact="commerce tech")
        self.fields["commerce_tech_user"].queryset = CompanyIndustry.objects.filter(domain=commerce_tech)

        self.fields['terms_conditions'].required = True

    def save(self, commit=False, user=None):
        self.instance.who_am_I = "Startup"
        self.instance.user_id = user
        startup = super(StartupForm, self).save()

        region_list = self.cleaned_data["regions_user"]
        startup.regions_user.add(*region_list)

        international_list = self.cleaned_data["regions_international"]
        startup.regions_international.add(*international_list)

        c_level_list = self.cleaned_data["c_level_user"]
        startup.c_level_user.add(*c_level_list)

        commerce_tech_list = self.cleaned_data["commerce_tech_user"]
        startup.commerce_tech_user.add(*commerce_tech_list)

        fintech_list = self.cleaned_data["fintech_user"]
        startup.fintech_user.add(*fintech_list)

        c_level_list = self.cleaned_data["c_level_user"]
        startup.c_level_user.add(*c_level_list)

        if commit:
            startup.save()

        return startup


"""Render a Conway's Game of Life."""
import random
import time
from typing import Any, NamedTuple, Set

from ledmatrix import matrix
from ledmatrix.utilities import colors
from ledmatrix.utilities.colors import BLACK

INITIAL_POPULATION_DENSITY = 0.7  # type: float

CellCoords = NamedTuple('CellCoords', [('row_index', int), ('col_index', int)])

class GameOfLife(matrix.LedMatrix):
    """Matrix for playing Conway's Game of Life.

    See: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life.
    """
    def __init__(
        self,
        *args,  # type: Any
        **kwargs  # type: Any
    ):  # type: (...) -> None
        super().__init__(*args, **kwargs)
        self._living_cell_coordinates = set()  # type: Set[CellCoords]

        # initialize to random state
        for row_index in range(self.height):
            for col_index in range(self.width):
                if random.random() > INITIAL_POPULATION_DENSITY:  # noqa: S311
                    cell = CellCoords(row_index, col_index)
                    self._living_cell_coordinates.add(cell)
                    self[row_index][col_index] = self.default_color  # type: ignore

    def next_state(self):  # type: () -> None
        """Determine which cells live and die and apply to matrix."""
        next_living_cell_coordinates = set()  # type: Set[CellCoords]

        # get batch of cells that will survive / regenerate in the next round
        for cell in self._living_cell_coordinates:
            if self._will_live(cell):
                next_living_cell_coordinates.add(cell)
            for neighbor_cell in self._get_neighbor_coordinates(cell):
                if self._will_live(neighbor_cell):
                    next_living_cell_coordinates.add(neighbor_cell)

        # update living cell coordinates on class
        self._living_cell_coordinates = next_living_cell_coordinates

        # apply changes to the matrix
        for row_index in range(self.height):
            for col_index in range(self.width):
                cell = CellCoords(row_index, col_index)
                if self._is_alive(cell):
                    self[row_index][col_index] = self.default_color    # type: ignore
                else:
                    self[row_index][col_index] = BLACK    # type: ignore

    def _is_alive(self, cell):  # type: (CellCoords) -> bool
        """Return True if the given cell is alive in the current round."""
        return cell in self._living_cell_coordinates

    def _will_live(self, cell):  # type: (CellCoords) -> bool
        """Return True if the given cell will survive to the next round."""
        neighbors = self._get_neighbor_coordinates(cell)
        num_living_neighbors = sum(self._is_alive(neighbor) for neighbor in neighbors)

        # any cell with three live neighbors will live or revive by "reproduction"
        if num_living_neighbors == 3:
            return True
        # any dead cell not revived by the step above will remain dead
        if not self._is_alive(cell):
            return False
        # any live cell with more than three live neighbors dies by "overpopulation"
        if num_living_neighbors > 3:
            return False
        # any live cell with fewer than two live neighbors dies by "underpopulation"
        if num_living_neighbors < 2:
            return False
        # any remaining cells are alive and will survive to the next round
        return True

    def _get_neighbor_coordinates(self, cell):  # type: (CellCoords) -> Set[CellCoords]
        """Return the coordinates of all 8 neighboring cells for the given cell.

        The board "wraps" so that cells along opposite boundaries are considered neighbors.
        """
        neighbors = set()  # type: Set[CellCoords]

        for row_offset in (-1, 0, 1):
            for col_offset in (-1, 0, 1):
                # skip the origin cell
                if not row_offset and not col_offset:
                    continue

                row_index = cell.row_index + row_offset
                col_index = cell.col_index + col_offset

                # wrap neighbors around board
                if row_index < 0:
                    row_index = row_index + self.height
                if row_index >= self.height:
                    row_index >= row_index - self.height
                if col_index < 0:
                    col_index = col_index + self.width
                if col_index >= self.width:
                    col_index = col_index - self.width

                # add the neighbor cell to the set of neighbors
                neighbor = CellCoords(row_index, col_index)
                neighbors.add(neighbor)

        return neighbors

if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--rows', '-r', type=int, default=4)
    parser.add_argument('--cols', '-c', type=int, default=4)
    parser.add_argument('--brightness', '-b', type=float, default=1)
    parser.add_argument('--turns', '-t', type=int, default=20)
    parser.add_argument('--delay', '-d', type=float, default=0.2)
    parser.add_argument('--orient', '-o', default='ALTERNATING_COLUMN')
    parser.add_argument('--start', '-s', default='NORTHEAST')
    parser.add_argument('--color', '-co', type=str, default='RED')
    args = parser.parse_args()

    game = GameOfLife(
        auto_write=False,
        num_rows=args.rows,
        num_cols=args.cols,
        origin=getattr(matrix.MATRIX_ORIGIN, args.start),
        orientation=getattr(matrix.MATRIX_ORIENTATION, args.orient),
        default_color=getattr(colors, args.color),
    )
    try:
        for _ in range(args.turns):
            time.sleep(args.delay)
            game.render()
            game.next_state()
    finally:
        game.deinit()


from multiprocessing import Event, Lock
from multiprocessing.managers import BaseManager
import os
import random
import signal
import sys
import sysv_ipc

T = ["airplane", "car", "train", "bike", "shoes"]
Transports = range(len(T))

class Shm():
    hands_start: list[list[int]] = []
    offers: list[int] = []
    playersPID: list[int] = []
    lock = Lock()
    start_game = Event()
    stop_game = Event()
    winner: int = None

    def __init__(self) -> None:
        pass

    def win(self, i: int):
        with self.lock:
            self.winner = i
            self.stop_game.set()

    def wait_for_start(self):
        self.start_game.wait()

    def wait_for_stop(self):
        self.stop_game.wait()

    def get_hand(self, pid) -> tuple[list[int], int]:
        with self.lock:
            if len(self.hands_start) == 0:
                return (None, None)

            self.offers.append(0)
            self.playersPID.append(pid)
            i = len(self.offers) - 1
            hand = self.hands_start.pop()
            hand.sort()

            # launch game if all players are ready
            if len(self.hands_start) == 0:
                self.start_game.set()

            return (hand, i)

    def get_nb_players(self) -> int:
        with self.lock:
            return len(self.playersPID)

    def get_winner(self) -> int:
        with self.lock:
            return self.winner

    def get_offers(self) -> list[int]:
        with self.lock:
            return self.offers

    def get_playersPID(self) -> list[int]:
        with self.lock:
            return self.playersPID

    def offer(self, i: int, nb_cards: int):
        with self.lock:
            self.offers[i] = nb_cards

class Game():
    manager: BaseManager
    ishm: Shm = Shm()
    shm: Shm
    exchanges: sysv_ipc.MessageQueue
    nb_players: int

    def __init__(self, nb_players: int):
        self.nb_players = nb_players

        # clean old IPC mq
        try:
            mq = sysv_ipc.MessageQueue(666)
            mq.remove()
            print("old messagequeue removed")
        except:
            pass

        self.exchanges = sysv_ipc.MessageQueue(666, sysv_ipc.IPC_CREX)

        BaseManager.register("shm", lambda: self.ishm)

        self.manager = BaseManager(address=("127.0.0.1", 6666), authkey=b"cambiecolo")

        # Hands generation
        possible_transports = list(Transports)[-nb_players:]
        print("Possible transports:", possible_transports)
        cards = possible_transports * 5
        print("Cards pool         :", cards)
        random.shuffle(cards)
        print("Cards pool shuffled:", cards)
        self.ishm.hands_start = [cards[i:i + 5] for i in range(0, len(cards), 5)]
        print("Cards:", self.ishm.hands_start)

    def stop(self):
        print("Stopping game")
        self.manager.shutdown()
        self.exchanges.remove()

    def start(self):
        # Start game
        self.manager.start()
        self.manager.connect()
        self.shm = self.manager.shm()

        print("Waiting for all {} players to get ready...".format(self.nb_players))

        self.shm.wait_for_start()

        print("Game started")
        print("PIDs:", self.shm.get_playersPID())

        self.shm.wait_for_stop()

        print("Game finished")
        print("\n!!! Player {} (pid {}) won !!!\n".format(self.shm.get_winner(), self.shm.get_playersPID()[self.shm.get_winner()]))
        print("PIDs:", self.shm.get_playersPID())

        for pid in self.shm.get_playersPID():
            try:
                print("Killed pid {}".format(pid))
                os.kill(pid, signal.SIGINT)
            except:
                print("Can't kill pid {}".format(pid))

if __name__ == "__main__":
    try:
        n = int(sys.argv[1])
        if not 2 <= n <= 5:
            raise ValueError
    except:
        print("Usage: {} <nb_players [2;5]>".format(sys.argv[0]))
        exit()

    g = Game(n)
    g.start()
    g.stop()


from django.shortcuts import render

# Create your views here.
def stock_exchange(request):
    stock_details= { 'stock_name1': {'SBI': '400'},
                     'stock_name2': {'PNB': '45'}}

    data = {"my_stock":stock_details}

    return render(request,"stock.html",data)


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

class WeightNet(nn.Module):
    def __init__(self):
        super(WeightNet, self).__init__()
        self.scale = nn.Parameter(torch.tensor(5.))

    def forward(self, x, p):
        logits = torch.mm(F.normalize(x, dim=-1),
                          F.normalize(p, dim=-1).t())
        sim = torch.diagonal(logits)
        return sim * self.scale


from typing import List

class Solution:
    def connect(self,i,j):
        while self.dsu[i] != -1:
            i = self.dsu[i]
        while self.dsu[j] != -1:
            j = self.dsu[j]
        if i==j:
            return
        self.dsu[i] = j
    def findCircleNum(self, isConnected: List[List[int]]) -> int:
        length = len(isConnected)
        self.dsu = [-1]*length
        improve = 1
        for i in range(length):
            for j in range(improve,length):
                if isConnected[i][j]==1:
                    self.connect(i,j)
                else:
                    continue
            else:
                improve+=1
        return self.dsu.count(-1)
if __name__=='__main__':
    print(Solution().findCircleNum([[1,0,0,1],[0,1,1,0],[0,1,1,1],[1,0,1,1]]))


from django.test import TestCase
from model_bakery import baker

from .models import Post

class PostModelTestCase(TestCase):
    def test_list_only_published_posts(self):
        post_published = baker.make(Post, status="published")
        baker.make(Post, status="draft", _quantity=2)
        posts = Post.published.all()
        self.assertEqual(posts.count(), 1)
        self.assertIn(post_published, posts)


import argparse
import os
import torch.optim as optim
import torch.utils.data as util_data
import itertools

import network
import pre_process as prep
import lr_schedule
from util import *
from data_list import ImageList_au, ImageList_land_au

optim_dict = {'SGD': optim.SGD, 'Adam': optim.Adam}

def main(config):
    ## set loss criterion
    use_gpu = torch.cuda.is_available()

    ## prepare data
    dsets = {}
    dset_loaders = {}
    dsets['source'] = {}
    dset_loaders['source'] = {}

    dsets['source']['test'] = ImageList_au(config.src_test_path_prefix,
                                          transform=prep.image_test(crop_size=config.crop_size))

    dset_loaders['source']['test'] = util_data.DataLoader(dsets['source']['test'], batch_size=config.eval_batch_size,
                                                         shuffle=False, num_workers=config.num_workers)

    dsets['target'] = {}
    dset_loaders['target'] = {}

    dsets['target']['test'] = ImageList_au(config.tgt_test_path_prefix,
                                          transform=prep.image_test(crop_size=config.crop_size))

    dset_loaders['target']['test'] = util_data.DataLoader(dsets['target']['test'], batch_size=config.eval_batch_size,
                                                         shuffle=False, num_workers=config.num_workers)

    ## set network modules
    base_net = network.network_dict[config.base_net]()
    land_enc = network.network_dict[config.land_enc](land_num=config.land_num)
    au_enc = network.network_dict[config.au_enc](au_num=config.au_num)
    invar_shape_enc = network.network_dict[config.invar_shape_enc]()
    feat_gen = network.network_dict[config.feat_gen]()

    if use_gpu:
        base_net = base_net.cuda()
        land_enc = land_enc.cuda()
        au_enc = au_enc.cuda()
        invar_shape_enc = invar_shape_enc.cuda()
        feat_gen = feat_gen.cuda()

    base_net.train(False)
    land_enc.train(False)
    au_enc.train(False)
    invar_shape_enc.train(False)
    feat_gen.train(False)

    print(base_net, land_enc, au_enc, invar_shape_enc, feat_gen)

    if not os.path.exists(config.write_path_prefix + config.mode):
        os.makedirs(config.write_path_prefix + config.mode)
    if not os.path.exists(config.write_res_prefix + config.mode):
        os.makedirs(config.write_res_prefix + config.mode)

    test_type = 'target'  # 'source'

    if config.start_epoch <= 0:
        raise (RuntimeError('start_epoch should be larger than 0\n'))

    res_file = open(config.write_res_prefix + config.mode + '/' + test_type + '_test_AU_pred_' + str(config.start_epoch) + '.txt', 'w')

    for epoch in range(config.start_epoch, config.n_epochs + 1):

        base_net.load_state_dict(
            torch.load(config.write_path_prefix + config.mode + '/base_net_' + str(epoch) + '.pth'))
        land_enc.load_state_dict(
            torch.load(config.write_path_prefix + config.mode + '/land_enc_' + str(epoch) + '.pth'))
        au_enc.load_state_dict(
            torch.load(config.write_path_prefix + config.mode + '/au_enc_' + str(epoch) + '.pth'))
        invar_shape_enc.load_state_dict(
            torch.load(config.write_path_prefix + config.mode + '/invar_shape_enc_' + str(epoch) + '.pth'))
        feat_gen.load_state_dict(
            torch.load(config.write_path_prefix + config.mode + '/feat_gen_' + str(epoch) + '.pth'))

        if test_type == 'source':
            f1score_arr, acc_arr = AU_detection_eval_src(dset_loaders[test_type]['test'], base_net, au_enc, use_gpu=use_gpu)
        else:
            f1score_arr, acc_arr = AU_detection_eval_tgt(dset_loaders[test_type]['test'], base_net, land_enc, au_enc,
                                                         invar_shape_enc, feat_gen, use_gpu=use_gpu)

        print('epoch =%d, f1 score mean=%f, accuracy mean=%f' %(epoch, f1score_arr.mean(), acc_arr.mean()))
        print>> res_file, '%d\t%f\t%f' % (epoch, f1score_arr.mean(), acc_arr.mean())

    res_file.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    # Model configuration.
    parser.add_argument('--gpu_id', type=str, default='0', help='device id to run')
    parser.add_argument('--crop_size', type=int, default=176, help='crop size for images')
    parser.add_argument('--output_size', type=int, default=44, help='size for landmark response map')
    parser.add_argument('--au_num', type=int, default=6, help='number of AUs')
    parser.add_argument('--land_num', type=int, default=49, help='number of landmarks')
    parser.add_argument('--eval_batch_size', type=int, default=4, help='mini-batch size for evaluation')
    parser.add_argument('--start_epoch', type=int, default=1, help='starting epoch')
    parser.add_argument('--n_epochs', type=int, default=10, help='number of total epochs')
    parser.add_argument('--num_workers', type=int, default=4)
    parser.add_argument('--mode', type=str, default='weak', choices=['weak', 'full'])
    parser.add_argument('--base_net', type=str, default='Feat_Enc')
    parser.add_argument('--land_enc', type=str, default='Land_Detect')
    parser.add_argument('--au_enc', type=str, default='AU_Detect')
    parser.add_argument('--invar_shape_enc', type=str, default='Texture_Enc')
    parser.add_argument('--feat_gen', type=str, default='Generator')

    # Directories.
    parser.add_argument('--write_path_prefix', type=str, default='data/snapshots/')
    parser.add_argument('--write_res_prefix', type=str, default='data/res/')
    parser.add_argument('--src_test_path_prefix', type=str, default='data/list/BP4D_test')
    parser.add_argument('--tgt_test_path_prefix', type=str, default='data/list/emotioNet_test')

    config = parser.parse_args()
    os.environ['CUDA_VISIBLE_DEVICES'] = config.gpu_id

    print(config)
    main(config)


from fastapi import APIRouter
from typing import List
from datetime import datetime

from models.repository import ApiRepository
from models.mb import MetroBusByAlRes

repo = ApiRepository()

mb_alcal = APIRouter(
    prefix="/alcal",
    tags=["alcaldias"],
    responses={404: {"description": "Not found"}},
)

@mb_alcal.get(
    "/",
    response_description="Get all the alcaldias",
    status_code=200,
    response_model=List[str],
)
async def get_all_alcal():
    """
    Return all the valid alcaldias that have been saved
    """
    repo.set_collection(collection="alcal_mb", index_fields=("name",))

    cursor = repo.fetch_records_fields(
        filters={"name": {"$ne": "NOT_FOUND"}},
        fields={"_id": False, "mb_units_history": False},
    )

    return [doc.get("name") for doc in await cursor.to_list(length=100)]

@mb_alcal.get(
    "/units",
    response_description="Get mb units by alcaldia",
    status_code=200,
    response_model=List[MetroBusByAlRes],
)
async def get_units_by_alcal(alcal_name: str, start_date: datetime, end_date: datetime):
    """
    Get all the units that have been inside an alcaldia
    """
    repo.set_collection(collection="alcal_mb", index_fields=("name",))

    cursor = repo.fetch_records_aggregate(
        filters_list=[
            {"$match": {"name": alcal_name}},
            {
                "$project": {
                    "_id": False,
                    "units_history": {
                        "$filter": {
                            "input": "$mb_units_history",
                            "as": "item",
                            "cond": {
                                "$and": [
                                    {"$gte": ["$$item.datetime_record", start_date]},
                                    {"$lte": ["$$item.datetime_record", end_date]},
                                ]
                            },
                        }
                    },
                }
            },
        ]
    )

    return [
        MetroBusByAlRes(**record)
        for unit in await cursor.to_list(length=100)
        for record in unit.get("units_history")
    ]


from pathlib import Path
from typing import Any, List, Optional
import musicbrainzngs as mb
from ratelimit import sleep_and_retry, limits

import requests
from unitunes.services.services import (
    ServiceWrapper,
    StreamingService,
    cache,
)
from unitunes.track import AliasedString, Track
from unitunes.common_types import ServiceType
from unitunes.uri import MB_RECORDING_URI

class MusicBrainzWrapper(ServiceWrapper):
    def __init__(self, cache_root: Path) -> None:
        super().__init__("musicbrainz", cache_root)
        mb.set_useragent("unitunes", "0.1")

    @sleep_and_retry
    @limits(calls=1, period=1)
    def query_mb_api(self, query: str, params):
        headers = {"User-Agent": "unitunes"}
        r = requests.get(query, params=params, headers=headers)
        if r.status_code != 200:
            raise Exception(f"MusicBrainz API returned {r.status_code}")
        if "error" in r.json():
            raise Exception(f"MusicBrainz API returned error: {r.json()['error']}")
        return r.json()

    @cache
    def get_recording_by_id(self, id: str, use_cache=True, includes: List[str] = []):
        # Send this manually because the musicbrainzngs library doesn't return aliases for some reason
        params = {
            "inc": "+".join(includes),
            "fmt": "json",
        }
        return self.query_mb_api(
            f"http://musicbrainz.org/ws/2/recording/{id}", params=params
        )

    @cache
    def search_recordings(self, *args, use_cache=True, **kwargs):
        return mb.search_recordings(*args, **kwargs)

    @cache
    def get_release_by_id(self, *args, use_cache=True, **kwargs):
        return mb.get_release_by_id(*args, **kwargs)

class MusicBrainz(StreamingService):
    wrapper: MusicBrainzWrapper

    def __init__(self, cache_root: Path) -> None:
        super().__init__("MusicBrainz", ServiceType.MB, cache_root)
        self.wrapper = MusicBrainzWrapper(cache_root)

    def load_config(self, config) -> None:
        pass

    @staticmethod
    def parse_track(recording):
        def parse_aliases(obj) -> List[str]:
            return (
                [alias["name"] for alias in obj["aliases"]]
                if "obj" in recording
                else []
            )

        def parse_aliased_string(obj) -> Optional[AliasedString]:
            if "title" not in obj:
                return None
            return AliasedString(obj["title"], parse_aliases(obj))

        name = parse_aliased_string(recording)
        if not name:
            raise ValueError(f"Recording {recording} has no name")

        albums = []
        if "releases" in recording:
            for album in recording["releases"]:
                s = parse_aliased_string(album)
                if s:
                    albums.append(s)
        if "release-list" in recording:
            for album in recording["release-list"]:
                s = parse_aliased_string(album)
                if s:
                    albums.append(s)

        artists = []
        if "artist-credit" in recording:
            for artist in recording["artist-credit"]:
                if "artist" in artist:
                    a = artist["artist"]
                    if "name" in a:
                        aliases = (
                            [alias["alias"] for alias in a["alias-list"]]
                            if "alias-list" in a
                            else []
                        )
                        if "sort-name" in a:
                            aliases.append(a["sort-name"])
                        artists.append(AliasedString(value=a["name"], aliases=aliases))

        return Track(
            name=name,
            artists=artists,
            albums=albums,
            length=int(recording["length"]) // 1000
            if "length" in recording and recording["length"]
            else None,
            uris=[MB_RECORDING_URI.from_uri(recording["id"])],
        )

    def pull_track(self, uri: MB_RECORDING_URI) -> Track:
        results = self.wrapper.get_recording_by_id(
            id=uri.uri, includes=["releases", "artists", "aliases"]
        )
        if not results:
            raise ValueError(f"Recording {uri} not found")
        track = self.parse_track(results)
        return track

    def search_query(self, query: Any) -> List[Track]:
        # remove empty fields
        fields = {k: v for k, v in query.items() if v}

        results = self.wrapper.search_recordings(
            limit=5,
            **fields,
        )

        return list(map(self.parse_track, results["recording-list"]))

    def query_generator(self, track: Track) -> List[Any]:
        def escape_special_chars(s: str) -> str:
            # + - && || ! ( ) { } [ ] ^ " ~ * ? : \
            special_chars = [
                "\\",  # \ needs to be escaped first or else it will be escaped twice
                "+",
                "-",
                "&&",
                "||",
                "!",
                "(",
                ")",
                "{",
                "}",
                "[",
                "]",
                "^",
                '"',
                "~",
                "*",
                "?",
                ":",
            ]
            for char in special_chars:
                s = s.replace(char, f"\\{char}")
            return s

        all_fields = {
            "recording": escape_special_chars(track.name.value),
            "artist": escape_special_chars(
                " ".join([artist.value for artist in track.artists])
            ),
            "release": escape_special_chars(" ".join([a.value for a in track.albums])),
        }

        fields_to_remove = [
            [],
            ["release"],
            ["artist"],
            ["recording"],
            ["artist", "release"],
        ]

        queries = []
        for removed_fields in fields_to_remove:
            fields = {
                field: all_fields[field]
                for field in all_fields
                if field not in removed_fields
            }
            if not any(fields.values()):
                continue

            queries.append(fields)
        return queries


import os
import re
from functools import partial

import telegram
from django.core.management.base import BaseCommand
from environs import Env
from jinja2 import Environment, FileSystemLoader
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram import LabeledPrice, Update
from telegram.ext import (
    CallbackContext,
    Updater,
    CommandHandler,
    MessageHandler,
    Filters,
    ConversationHandler,
    CallbackQueryHandler,
    PreCheckoutQueryHandler
)

from meetups import views, models
from python_meetup.settings import BASE_DIR

TEMPLATES_PATH = os.path.join(BASE_DIR, 'meetups', 'templates', 'bot', '')

def start(update, context, jinja):
    template = jinja.get_template('title.txt')
    query = update.callback_query
    user, user_created = models.Visitor.objects.get_or_create(telegram_id=update.effective_chat.id)

    if query:
        query.answer()

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Показать мероприятия',
                callback_data='show_events'
            ),
            InlineKeyboardButton(
                text='Добавить/изменить свою анкету',
                callback_data='show_account'
            ),
            InlineKeyboardButton(
                text='Донат',
                callback_data='donate_processing'
            ),
        ],
    ]

    if user.is_speaker:
        buttons_md[0].append(
            InlineKeyboardButton(
                text='Показать вопросы',
                callback_data='show_questions'
            )
        )

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        parse_mode=telegram.ParseMode.MARKDOWN,
        reply_markup=InlineKeyboardMarkup(buttons_md),
    )

def donate_processing(update, context, provider_token, img_url):
    donate_amount = 1

    context.bot.send_invoice(
        chat_id=update.effective_chat.id,
        title='Обработка доната',
        description='Спасибо за донат нашему мероприятию!',
        payload="Custom-Payload",
        provider_token=provider_token,
        currency="USD",
        prices=[LabeledPrice("На чай", donate_amount * 100)],
        photo_url=img_url,
        photo_size=512,
        photo_width=512,
        photo_height=512,
    )

def precheckout_callback(update: Update, context: CallbackContext):

    query = update.pre_checkout_query
    if query.invoice_payload != 'Custom-Payload':
        query.answer(ok=False, error_message="Что-то пошло не так...")
    else:
        query.answer(ok=True)

def successful_payment_callback(update: Update, context: CallbackContext):
    update.message.reply_text("Спасибо за поддержку!!")

def show_events(update, context, jinja):
    events = views.get_active_events()

    template = jinja.get_template('events.txt')
    query = update.callback_query

    event_buttons = []

    for event in events:
        event_buttons.append(
            [
                InlineKeyboardButton(
                    text=event.title,
                    callback_data='show_event_{}'.format(event.pk)
                )
            ]
        )

    buttons_md = [
        *event_buttons,
        [
            InlineKeyboardButton(
                text='Назад',
                callback_data='show_title'
            )
        ]
    ]

    query.message.reply_text(
        text=template.render(
            events=events
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    query.answer()

def show_event(update, context, jinja):
    template = jinja.get_template('event.txt')

    query = update.callback_query

    if query:
        event_id = query.data.replace('show_event_', '')
        query.answer()
    else:
        event_id = update.message.text.replace('/show_event_', '')

    event = views.get_event_with_sections(event_id)

    block_buttons = []

    for block in event['sections']:
        block_id = block.pk
        block_buttons.append(
            [
                InlineKeyboardButton(
                    text=block.title,
                    callback_data=f'show_block_{block_id}'
                )
            ]
        )

    buttons_md = [
        *block_buttons,
        [
            InlineKeyboardButton(
                text='Назад',
                callback_data='show_events'
            ),
            InlineKeyboardButton(
                text='Главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(
            name=event['event'].title,
            date=event['event'].start_date,
            description=event['event'].description,
            blocks=event['sections']
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

def show_block(update, context, jinja):
    query = update.callback_query

    if query:
        block_id = query.data.replace('show_block_', '')
    else:
        block_id = update.message.text.replace('/show_block_', '')

    template = jinja.get_template('block.txt')

    block = views.get_section_with_speakers(block_id)

    event_id = block['section'].event.pk

    speakers_btns = []

    for speaker in block['speakers']:
        speakers_btns.append(
            [
                InlineKeyboardButton(
                    text='Задать вопрос: {} {}'.format(speaker.firstname, speaker.lastname),
                    callback_data='ask_block_{}_speaker_{}'.format(
                        block_id,
                        speaker.pk
                    )
                )
            ]
        )

    buttons_md = [
        *speakers_btns,
        [
            InlineKeyboardButton(
                text='Назад',
                callback_data=f'show_event_{event_id}'
            ),
            InlineKeyboardButton(
                text='Главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(
            name=block['section'].title,
            time_from=block['section'].start_time,
            time_to=block['section'].end_time,
            description=block['section'].description,
            speakers=block['speakers']
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return ConversationHandler.END

def ask_question(update, context, jinja, state):
    template = jinja.get_template('ask.txt')

    query = update.callback_query
    query.answer()

    block_id = re.search(r'^(ask_block_[\d]+)', query.data).group(1).replace(
        'ask_block_', ''
    )
    speaker_id = re.search(r'(speaker_[\d]+)$', query.data).group(1).replace(
        'speaker_', ''
    )

    state['block_id'] = block_id
    state['speaker_id'] = speaker_id
    speaker = models.Visitor.objects.get(pk=speaker_id)

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Назад',
                callback_data=f'show_block_{block_id}'
            ),
            InlineKeyboardButton(
                text='Главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(
            speaker_name=f'{speaker.firstname} {speaker.lastname}'
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md)
    )

    return 'question'

def question_handler(update, context, jinja, state):
    template = jinja.get_template('question_asked.txt')
    question_template = jinja.get_template('send_question.txt')
    question = update.message.text

    user = update.message.from_user

    buttons_md = [
        [
            InlineKeyboardButton(
                'Назад к блоку',
                callback_data='show_block_{}'.format(state['block_id'])
            ),
            InlineKeyboardButton(
                'Главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md)
    )

    saved_question, block = views.save_question(state['block_id'], user['id'], state['speaker_id'], question)

    if saved_question:
        context.bot.send_message(
            chat_id=saved_question.speaker.telegram_id,
            text=question_template.render(
                block=saved_question.section.title,
                question=question,
                question_id=saved_question.pk
            ),
            parse_mode=telegram.ParseMode.MARKDOWN
        )

    return ConversationHandler.END

def show_account(update, context, jinja):
    template = jinja.get_template('account/account.txt')

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN

    )

    return 'first_name'

def cancel_account(update, context):
    buttons_md = [
        [
            InlineKeyboardButton(
                text='В главное меню',
                callback_data='show_title'
            )
        ]
    ]
    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text='Хорошо, ты всегда можешь заполнить анкету позже из главного меню.',
        reply_markup=InlineKeyboardMarkup(buttons_md)
    )

    return ConversationHandler.END

def account_first_name(update, context, jinja, state):
    template = jinja.get_template('account/first_name.txt')

    state['first_name'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'last_name'

def account_last_name(update, context, jinja, state):
    template = jinja.get_template('account/last_name.txt')

    state['last_name'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_company'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'company'

def account_company(update, context, jinja, state):
    template = jinja.get_template('account/company.txt')

    state['company'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_position'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'position'

def account_skip_company(update, context, jinja):
    template = jinja.get_template('account/company.txt')

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_position'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'position'

def account_position(update, context, jinja, state):
    template = jinja.get_template('account/position.txt')

    state['position'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_phonenumber'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'phonenumber'

def account_skip_position(update, context, jinja, state):
    template = jinja.get_template('account/position.txt')
    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_phonenumber'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'phonenumber'

def account_phonenumber(update, context, jinja, state):
    template = jinja.get_template('account/phonenumber.txt')

    state['phonenumber'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_email'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'email'

def account_skip_phonenumber(update, context, jinja):
    template = jinja.get_template('account/phonenumber.txt')

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_email'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'email'

def account_email(update, context, jinja, state):
    template = jinja.get_template('account/email.txt')

    state['email'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_about'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'about'

def account_skip_email(update, context, jinja):
    template = jinja.get_template('account/email.txt')

    buttons_md = [
        [
            InlineKeyboardButton(
                text='Пропустить',
                callback_data='skip_about'
            ),
            InlineKeyboardButton(
                text='Отмена',
                callback_data='cancel_account'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'about'

def account_about(update, context, jinja, state):
    template = jinja.get_template('account/thank_you.txt')

    state['about'] = update.message.text

    buttons_md = [
        [
            InlineKeyboardButton(
                text='В главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return ConversationHandler.END

def account_skip_about(update, context, jinja, state):
    template = jinja.get_template('account/thank_you.txt')

    buttons_md = [
        [
            InlineKeyboardButton(
                text='В главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return ConversationHandler.END

def show_questions(update, context, jinja):
    template = jinja.get_template('questions.txt')
    user_id = update.effective_chat.id

    speaker = models.Visitor.objects.get(telegram_id=user_id)

    if not speaker.is_speaker:
        context.bot.send_message(
            chat_id=user_id,
            text='Нет доступа к команде',
        )

        return

    questions = views.get_questions(user_id)

    buttons_md = [
        [
            InlineKeyboardButton(
                'В главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(
            questions=questions
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

def show_question(update, context, jinja, state):
    template = jinja.get_template('question.txt')
    question_id = update.message.text.replace('/answer_', '')

    question = views.get_question(
        question_id,
        update.effective_chat.id
    )

    if not question:
        context.bot.send_message(
            chat_id=update.effective_chat.id,
            text='Нет доступа к команде'
        )

    state['question_id'] = question_id
    state['speaker_id'] = question.speaker.telegram_id

    buttons_md = [
        [
            InlineKeyboardButton(
                'Назад',
                callback_data='show_questions'
            ),
            InlineKeyboardButton(
                'В главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text=template.render(
            id=question.pk,
            block=question.section.title,
            question=question.question
        ),
        reply_markup=InlineKeyboardMarkup(buttons_md),
        parse_mode=telegram.ParseMode.MARKDOWN
    )

    return 'answer'

def answer_question(update, context, jinja, state):
    template = jinja.get_template('question_answered.txt')
    answer = update.message.text
    question_id = state['question_id']
    speaker_id = state['speaker_id']

    speaker, visitor, question = views.answer_question(
        question_id,
        speaker_id,
        answer
    )

    buttons_md = [
        [
            InlineKeyboardButton(
                'Назад',
                callback_data='show_questions'
            ),
            InlineKeyboardButton(
                'В главное меню',
                callback_data='show_title'
            )
        ]
    ]

    context.bot.send_message(
        chat_id=update.effective_chat.id,
        text='Спасибо! Ваш ответ отправлен пользователю.',
        reply_markup=InlineKeyboardMarkup(buttons_md)
    )

    context.bot.send_message(
        chat_id=visitor.telegram_id,
        text=template.render(
            question=question.question,
            speaker=f'{speaker.firstname} {speaker.lastname}',
            answer=question.answer,
            block=question.section.title
        )
    )

    return ConversationHandler.END

class Command(BaseCommand):
    help = 'Start Telegram bot'

    def handle(self, *args, **options):
        env = Env()
        env.read_env()

        telegram_token = env('TELEGRAM_TOKEN')
        provider_token = env('PAYMENTS_TOKEN')
        donate_img_url = env('DONATE_IMG_URL')

        jinja = Environment(
            loader=FileSystemLoader(TEMPLATES_PATH)
        )

        updater = Updater(token=telegram_token, use_context=True)
        dp = updater.dispatcher

        dp.add_handler(CommandHandler('start', partial(start, jinja=jinja)))
        dp.add_handler(CommandHandler('donate_processing', donate_processing))
        dp.add_handler(
            CallbackQueryHandler(
                partial(
                    donate_processing,
                    provider_token=provider_token,
                    img_url=donate_img_url

                ),
                pattern='donate_processing'
            )
        )
        dp.add_handler(PreCheckoutQueryHandler(precheckout_callback))
        dp.add_handler(CallbackQueryHandler(
            partial(start, jinja=jinja),
            pattern='show_title')
        )
        dp.add_handler(CallbackQueryHandler(
            partial(show_events, jinja=jinja),
            pattern='show_events')
        )
        dp.add_handler(MessageHandler(
            Filters.regex(r'^(/show_event_[\d]+)$'),
            partial(show_event, jinja=jinja))
        )
        dp.add_handler(MessageHandler(
            Filters.regex(r'^(/show_block_[\d]+)$'),
            partial(show_block, jinja=jinja))
        )
        dp.add_handler(CallbackQueryHandler(
            partial(show_event, jinja=jinja),
            pattern=r'^(show_event_[\d]+)$')
        )
        dp.add_handler(CallbackQueryHandler(
            partial(show_block, jinja=jinja),
            pattern=r'^(show_block_[\d]+)$')
        )
        dp.add_handler(CallbackQueryHandler(
            partial(show_questions, jinja=jinja),
            pattern='show_questions')
        )

        ask_state = {
            'block_id': None,
            'speaker_id': None,
        }

        ask_conv_handler = ConversationHandler(
            entry_points=[CallbackQueryHandler(
                partial(ask_question, jinja=jinja, state=ask_state),
                pattern=r'^(ask_block_[\d]+_speaker_[\d]+)$'
            )],
            allow_reentry=True,
            states={
                'question': [
                    MessageHandler(
                        Filters.text,
                        partial(question_handler, jinja=jinja, state=ask_state)
                    )
                ]
            },
            fallbacks=[
                CallbackQueryHandler(
                    partial(show_block, jinja=jinja),
                    pattern=r'^(show_block_[\d]+)$',
                )
            ],
        )

        dp.add_handler(ask_conv_handler)

        account_state = {
            'first_name': '',
            'last_name': '',
            'company': '',
            'position': '',
            'phonenumber': '',
            'email': '',
            'about': ''
        }

        account_info_handler = ConversationHandler(
            entry_points=[CallbackQueryHandler(
                partial(show_account, jinja=jinja),
                pattern='show_account'
            )],
            states={
                'first_name': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_first_name,
                            jinja=jinja,
                            state=account_state
                        )
                    )
                ],
                'last_name': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_last_name,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                ],
                'company': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_company,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                    CallbackQueryHandler(
                        partial(
                            account_skip_company,
                            jinja=jinja
                        ),
                        pattern='skip_company'
                    )
                ],
                'position': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_position,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                    CallbackQueryHandler(
                        partial(
                            account_skip_position,
                            jinja=jinja,
                            state=account_state
                        ),
                        pattern='skip_position'
                    )
                ],
                'phonenumber': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_phonenumber,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                    CallbackQueryHandler(
                        partial(
                            account_skip_phonenumber,
                            jinja=jinja
                        ),
                        pattern='skip_phonenumber'
                    )
                ],
                'email': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_email,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                    CallbackQueryHandler(
                        partial(
                            account_skip_email,
                            jinja=jinja
                        ),
                        pattern='skip_email'
                    )
                ],
                'about': [
                    MessageHandler(
                        Filters.text,
                        partial(
                            account_about,
                            jinja=jinja,
                            state=account_state
                        )
                    ),
                    CallbackQueryHandler(
                        partial(
                            account_skip_about,
                            jinja=jinja,
                            state=account_state
                        ),
                        pattern='skip_about'
                    )
                ]
            },
            fallbacks=[
                CallbackQueryHandler(
                    cancel_account,
                    pattern='cancel_account',
                )
            ],
            allow_reentry=True
        )

        dp.add_handler(account_info_handler)

        answer_state = {
            'question_id': None,
            'speaker_id': None
        }

        question_answer_handler = ConversationHandler(
            entry_points=[
                MessageHandler(
                    Filters.regex(r'^(/answer_[\d]+)$'),
                    partial(show_question, jinja=jinja, state=answer_state)
                )
            ],
            states={
                'answer': [
                    MessageHandler(
                        Filters.text,
                        partial(answer_question, jinja=jinja, state=answer_state)
                    )
                ]
            },
            fallbacks=[
                CallbackQueryHandler(
                    partial(show_questions, jinja=jinja),
                    pattern=r'show_questions',
                ),
                CallbackQueryHandler(
                    partial(start, jinja=jinja),
                    pattern=r'show_title',
                )
            ],
            allow_reentry=True
        )

        dp.add_handler(question_answer_handler)

        updater.start_polling()

        updater.idle()


import numpy as np
import json
from utilities import json_numpy
import utilities.constants as c
import random
import time

def activation(x, output=False):
    activation_function = c.OUTPUT_ACTIVATION if output else c.ACTIVATION
    if activation_function in ['sigmoid', 'Sigmoid', 'SIGMOID']:
        return 1 / (1 + np.exp(-x))
    elif activation_function in ['ReLU', 'relu', 'RELU']:
        return x * (x > 0)
    elif activation_function in ['linear', 'Linear', 'line']:
        return x

def d_activation(x, output=False):
    activation_function = c.OUTPUT_ACTIVATION if output else c.ACTIVATION
    if activation_function in ['sigmoid', 'Sigmoid', 'SIGMOID']:
        return x * (1 - x)
    elif activation_function in ['ReLU', 'relu', 'RELU']:
        return 1 * (x > 0)
    elif activation_function in ['linear', 'Linear', 'line']:
        return np.ones_like(x)

class NeuralNetwork:
    def __init__(self, inputs, hidden, outputs, learning_rate=0.1):
        self.inputs = inputs
        if isinstance(hidden, int):
            hidden = [hidden]
        self.hidden = np.array(hidden)
        self.outputs = outputs
        self.learning_rate = learning_rate

        # create weights and gradients for first hidden layer (defined based on number of inputs)
        self.weights = [np.random.uniform(-1, 1, size=(self.hidden[0], self.inputs)).astype(np.float128)]
        self.gradients = [np.zeros((self.hidden[0], self.inputs), np.float128)]

        # create weights and gradients for interior hidden layers (defined based on previous layer)
        if self.hidden.ndim > 0:
            for idx, hidden_col in enumerate(self.hidden[1:]):
                self.weights.append(np.random.uniform(-1, 1, size=(hidden_col, self.hidden[idx])).astype(np.float128))
                self.gradients.append(np.zeros((hidden_col, self.hidden[idx]), np.float128))

        # create weights and gradients for output layer (defined based on number of outputs)
        self.weights.append(np.random.uniform(-1, 1, size=(self.outputs, self.hidden[-1],)).astype(np.float128))
        self.gradients.append(np.zeros((self.outputs, self.hidden[-1],), np.float128))

        # create bias and bias_gradients lists of matrices (one per hidden layer and one for the output)
        self.bias = []
        self.bias_gradients = []
        for idx, hidden_col in enumerate(self.hidden):
            self.bias.append(np.random.uniform(-1, 1, size=(hidden_col, 1)).astype(np.float128))
            self.bias_gradients.append(np.zeros((hidden_col, 1), np.float128))
        self.bias.append(np.random.uniform(-1, 1, size=(self.outputs, 1)).astype(np.float128))
        self.bias_gradients.append(np.zeros((self.outputs, 1), np.float128))

        # Create the dictionary variables for the optimization methods
        self.v = {}
        self.s = {}
        for i in range(self.hidden.size + 1):
            self.v["dW" + str(i)] = 0
            self.v["db" + str(i)] = 0
            self.s["dW" + str(i)] = 0
            self.s["db" + str(i)] = 0

    def gradient_zeros(self):
        self.gradients = [np.zeros((self.hidden[0], self.inputs), np.float128)]
        self.bias_gradients = [np.zeros((self.hidden[0], 1), np.float128)]
        if self.hidden.ndim > 0:
            for idx, hidden_col in enumerate(self.hidden[1:]):
                self.gradients.append(np.zeros((hidden_col, self.hidden[idx]), np.float128))
                self.bias_gradients.append(np.zeros((hidden_col, 1), np.float128))
        self.gradients.append(np.zeros((self.outputs, self.hidden[-1]), np.float128))
        self.bias_gradients.append(np.zeros((self.outputs, 1), np.float128))

    def copy_from(self, neural_net):
        self.weights = neural_net.weights
        self.bias = neural_net.bias

    def save_to_file(self, file_name='NeuralNet.json'):
        json_file = {
            'weights': self.weights,
            'biases': self.bias}
        try:
            with open(file_name, 'w') as file:
                json.dump(
                    json_file,
                    file,
                    ensure_ascii=False,
                    cls=json_numpy.EncodeFromNumpy)
                print('weights saved to file')
        except:
            print('cannot save to ', file)

    def load_from_file(self, file_name='NeuralNet.json'):
        try:
            with open(file_name) as file:
                json_file = json.load(file, cls=json_numpy.DecodeToNumpy)
                print('weights loaded from file')
                self.weights = json_file['weights']
                self.bias = json_file['biases']
        except:
            print('cannot open ', file_name)

    def forward_propagation(self, input_values, **kwargs):
        # create hidden results list for results matrices per hidden layer
        hidden_results = []

        # prepare the input values for matrix multiplication
        input_values = np.array(input_values)[np.newaxis].T

        # calculate results for the first hidden layer (depending on the inputs)
        hidden_results.append(activation(np.matmul(self.weights[0], input_values) + self.bias[0]))

        # calculate results for subsequent hidden layers if any (depending on the previous layer)
        if self.hidden.ndim > 0:
            for idx, hidden_cells in enumerate(self.hidden[1:]):
                hidden_results.append(activation(np.matmul(self.weights[idx + 1],
                                                           hidden_results[idx]) +
                                                 self.bias[idx + 1]))

        # calculate final result and return, if explicit is set then return all the intermediate results as well
        output = []
        if 'explicit' in kwargs.keys():
            if kwargs.get('explicit') in ['yes', 'y', 1]:
                output = hidden_results
        output.append(activation(np.matmul(self.weights[-1], hidden_results[-1]) + self.bias[-1], True))
        return output

    def train_once(self, inputs, targets):
        # get the results including the hidden layers' (intermediate results)
        results = self.forward_propagation(inputs, explicit='yes')

        # prepare the targets and inputs for matrix operations
        targets = np.array(targets)[np.newaxis].T
        input_values = np.array(inputs)[np.newaxis].T

        # calculate the derivative error_matrix (targets vs outputs), index 0
        d_error_matrix = [((targets - results[-1]) * d_activation(results[-1], True)) / c.BATCH_SIZE]

        # calculate the derivative error_matrix of the hidden layers from last to first but insert in the correct order
        for idx in range(len(results) - 1, 0, -1):
            d_error_matrix.insert(0, np.matmul(self.weights[idx].T, d_error_matrix[0] * d_activation(results[idx])))

        # calculate the gradient for the weights that feed the output layer
        self.weights[-1] -= self.learning_rate * np.matmul(d_error_matrix[-1], results[-2].T)
        self.bias[-1] -= self.learning_rate * d_error_matrix[-1]

        # calculate the gradient for all subsequent hidden layers
        for idx, weight_col in enumerate(self.weights[1:-1]):
            weight_col -= self.learning_rate * np.matmul(d_error_matrix[idx + 1], results[idx].T)
            self.bias[idx + 1] -= self.learning_rate * d_error_matrix[idx + 1]

        # calculate the gradient for the first layer weights (input -> first hidden layer)
        self.weights[0] -= self.learning_rate * np.matmul(d_error_matrix[0], input_values.T)
        self.bias[0] -= self.learning_rate * d_error_matrix[0]

    def calculate_gradient(self, inputs, targets):
        # get the results including the hidden layers' (intermediate results)
        results = self.forward_propagation(inputs, explicit='yes')

        # prepare the targets and inputs for matrix operations
        input_values = np.array(inputs)[np.newaxis].T
        # targets = np.array(targets)[np.newaxis].T

        # calculate the error (outputs vs targets), index 0
        error = [(results[-1] - targets) / c.BATCH_SIZE]

        loss = (np.sum((targets - results[-1]) ** 2) / len(targets))

        # calculate the error of the hidden layers from last to first but insert in the correct order
        for idx in range(len(results) - 2, -1, -1):
            error.insert(0, np.matmul(self.weights[idx + 1].T, error[0]))

        # modify weights and biases gradients (input -> first hidden layer)
        self.gradients[0] += np.matmul((error[0] * d_activation(results[0])), input_values.T)
        self.bias_gradients[0] += (error[0] * d_activation(results[0]))

        # modify weights and biases gradients (all subsequent hidden layers and output)
        for idx, gradient_cols in enumerate(self.gradients[1:-1]):
            gradient_cols += np.matmul((error[idx + 1] * d_activation(results[idx + 1])),
                                       results[idx].T)
            self.bias_gradients[idx + 1] += (error[idx + 1] * d_activation(results[idx + 1]))

        self.gradients[-1] += np.matmul((error[-1] * d_activation(results[-1], True)),
                                        results[-2].T)
        self.bias_gradients[-1] += (error[-1] * d_activation(results[-1], True))

        return loss

    @staticmethod
    def cyclic_learning_rate(learning_rate, epoch):
        max_lr = learning_rate * c.MAX_LR_FACTOR
        cycle = np.floor(1 + (epoch / (2 * c.LR_STEP_SIZE)))
        x = np.abs((epoch / c.LR_STEP_SIZE) - (2 * cycle) + 1)
        return learning_rate + (max_lr - learning_rate) * np.maximum(0, (1 - x))

    def apply_gradients(self, epoch):
        true_epoch = epoch - c.BATCH_SIZE
        eta = self.learning_rate * (1 / (1 + c.DECAY_RATE * true_epoch))

        if c.CLR_ON:
            eta = self.cyclic_learning_rate(eta, true_epoch)

        if epoch % 100 == 0:
            print('epoch: ', true_epoch, '| LR:', eta)

        for i, weight_col in enumerate(self.weights):

            if c.OPTIMIZATION == 'vanilla':
                weight_col -= eta * np.array(self.gradients[i]) / c.BATCH_SIZE
                self.bias[i] -= eta * np.array(self.bias_gradients[i]) / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'SGD_momentum':
                self.v["dW" + str(i)] = ((c.BETA1 * self.v["dW" + str(i)])
                                         + (eta * np.array(self.gradients[i])))
                self.v["db" + str(i)] = ((c.BETA1 * self.v["db" + str(i)])
                                         + (eta * np.array(self.bias_gradients[i])))

                weight_col -= self.v["dW" + str(i)] / c.BATCH_SIZE
                self.bias[i] -= self.v["db" + str(i)] / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'NAG':
                v_prev = {"dW" + str(i): self.v["dW" + str(i)], "db" + str(i): self.v["db" + str(i)]}

                self.v["dW" + str(i)] = (c.NAG_COEFF * self.v["dW" + str(i)]
                                         - eta * np.array(self.gradients[i]))
                self.v["db" + str(i)] = (c.NAG_COEFF * self.v["db" + str(i)]
                                         - eta * np.array(self.bias_gradients[i]))

                weight_col += -1 * ((c.BETA1 * v_prev["dW" + str(i)])
                                    + (1 + c.BETA1) * self.v["dW" + str(i)]) / c.BATCH_SIZE
                self.bias[i] += ((-1 * c.BETA1 * v_prev["db" + str(i)])
                                 + (1 + c.BETA1) * self.v["db" + str(i)]) / c.BATCH_SIZE

            elif c.OPTIMIZATION == 'RMSProp':
                self.s["dW" + str(i)] = ((c.BETA1 * self.s["dW" + str(i)])
                                         + ((1 - c.BETA1) * (np.square(np.array(self.gradients[i])))))
                self.s["db" + str(i)] = ((c.BETA1 * self.s["db" + str(i)])
                                         + ((1 - c.BETA1) * (np.square(np.array(self.bias_gradients[i])))))

                weight_col -= (eta * (np.array(self.gradients[i])
                                      / (np.sqrt(self.s["dW" + str(i)] + c.EPSILON)))) / c.BATCH_SIZE
                self.bias[i] -= (eta * (np.array(self.bias_gradients[i])
                                        / (np.sqrt(self.s["db" + str(i)] + c.EPSILON)))) / c.BATCH_SIZE

            if c.OPTIMIZATION == "ADAM":
                # decaying averages of past gradients
                self.v["dW" + str(i)] = ((c.BETA1 * self.v["dW" + str(i)])
                                         + ((1 - c.BETA1) * np.array(self.gradients[i])))
                self.v["db" + str(i)] = ((c.BETA1 * self.v["db" + str(i)])
                                         + ((1 - c.BETA1) * np.array(self.bias_gradients[i])))

                # decaying averages of past squared gradients
                self.s["dW" + str(i)] = ((c.BETA2 * self.s["dW" + str(i)])
                                         + ((1 - c.BETA2) * (np.square(np.array(self.gradients[i])))))
                self.s["db" + str(i)] = ((c.BETA2 * self.s["db" + str(i)])
                                         + ((1 - c.BETA2) * (np.square(np.array(self.bias_gradients[i])))))

                if c.ADAM_BIAS_Correction:
                    # bias-corrected first and second moment estimates
                    self.v["dW" + str(i)] = self.v["dW" + str(i)] / (1 - (c.BETA1 ** true_epoch))
                    self.v["db" + str(i)] = self.v["db" + str(i)] / (1 - (c.BETA1 ** true_epoch))
                    self.s["dW" + str(i)] = self.s["dW" + str(i)] / (1 - (c.BETA2 ** true_epoch))
                    self.s["db" + str(i)] = self.s["db" + str(i)] / (1 - (c.BETA2 ** true_epoch))

                # apply to weights and biases
                weight_col -= ((eta * (self.v["dW" + str(i)]
                                       / (np.sqrt(self.s["dW" + str(i)]) + c.EPSILON)))) / c.BATCH_SIZE
                self.bias[i] -= ((eta * (self.v["db" + str(i)]
                                         / (np.sqrt(self.s["db" + str(i)]) + c.EPSILON)))) / c.BATCH_SIZE

        self.gradient_zeros()

    def RL_train(self, replay_memory, target_network, experience, iteration):
        loss = 0
        states_to_train = []
        targets_to_train = []
        batch = experience(*zip(*replay_memory))
        states = np.array(batch.state)
        actions = np.array(batch.action)
        rewards = np.array(batch.reward)
        next_states = np.array(batch.next_state)
        eps = np.finfo(np.float32).eps.item()

        if c.REWARD_NORMALIZATION:
            rewards_normalized = (rewards - rewards.mean()) / (rewards.std() + eps)
        else:
            rewards_normalized = rewards

        for i in range(len(replay_memory)):
            empty_cells = np.where(next_states[i] == 0)
            if len(empty_cells[0]) == 0:
                target_q = rewards_normalized[i]
            else:
                # calculate max_Q2
                next_state = c.one_hot(next_states[i])
                results_q2 = target_network.forward_propagation(next_state)
                results_q2 = results_q2[0]
                max_q2 = np.max(results_q2)
                target_q = rewards_normalized[i] + c.GAMMA * max_q2

            # form targets vector
            state = c.one_hot(states[i])
            targets = self.forward_propagation(state)
            targets = targets[0]
            old_targets = targets.copy()
            targets[actions[i], 0] = target_q
            loss += (np.sum((targets - old_targets) ** 2) / len(targets)) / (c.BATCH_SIZE * c.EPOCHS)
            states_to_train.append(state)
            targets_to_train.append(targets)

        for i in range(c.EPOCHS):
            ti = time.perf_counter()
            loss2 = 0
            states_to_train_batch = random.sample(states_to_train, c.BATCH_SIZE)
            targets_to_train_batch = random.sample(targets_to_train, c.BATCH_SIZE)
            for j in range(c.BATCH_SIZE):
                loss2 += self.calculate_gradient(states_to_train_batch[j], targets_to_train_batch[j])
            self.apply_gradients(iteration)
            tf = time.perf_counter()
            print('Epoch ', i, '/', c.EPOCHS, f"{tf - ti:0.4f}s", ' - loss: ', loss2 / c.BATCH_SIZE)

        return loss


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

#please make sure to keep the face images in the same folder as code
#before running this

#Task 4
#Part 1
I = cv2.imread('face 02 7043565.jpg', 1);
I = cv2.cvtColor(I, cv2.COLOR_BGR2RGB)
I = np.clip(I, 0, 255)

J = I[50:600, 200:800]
#cropped the image

J = cv2.cvtColor(J, cv2.COLOR_RGB2GRAY)
cropped_resized_gray = cv2.resize(J, (256, 256), interpolation = cv2.INTER_AREA)
cropped_resized_gray = np.clip(cropped_resized_gray, 0, 255)

fig, ax = plt.subplots(1, 2)
fig.suptitle('Display img and resized gray img')

ax[0].imshow(I)
ax[0].set(title="The original img")

ax[1].imshow(cropped_resized_gray, cmap=plt.cm.gray)
ax[1].set(title="Cropped resized gray img")
plt.show()

#Part 2
noise = 15*np.random.randn(256,256)
gray_with_noise = cropped_resized_gray + noise

plt.imshow(gray_with_noise, cmap=plt.cm.gray)

#Part 3
hist_without_noise = cv2.calcHist([cropped_resized_gray], [0], None, [256], [0,256])
hist_with_noise = cv2.calcHist([np.clip(np.uint8(gray_with_noise), 0, 255)], [0], None, [256], [0,256])

fig, ax = plt.subplots(1, 2)
fig.suptitle('Added Gaussian noise')

ax[0].plot(hist_without_noise)
ax[0].set(title="Without Noise")

ax[1].plot(hist_with_noise)
ax[1].set(title="With Noise")
plt.show()

#Part 4

#First let's create a Gaussian kernel
kernel_1 = cv2.getGaussianKernel(5, 1) #size, std is 1
kernel_5 = cv2.getGaussianKernel(5, 5) #std=5
kernel_10 = cv2.getGaussianKernel(5, 10) #std=10
kernel_15 = cv2.getGaussianKernel(5, 15) #std=15
window = np.outer(kernel_1, kernel_1.T) #the 5x5 kernel

#The convolution function
def conv(img, kernel):
    img_row, img_col = img.shape
    kernel_row, kernel_col = kernel.shape
    final_img = np.zeros(img.shape)

    pad_h = int((kernel_row-1)/2)
    pad_w = int((kernel_col-1)/2)

    padded_img = np.zeros((img_row+2*pad_h, img_col+2*pad_w))

    padded_img[pad_h:padded_img.shape[0]-pad_h,pad_w:padded_img.shape[1]-pad_w] = img

    for i in range(img_row):
        for j in range(img_col):
            final_img[i, j] = np.sum(kernel*padded_img[i:i+kernel_row, j:j+kernel_col])

    return final_img

#Part 5
output_img = conv(gray_with_noise, window)

fig, ax = plt.subplots(1, 2)
fig.suptitle('Applying custom Gaussian filter')

ax[0].imshow(gray_with_noise, cmap=plt.cm.gray)
ax[0].set(title="Gray img with noise")

ax[1].imshow(output_img, cmap=plt.cm.gray)
ax[1].set(title="Gray img after filter applied")
plt.show()

fig, ax = plt.subplots(2, 2)

ax[0,0].imshow(output_img, cmap=plt.cm.gray)
ax[0,0].set(title="Gaussian Filter with std 1")

ax[0,1].imshow(conv(gray_with_noise, np.outer(kernel_5, kernel_5.T)), cmap=plt.cm.gray)
ax[0,1].set(title="Gaussian Filter with std 1")

ax[1,0].imshow(conv(gray_with_noise, np.outer(kernel_10, kernel_10.T)), cmap=plt.cm.gray)
ax[1,0].set(title="Gaussian Filter with std 10")

ax[1,1].imshow(conv(gray_with_noise, np.outer(kernel_15, kernel_15.T)), cmap=plt.cm.gray)
ax[1,1].set(title="Gaussian Filter with std 15")

plt.tight_layout()
plt.show()

#Part 6
output_img = conv(gray_with_noise, np.outer(kernel_15, kernel_15.T))
output_with_built_in = cv2.GaussianBlur(gray_with_noise, (5,5), 15)
#https://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html#void%20GaussianBlur(InputArray%20src,%20OutputArray%20dst,%20Size%20ksize,%20double%20sigmaX,%20double%20sigmaY,%20int%20borderType)

fig, ax = plt.subplots(1, 2)
fig.suptitle('Comparing custom filter with built-in filter')

ax[0].imshow(output_img, cmap=plt.cm.gray)
ax[0].set(title="Gray img after Custom filter")

ax[1].imshow(output_with_built_in, cmap=plt.cm.gray)
ax[1].set(title="Gray img after Built-in Filter")
plt.show()

hist_my_func = cv2.calcHist([np.uint8(output_img)], [0], None, [256], [0,256])
hist_built_in = cv2.calcHist([np.uint8(output_with_built_in)], [0], None, [256], [0,256])

fig, ax = plt.subplots(1, 2)
fig.suptitle('Compare histograms of filtered images')

ax[0].plot(hist_my_func)
ax[0].set(title="Hist custom function")

ax[1].plot(hist_built_in)
ax[1].set(title="Hist built-in function")
plt.show()

#Task 5

sobel_filter = np.array([[-1, 0, 1],[-2, 0, 2],[-1, 0, 1]])
output_sobel_vertical = conv(cropped_resized_gray, sobel_filter)
output_sobel_horizontal = conv(cropped_resized_gray, sobel_filter.T)#np.flip axis=0

fig, ax = plt.subplots(1, 3)
fig.suptitle('')

ax[0].imshow(cropped_resized_gray, cmap=plt.cm.gray)
ax[0].set(title="Original image")

ax[1].imshow(output_sobel_vertical, cmap=plt.cm.gray)
ax[1].set(title="After vertical sobel")

ax[2].imshow(output_sobel_horizontal, cmap=plt.cm.gray)
ax[2].set(title="After horizontal sobel")
plt.show()

#Comparison with in-built func
sobelx = cv2.Sobel(cropped_resized_gray,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(cropped_resized_gray,cv2.CV_64F,0,1,ksize=3)

fig, ax = plt.subplots(1, 3)
fig.suptitle('In-built Sobel Filter')

ax[0].imshow(cropped_resized_gray, cmap=plt.cm.gray)
ax[0].set(title="Original image")

ax[1].imshow(sobelx, cmap=plt.cm.gray)
ax[1].set(title="After vertical sobel")

ax[2].imshow(sobely, cmap=plt.cm.gray)
ax[2].set(title="After horizontal sobel")
plt.show()

#On more images
I = cv2.imread('face 03 7043565.jpg', 1);
I = cv2.cvtColor(I, cv2.COLOR_BGR2RGB)
I = np.clip(I, 0, 255)

J = I[50:600, 200:800]
#cropped the image

J = cv2.cvtColor(J, cv2.COLOR_RGB2GRAY)
cropped_resized_gray = cv2.resize(J, (256, 256), interpolation = cv2.INTER_AREA)
cropped_resized_gray = np.clip(cropped_resized_gray, 0, 255)

output_sobel_vertical = conv(cropped_resized_gray, sobel_filter)
output_sobel_horizontal = conv(cropped_resized_gray, sobel_filter.T)#np.flip axis=0

fig, ax = plt.subplots(1, 3)
fig.suptitle('')

ax[0].imshow(cropped_resized_gray, cmap=plt.cm.gray)
ax[0].set(title="Original image")

ax[1].imshow(output_sobel_vertical, cmap=plt.cm.gray)
ax[1].set(title="After vertical sobel")

ax[2].imshow(output_sobel_horizontal, cmap=plt.cm.gray)
ax[2].set(title="After horizontal sobel")
plt.show()

sobelx = cv2.Sobel(cropped_resized_gray,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(cropped_resized_gray,cv2.CV_64F,0,1,ksize=3)

fig, ax = plt.subplots(1, 3)
fig.suptitle('In-built Sobel Filter')

ax[0].imshow(cropped_resized_gray, cmap=plt.cm.gray)
ax[0].set(title="Original image")

ax[1].imshow(sobelx, cmap=plt.cm.gray)
ax[1].set(title="After vertical sobel")

ax[2].imshow(sobely, cmap=plt.cm.gray)
ax[2].set(title="After horizontal sobel")
plt.show()