####################################################################################
#   Train_unet.py
#       Script for implementing and training a unet model for segmentation
#   Name: Yuqi Yang
#   Student Number: 20150516
#   Date: 03/21/2023
#####################################################################################

import cv2
import numpy as np
from numpy import newaxis
import os

import scipy.spatial.distance
from scipy.spatial.distance import directed_hausdorff
import tensorflow as tf
from matplotlib import pyplot as plt
from tensorflow.keras import layers
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.layers import Input, Average
from tensorflow.keras import optimizers
from tensorflow.keras.regularizers import l1
from tensorflow.keras import backend as K
from tensorflow.keras.callbacks import *
import math
import random
from UnetSequence import UnetSequence
from sklearn.model_selection import train_test_split

def define_UNet_Architecture(imageSize,numClasses,filterMultiplier=10):
    input_ = layers.Input(imageSize)
    skips = []
    output = input_

    num_layers = int(np.floor(np.log2(imageSize[0])))
    down_conv_kernel_sizes = np.zeros([num_layers],dtype=int)
    up_conv_kernel_sizes = np.zeros([num_layers], dtype=int)

    down_filter_numbers = np.zeros([num_layers],dtype=int)
    up_filter_numbers = np.zeros([num_layers],dtype=int)

    for layer_index in range(num_layers):
        up_conv_kernel_sizes[layer_index]=int(4)
        down_conv_kernel_sizes[layer_index] = int(3)
        down_filter_numbers[layer_index] = int((layer_index+1)*filterMultiplier + numClasses)
        up_filter_numbers[layer_index] = int((num_layers-layer_index-1)*filterMultiplier + numClasses)

    #Create contracting path
    for kernel_shape,num_filters in zip(down_conv_kernel_sizes,down_filter_numbers):
        skips.append(output)
        output = layers.Conv2D(num_filters,(kernel_shape,kernel_shape),
                               strides=2,
                               padding="same",
                               activation="relu",
                               bias_regularizer=l1(0.))(output)

    #Create expanding path
    lastLayer = len(up_conv_kernel_sizes)-1
    layerNum = 0
    for kernel_shape,num_filters in zip(up_conv_kernel_sizes,up_filter_numbers):
        output = layers.UpSampling2D()(output)
        skip_connection_output = skips.pop()
        output = layers.concatenate([output,skip_connection_output],axis=3)
        if layerNum!=lastLayer:
            output = layers.Conv2D(num_filters,(kernel_shape,kernel_shape),
                                   padding="same",
                                   activation="relu",
                                   bias_regularizer=l1(0.))(output)
        else: #Final output layer
            output = layers.Conv2D(num_filters, (kernel_shape, kernel_shape),
                                   padding="same",
                                   activation="softmax",
                                   bias_regularizer=l1(0.))(output)
        layerNum+=1
    return Model([input_],[output])

#############################################################################################################
# Question 2:
#    Complete the following function to generate your simulated images and segmentations. You may implement
#    many helper functions as necessary to do so.
#############################################################################################################

# CISC472 Homework 1 Question 7
# Generate a simulated image
def ellipse_bg_generator(dimension_x, dimension_y):
    # 0 for background and 255 for ellipse
    # first generate an array filled with 0s with a given dimension
    bg_arr = np.zeros((dimension_x, dimension_y))
    return bg_arr

def ellipse_generator(dimension_x, dimension_y, depth):
    ellipse_bg_arr = ellipse_bg_generator(dimension_x, dimension_y)
    ellipse_arr = np.copy(ellipse_bg_arr)
    # the size and location of ellipse is random every time.
    # the location of the ellipse is considered its conter point here.
    # in case of some extreme random location generated, I will leave a
    # padding of around 15 pixel for room. where my ellipse
    # has a y or x value of minimum 1/10 pixel value of the background dimension
    # rounding to the nearest int
    pixel_val = math.ceil((min(dimension_x, dimension_y))/10)
    ellipse_loc_x = random.randint(15,(dimension_x-16))
    ellipse_loc_y = random.randint(15,(dimension_y-16))

    # The edges of the ellipse may not be cut off by the boundaries of the image,
    # which means depending on the location of the ellipse, the size of the ellipse
    # will be restrained accordingly
    ellipse_x = random.randint(15,(min(ellipse_loc_x, dimension_x-ellipse_loc_x)))
    ellipse_y = random.randint(15,(min(ellipse_loc_y, dimension_y-ellipse_loc_y)))

    # generate a rectangle with x and y value
    for i in range(ellipse_x):
        for j in range(ellipse_y):
            # generate at all 4 direction
            ellipse_arr[ellipse_loc_x+i][ellipse_loc_y+j] = (2**depth)-1
            ellipse_arr[ellipse_loc_x-i][ellipse_loc_y-j] = (2**depth)-1
            ellipse_arr[ellipse_loc_x-i][ellipse_loc_y+j] = (2**depth)-1
            ellipse_arr[ellipse_loc_x+i][ellipse_loc_y-j] = (2**depth)-1

    # determine if point is in ellipse range
    for row in range(ellipse_arr.shape[0]):
        for col in range(ellipse_arr.shape[1]):
            if ellipse_arr[row][col] == (2**depth)-1:
                flag = (((row-ellipse_loc_x)**2)/(ellipse_x**2))+(((col-ellipse_loc_y)**2)/(ellipse_y**2))
                if flag > 1:
                    ellipse_arr[row][col] = 0
                else:
                    ellipse_arr[row][col] = (2**depth)-1

    # Now we have a narray of every points in the ellipsoid covering every pixel value
    # return the simulated image as an array
    return ellipse_arr

# smoothing filter
def smooth(arr, count):
    for i in range(count):
        arr = cv2.GaussianBlur(arr,(5,5),cv2.BORDER_DEFAULT)
    return arr

def generateDataset(datasetDirectory,num_images,imageSize):
    '''
    Args:
        datasetDirectory: the path to the directory where your images and segmentations will be stored
        num_images: the number of images that you wish to generate
        imageSize: the shape of your images and segmentations
    Returns:
        None: Saves all images and segmentations to the dataset directory
    '''
    # generate 100 sample images of a white ellipse on a black background with
    # a bit-depth of 8 (max value 255) with shape (128,128)
    for i in range(num_images):
        random_ellipse = ellipse_generator(imageSize[0], imageSize[1], 8)
        modified_image = np.copy(random_ellipse)
        # To obtain the ground truth segmentations rescale the images to a bit-depth of 1 (max value 1)
        # resized dim is still (128,128), only add 1 then divide the whole set by 128, then minus 1
        # and round value as 0 or 1
        for row in range((random_ellipse.shape[0])):
            for col in range((random_ellipse.shape[1])):
                temp_val = round(((random_ellipse[row,col]+1)/128)-1)
                modified_image[row][col] = temp_val

        if i < 10:
            rescaledFilename = datasetDirectory + "\segmentation_0" + str(i) + ".png"
            smoothFilename = datasetDirectory + "\image_0" + str(i) + ".png"
        else:
            rescaledFilename = datasetDirectory + "\segmentation_" + str(i) + ".png"
            smoothFilename = datasetDirectory + "\image_" + str(i) + ".png"
        # save rescaled image files
        plt.imsave(rescaledFilename, modified_image, cmap = 'gray')

        # apply the smoothing filter 1-100 times for each images
        count = random.randint(1, 100)
        random_ellipse = smooth(random_ellipse, count)
        # save smoothed image files
        plt.imsave(smoothFilename, random_ellipse, cmap = 'gray')

#############################################################################################################
# Question 3:
#    Complete the following function so that it returns your data divided into 3 non-overlapping sets
# You do not need to read the images at this stage
#############################################################################################################
def splitDataIntoSets(images,segmentations):
    '''
    Args:
        images: list of all image filepaths in the dataset
        segmentations: list of all segmentation filepaths in the dataset

    Returns:
        trainImg: list of all image filepaths to be used for training
        trainSeg: list of all segmentation filepaths to be used for training
        valImg: list of all image filepaths to be used for validation
        valSeg: list of all segmentation filepaths to be used for validation
        testImg: list of all image filepaths to be used for testing
        testSeg: list of all segmentation filepaths to be used for testing
    '''
    # Divide data randomly into 3 distinct sets for training, validation and testing
    # ideally training data should be around 70%, where validation and test split the 30%
    # now test only consist of 10%, we can split the data into 70% training, 20% validation, 10% test.
    train_scalar = 70
    valid_scalar = 20
    test_scalar = 10
    # set seed, so randomnization is reproducible
    random.seed(10)
    data_index = list(range(0,100))
    # shuffle the index list to obtain a randomized effect
    random.shuffle(data_index)
    trainId, valID, testID = data_index[:train_scalar],data_index[train_scalar:valid_scalar+train_scalar],data_index[(valid_scalar+train_scalar):]
    # split images and segmentations sets accordingly
    trainImg = [ images[i] for i in trainId]
    valImg = [ images[i] for i in valID]
    testImg = [ images[i] for i in testID]
    trainSeg = [ segmentations[i] for i in trainId]
    valSeg = [ segmentations[i] for i in valID]
    testSeg = [ segmentations[i] for i in testID]
    return (trainImg,trainSeg),(valImg,valSeg),(testImg,testSeg)

#############################################################################################################
# Question 5:
#    Complete the following function so that it will create a plot for the training and validation loss/metrics.
#    Training and validation should be shown on the same graph so there should be one plot per loss/metric
#############################################################################################################
def plotLossAndMetrics(trainingHistory):
    '''
    Args:
        trainingHistory: The dictionary containing the progression of the loss and metrics for training and validation
    Returns:
        None: should save each graph as a png
    '''
    # summarize history for accuracy
    plt.plot(trainingHistory.history['accuracy'])
    plt.plot(trainingHistory.history['val_accuracy'])
    plt.title('model accuracy')
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(['train', 'validation'], loc='upper left')
    plt.show()

    # summarize history for loss
    plt.plot(trainingHistory.history['loss'])
    plt.plot(trainingHistory.history['val_loss'])
    plt.title('model loss')
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'validation'], loc='upper left')
    plt.show()

    # below code should be commented out accordingly
    #  IOU coefficient
    # plt.plot(trainingHistory.history['IOU'])
    # plt.plot(trainingHistory.history['val_IOU'])
    # plt.title('IOU')
    # plt.ylabel('IOU Coefficient')
    # plt.xlabel('epoch')
    # plt.legend(['train', 'validation'], loc='upper left')
    # plt.show()

    #  hausdorff coefficient
    # plt.plot(trainingHistory.history['hausdorffDistance'])
    # plt.plot(trainingHistory.history['val_hausdorffDistance'])
    # plt.title('hausdorff')
    # plt.ylabel('hausdorff Distance')
    # plt.xlabel('epoch')
    # plt.legend(['train', 'validation'], loc='upper left')
    # plt.show()

    #  for dice coefficient
    # plt.plot(trainingHistory.history['diceCoefficient'])
    # plt.plot(trainingHistory.history['val_diceCoefficient'])
    # plt.title('dice Coefficient')
    # plt.ylabel('dice Distance')
    # plt.xlabel('epoch')
    # plt.legend(['train', 'validation'], loc='upper left')
    # plt.show()

    return

def display_image(img_arr, tittle):
    # Displays the image in a GUI window. Press ESC key and the window is removed automatically
    # when color image is displayed
    if len(img_arr.shape) == 3:
        cv2.imshow(tittle, img_arr)
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    # every non color image is considered grayscale here
    else:
        plt.imshow(img_arr, cmap='gray')
        plt.title(tittle)
        plt.show()

def mean_precision(y_true,y_pred):
    '''
    Computes the mean precision between predicted segmentation and ground truth. All values are original
    image array.
    Args:
        ytrue: ground truth segmentation, shape = (batchSize, imgHeight,imgWidth)
        ypred: predicted segmentation, shape = (batchSize, imgHeight,imgWidth)

    Returns:
        mean_precision: the mean precision of the prediction
    '''
    # TP
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    # TP+FP
    possible_positives = K.sum(K.round(K.clip(y_true, 0, 1)))
    # precision = TP/(TP+FP)
    mean_precision = true_positives / (possible_positives + K.epsilon())
    return mean_precision

def mean_recall(y_true,y_pred):
    '''
    Computes the mean recall between predicted segmentation and ground truth. All values are original
    image array.
    Args:
        ytrue: ground truth segmentation, shape = (batchSize, imgHeight,imgWidth)
        ypred: predicted segmentation, shape = (batchSize, imgHeight,imgWidth)

    Returns:
        mean_recall: the mean recall of the prediction
    '''
    # TP
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 0, 1)))
    # TP+FN
    predicted_positives = K.sum(K.round(K.clip(y_pred, 0, 1)))
    # recall = TP/(TP+FN)
    mean_recall = true_positives / (predicted_positives + K.epsilon())
    return mean_recall

def main():
    dataSetPath = os.path.join(os.getcwd(), "CISC_472_dataset")
    # generateDataset(dataSetPath, 100,imageSize = (128,128,1)) #this line only needs to be run once
    images = sorted([os.path.join(dataSetPath,x) for x in os.listdir(dataSetPath) if "image" in x])
    segmentations = sorted([os.path.join(dataSetPath,x) for x in os.listdir(dataSetPath) if "segmentation" in x])

    trainData,valData,testData = splitDataIntoSets(images,segmentations)

    trainSequence = UnetSequence(trainData)
    valSequence = UnetSequence(valData)
    testSequence = UnetSequence(testData,shuffle=False)

    unet = define_UNet_Architecture(imageSize=(128,128,1),numClasses=2)
    unet.summary()

    # #############################################################################################################
    # # Set the values of the following hyperparameters
    # #############################################################################################################

    # Q5,6,7
    # Compile the model using the loss function “categorical_crossentropy” and IOU
    lossFunction_ce="categorical_crossentropy"
    lossFunction_IOU = [IOU_Loss]

    # I wanted to add precision and recall just for fun
    # metrics for different model mofication
    metrics_ce=["accuracy",
             tf.keras.metrics.Precision(name='precision'),
             tf.keras.metrics.Recall(name='recall')]
    metrics_IOU=[IOU, "accuracy"]
    metrics_dice=[diceCoefficient, "accuracy"]
    metrics_hausdorff=[hausdorffDistance, "accuracy"]

    # adjust epoch size here
    numEpochs=20

    # #############################################################################################################
    # # Create model checkpoints here, and add the variable names to the callbacks list in the compile command
    # #############################################################################################################

    # Early Stopping
    callbacks = [EarlyStopping(monitor='val_loss', patience=3)]

    # model checkpoints parameter to save best model per epoch, for both question 5 and 6
    # model_filepath = "../a3/unet_ce_loss_accuracy_.h5"
    model_filepath = "../a3/unet_ce_loss_accuracy.h5"
    model_filepath_IOUloss = "../a3/unet_iou_loss_accuracy.h5"
    model_filepath_IOU = "../a3/unet_ce_IOU.h5"
    model_filepath_hausdorff = "../a3/unet_ce_hausdorffDistance.h5"
    model_filepath_dice = "../a3/unet_ce_dice.h5"
    monitor_ce = 'val_accuracy'
    monitor_IOU = 'val_IOU'
    monitor_haus= 'val_hausdorffDistance'
    monitor_dice = 'val_diceCoefficient'
    # implement check point
    checkpoint = ModelCheckpoint(
        filepath=model_filepath,
        monitor=monitor_ce,
        mode='max', # or min for hausdorff distance
        save_best_only=True,
        save_weights_only=True
    )

    # change learning rate here !!
    unet.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
                 loss=lossFunction_ce,
                 metrics=metrics_ce,
                 run_eagerly=True)

    history = unet.fit(x=trainSequence,
                       validation_data=valSequence,
                       epochs=numEpochs,
                       batch_size = 16,
                       verbose=2,
                       callbacks=[checkpoint, callbacks])

    plotLossAndMetrics(history)

    unet.evaluate(testSequence)
    predictions = unet.predict(testSequence)

    #############################################################################################################
    # Add additional code for generating predictions and evaluations here
    #############################################################################################################

    ########### generating predictions, visulization ###########
    # saved model weight file path
    model_filepath = "unet_ce_loss_accuracy.h5"
    model_filepath_IOUloss = "../a3/unet_iou_loss_accuracy.h5"
    model_filepath_IOU = "../a3/unet_ce_IOU.h5"
    model_filepath_hausdorff = "../a3/unet_ce_hausdorffDistance.h5"
    model_filepath_dice = "../a3/unet_ce_dice.h5"

    # standard ce model
    unet.load_weights(model_filepath)
    standard_predictions = unet.predict(testSequence)
    for batchSize in range(0, (standard_predictions.shape[0])):
        org_std_predict = convert(standard_predictions, batchSize)
        display_image(org_std_predict, 'standard model - q5')
    unet.evaluate(testSequence)

    # # IOU loss model
    # unet.load_weights(model_filepath_IOUloss)
    # IOUloss_predictions = unet2.predict(testSequence)
    # for batchSize in range(0, (IOUloss_predictions.shape[0])):
    #     org_IOUloss_predict = convert(IOUloss_predictions, batchSize)
    #     display_image(org_IOUloss_predict, 'IOU loss model - q6')
    # unet.evaluate(testSequence)

    # # IOU ce model
    # unet.load_weights(model_filepath_IOU)
    # IOU_predictions = unet.predict(testSequence)
    # for batchSize in range(0, (IOU_predictions.shape[0])):
    #     org_IOU_predict = convert(IOU_predictions, batchSize)
    #     display_image(org_IOU_predict, 'IOU ce model - q7')
    # unet.evaluate(testSequence)

    # # hausdorff distance ce model
    # unet.load_weights(model_filepath_hausdorff)
    # haus_predictions = unet.predict(testSequence)
    # for batchSize in range(0, (haus_predictions.shape[0])):
    #     org_haus_predict = convert(haus_predictions, batchSize)
    #     display_image(org_haus_predict, 'hausdorff distance ce model - q7')
    # unet.evaluate(testSequence)

    # # dice coefficient ce model
    # unet.load_weights(model_filepath_dice)
    # dice_predictions = unet.predict(testSequence)
    # for batchSize in range(0, (dice_predictions.shape[0])):
    #     org_dice_predict = convert(dice_predictions, batchSize)
    #     display_image(org_dice_predict, 'dice coefficient ce model - q7')
    # unet.evaluate(testSequence)

    ########### evaluation ###########
    # ground truth array
    all_arr = []
    for i in range(len(testData[1])):
        # read in ground truth image
        seg = cv2.imread(testData[1][i], cv2.IMREAD_ANYDEPTH)
        all_arr.append(seg[None,:])
    groundTruth = np.vstack(all_arr)

    def quantitative_evaluation(prediction):
        hot_encode_arr = []
        # prediction array
        for batchSize in range(0, (prediction.shape[0])):
            temp = convert(prediction, batchSize)
            hot_encode_arr.append(temp[None,:])
        predict = np.vstack(hot_encode_arr)

        # q5 ce standard
        model_precision = mean_precision(groundTruth, predict)
        model_recall = mean_recall(groundTruth, predict)
        print(model_precision, model_recall)

    # evaluation of models in order
    quantitative_evaluation(standard_predictions)
    # quantitative_evaluation(IOUloss_predictions)
    # quantitative_evaluation(IOU_predictions)
    # quantitative_evaluation(haus_predictions)
    # quantitative_evaluation(dice_predictions)

def IOU(y_true,y_pred):
    y_true_f = K.flatten(y_true[:,:,1])
    y_pred_f = K.flatten(y_pred[:,:,1])
    intersection = K.sum(y_true_f*y_pred_f)
    return(intersection)/(K.sum(y_true_f)+K.sum(y_pred_f)-intersection)

def IOU_Loss(y_true,y_pred):
    return 1-IOU(y_true,y_pred)

#############################################################################################################
# Question 7:
#    Complete the following function to compute the mean hausdorff distance
#############################################################################################################
def convert(img_arr, batchSize):
        oneHot_y = img_arr[batchSize,:,:,:]
        # 0 = background, 1=object
        oneHot_object = oneHot_y[:,:,1]
        return oneHot_object
def hausdorffDistance(ytrue,ypred):
    '''
    Computes the mean hausdorff distance between predicted segmentation and ground truth. All values are one-hot encoded.
    Args:
        ytrue: ground truth segmentation, shape = (batchSize,imgHeight,imgWidth,numClasses)
        ypred: predicted segmentation, shape = (batchSize,imgHeight,imgWidth,numClasses)

    Returns:
        mean_hausdorff_distance: the mean hausdorff distance across all samples in the batch
    '''
    distance = []
    for batchSize in range(0, (ytrue.shape[0])):
        ytrue_h = convert(ytrue,batchSize)
        ypred_h = convert(ypred,batchSize)
        hausdorff_distance = directed_hausdorff(ytrue_h, ypred_h)[0]
        distance.append(hausdorff_distance)
    mean_hausdorff_distance = sum(distance) / len(distance)
    return mean_hausdorff_distance

#############################################################################################################
# Question 7:
#    Complete the following function to compute the mean dice coefficient
#############################################################################################################

def diceCoefficient(ytrue,ypred):
    '''
    Computes the mean dice coefficient between predicted segmentation and ground truth. All values are one-hot encoded.
    Args:
        ytrue: ground truth segmentation, shape = (batchSize,imgHeight,imgWidth,numClasses)
        ypred: predicted segmentation, shape = (batchSize,imgHeight,imgWidth,numClasses)

    Returns:
        mean_dice_coefficient: the mean dice coefficient across all samples in the batch
    '''
    # dice coefficient = 2*TP/((TP+FP)+(TP+FN))
    ytrue = ytrue.astype(ypred.dtype)
    y_true_f = K.flatten(ytrue[:,:,1])
    y_pred_f = K.flatten(ypred[:,:,1])
    intersection = K.sum(y_true_f*y_pred_f)
    mean_dice_coefficient = (2*intersection)/(K.sum(y_true_f)+K.sum(y_pred_f))
    return mean_dice_coefficient

main()


import tkinter
from tkinter.constants import END, X
import turtle
import math
import time

# create the window
window = tkinter.Tk()

sisi = 300
canvas_width = sisi
canvas_height = sisi
canvas = tkinter.Canvas(
    master=window, width=canvas_width, height=canvas_height)
canvas.grid(padx=2, pady=2, row=0, column=0, rowspan=10,
            columnspan=10)

commandscanvas = tkinter.Canvas(master=window, width=sisi, height=100)
commandscanvas.grid(padx=2, pady=2, row=15, column=0,
                    rowspan=10, columnspan=10)

class Pen(turtle.RawTurtle):
    def __init__(self):
        turtle.RawTurtle.__init__(self, canvas)
        self.shape('square')
        self.color('blue')
        self.penup()
        self.speed(0)

class Player(turtle.RawTurtle):
    def __init__(self):
        turtle.RawTurtle.__init__(self, canvas)
        self.shape('turtle')
        self.color('red')
        self.penup()
        self.speed(0)
        self.commands = []

    def forward(self):
        self.commands.append('f')
        show_commands()

    def turn_left(self):
        self.commands.append('tl')
        show_commands()

    def turn_right(self):
        self.commands.append('tr')
        show_commands()

    def start_loop(self):
        self.commands.append('sl')
        show_commands()

    def end_loop(self):
        self.commands.append('el')
        show_commands()

    def is_collision(self, other):
        a = self.xcor() - other.xcor()
        b = self.ycor() - other.ycor()
        distance = math.sqrt((a**2) + (b**2))

        if distance < 5:
            return True
        else:
            return False

class Treasure(turtle.RawTurtle):
    def __init__(self, x, y):
        turtle.RawTurtle.__init__(self, canvas)
        self.shape('circle')
        self.color('gold')
        self.penup()
        self.speed(0)
        self.goto(x, y)

    def destroy(self):
        self.hideturtle()

class CommandPen(turtle.RawTurtle):
    def __init__(self):
        turtle.RawTurtle.__init__(self, commandscanvas)
        self.shape('triangle')
        self.color('white')
        self.penup()
        self.speed(0)

    def frontstamp(self):
        self.shape('triangle')
        self.color('yellow')
        self.setheading(90)

    def leftstamp(self):
        self.shape('triangle')
        self.color('blue')
        self.setheading(180)

    def rightstamp(self):
        self.shape('triangle')
        self.color('red')
        self.setheading(0)

    def startloopstamp(self):
        self.shape('arrow')
        self.color('black')
        self.setheading(180)

    def endloopsstamp(self):
        self.shape('arrow')
        self.color('black')
        self.setheading(0)

    def newline(self):
        self.goto(-130, self.ycor()-24)

# Create Levels List
levels = []

# Define First Level
level_1 = [
    "00000",
    "0P T0",
    "00000"
]

level_2 = [
    "00000",
    "000T0",
    "000 0",
    "0P  0",
    "00000"
]

level_3 = [
    "00000",
    "0P  0",
    "000 0",
    "000T0",
    "00000"
]

level_4 = [
    "0000000",
    "000  T0",
    "000 000",
    "0P  000",
    "0000000"
]

level_5 = [
    "0000000",
    "0P  000",
    "000 000",
    "000  T0",
    "0000000"
]

level_6 = [
    "0000000",
    "0P    0",
    "00000 0",
    "0T    0",
    "0000000"
]

level_7 = [
    "0000000",
    "0T    0",
    "00000 0",
    "0P    0",
    "0000000"
]

level_8 = [
    "0000000",
    "0T    0",
    "00000 0",
    "0     0",
    "0 00000",
    "0    P0",
    "0000000"
]

level_9 = [
    "0000000",
    "0    T0",
    "0 00000",
    "0     0",
    "00000 0",
    "0P    0",
    "0000000"
]

level_10 = [
    "000000",
    "0000T0",
    "0000 0",
    "0000 0",
    "0P   0",
    "000000",
]

level_11 = [
    "000000",
    "0T0000",
    "0 0000",
    "0 0000",
    "0   P0",
    "000000",
]

level_12 = [
    "000000",
    "000T 0",
    "0000 0",
    "0000 0",
    "0P   0",
    "000000",
]

level_13 = [
    "000000",
    "0 T000",
    "0 0000",
    "0 0000",
    "0   P0",
    "000000",
]

level_14 = [
    "000000",
    "0P0T 0",
    "0 00 0",
    "0 00 0",
    "0    0",
    "000000",
]

level_15 = [
    "000000",
    "0 T0P0",
    "0 00 0",
    "0 00 0",
    "0    0",
    "000000",
]

win_level = [
    "0 0      ",
    "0 0      ",
    "000      ",
    "         ",
    "0 0 0    ",
    "0 0   000",
    "000 0 0 0",
    "0 0 0 0 0"
]

# Add maze to mazes list
levels.append(level_1)
levels.append(level_2)
levels.append(level_3)
levels.append(level_4)
levels.append(level_5)
levels.append(level_6)
levels.append(level_7)
levels.append(level_8)
levels.append(level_9)
levels.append(level_10)
levels.append(level_11)
levels.append(level_12)
levels.append(level_13)
levels.append(level_14)
levels.append(level_15)
levels.append(win_level)

# Add treasures list
treasures = []

# Create wall coordinate list
walls = []

# Specify which level to start with first
current_level_idx = 0

# Create class instances
pen = Pen()
player = Player()
commandpen = CommandPen()

def setup_maze(level):
    for y in range(len(level)):
        for x in range(len(level[y])):
            # Get character at each x,y coordinate
            # Note the order of y and x in the next line
            character = level[y][x]

            # Calculate the screen x,y coordinates
            screen_x = -(canvas_width * 0.25) + (x * 24)

            screen_y = (canvas_height*0.25) - (y * 24)

            # Check if it is an 0 (representing a wall)
            if character == '0':
                pen.goto(screen_x, screen_y)
                pen.stamp()
                # Add coordinates to wall list
                walls.append((screen_x, screen_y))

            # Check player
            if character == 'P':
                player.goto(screen_x, screen_y)
                player.setheading(0)

            # Check treasure
            if character == 'T':
                treasures.append(Treasure(screen_x, screen_y))

def execute_commands():
    global treasures

    # represents whether player has collided with wall
    flag = 0
    for x in player.commands:
        if x == 'f':
            # Calculate spot to move to
            direction = player.heading()
            # print(direction)
            if (direction == 0):
                move_to_x = player.xcor() + 24
                move_to_y = player.ycor()
            elif (direction == 90):
                move_to_x = player.xcor()
                move_to_y = player.ycor() + 24
            elif (direction == 180):
                move_to_x = player.xcor() - 24
                move_to_y = player.ycor()
            elif (direction == 270):
                move_to_x = player.xcor()
                move_to_y = player.ycor() - 24
            elif (direction == 360):
                direction = 0
                move_to_x = player.xcor() + 24
                move_to_y = player.ycor()
            else:
                # print('direction is weird')
                pass
                # Check if the space has a wall
            if(move_to_x, move_to_y) not in walls:
                player.goto(move_to_x, move_to_y)
            else:
                tkinter.messagebox.showinfo(
                    "You died", "Cause of Death: You faceplanted into a wall.")
                flag = 1
                # repeat
                repeat_maze()
                break
        elif x == 'tl':
            player.left(90)
        elif x == 'tr':
            player.right(90)
        elif x == 'sl':
            try:
                sl_idx = player.commands.index(x)
                el_idx = player.commands.index('el')
                loop_func(sl_idx, el_idx)
            except:
                pass
                # tkinter.messagebox.showinfo("Message", "you forgot end_loop")
                # repeat_maze()

        time.sleep(0.2)

    # if doesnt reach the end --> show u fail, and repeat ok
    if flag == 0:
        for treasure in treasures:
            if not player.is_collision(treasure):
                tkinter.messagebox.showinfo(
                    "You died", "Cause of Death: You didn't get to the end of the maze")
                repeat_maze()

    player.commands = []

def loop_func(sl_idx, el_idx):
    loop_ls = [player.commands[i] for i in range(sl_idx+1, el_idx)]

    for x in loop_ls:
        if x == 'f':
            # Calculate spot to move to
            direction = player.heading()
            # print(direction)
            if (direction == 0):
                move_to_x = player.xcor() + 24
                move_to_y = player.ycor()
            elif (direction == 90):
                move_to_x = player.xcor()
                move_to_y = player.ycor() + 24
            elif (direction == 180):
                move_to_x = player.xcor() - 24
                move_to_y = player.ycor()
            elif (direction == 270):
                move_to_x = player.xcor()
                move_to_y = player.ycor() - 24
            elif (direction == 360):
                direction = 0
                move_to_x = player.xcor() + 24
                move_to_y = player.ycor()
            else:
                pass
            # Check if the space has a wall
            if(move_to_x, move_to_y) not in walls:
                player.goto(move_to_x, move_to_y)
            else:
                tkinter.messagebox.showinfo(
                    "You died", "Cause of Death: You faceplanted into a wall.")
                # repeat
                break
        elif x == 'tl':
            player.left(90)
        elif x == 'tr':
            player.right(90)
        time.sleep(0.2)
    pass

def repeat_maze():
    global current_level_idx, treasures, walls
    for treasure in treasures:
        treasure.destroy()
        # len(treasures) will always be 1 after the first initiation of 'next level'
        treasures.remove(treasure)
        pen.clear()
        walls = []
        setup_maze(levels[current_level_idx-1])
    clear_commands()
    pass

def show_commands():
    commandpen.goto((-130, 30))

    for i in range(len(player.commands)):
        if commandpen.xcor() > 70:
            commandpen.newline()

        if player.commands[i] == 'f':
            commandpen.frontstamp()
            commandpen.goto(commandpen.xcor() + 24, commandpen.ycor())
            commandpen.stamp()
        elif player.commands[i] == 'tl':
            commandpen.leftstamp()
            commandpen.goto(commandpen.xcor() + 24, commandpen.ycor())
            commandpen.stamp()
        elif player.commands[i] == 'tr':
            commandpen.rightstamp()
            commandpen.goto(commandpen.xcor() + 24, commandpen.ycor())
            commandpen.stamp()
        elif player.commands[i] == 'sl':
            commandpen.startloopstamp()
            commandpen.goto(commandpen.xcor() + 24, commandpen.ycor())
            commandpen.stamp()
        elif player.commands[i] == 'el':
            commandpen.endloopsstamp()
            commandpen.goto(commandpen.xcor() + 24, commandpen.ycor())
            commandpen.stamp()

def clear_commands():
    player.commands = []
    show_commands()

    commandpen.clear()
    commandpen.color('white')

def next_level():
    global current_level_idx, treasures
    if len(treasures) == 0:
        # theres no treasure before the first level is created and after it is collected/destroyed
        if current_level_idx < len(levels):
            pass
            # print('current level idx now', current_level_idx)
        else:
            current_level_idx = 0
        setup_maze(levels[current_level_idx])
        current_level_idx += 1
        # clear commands
        clear_commands()

Play_Button = tkinter.Button(
    master=window, text="Play!",
    command=next_level)
Play_Button.config(bg="cyan", fg="black")
Play_Button.grid(padx=2, pady=2, row=11, column=1,
                 # sticky nsew will ensure that the widget expands to take up all the space allowed
                 sticky='nsew')

# print('the current level main ', current_level_idx)

Board_Button = tkinter.Button(
    master=window, text="Turn left", command=player.turn_left)
Board_Button.config(bg="blue", fg="black", width=15)
Board_Button.grid(padx=2, pady=2, row=12, column=0, sticky='nsew')

Board_Button = tkinter.Button(
    master=window, text="Forward", command=player.forward)
Board_Button.config(bg="yellow", fg="black")
Board_Button.grid(padx=2, pady=2, row=12, column=1, sticky='nsew')

Board_Button = tkinter.Button(
    master=window, text="Turn right", command=player.turn_right)
Board_Button.config(bg="red", fg="black")
Board_Button.grid(padx=2, pady=2, row=12, column=2, sticky='nsew')

Start_Loop_Button = tkinter.Button(
    master=window, text="Start Loop", command=player.start_loop)
Start_Loop_Button.config(bg="white", fg="black")
Start_Loop_Button.grid(padx=2, pady=2, row=13,
                       column=0, sticky='nsew')

End_Loop_Button = tkinter.Button(
    master=window, text="End Loop", command=player.end_loop)
End_Loop_Button.config(bg="white", fg="black")
End_Loop_Button.grid(padx=2, pady=2, row=13, column=1, sticky='nsew')

Execute_Button = tkinter.Button(
    master=window, text="Execute commands", command=execute_commands)
Execute_Button.config(bg="green", fg="white")
Execute_Button.grid(padx=2, pady=2, row=14, column=1, sticky='nsew')

Clear_Button = tkinter.Button(
    master=window, text="Clear commands", command=clear_commands)
Clear_Button.config(bg="orange", fg="black")
Clear_Button.grid(padx=2, pady=10, row=14, column=2, sticky='nsew')

# Turn off screen updates
# window.tracer(0)

# Main Game Loop
while True:
    try:
        for treasure in treasures:
            if player.is_collision(treasure):
                tkinter.messagebox.showinfo(
                    "You beat the level!", "Congratulations! Time for the next level")
                treasure.destroy()
                # len(treasures) will always be 1 after the first initiation of 'next level'
                treasures.remove(treasure)
                pen.clear()
                commandpen.clear()
                walls = []
                next_level()
                # turtle.Screen().bye()
        window.update()
    except:
        pass


import math

def bubble(t):  #definiujemy funkcję sortującą - korzystamy z metody bubble sort
    assert len(t)>0
    n = len(t)
    for i in range(n, 0, -1):    #i jest zakresem wykonywania drugiej petli, w kolejnym kroku jest on o 1 mniejszy
        for j in range(1, i):
            if t[j-1] > t[j]:
               a = t[j-1]
               t[j-1] = t[j]
               t[j] = a
    return t

def median(t):      #funkcja wyliczajaca wartosc mediany
    assert len(t)>0
    n = len(t)
    if n%2==0:
        mediana = 0.5*(t[n//2]+t[(n-2)//2])     #wynik dzielenia i tak jest l. calkowita, ale // zwraca int
        return mediana
    return t[(n-1)//2]   #w przeciwnym wypadku (dla nieparzystego n) - dzielenie calkowite by zwracalo inta

def piekwartyl(t):    #wartosc pierwszego kwartyla
    assert len(t)>0
    n = len(t)
    kon = math.floor((n-1)/2) #!!!!!!  #koncowy indeks pierwszej polowy ciagu, potrzebny do obliczenia kwartyla
    x = [None]*(kon+1)
    for i in range(kon+1):
        x[i] = t[i]
    return median(x)

def trzkwartyl(t):   #wartosc trzeciego kwartyla
    assert len(t)>0
    n = len(t)
    pocz = math.ceil((n-1)/2)
    x = [None]*(n-pocz)
    for i in range(pocz, n):
        x[i-pocz] = t[i]   #wpisywanie do tablicy x zaczynamy od indeksu pocz-pocz(=0), konczymy na n-1-pocz, czyli ostatnim miejscu listy x
    return median(x)

def iqr(t):         #rozstep cwiartkowy
    assert len(t)>0
    return trzkwartyl(t)-piekwartyl(t)

def dolnywas(t):    #granica dolna wasa, przy zalozeniu ze tablica jest juz posortowana
    assert len(t)>0
    minim = piekwartyl(t)   #ostatecznie dolna granica wasa powinna byc niewieksza niz wartosc 1 kwartyla
    for i in range(math.ceil(len(t)/4)+1):  #ograniczenie jest taki, ze znajdzie sie w 'range' indeks wartosci pierwszego kwartyla lub nastepny indeks
         if t[i]>=piekwartyl(t)-1.5*iqr(t) and t[i]<minim:
            minim = t[i]
    return minim

def gornywas(t):    #granica gorna wasa, przy zal. ze wartosci sa posortowane
    assert len(t)>0
    maxim= trzkwartyl(t)   #ostatecznie gorna granica wasa powinna byc niemniejsza niz wartosc 3 kwartyla
    for i in range(math.floor(len(t)*0.75)-1, len(t)):
        if t[i]<=trzkwartyl(t)+1.5*iqr(t) and t[i]>maxim:
            maxim = t[i]
    return maxim

def boxplot(t):         #funkcja rysujaca wykres
    assert len(t)>0
    n = len(t)
    tab = bubble(t)     #tab jest posortowana tablica t
    diff = tab[len(tab)-1]-tab[0]
    difference = diff/10
    for i in range(11):
        print("{0:4.2f}".format(t[0]+difference*i)+" "*4, end="")   #wypisanie pierwszej linii
    print("\n"+" "+"|-------"*10+"| ")
    print("\n")
    medi = median(t)
    pie = piekwartyl(t)
    trz = trzkwartyl(t)
    dol = dolnywas(t)
    gor = gornywas(t)
    y1 = int((pie - tab[0])/diff*84) #liczba spacji poczatkowych w pierwszej linijce pudelka
    y2 = int((tab[len(tab)-1]-trz)/diff*82) #liczba spacji koncowych
    z1 = int((dol-tab[0])/diff*84)
    z2 = int((tab[len(tab)-1]-gor)/diff*84)
    print(" "*y1+"-"*(84-y2-y1)+" "*y2)
    print(" "*z1+"-"*(y1-z1)+"|"+" "*(z2-z1)+"|"+"-"*(84-z2))
    print(" "*y1+"-"*(84-y2-y1)+" "*y2)
#Koniec czasu :(


#소수찾기이이이

#일단 에라토스테네스의 체 먼저 찾아봄

def prime_list(n):
    sieve=[True]*n

    m = int(n ** 0.5)
    for i in range(2, m + 1):
        if sieve[i] == True:
            for j in range(i+i, n, i):
                sieve[j] = False

    return [i for i in range(2, n) if sieve[i] == True]

#풀이풀이

def solution(n):
    num = set(range(2,n+1)) #2~에서 n+1까지

    for i in range(2,n+1): #반복반복
        if i in num:
            num -= set(range(2*i, n+1, i)) #i가 num 안에 있으면 나가리

    return len(num) #num에 있는 i 개수!! len은 리스트에 있는 원소 개수 알랴쥼


from fpdf import FPDF

pdf = FPDF(orientation='P', unit='pt', format='A4')
pdf.add_page()

pdf.image('logo.jpeg', w=80, h=80)

pdf.set_font(family='Times', style='B', size=24)
pdf.cell(w=0, h=50, txt='Offerta', align='C', border=1, ln=1)

pdf.set_font(family='Times', style='B', size=14)
pdf.cell(w=0, h=15, txt='Description', ln=1)
pdf.set_font(family='Times', size=12)
content = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque euismod, erat vitae tincidunt congue, mi felis varius nisi, quis consectetur nibh ipsum ac urna. Pellentesque iaculis erat massa, in accumsan sapien tempor vel. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis convallis enim nisi. Donec imperdiet varius mi non pharetra. Aliquam sit amet dictum sapien. Pellentesque orci eros, malesuada ac tristique non, finibus ut risus.
'''
pdf.multi_cell(w=0, h=15, txt=content)

pdf.set_font(family='Times', style='B', size=14)
pdf.cell(w=100, h=25, txt='Kindom:')

pdf.set_font(family='Times', size=14)
pdf.cell(w=100, h=25, txt='Animalia', ln=1)

pdf.set_font(family='Times', style='B', size=14)
pdf.cell(w=100, h=25, txt='ABC:')

pdf.set_font(family='Times', size=14)
pdf.cell(w=100, h=25, txt='DEF', ln=1)

pdf.output('output.pdf')

import os
os.system('output.pdf')


#!/usr/bin/env python3
# lib/gdb.py

# internal imports
import html
import logging
import re
from datetime import datetime

# python <3.9 workaround
try:
    from zoneinfo import ZoneInfo
except ImportError:
    from backports.zoneinfo import ZoneInfo

# external imports
import mechanicalsoup
from netaddr import IPNetwork

# local imports

# module logger
log = logging.getLogger(__name__)

class GRAYDB:
    """Class for interacting with the gray database"""

    def __init__(self, url, credentials):
        """Init of graydb class

        url: string         - base url to database

        credentials: dict {
            login: str      - user name
            password: str   - password
        }
        """
        self.browser = mechanicalsoup.StatefulBrowser(
            raise_on_404=True,
            user_agent='inkotools-api/0.4',
        )
        self.baseurl = url
        self.credentials = credentials
        self._login()

    def __del__(self):
        self.browser.close()

    class CredentialsError(Exception):
        """Custom exception on wrong creds"""

        def __init__(self, msg="Wrong login or password!"):
            self.message = msg
            super().__init__(self.message)

    class NotFoundError(Exception):
        """Custom exception for not found errors"""

        def __init__(self, msg="Client not found"):
            self.message = msg
            super().__init__(self.message)

    def _login(self):
        """Login to graydb"""
        b = self.browser
        b.open(self.baseurl)
        # check that there is the auth form
        if len(b.page.select('form[name=auth]')) > 0:
            b.select_form('form[name=auth]')
            b['username'] = self.credentials['login'].encode('cp1251')
            b['password'] = self.credentials['password'].encode('cp1251')
            b.submit_selected()
            # check if auth form again - wrong login
            if len(b.page.select('form[name=auth]')) > 0:
                raise self.CredentialsError()
            else:
                log.debug('Logged in successfully')
        else:
            log.debug('Already logged in')

    def _check_auth(func):
        """Check auth decorator"""

        def wrapper(self, *args, **kwargs):
            self._login()
            return func(self, *args, **kwargs)
        return wrapper

    def get_client_ip_list(self, contract_id: str):
        """Get list of client ips from billing"""
        inet = self.get_billing_accounts(contract_id)['internet']
        if len(inet) == 0:
            raise self.NotFoundError('Internet account not found')
        return inet['ip_list']

    def get_billing_accounts(self, contract_id: str):
        """Get list of client services from billing"""
        raw = self.browser.post(f'{self.baseurl}/bil.php',
                                data={"nome_dogo": contract_id})
        account_types = [
            "internet",
            "telephony",
            "ld_telephony",
            "television",
        ]
        # init res dict with empty accounts
        res = dict.fromkeys(account_types, {})
        # iterate through all accounts (first row is table header)
        for idx, row in enumerate(raw.soup.select('table tr')[1:]):
            item = {}
            item['account_id'] = int(row.contents[0].string)
            item['services'] = list(map(gdb_decode, row.contents[1].strings))
            # remove `,` and `;` symbols and split without empty strings
            ip_tel = row.contents[3].string.translate(
                {ord(i): None for i in ';,'}).split()
            # internet
            if idx == 0:
                item['tariff'] = gdb_decode(row.contents[2].string)
                item['ip_list'] = ip_tel
            # telephony
            elif idx < 3:
                item['number_list'] = ip_tel
            item['balance'] = float(row.contents[5].string)
            item['credit'] = float(row.contents[7].string)
            status = gdb_decode(row.contents[8].string)
            item['enabled'] = True if status == 'Разблокирован' else False
            res[account_types[idx]] = item

        return res

    def get_billing_speed(self, ip: str):
        """Get billing speed for ip address"""
        ip = str(ip)
        raw = self.browser.get(f'http://62.182.48.36/speed/index.php?ip1={ip}')
        raw = raw.soup.get_text().strip()
        rgx = r'esteblished= ([.\d]+ Mbit/sec)'
        r = re.findall(rgx, raw)
        if len(r) == 0:
            res = "error"
            log.error(raw)
        else:
            res = r[0]
        return res

    def get_billing_summary(self, contract_id: str):
        """Get billing accounts with billing speed included"""
        res = self.get_billing_accounts(contract_id)
        if res['internet'] and len(res['internet']['ip_list']) > 0:
            # speed is the same for all ips, so, select first
            res['internet']['speed'] = self.get_billing_speed(
                res['internet']['ip_list'][0])
        return res

    def get_contract_by_ip(self, client_ip: str):
        """Find contract with client ip"""
        client_ip = str(client_ip)
        raw = self.browser.post(f'{self.baseurl}/poisk_test.php',
                                data={"ip": client_ip, "go99": 1})
        # graydb has fuzzy search, so iterate through several contracts
        # and check if contract's ip is the searched ip
        for row in raw.soup.select('tbody tr'):
            contract_id = row.find('td').string.strip()
            # workaround for billing random fails
            for i in range(3):
                try:
                    client_ips = self.get_client_ip_list(contract_id)
                    break
                except self.NotFoundError:
                    log.warning(f'Failed! Attempt: {i+1}/3')
            if client_ip in client_ips:
                return contract_id
        raise self.NotFoundError(f"Client with ip '{client_ip}' not found")

    def get_free_ip(self, vid: int, return_list: bool = False):
        """Find free ip in billing"""
        base = f'192.168.{vid}.'
        res = []
        # search in ip range splitting them by 10
        for i in range(1, 13):
            expr = base+str(i)
            raw = self.browser.post(f'{self.baseurl}/poisk_test.php',
                                    data={"ip": expr, "go99": 1})
            cnt = len(raw.soup.select('tbody tr'))
            # cnt < 11 --> there are free ips
            # cnt > 11 and i in [1,2] --> 1xx and 2xx ips workaround
            if cnt != 11:
                for j in ['']+list(range(0, 10)):
                    ip = expr+str(j)
                    host = int(ip.split('.')[3])
                    if host == 1:
                        continue
                    if host > 128:
                        break
                    try:
                        self.get_contract_by_ip(ip)
                    except self.NotFoundError:
                        log.debug(f'found: {ip}')
                        if return_list:
                            res.append(ip)
                        else:
                            return ip
        if len(res) > 0:
            return res
        raise self.NotFoundError(f'No free ip addresses found in vlan {vid}')

    def get_vlan_ip_count(self, vid: int):
        """Get ip count in vlan from billing"""
        expr = f'192.168.{vid}.'
        raw = self.browser.post(f'{self.baseurl}/poisk_test.php',
                                data={"ip": expr, "go99": 1})
        cnt = len(raw.soup.select('tbody tr'))
        return cnt

    def get_free_pip(self, subnet: str, return_list: bool = False):
        """Find free public ip in billing"""
        res = []
        subnet = IPNetwork(subnet)
        for ip in list(map(str, subnet.iter_hosts()))[1::]:
            try:
                self.get_contract_by_ip(ip)
            except self.NotFoundError:
                log.debug(f'found: {ip}')
                if return_list:
                    res.append(ip)
                else:
                    return ip
        if len(res) > 0:
            return res
        raise self.NotFoundError(f'No free ip addresses found for {subnet}')

    def get_internal_client_id(self, contract_id: str):
        """Get internal client id in gray database"""
        raw = self.browser.post(f'{self.baseurl}/poisk_test.php',
                                data={"dogovor": contract_id, "startt": 1})
        f = raw.soup.find('input', {'name': 'id_aabon'})
        if f is None:
            raise self.NotFoundError(f"Contract '{contract_id}' not found")
        res = int(f.get('value'))
        return res

    @_check_auth
    def get_client_data(self, contract_id: str):
        """Get client info from gray database"""
        client_id = self.get_internal_client_id(contract_id)
        raw = self.browser.get(f'{self.baseurl}/index.php',
                               params={"id_aabon": client_id})
        raw = raw.soup
        res = {'contract_id': contract_id, 'client_id': client_id}
        # matching dict between returning keys and form input names
        m_dict = {
            'name': 'fio',
            'company': 'organizatsiya',
            'house': 'dom',
            'room': 'kvartira',
            'office': 'ofis_tseh',
            'sw_ip': 'loyalnost',
            'port': 'port',
            'cable_length': 'dlina_cab',
        }
        # select form with client data (form with input `fio`)
        d = list(raw.find('input', {'name': 'fio'}).parents)[6]
        # iterate through the form inputs
        for key, val in m_dict.items():
            res[key] = d.find(attrs={'name': val}).get('value').strip()
        # get city and street from first (selected) option in select
        m_dict = {
            'city': 'gorod',
            'street': 'ulitsa',
            'entrance': 'podezd',
            'floor': 'etaj',
        }
        for key, val in m_dict.items():
            res[key] = d.find(
                'select', {'name': val}).option.get('value').strip()
        # generate contact list
        res['contact_list'] = []
        for i in range(1, 4):
            c = d.find(attrs={'name': f'cont{i}'}).get('value').strip()
            # skip empty strings and legacy values
            if c in ['', '0']:
                continue
            # ignore duplicates
            if c not in res['contact_list']:
                res['contact_list'].append(c)
        # comment string from textarea
        res['comment'] = d.find(attrs={'name': 'primechanie'}).string
        res['comment'] = res['comment'].strip(
        ) if res['comment'] is not None else ''
        # search for terminated mark
        res['terminated'] = bool(
            raw.find('font', {'color': 'red', 'size': '2px'}))
        # clear legacy values from old db
        for i in res:
            if res[i] == '0':
                res[i] = ''
        # search for tickets
        tickets = []
        keys = ['ticket_id', 'issue', 'date', 'master', 'creator', 'comments']
        for t in raw.find_all(attrs={'name': 'id_start_zay'}):
            # get columns as reverted previous siblings of current
            cols = t.parent.find_previous_siblings()[4::-1]
            # generate values from td tags without last (comments)
            values = [x.string.split(':', 1)[1].strip() for x in cols[:-1]]
            # split comments (last column)
            comments = parse_comments(cols[-1])
            # join ticket_id and other values
            values = [int(t['value']), *values, comments]
            ticket = dict(zip(keys, values))
            # convert date from string to date
            ticket['date'] = datetime.strptime(
                ticket['date'], '%d-%m-%y').replace(
                tzinfo=ZoneInfo('Europe/Moscow'))
            # raw comments for add_comment
            ticket['raw_comments'] = t.parent.find(
                attrs={"name": "id_start_stadya"})["value"]
            tickets.append(ticket)
        res['tickets'] = tickets
        return res

    @_check_auth
    def get_tickets(self):
        """Get list of user tickets"""
        tickets = []
        keys = ['ticket_id', 'contract_id', 'name', 'issue', 'address',
                'contacts', 'date', 'creator']
        raw = self.browser.get(f'{self.baseurl}/zayavki.php')
        raw = raw.soup
        for row in raw.tbody.find_all('tr'):
            values = list(map(lambda x: re.sub(
                r' +', ' ', gdb_decode(x.text).strip()), row.find_all('td')))
            # normal user tickets
            if len(values) == 11:
                values = values[0:8]
            # boss tickets
            elif len(values) == 13:
                values = values[1:9]
            # error
            else:
                self.log.error(f'Wrong ticket structure: {values}')
                continue
            ticket = dict(zip(keys, values))
            # strip '№ ' from ticket id and convert to int
            ticket['ticket_id'] = int(ticket['ticket_id'][2:])
            # convert contacts to list and remove duplicates
            ticket['contacts'] = list(set(ticket['contacts'].split()))
            # convert str to date
            ticket['date'] = datetime.strptime(
                ticket['date'], '%d-%m-%y').replace(
                tzinfo=ZoneInfo('Europe/Moscow'))
            # add comments
            ticket['comments'] = parse_comments(row)
            tickets.append(ticket)
        return tickets

    @_check_auth
    def add_comment(self, contract_id: str, ticket_id: int, comment: str):
        """Add comment to ticket"""
        ticket_found = False
        client = self.get_client_data(contract_id)
        # ticket validation and getting old comments
        # in graydb we need to add all the old comments with the new one
        for i in client["tickets"]:
            if i["ticket_id"] == ticket_id:
                ticket_found = True
                old_comments = i["raw_comments"]
                break
        if not ticket_found:
            raise self.NotFoundError('Ticket not found')
        comment = html.escape(comment)
        data = {"tekst_zay": comment.encode("cp1251"),
                "id_start_zay": ticket_id,
                "id_start_stadya": old_comments.encode("cp1251"),
                }
        user = self.credentials['login']
        log.debug(f'[{user}] contract {contract_id}, ticket {ticket_id}, '
                  f'comment: {comment}')
        res = self.browser.post(f'{self.baseurl}/index.php', data=data)
        if res.ok:
            log.info(f'[{user}] commented [{contract_id}]')
            return 'Comment added successfully'
        else:
            log.error(f'[{user}] failed to comment [{contract_id}]')
            return {'error': 'Failed to add comment'}

    @_check_auth
    def search_ticket(self, contract_id: str, keywords: list = [],
                      search_in_comments: bool = False,
                      master: str = ''):
        """Search ticket by keywords"""
        res = []
        if not isinstance(keywords, list):
            keywords = [keywords]
        client = self.get_client_data(contract_id)
        kw = r'(?i)' + r'|'.join(keywords)
        for ticket in client['tickets']:
            if ((master == '' or ticket['master'] == master)
                and (re.search(kw, ticket['issue'])
                     or (search_in_comments
                         and re.search(kw, ticket['raw_comments'])))):
                res.append(ticket)
        return res

    @_check_auth
    def change_client_data(self, contract_id: str, data: dict):
        """Change client data in gray database

        Possible data: sw_ip, port, cable_length

        Returns: bool"""
        m_dict = {
            'sw_ip': 'loyalnost',
            'port': 'port',
            'cable_length': 'dlina_cab',
        }
        client_id = self.get_internal_client_id(contract_id)
        # get old user data from gray database
        raw = self.browser.get(f'{self.baseurl}/index.php',
                               params={"id_aabon": client_id})
        raw = raw.soup
        form = list(raw.find('input', {'name': 'fio'}).parents)[6]
        fields = form.find_all(['input', 'select', 'textarea'])
        old_data = {}
        for item in fields:
            if (not 'name' in item.attrs
                    or item.attrs['name'] in ['zayavka', 'otvetstv']):
                # skip submit button and ticket inputs
                continue
            key = item.attrs['name']
            if item.name == 'input':
                val = item.attrs['value'].encode("cp1251")
            elif item.name == 'textarea':
                val = item.text.encode("cp1251")
            elif item.name == 'select':
                val = item.option.get('value').encode("cp1251")
            old_data[key] = val
        # update data with provided values
        new_data = old_data
        for k, v in data.items():
            if k in m_dict:
                new_data[m_dict[k]] = v
        # send data to gray database
        raw = self.browser.post(f'{self.baseurl}/index.php', data=new_data)
        res = list(raw.soup.stripped_strings)[-1] == 'Завершено успешно!'
        if res:
            log.debug(f'[{contract_id}] data changed: {data}')
        else:
            log.error(f'[{contract_id}] failed to change data: {data}')
        return res

    @_check_auth
    def terminate_contract(self, contract_id: str):
        """Terminate contract in gray database"""
        # check for services in billing
        for k, v in self.get_billing_accounts(contract_id).items():
            if 'services' in v.keys() and len(v['services']) > 0:
                log.warning(
                    f'Found services in {k} billing account, aborting.')
                return False
        # send termination request
        self.browser.post(f'{self.baseurl}/index.php',
                          data={"rastorg": contract_id})
        return True

########################################################################
# common functions

def parse_comments(tag):
    """Search for comments within soup tag"""
    comments = []
    for i, e in enumerate(tag.find_all(href='#')):
        comments.append({
            "time": datetime.strptime(
                e['title'], '%d.%m.%Y %H:%M:%S').replace(
                tzinfo=ZoneInfo('Europe/Moscow')),
            "author": gdb_decode(e.string),
            "comment": list(map(
                gdb_decode, e.parent.strings))[(i+1)*2].strip()[2:-1],
        })
    return comments

def gdb_decode(s):
    try:
        res = s.encode('ISO-8859-1').decode('cp1251')
    except Exception:
        res = s
    return res


'''
A viewer for cta events, currently only LSTs are supported

Usage:
    cta_event_viewer.py [options]

Options:
    --ip=<ip>       the ip to listen to [default: 127.0.0.1]
    --port=<port>   the port to listen to [default: 5000]
    --rows=<rows>   number of rows of telescope views [default: 2]
    --cols=<cols>   number of columns of telescope views [default: 2]
'''
from telescope import LST
from windows import TelescopeEventView
import Tkinter as tk
from itertools import cycle
import numpy as np
from read import ReadProtoBuf
from threading import Event

from collections import deque
from docopt import docopt

args = docopt(__doc__)

queue = deque(maxlen=100)
stop_event = Event()

def check_queue():
    if len(queue) > 0:
        root.event_generate('<<new_event>>')
    root.after(10, check_queue)

def handle_new_event(event):
    viewer = next(viewers)
    event = queue.popleft()
    print(len(queue))
    viewer.data = np.array(event.data)

root = tk.Tk()

reader = ReadProtoBuf(
    args['--ip'],
    args['--port'],
    queue,
    stop_event,
)

viewers = []
for i in range(int(args['--rows'])):
    root.rowconfigure(i, weight=1)
    for j in range(int(args['--cols'])):
        root.columnconfigure(j, weight=1)
        viewer = TelescopeEventView(root, LST, cmap='hot')
        viewer.grid(row=i, column=j, sticky='nswe')
        viewers.append(viewer)

viewers = cycle(viewers)
root.bind('<<new_event>>', handle_new_event)

if __name__ == '__main__':
    try:
        reader.start()
        root.after(1, check_queue)
        root.mainloop()
    except (SystemExit, KeyboardInterrupt):
        pass
    stop_event.set()


import re
line = 0
other = 0
p = re.compile(r'\bPortland IN\b')
with open("johnJayCenter.txt") as inFile:
    inFile.readline()       # Skip Header Line
    for aLine in inFile:
        if not p.search(aLine):
            other += 1
        else:
            line += 1
print("Number Portland INs in file: %4d." % line)
print("Number other cities in file: %4d." % other)


from urllib.request import urlretrieve
from datetime import datetime
import pandas as pd
import numpy as np
import mplfinance as mpl
import time
from statsmodels.tsa.seasonal import seasonal_decompose

# Download daily historical data for DAX
ticker = '^dax'
interval = 'd'
url = f'https://stooq.com/q/d/l/?s={ticker}&i={interval}'
csv_file = ticker + '.csv'

urlretrieve(url, csv_file)

# Read the data
data = pd.read_csv(csv_file, index_col='Date', parse_dates=['Date'], date_parser=lambda x: datetime.strptime(x, '%Y-%m-%d'))

data.dropna()

data = data.tail(100)

#mpl.plot(data.tail(100), type='candle', volume=False)

# Descriptive statistics
print(data.describe())

data['OC'] = data['Close']-data['Open']

print(data.describe())

data['OCa'] = data['OC'] - data['OC'].min() + 1
analysis = data[['OCa']].copy()

decompose_result_mult = seasonal_decompose(analysis, model="multiplicative", period=9)

trend = decompose_result_mult.trend
seasonal = decompose_result_mult.seasonal
residual = decompose_result_mult.resid

fig = decompose_result_mult.plot()
fig.savefig('result.png', dpi=300)


import pandas as pd
import numpy as np
import robin_stocks.robinhood as robin
import yfinance as yf
import datetime as dt
import indicators
import credentials
import pyotp
import pickle
from csv import writer

companies=list(pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')[0]['Symbol'])

def trade():
  totp = pyotp.TOTP(credentials.mfa).now()
  robin.login(credentials.username, credentials.password, mfa_code = totp)
  owned = robin.build_holdings()

  start_date = '20' + (dt.datetime.now() - dt.timedelta(days = 60)).strftime(('%y-%m-%d'))
  # print(start_date)
  stoch_owned = []
  with open('stoch.pkl', 'rb') as f:
    stoch_owned = pickle.load(f)

  with open('trades.csv', 'a') as f:
    writ = writer(f)

    # bought = 0
    for i, comp in enumerate(companies):
      # print(comp)
      df = yf.download(tickers= comp, start= start_date, interval="1h", threads=False).reset_index()
      df = df[['Adj Close','High','Low','Volume']]

      indicators.RSI_Adj(df,14)
      indicators.MACD(df,12,26,9)
      indicators.Stochastic(df,14,3)
      # print(len(df))
      # print(i) if i%25 == 0 else _

      stoch_buy = False
      for index, row in df[::-1].iterrows():
        if row['%K'] < 20 and row['%D'] < 20:
          stoch_buy = True
          break
        elif row['%K'] > 80 or row['%D'] > 80:
          stoch_buy = False
          break

      last_row = df.iloc[-1]
      #print(last_row['RSI Adj'])
      if stoch_buy and last_row['RSI Adj'] > 50 and last_row['MACD'] > last_row['Signal'] and (comp not in stoch_owned):
        #buy
        cost = last_row['Adj Close']
        writ.writerow([dt.datetime.now() - dt.timedelta(hours=4), comp, 'BUY', last_row['RSI Adj'], 0, cost,'Stoch'])
        stoch_owned.append(comp)
        # print(last_row['Adj Close'])
        # print(bought)

      if last_row['RSI Adj'] < 25 and last_row['RSI Adj'] > 0.0 and (comp not in owned):

        print('Buy',comp,last_row['Adj Close'])
        bought = robin.orders.order_buy_market(comp, round(50/last_row['Adj Close'],4), timeInForce='gfd')
        print(bought,'bought',type(bought))
        if bought != None and bought != {}:
          writ.writerow([dt.datetime.now() - dt.timedelta(hours=4), comp, 'BUY',bought['quantity'], float(bought['quantity']) * last_row['Adj Close'],last_row['Adj Close'],'RSI'])

      stoch_sell = False
      for index,row in df[::-1].iterrows():
        if row['%K'] > 80 and row['%D'] > 80:
          stoch_sell = True
          break
        elif row['%K'] < 20 or row['%D'] < 20:
          stoch_sell = False
          break

      if comp in owned:
        print('In Owned', comp, last_row['RSI Adj'])

      if (comp in stoch_owned and stoch_sell and last_row['RSI Adj'] < 50 and last_row['MACD'] < last_row['Signal']):
        #sell
        cost = last_row['Adj Close']
        writ.writerow([dt.datetime.now() - dt.timedelta(hours=4), comp, 'SELL', last_row['RSI Adj'], 0, cost,'Stoch'])
        stoch_owned.remove(comp)

      if last_row['RSI Adj'] > 75 and (comp in owned):

        print('Sell',comp,last_row['Adj Close'])
        sold = robin.orders.order_sell_fractional_by_quantity(comp, owned[comp]['quantity'])
        print(sold, 'sold',type(sold))
        if sold != None and sold != {}:
          writ.writerow([dt.datetime.now() - dt.timedelta(hours=4), comp, 'SELL',sold['quantity'], float(sold['quantity']) * last_row['Adj Close'],last_row['Adj Close'],'RSI'])

    robin.logout()

  with open('stoch.pkl', 'wb') as f:
    pickle.dump(stoch_owned,f)

  f.close()


import pygame
from random import randint

class Card(pygame.sprite.Sprite):
    """Class Card

    Attributes:
        image (pygame.Surface): The image/sprite for your card
        ih (int): How tall your card is (in px)
        iw (int): How wide your card is (in px)

        hm (int):
        mm (int):
        lm (int):
        am (int):

        name (str):
        desc (str):

    """
    def __init__(self, image, ih, iw):
        super(Card, self).__init__()

        self.image = pygame.image.load(image)
        self.image = pygame.transform.scale(self.image, (iw, ih))
        self.rect = self.image.get_rect()

        # Sets stats to random values between -5 and 5
        self.hm = randint(-5, 5)
        self.mm = randint(-5, 5)
        self.lm = randint(-5, 5)
        self.am = randint(-5, 5)
        self.__randomizer()

        # Initializes the title of the card and its description
        self.name = " the "
        self.desc = ""
        self.__name_desc()

    # Corrects any invalid stat totals (all variables must add to 0)
    def __randomizer(self):
        total = self.hm + self.mm + self.lm + self.am
        args = [self.hm, self.mm, self.lm, self.am]
        while total != 0:
            random = randint(0, 3)
            if 5 > args[random] > -5:
                if total > 0:
                    if random == 0:
                        self.hm -= 1
                    if random == 1:
                        self.mm -= 1
                    if random == 2:
                        self.lm -= 1
                    if random == 3:
                        self.am -= 1
                    args[random] -= 1
                    total -= 1
                if total < 0:
                    if random == 0:
                        self.hm += 1
                    if random == 1:
                        self.mm += 1
                    if random == 2:
                        self.lm += 1
                    if random == 3:
                        self.am += 1
                    args[random] += 1
                    total += 1

    # Sets the title of the card and its description
    def __name_desc(self):
        args = [self.hm, self.mm, self.lm, self.am]
        args.sort(reverse=True)
        if args[0] == 0:
            self.name += "Balanced"
            self.desc += "All stats equal"
        elif args[0] > 3:
            if args[0] == self.hm:
                self.name += "Steadfast"
                self.desc += "Substantial increase in health"
            if args[0] == self.mm:
                self.name += "Swift"
                self.desc += "Substantial increase in speed"
            if args[0] == self.lm:
                self.name += "Favored"
                self.desc += "Substantial increase in luck"
            if args[0] == self.am:
                self.name += "Merciless"
                self.desc += "Substantial increase in attack"
        else:
            if args[0] == self.hm:
                if args[1] == self.mm:
                    self.name += "Resilient"
                    self.desc += "Moderate increase in health and speed"
                if args[1] == self.lm:
                    self.name += "Gifted"
                    self.desc += "Moderate increase in health and luck"
                if args[1] == self.am:
                    self.name += "Barbarous"
                    self.desc += "Moderate increase in health and attack"
            if args[0] == self.mm:
                if args[1] == self.hm:
                    self.name += "Resilient"
                    self.desc += "Moderate increase in health and speed"
                if args[1] == self.lm:
                    self.name += "Nimble"
                    self.desc += "Moderate increase in speed and luck"
                if args[1] == self.am:
                    self.name += "Rutheless"
                    self.desc += "Moderate increase in speed and attack"
            if args[0] == self.lm:
                if args[1] == self.hm:
                    self.name += "Gifted"
                    self.desc += "Moderate increase in health and luck"
                if args[1] == self.mm:
                    self.name += "Nimble"
                    self.desc += "Moderate increase in speed and luck"
                if args[1] == self.am:
                    self.name += "Calculated"
                    self.desc += "Moderate increase in luck and attack"
            if args[0] == self.am:
                if args[1] == self.hm:
                    self.name += "Barbarous"
                    self.desc += "Moderate increase in health and attack"
                if args[1] == self.mm:
                    self.name += "Ruthless"
                    self.desc += "Moderate increase in speed and attack"
                if args[1] == self.lm:
                    self.name += "Calculated"
                    self.desc += "Moderate increase in luck and attack"

    def get_stats(self) -> tuple:
        """Wrap up the stats of the card and return
        Used by `Entity` class in *entity.entity.py*

        Returns:
            tuple: the packaged stats of the card

        """
        return tuple([self.hm, self.mm, self.lm, self.am])


import argparse
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython import embed

from sklearn_lvq import GrlvqModel, GmlvqModel
from sklearn_lvq.utils import plot2d
from sklearn.preprocessing import LabelEncoder
from tqdm.auto import tqdm

from transformers import RobertaTokenizer, RobertaModel
import torch

tokenizer = RobertaTokenizer.from_pretrained("roberta-base")

model = RobertaModel.from_pretrained("roberta-base")

def get_embeddings(text: str, tokenizer: RobertaTokenizer, model: RobertaModel) -> np.array:
    inputs = tokenizer(text, return_tensors="pt")
    outputs = model(**inputs)
    pooled_output = outputs.pooler_output.detach().cpu().numpy()
    return pooled_output

def create_dataset(csv_file: str, cap=100):
    df = pd.read_csv(csv_file)
    # df = df[df['updated_label'].isin(['ad populum', 'ad hominem'])]
    texts = df['masked_articles'].tolist()
    labels = df['updated_label'].tolist()

    embeddings = []
    for text in tqdm(texts, leave=False):
        embeddings.append(get_embeddings(text, tokenizer, model))
    return np.array(embeddings), np.array(labels)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--input_file", type=str, default="edu_train.csv")

    args = parser.parse_args()

    embeddings, labels = create_dataset(args.input_file)
    embeddings = embeddings.squeeze(axis=1)

    label_encoder = LabelEncoder()
    label_encoder.fit(labels)

    labels_encoded = label_encoder.transform(labels).reshape(-1, 1)

    print('GMLVQ:')

    gmlvq = GmlvqModel()
    gmlvq.fit(embeddings, labels)
    plot2d(gmlvq, embeddings, labels, 1, 'gmlvq')

    print('classification accuracy:', gmlvq.score(embeddings, labels))
    plt.show()

    embed()
    exit()


"""Provide Python wrappers for UNO widgets.

Copyright (c) 2023 Peter Triesberger
For further information see https://github.com/peter88213/yw-cnv
Published under the MIT License (https://opensource.org/licenses/mit-license.php)
"""
import uno
from com.sun.star.awt.MessageBoxType import MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX
from com.sun.star.awt.MessageBoxButtons import BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_YES_NO, BUTTONS_YES_NO_CANCEL, BUTTONS_RETRY_CANCEL, BUTTONS_ABORT_IGNORE_RETRY

CTX = uno.getComponentContext()
SM = CTX.getServiceManager()

def create_instance(name, with_context=False):
    if with_context:
        instance = SM.createInstanceWithContext(name, CTX)
    else:
        instance = SM.createInstance(name)
    return instance

def msgbox(message, title='yWriter import/export', buttons=BUTTONS_OK, type_msg=INFOBOX):
    """ Create message box
        type_msg: MESSAGEBOX, INFOBOX, WARNINGBOX, ERRORBOX, QUERYBOX

        MSG_BUTTONS: BUTTONS_OK, BUTTONS_OK_CANCEL, BUTTONS_YES_NO,
        BUTTONS_YES_NO_CANCEL, BUTTONS_RETRY_CANCEL, BUTTONS_ABORT_IGNORE_RETRY

        MSG_RESULTS: OK, YES, NO, CANCEL

        http://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1XMessageBoxFactory.html
    """
    toolkit = create_instance('com.sun.star.awt.Toolkit')
    parent = toolkit.getDesktopWindow()
    mb = toolkit.createMessageBox(parent, type_msg, buttons, title, str(message))
    return mb.execute()

class Stub():

    def dummy(self):
        pass

def FilePicker(path=None, mode=0):
    """
    Read file:  `mode in (0, 6, 7, 8, 9)`
    Write file: `mode in (1, 2, 3, 4, 5, 10)`
    see: (http://api.libreoffice.org/docs/idl/ref/
            namespacecom_1_1sun_1_1star_1_1ui_1_1
            dialogs_1_1TemplateDescription.html)

    See: https://stackoverflow.com/questions/30840736/libreoffice-how-to-create-a-file-dialog-via-python-macro
    """
    # shortcut:
    createUnoService = (
        XSCRIPTCONTEXT
        .getComponentContext()
        .getServiceManager()
        .createInstance
    )
    filepicker = createUnoService("com.sun.star.ui.dialogs.OfficeFilePicker")
    if path:
        filepicker.setDisplayDirectory(path)
    filepicker.initialize((mode,))
    filepicker.appendFilter("yWriter 7 Files (.yw7)", "*.yw7")
    if filepicker.execute():
        return filepicker.getFiles()[0]

if __name__ == '__main__':
    XSCRIPTCONTEXT = Stub()


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from linearRegression.linearRegression import LinearRegression
import torch
torch.manual_seed(10)

x = np.array([i*np.pi/180 for i in range(60,300,4)]).reshape(-1,1)
np.random.seed(10)  #Setting seed for reproducibility
y = 4*x**2 + 7 + np.random.normal(0,3,len(x)).reshape(-1,1)

'''
lr = LinearRegression()
lr.fit_normal(x.reshape(1,-1),y)
y_hat = lr.predict(x.reshape(-1,1))
plt.plot(x, y, 'ro')
plt.plot(x, y_hat, 'b')

plt.show()
'''

lr = 0.01

coef = np.random.uniform(0, 5, size=[x.shape[1]+1,1])
loss_prev = np.inf

x = np.concatenate((np.ones((x.shape[0],1)),x),axis=1)
for i in range(10):
  plt.figure(figsize=(10,10))
  plt.plot(x[:, 1:], y, 'ro')
  y_pred = np.dot(x,coef)
  D = (2/x.shape[0]) * np.sum(np.dot(x.T, y_pred - y))
  loss = np.sum(np.square(y_pred - y))
  if np.abs(loss_prev - loss) < 0.0001:
    print(i, 'Converged')
    break
  if loss_prev == np.inf:
    loss_prev = loss
  coef = coef - lr * D
  loss_prev = loss
  plt.plot(x[:, 1:], y_pred, label = 'Loss: ' + str(np.sqrt(loss)))
  plt.legend()
  plt.title('Iteration: ' + str(i))
  plt.savefig('lr_'+str(i)+'.png')


import logging

from django.contrib.gis.db.models import PointField
from django.core.exceptions import ValidationError
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField

from ..states import states
from ..utils import full_url
from .geo import ZipCode
from .user import User

logger = logging.getLogger(__name__)

class Awg(models.Model):
    class AwgType(models.TextChoices):
        SHELTER_ONLY = "SHELTER_ONLY", "Shelter only"
        FOSTER_ONLY = "FOSTER_ONLY", "Foster only"
        SHELTER_AND_FOSTER = "SHELTER_AND_FOSTER", "Both Shelter and Foster"

    class AwgStatus(models.TextChoices):
        APPLIED = "APPLIED", "Applied"
        PUBLISHED = "PUBLISHED", "Published"
        UNPUBLISHED = "UNPUBLISHED", "Unpublished"

    id = models.AutoField(primary_key=True, verbose_name="CAIM ID")
    name = models.CharField(max_length=100, verbose_name="Organization name")
    petfinder_id = models.CharField(max_length=32, blank=True, null=True, default=None)
    status = models.CharField(
        max_length=16,
        choices=AwgStatus.choices,
        default=AwgStatus.APPLIED,
        verbose_name="Listing status",
    )
    description = models.TextField(blank=True)
    awg_type = models.CharField(
        max_length=32,
        choices=AwgType.choices,
        default=None,
        blank=True,
        null=True,
        verbose_name="Organization type",
    )

    has_501c3_tax_exemption = models.BooleanField(
        default=False, verbose_name="501c3 tax exempt charity"
    )
    company_ein = models.CharField(
        max_length=16,
        blank=True,
        null=True,
        default=None,
        verbose_name="Employer Identification Number (EIN)",
    )

    workwith_dogs = models.BooleanField(default=False, verbose_name="Works with dogs?")
    workwith_cats = models.BooleanField(default=False, verbose_name="Works with cats?")
    workwith_other = models.BooleanField(
        default=False, verbose_name="Works with other animals?"
    )

    geo_location = PointField()
    zip_code = models.CharField(max_length=16, blank=True, null=True, default=None)
    city = models.CharField(max_length=32, blank=True, null=True, default=None)
    state = models.CharField(max_length=2, choices=states.items())
    is_exact_location_shown = models.BooleanField(
        default=False, verbose_name="Show exact location?"
    )
    email = models.EmailField(max_length=255, blank=True, null=True, default=None)
    phone = PhoneNumberField(blank=True, null=True, default=None)
    website_url = models.URLField(
        max_length=255, blank=True, null=True, default=None, verbose_name="Website URL"
    )
    facebook_url = models.URLField(
        max_length=255, blank=True, null=True, default=None, verbose_name="Facebook URL"
    )
    instagram_url = models.URLField(
        max_length=255,
        blank=True,
        null=True,
        default=None,
        verbose_name="Instagram URL",
    )
    twitter_url = models.URLField(
        max_length=255, blank=True, null=True, default=None, verbose_name="Twitter URL"
    )
    tiktok_url = models.URLField(
        max_length=255, blank=True, null=True, default=None, verbose_name="TikTok URL"
    )

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        try:
            zip_info = ZipCode.objects.get(zip_code=self.zip_code)
            self.geo_location = zip_info.geo_location
        except Exception:
            logger.warn("ZIP code not valid")
        super(Awg, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return full_url(f"/organization/{self.id}")

    def user_is_member_of_awg(self, user):
        if not user.id:
            return False
        member = self.awgmember_set.filter(user=user).first()
        return bool(member)

    def get_permissions_for_user(self, user):
        ret = []
        if not user.id:
            return ret
        # If CAIM staff, all permissions
        if user.is_staff:
            ret.append("EDIT_PROFILE")
            ret.append("MANAGE_ANIMALS")
            ret.append("MANAGE_MEMBERS")
            ret.append("MANAGE_APPLICATIONS")
        # Look for AWGMember for this user and AWG
        member = self.awgmember_set.filter(user=user).first()
        if member:
            if member.canEditProfile:
                ret.append("EDIT_PROFILE")
            if member.canManageAnimals:
                ret.append("MANAGE_ANIMALS")
            if member.canManageMembers:
                ret.append("MANAGE_MEMBERS")
            if member.canManageApplications:
                ret.append("MANAGE_APPLICATIONS")
            if member.canViewApplications:
                ret.append("VIEW_APPLICATIONS")
        return ret

    def is_currently_published(self):
        return self.status == Awg.AwgStatus.PUBLISHED

    def clean(self):
        # Validate zip_code
        if self.zip_code:
            zip_info = ZipCode.objects.filter(zip_code=self.zip_code).first()
            if not zip_info:
                raise ValidationError({"zip_code": "Invalid US zip code"})

class AwgMember(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    awg = models.ForeignKey(Awg, on_delete=models.CASCADE)
    canEditProfile = models.BooleanField(default=False)
    canManageAnimals = models.BooleanField(default=False)
    canManageMembers = models.BooleanField(default=False)
    canManageApplications = models.BooleanField(default=False)
    canViewApplications = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (
            "user",
            "awg",
        )

    def __str__(self) -> str:
        return f"{self.user} - {self.awg}"

    def to_dict(self):
        return {
            "id": self.id,
            "user_id": self.user_id,
            "user_email": self.user.email,
            "user_username": self.user.username,
            "awg_id": self.awg_id,
            "canEditProfile": self.canEditProfile,
            "canManageAnimals": self.canManageAnimals,
            "canManageMembers": self.canManageMembers,
            "canManageApplications": self.canManageApplications,
            "canViewApplications": self.canViewApplications,
        }


import numpy as np

x = np.array([[1,2], [3,4]])
y = np.array([[1,0]])

def sigmoid(z):
    s =   1/ (1 + np.exp(-1 * z))
    return s

def initialize_parameters():
    w = np.zeros((2, 1))
    b = np.zeros((1,2))
    return w,b

def y_hat(w, x, b):

    return sigmoid(np.dot(w.T, x) + b)

def calculate_cost(y, y_hat):
    cost = -1/2 * np.sum((np.multiply(y, np.log(y_hat)) + np.multiply((1-y), np.log(1-y_hat))))
    return cost

def backpropgate(x, y, y_hat):

    dw = np.dot(x, (y_hat - y).T)
    db = np.sum(y_hat -y)
    dw /= 2
    db /=2
    grads = dw, db
    return grads

def train():
    # step 1: initialize parameters to zero
    w, b = initialize_parameters()

    epochs = 10000
    learning_rate = 0.01
    for i in range(epochs):
        # in each epoch do the following:
        # 1- propogate forward to calculate y_hat and cost function
        # 2- propogate backward to compute  gradients : dw and db
        # 3- update the weights with graident descent
        y_pred = y_hat(w,x,b)
        cost = calculate_cost(y, y_pred)
        print(cost)
        dw, db = backpropgate(x, y, y_pred)
        w -= learning_rate * dw
        b -= learning_rate * db
    print(w)
    print(b)

train()


#!/usr/bin/env python

from scipy.sparse import coo_matrix

class Matrix:
    """A sparse matrix class (indexed from zero). Replace with NumPy arrays."""
    def __init__(self, matrix=None, size=None):
        """Size is a tuple (m,n) representing m rows and n columns."""
        if matrix is None:
            self.data = {}
            if size is None:
                self.column_count = 0
                self.row_count = 0
                self.size = (self.row_count, self.column_count)
            else:
                self.row_count, self.column_count = size[:2]
                self.size = size
        else:
            """Initialize to be a clone of the given matrix."""
            self.column_count = matrix.column_count
            self.data = matrix.data
            self.row_count = matrix.row_count
            self.size = matrix.size

    def get(self, subscript):
        """Return the matrix element indexed by the given (valid) subscript."""
        row, column = subscript[:2]
        if self.__is_valid(subscript):
            # Get the value if it's present, else a zero.
            result = self.data.get(subscript, 0)
        else:
            raise IndexError
        return result

    def set(self, subscript, value):
        """Set the matrix element indexed by the given (valid) subscript."""
        if value != 0 and self.__is_valid(subscript):
            self.data[subscript] = value
        else:
            raise IndexError

    def __is_valid(self, subscript):
        """Return whether the given subscript is within the matrix's bounds."""
        return ((0,0) <= subscript and subscript < self.size)

    def __is_valid_row(self, row_number):
        """Return whether the given row is within the matrix's bounds."""
        return self.__is_valid((row_number, 0))

    def __str__(self):
        """Return a NumPy-like matrix representation."""
        result = ""
        for row in range(self.size):
            current = []
            for column in range(self.size):
                subscript = (row, column)
                current.append(self.get(subscript))
            if result == "":
                result = "[{}".format(current)
            else:
                result = "{0}\n {1}".format(result, current)
        return "{}]".format(result)

    def extend_columns(self, matrix):
        raise NotImplementedError

    def extend_rows(self, matrix):
        """Extend the current matrix with the given matrix."""
        row_count, column_count = matrix.size[:2]
        if column_count != self.column_count:
            raise ValueError
        self.row_count += row_count
        self.size = (self.row_count, self.column_count)
        base_row_count = self.row_count
        for key, value in matrix.data.items():
            row, column = key[:2]
            self.set((base_row_count + row, column), value)
        return self

    def replace_row(self, row_number, vector):
        """Replace the specified row with the given vector."""
        if not self.__is_valid_row(row_number):
            raise ValueError
        row_count, column_count = vector.size[:2]
        if row_count != 1 and column_count != 1:
            raise ValueError
        # Eliminate current row entries.
        for col in [col for (row, col) in self.data.items()
                    if row == row_number]:
            self.data.pop(row_number, col)
        # Update row with vector elements.
        if row_count == 1:
            new_row = vector.transpose()
        else:
            new_row = vector
        for key, value in new_row.data.items():
            row, _ = key[:2]
            self.set((row_number, row), value)
        return self

    def submatrix(self, row_set, column_set):
        """Return a submatrix with the given rows and columns."""
        submatrix = Matrix(len(row_set), len(column_set))
        raise NotImplementedError

    def to_vec(self):
        """Return an m*n length vector comprising all the matrix's columns."""
        column_count = self.column_count
        vector = Matrix(size=(self.row_count * column_count, 1))
        for key, value in self.data.items():
            row, column = key[:2]
            subscript = (column * column_count + row, 0)
            vector.set(subscript, value)
        return vector

    def to_ijv(self):
        """Return the matrix in ijv (triplet) array format."""
        row_indices = []
        column_indices = []
        nonzero_elements = []
        k = 0
        for key, value in self.data.items():
            if value == 0:
                continue
            row, col = key[:2]
            row_indices.append(row)
            column_indices.append(col)
            nonzero_elements.append(value)
            k += 1
        return row_indices, column_indices, nonzero_elements

    def to_coo_matrix(self):
        """Return the matrix in COOrdinate format."""
        row_indices, column_indices, nonzero_elements = self.to_ijv()
        return coo_matrix((nonzero_elements, (row_indices, column_indices)),
                          shape=(self.size, self.size))

    def transpose(self):
        """Transpose the matrix."""
        m, n = self.size[:2]
        transposed_size = (n, m)
        transposed_matrix = {}
        for key, value in matrix.data.items():
            i, j = key[:2]
            transposed_key = (j, i)
            transposed_matrix[transposed_key] = value
        self.matrix = transposed_matrix
        self.size = transposed_size