# cnn .py

import numpy as np

from nndl.layers import *
from nndl.conv_layers import *
from utils.fast_layers import *
from nndl.layer_utils import *
from nndl.conv_layer_utils import *

import pdb

class ThreeLayerConvNet(object):
  """
  A three-layer convolutional network with the following architecture:

  conv - relu - 2x2 max pool - affine - relu - affine - softmax

  The network operates on minibatches of data that have shape (N, C, H, W)
  consisting of N images, each with height H and width W and with C input
  channels.
  """

  def __init__(self, input_dim=(3, 32, 32), num_filters=32, filter_size=7,
               hidden_dim=100, num_classes=10, weight_scale=1e-3, reg=0.0,
               dtype=np.float32, use_batchnorm=False):
    """
    Initialize a new network.

    Inputs:
    - input_dim: Tuple (C, H, W) giving size of input data
    - num_filters: Number of filters to use in the convolutional layer
    - filter_size: Size of filters to use in the convolutional layer
    - hidden_dim: Number of units to use in the fully-connected hidden layer
    - num_classes: Number of scores to produce from the final affine layer.
    - weight_scale: Scalar giving standard deviation for random initialization
      of weights.
    - reg: Scalar giving L2 regularization strength
    - dtype: numpy datatype to use for computation.
    """
    self.use_batchnorm = use_batchnorm
    self.params = {}
    self.reg = reg
    self.dtype = dtype

    # ================================================================ #
    # YOUR CODE HERE:
    #   Initialize the weights and biases of a three layer CNN. To initialize:
    #     - the biases should be initialized to zeros.
    #     - the weights should be initialized to a matrix with entries
    #         drawn from a Gaussian distribution with zero mean and
    #         standard deviation given by weight_scale.
    # ================================================================ #

    C, H, W = input_dim

    # cnn params
    pad = (filter_size - 1) / 2
    conv_stride = 1
    pool_size = 2
    pool_stride = 2
    # output sizes after convolution and pooling
    H_out_conv, W_out_conv = int(1 + (H - filter_size + 2*pad) / conv_stride), int(1 + (W - filter_size + 2*pad) / conv_stride)
    H_out_pool, W_out_pool = int(1 + (H_out_conv - pool_size) / pool_stride), int(1 + (H_out_conv - pool_size) / pool_stride)

    # W1 = conv weights
    self.params['W1'] = weight_scale * np.random.randn(num_filters, C, filter_size, filter_size)
    self.params['b1'] = np.zeros(num_filters)

    max_pool_output_size = int(num_filters * H_out_pool * W_out_pool)
    self.params['W2'] = weight_scale * np.random.randn(max_pool_output_size, hidden_dim)
    self.params['b2'] = np.zeros(hidden_dim)
    self.params['W3'] = weight_scale * np.random.randn(hidden_dim, num_classes)
    self.params['b3'] = np.zeros(num_classes)

    # ================================================================ #
    # END YOUR CODE HERE
    # ================================================================ #

    for k, v in self.params.items():
      self.params[k] = v.astype(dtype)

  def loss(self, X, y=None):
    """
    Evaluate loss and gradient for the three-layer convolutional network.

    Input / output: Same API as TwoLayerNet in fc_net.py.
    """
    W1, b1 = self.params['W1'], self.params['b1']
    W2, b2 = self.params['W2'], self.params['b2']
    W3, b3 = self.params['W3'], self.params['b3']

    # pass conv_param to the forward pass for the convolutional layer
    filter_size = W1.shape[2]
    conv_param = {'stride': 1, 'pad': (filter_size - 1) / 2}

    # pass pool_param to the forward pass for the max-pooling layer
    pool_param = {'pool_height': 2, 'pool_width': 2, 'stride': 2}

    scores = None

    # ================================================================ #
    # YOUR CODE HERE:
    #   Implement the forward pass of the three layer CNN.  Store the output
    #   scores as the variable "scores".
    # ================================================================ #

    # conv - relu - 2x2 max pool - affine - relu - affine - softmax

    # get scores for first layer (conv + relu + pool)
    h1, cache1 = conv_relu_pool_forward(x=X, w=W1, b=b1, conv_param=conv_param, pool_param=pool_param)
    # get scores for second layer (fc)
    h2, cache2 = affine_relu_forward(x=h1, w=W2, b=b2) # get scores for output layer (fc)
    scores, cache3 = affine_forward(x=h2, w=W3, b=b3)

    # ================================================================ #
    # END YOUR CODE HERE
    # ================================================================ #

    if y is None:
      return scores

    loss, grads = 0, {}
    # ================================================================ #
    # YOUR CODE HERE:
    #   Implement the backward pass of the three layer CNN.  Store the grads
    #   in the grads dictionary, exactly as before (i.e., the gradient of
    #   self.params[k] will be grads[k]).  Store the loss as "loss", and
    #   don't forget to add regularization on ALL weight matrices.
    # ================================================================ #

    loss, dl = softmax_loss(x=scores, y=y)
    loss += 0.5*self.reg*np.sum(W1**2) + 0.5*self.reg*np.sum(W2**2) + 0.5*self.reg*np.sum(W3**2)
    dout, dW3, db3 = affine_backward(dl, cache3) # now backprop, starting from the last affine layer
    dW3 += self.reg * W3
    dout, dW2, db2 = affine_relu_backward(dout, cache2)
    dW2 += self.reg * W2
    dx, dW1, db1 = conv_relu_pool_backward(dout, cache1)
    dW1 += self.reg * W1
    # now store all the gradients in the gradient dictionary
    grads["W1"] = dW1
    grads["W2"] = dW2
    grads["W3"] = dW3
    grads["b1"] = db1
    grads["b2"] = db2
    grads["b3"] = db3

    # ================================================================ #
    # END YOUR CODE HERE
    # ================================================================ #

    return loss, grads

class BestCNN(object):
  def __init__(self, input_dim=(3, 32, 32), num_filters=32, filter_size=7, hidden_dim=100, num_classes=10, weight_scale=1e-3, reg=0.0, dtype=np.float32, use_batchnorm=False):
    """
    Initialize a new network.
    Inputs:
    - input_dim: Tuple (C, H, W) giving size of input data
    - num_filters: Number of filters to use in the convolutional layer
    - filter_size: Size of filters to use in the convolutional layer
    - hidden_dim: Number of units to use in the fully-connected hidden layer
    - num_classes: Number of scores to produce from the final affine layer.
    - weight_scale: Scalar giving standard deviation for random initialization
    of weights.
    - reg: Scalar giving L2 regularization strength
    - dtype: numpy datatype to use for computation. """
    self.use_batchnorm = use_batchnorm
    self.params = {}
    self.reg = reg
    self.dtype = dtype
    # ================================================================ # # YOUR CODE HERE:
    # # # # # #

    # plan
    # {conv relu conv relu pool} x 2 -> affine -> relu -> affine -> output
    # bn plan
    # {conv bn relu conv bn relu pool} x 2 -> affine -> bn -> relu -> affine -> output, thus need 5 bns

    C, H, W = input_dim
    # hyperparams to use
    pad = (filter_size - 1) / 2
    conv_stride = 1
    pool_size = 2
    pool_stride = 2

    # init
    self.params["W1"] = np.random.normal(loc=0, scale=weight_scale, size=(num_filters, C, filter_size, filter_size))
    self.params["b1"] = np.zeros(num_filters)
    self.params["W2"] = np.random.normal(loc=0, scale=weight_scale, size=(num_filters, num_filters, filter_size, filter_size))
    self.params["b2"] = np.zeros(num_filters)
    self.params["W3"] = np.random.normal(loc=0, scale=weight_scale, size=(num_filters, num_filters, filter_size, filter_size))
    self.params["b3"] = np.zeros(num_filters)
    self.params["W4"] = np.random.normal(loc=0, scale=weight_scale, size=(num_filters, num_filters, filter_size, filter_size))
    self.params["b4"] = np.zeros(num_filters)
    self.params["W5"] = np.random.normal(loc=0, scale=weight_scale, size=(num_filters*8*8, hidden_dim)) ## 8 because this is the H_out_pool
    self.params["b5"] = np.zeros(hidden_dim)

    # output layer is different
    self.params["W6"] = np.random.normal(loc=0, scale=weight_scale, size=(hidden_dim, num_classes))
    self.params["b6"] = np.zeros(num_classes)

    if self.use_batchnorm:
      for i in range(1,5):
        self.params['gamma'+str(i)] = np.ones(num_filters)
        self.params['beta'+str(i)] = np.zeros(num_filters)

    self.params['gamma5'] = np.ones(hidden_dim)
    self.params['beta5'] = np.zeros(hidden_dim)

    self.bn_params = []
    if self.use_batchnorm:
        self.bn_params = [{'mode': 'train'} for i in np.arange(5)]

    # ================================================================ # # END YOUR CODE HERE
    # ================================================================ #
    for k, v in self.params.items(): self.params[k] = v.astype(dtype)

  def loss(self, X, y=None):
    # print("input sahpe", X.shape)
    mode = 'test' if y is None else 'train'

    W1, b1 = self.params['W1'], self.params['b1']
    W2, b2 = self.params['W2'], self.params['b2']
    W3, b3 = self.params['W3'], self.params['b3']
    W4, b4 = self.params['W4'], self.params['b4']
    W5, b5 = self.params['W5'], self.params['b5']
    W6, b6 = self.params['W6'], self.params['b6']

    if self.use_batchnorm:
      for bn_param in self.bn_params:
          bn_param['mode'] = mode

    # take care of conv
    filter_size = W1.shape[2]
    conv_param = {'stride': 1, 'pad': (filter_size - 1) / 2}
    # take care of pool
    pool_param = {'pool_height': 2, 'pool_width': 2, 'stride': 2}
    scores = None

    if not self.use_batchnorm:
      # forward pass w/o bn
      h1, cache1 = conv_relu_forward(x=X, w=W1, b=b1, conv_param=conv_param) # conv relu

      h2, cache2 = conv_relu_forward(x=h1, w=W2, b=b2, conv_param=conv_param) # conv relu
      h3, cache3 = max_pool_forward_fast(x=h2, pool_param=pool_param) # pool
      h4, cache4 = conv_relu_forward(x=h3, w=W3, b=b3, conv_param=conv_param) # conv relu
      h5, cache5 = conv_relu_forward(x=h4, w=W4, b=b4, conv_param=conv_param) # conv relu
      h6, cache6 = max_pool_forward_fast(x=h5, pool_param=pool_param) # pool
      # FC
      h7, cache7 = affine_relu_forward(x=h6, w=W5, b=b5)  # affine
      scores, cache8 = affine_forward(x=h7, w=W6, b=b6) # affine - output

    bn_cache=[]
    # forward pass w bn
    if self.use_batchnorm:
      h1, cache1 = conv_bn_relu_forward(x=X, w=W1, b=b1, conv_param=conv_param, gamma=self.params['gamma1'], beta=self.params['beta1'], bn_param=self.bn_params[0]) # conv relu
      h2, cache2 = conv_bn_relu_forward(x=h1, w=W2, b=b2, conv_param=conv_param, gamma=self.params['gamma2'], beta=self.params['beta2'], bn_param=self.bn_params[1]) # conv relu
      h3, cache3 = max_pool_forward_fast(x=h2, pool_param=pool_param) # pool
      h4, cache4 = conv_bn_relu_forward(x=h3, w=W3, b=b3, conv_param=conv_param, gamma=self.params['gamma3'], beta=self.params['beta3'], bn_param=self.bn_params[2]) # conv relu
      h5, cache5 = conv_bn_relu_forward(x=h4, w=W4, b=b4, conv_param=conv_param, gamma=self.params['gamma4'], beta=self.params['beta4'], bn_param=self.bn_params[3]) # conv relu
      h6, cache6 = max_pool_forward_fast(x=h5, pool_param=pool_param) # pool
      # FC
      h7, cache7 = affine_batchnorm_relu_forward(x=h6, w=W5, b=b5, gamma=self.params['gamma5'], beta=self.params['beta5'], bn_params=self.bn_params[4])  # affine
      scores, cache8 = affine_forward(x=h7, w=W6, b=b6) # affine - output

    if y is None:
      return scores
    loss, grads = 0, {}

    loss, dl = softmax_loss(x=scores, y=y) # then regularize the loss
    loss += 0.5*self.reg*np.sum(W1**2) + 0.5*self.reg*np.sum(W2**2) + 0.5*self.reg*np.sum(W3**2) + 0.5*self.reg*np.sum(W4**2) + 0.5*self.reg*np.sum(W5**2) + 0.5*self.reg*np.sum(W6**2)

    if not self.use_batchnorm:
      dout, dW6, db6 = affine_backward(dl, cache8) # affine
      dW6 += self.reg * W6
      dout, dW5, db5 = affine_relu_backward(dout, cache7) # affine relu
      dW5 += self.reg * W5

      dout = max_pool_backward_fast(dout, cache6) # pool
      dout, dW4, db4 = conv_relu_backward(dout, cache5)# conv relu
      dW4 += self.reg * W4
      dout, dW3, db3 = conv_relu_backward(dout, cache4) # conv relu
      dW3 += self.reg * W3
      dout = max_pool_backward_fast(dout, cache3) # pool
      dout, dW2, db2 = conv_relu_backward(dout, cache2) # conv relu
      dW2 += self.reg * W2
      dout, dW1, db1 = conv_relu_backward(dout, cache1) # conv relu
      dW1 += self.reg * W1

    else:
      dout, dW6, db6 = affine_backward(dl, cache8) # affine
      dW6 += self.reg * W6
      dout, dW5, db5, dgamma5, dbeta5 = affine_batchnorm_relu_backward(dout, cache7) # affine relu
      dW5 += self.reg * W5
      grads['gamma5'] = dgamma5
      grads['beta5'] = dbeta5

      dout = max_pool_backward_fast(dout, cache6) # pool
      dout, dW4, db4, dgamma4, dbeta4 = conv_bn_relu_backward(dout, cache5)# conv relu
      dW4 += self.reg * W4
      grads['gamma4'] = dgamma4
      grads['beta4'] = dbeta4

      dout, dW3, db3, dgamma3, dbeta3 = conv_bn_relu_backward(dout, cache4) # conv relu
      grads['gamma3'] = dgamma3
      grads['beta3'] = dbeta3
      dW3 += self.reg * W3

      dout = max_pool_backward_fast(dout, cache3) # pool
      dout, dW2, db2, dgamma2, dbeta2 = conv_bn_relu_backward(dout, cache2) # conv relu
      grads['gamma2'] = dgamma2
      grads['beta2'] = dbeta2
      dW2 += self.reg * W2
      dout, dW1, db1, dgamma1, dbeta1 = conv_bn_relu_backward(dout, cache1) # conv relu
      grads['gamma1'] = dgamma1
      grads['beta1'] = dbeta1
      dW1 += self.reg * W1

    # storage of w's and b's
    grads["W1"] = dW1
    grads["W2"] = dW2
    grads["W3"] = dW3
    grads["W4"] = dW4
    grads["W5"] = dW5
    grads["W6"] = dW6
    grads["b1"] = db1
    grads["b2"] = db2
    grads["b3"] = db3
    grads["b4"] = db4
    grads["b5"] = db5
    grads["b6"] = db6

    return loss, grads


import sys, os
import selectors
import json
import io
import struct
import string
import random
import pickle
from functools import reduce

sys.path.append(os.path.abspath(os.path.join('.')))
sys.path.append(os.path.abspath(os.path.join('..')))

import cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from dominoes.deck_utils import Player
from utils import Colors as Colors
from security.symCiphers import AESCipher
from security.asymCiphers import readPublicKeyFromPEM, RSAKeychain
from security.handCommit import *
from security.hashFunctions import *

# Main socket code from https://realpython.com/python-sockets/

class Message:
    def __init__(self, selector, sock, addr, request, player, keychain, player_cc, aes_cipher=None, cheater=None):
        self.selector = selector
        self.sock = sock
        self.addr = addr
        self.player = player
        self.keychain = keychain
        self.cc = player_cc
        self.aes_cipher = aes_cipher
        self.aes_player_keys = {}
        self.exchange_aes = None
        self.request = request
        self._recv_buffer = b""
        self._send_buffer = b""
        self._request_queued = False
        self.response = None
        self.cheater = cheater

    def process_events(self, mask):
        if mask & selectors.EVENT_READ:
            self.read()
        if mask & selectors.EVENT_WRITE:
            self.write()

    def read(self):
        self._read()
        self.process_response()

    def write(self):
        if not self._request_queued:
            self.queue_request()

        self._write()

        if self._request_queued:
            if not self._send_buffer:
                # Set selector to listen for read events, we're done writing.
                self._set_selector_events_mask("r")

    def close(self):
        print("closing connection to", self.addr)
        try:
            self.selector.unregister(self.sock)
        except Exception as e:
            print(
                "error: selector.unregister() exception for",
                f"{self.addr}: {repr(e)}",
            )

        try:
            self.sock.close()
        except OSError as e:
            print(
                "error: socket.close() exception for",
                f"{self.addr}: {repr(e)}",
            )
        finally:
            # Delete reference to socket object for garbage collection
            self.sock = None

    def queue_request(self):
        if "content" in self.request:
            content = self.request["content"]
        elif "action" in self.request:
            content = self.request
        req = {
            "content_bytes": self._pickle_encode(content),
        }
        message = self._create_message(**req)
        self._send_buffer += message
        self._request_queued = True

    def process_response(self):
        data = self._recv_buffer
        self.response = self._pickle_decode(data)
        #print("received response", repr(self.response), "from", self.addr)
        self._process_response_json_content()
        self._recv_buffer = b""

    # -----------------------------------------------------------Private Methods------------------------------------------------------------------

    def _set_selector_events_mask(self, mode):
        """Set selector to listen for events: mode is 'r', 'w', or 'rw'."""
        if mode == "r":
            events = selectors.EVENT_READ
        elif mode == "w":
            events = selectors.EVENT_WRITE
        elif mode == "rw":
            events = selectors.EVENT_READ | selectors.EVENT_WRITE
        else:
            raise ValueError(f"Invalid events mask mode {repr(mode)}.")
        self.selector.modify(self.sock, events, data=self)

    def _read(self):
        try:
            # Should be ready to read
            data = self.sock.recv(16384)
        except BlockingIOError:
            # Resource temporarily unavailable (errno EWOULDBLOCK)
            pass
        else:
            if data:
                self._recv_buffer += data
            else:
                raise RuntimeError("Peer closed.")

    def _write(self):
        if self._send_buffer:
            #print("sending", repr(self._send_buffer), "to", self.addr)
            try:
                # Should be ready to write
                sent = self.sock.send(self._send_buffer)
            except BlockingIOError:
                # Resource temporarily unavailable (errno EWOULDBLOCK)
                pass
            else:
                self._send_buffer = self._send_buffer[sent:]

    def _json_encode(self, obj, encoding):
        return json.dumps(obj, ensure_ascii=False).encode(encoding)

    def _json_decode(self, json_bytes, encoding):
        tiow = io.TextIOWrapper(io.BytesIO(json_bytes), encoding=encoding, newline="")
        obj = json.load(tiow)
        tiow.close()
        return obj

    def _pickle_encode(self, obj):
        return pickle.dumps(obj)

    def _pickle_decode(self, pickle_bytes):
        return pickle.loads(pickle_bytes)

    def _create_message(self, content_bytes):
        message = content_bytes
        return message

    def _create_request(self, action):
        return dict(content=dict(action=action))

    def _handle_login(self):
        server_cert_PEM = self.response.get("server_cert")
        server_cert = cryptography.x509.load_pem_x509_certificate(server_cert_PEM, default_backend())
        server_pub_key_PEM = server_cert.public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
        nickname = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))  # input(data["msg"])
        #signature, data = self.cc.signData(nickname)
        cert = self.cc.get_signature_cert()
        # cc_pubKey = self.cc.get_pubKey()
        print(Colors.BYellow + "Your name is " + Colors.BBlue + nickname + Colors.Color_Off)
        msg = {"action": "req_login", "pubkey": self.keychain.exportPubKey(), "msg": nickname, "cert": cert}
        signature, data = self.cc.signData(pickle.dumps(msg))
        msg.update({"signature": signature, "data": data})
        self.player = Player(nickname, self.sock, self.cheater)
        self.player.server_pub_key = readPublicKeyFromPEM(server_pub_key_PEM)
        return msg

    def _handle_you_host(self):
        print(Colors.Yellow, "Checking if session key message was compromised", Colors.Color_Off)
        if not self.keychain.verify_sign(self.response.get("session_key"), self.response.get("signed_session_key"), self.player.server_pub_key):
                print(Colors.Red, "Messages has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
        print(Colors.BGreen, "Message integrity not compromised", Colors.Color_Off)
        aes_secret = self.keychain.decrypt(self.response.get("session_key"))
        self.aes_cipher = AESCipher(aes_secret)
        print("Session", aes_secret)
        self.player.server_aes_cipher = AESCipher(aes_secret)
        self.player.host = True
        print(Colors.Blue + "Player " + self.player.name + "is hosting the game!" + Colors.Color_Off)

    def _handle_new_player(self):
        if "session_key" in self.response:
            print(Colors.Yellow, "Checking if session key message was compromised", Colors.Color_Off)
            if not self.keychain.verify_sign(self.response.get("session_key"), self.response.get("signed_session_key"), self.player.server_pub_key):
                print(Colors.Red, "Messages has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
            print(Colors.BGreen, "Message integrity not compromised", Colors.Color_Off)
            aes_secret = self.keychain.decrypt(self.response.get("session_key"))
            print("Session", aes_secret)
            self.aes_cipher = AESCipher(aes_secret)
            self.player.server_aes_cipher = AESCipher(aes_secret)
        print(self.response.get("msg"))
        print("There are " + str(self.response.get("nplayers")) + "\\" + str(self.response.get("game_players")))
        self.player.nplayers = self.response.get("nplayers")

    def _handle_key_exchange(self):
        print(self.response.get("msg"))
        if "session_keys" in self.response:
            aes_exchange_keys = {}
            aes_keys = {}
            signed_aes_exchange_keys = {}
            signed_aes_keys = {}
            print(Colors.Yellow, "Checking if server message was compromised", Colors.Color_Off)
            if not self.keychain.verify_sign(pickle.dumps(self.response.get("session_keys")), self.response.get("signed_session_keys"), self.player.server_pub_key):
                print(Colors.Red, "Key Exchange has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
            print(Colors.BGreen, "Key Exchange integrity not compromised", Colors.Color_Off)
            players_pub_keys = self.response.get("session_keys")
            list_of_keys = list(players_pub_keys.keys())
            for keys in list_of_keys:
                if keys not in self.player.name:
                    self.exchange_aes = AESCipher()
                    encrypted_secret = self.keychain.encrypt(self.exchange_aes.secret,
                                                             readPublicKeyFromPEM(players_pub_keys[keys]))
                    aes_exchange_keys[keys] = encrypted_secret
                    signed_aes_exchange_keys[keys] = self.keychain.sign(encrypted_secret)
                    aes_keys[self.player.name] = aes_exchange_keys
                    signed_aes_keys[self.player.name] = signed_aes_exchange_keys
                    self.player.aes_player_keys_dec[keys] = self.exchange_aes
            msg = {"action": "aes_exchange", "aes_keys": aes_keys, "signed_aes_keys": signed_aes_keys}
        return msg

    def _handle_receiving_aes(self):
        if "aes_key" in self.response:
            aes_key = self.response.get("aes_key")
            signed_aes_key = self.response.get("signed_aes_key")
            print(aes_key)
            if self.player.name in self.response.get("player_receive"):
                for key in aes_key:
                    print(Colors.Yellow, "Checking if session key between clients message was compromised", Colors.Color_Off)
                    if not self.keychain.verify_sign(aes_key[key], signed_aes_key[key], readPublicKeyFromPEM(self.player.player_pub_keys[key])):
                        print(Colors.Red, key, "session key has been compromised. Shutting Down!", Colors.Color_Off)
                        exit(-1)
                    print(Colors.BGreen, key, "session key not compromised", Colors.Color_Off)
                    self.player.aes_player_keys[key] = aes_key[key]
                    print(aes_key[key])
                    print(self.player.aes_player_keys[key])

    def _handle_keys_exchanged(self):
        print(self.response.get("msg"))
        list_of_keys = list(self.player.aes_player_keys.keys())
        for key in list_of_keys:
            aes_secret = self.keychain.decrypt(self.player.aes_player_keys[key])
            self.player.aes_player_keys_dec[key] = AESCipher(aes_secret)

        print(self.player.name)
        for secret in self.player.aes_player_keys_dec:
            print("RESULTADO", secret, self.player.aes_player_keys_dec[secret].secret)

        if len(
                self.player.aes_player_keys_dec) >= self.player.nplayers - 1 and not self.player.already_have_player_keys:
            msg = {"action": "finished_setup"}
            self.player.already_have_player_keys = True
            return msg

    def _handle_waiting_for_host_as_host(self):
        input(Colors.BGreen + "PRESS ENTER TO START THE GAME" + Colors.Color_Off)
        msg = {"action": "start_game"}
        return msg

    def _handle_waiting_for_host_as_player(self):
        print(self.response.get("msg"))

    def _handle_host_start_game(self):
        print(self.response.get("msg"))
        msg = {"action": "get_game_properties"}
        return msg

    def _handle_randomization_stage(self):
        deck = self.response.get("pseudo_deck")
        new_deck = []

        self.player.randomized_tuple_mapping = {}

        print(Colors.Yellow + "Ciphering each piece in deck" + Colors.Color_Off)

        for piece in deck:
            new_cipher = AESCipher()
            ciphertext, nonce, auth_tag = new_cipher.encrypt_aes_gcm(pickle.dumps(piece))
            # If collision exists, generates new key encrypts again
            while ciphertext in new_deck:
                print(Colors.Red, "Cipher already existed. Generating a new one", Colors.Color_Off)
                new_cipher = AESCipher()
                ciphertext, nonce, auth_tag = new_cipher.encrypt_aes_gcm(pickle.dumps(piece))

            self.player.randomized_tuple_mapping[new_cipher.secret] = (ciphertext, nonce, auth_tag)
            new_deck.append(ciphertext)

        random.shuffle(new_deck)

        return {'action': 'next_randomization_step', 'deck': new_deck}

    def _handle_start_selection_stage(self):
        # Picks a piece from the deck or passes, shuffles and sends to another player
        pseudo_deck = self.response.get("deck")
        padding = self.response.get("padding")
        self.player.npieces = self.response.get("pieces_per_player")

        print(Colors.Yellow + "Do you pick or pass?" + Colors.Color_Off)

        if random.random() < 0.05:
            print(Colors.Green + "Selecting a piece" + Colors.Color_Off)
            random.shuffle(pseudo_deck)
            padding.append(os.urandom(sys.getsizeof(pseudo_deck[-1])))
            self.player.encrypted_hand.append(pseudo_deck.pop())
        else:
            random.shuffle(pseudo_deck)

        players_nicks = list(self.player.aes_player_keys_dec.keys())
        player_to_send_deck = random.choice(players_nicks)

        encrypted_message = pickle.dumps({'action': "selection_stage", "deck": pseudo_deck,
                                          'pieces_per_player': self.response.get("pieces_per_player"),
                                          "stock_low": self.response.get("stock_low"), "padding": padding})

        encrypted_tuple = self.player.aes_player_keys_dec[player_to_send_deck].encrypt_aes_gcm(encrypted_message)

        msg = {'action': 'send_to_player', 'sender': self.player.name, 'rec': player_to_send_deck,
               'to_send': encrypted_tuple}

        return msg

    def _handle_selection_stage(self):
        # Picks a piece from the deck or passes, shuffles and sends to another player
        pseudo_deck = self.response.get("deck")
        padding = self.response.get("padding")
        self.player.npieces = self.response.get("pieces_per_player")
        print(Colors.Yellow + "Do you pick, substitute or pass?" + Colors.Color_Off)

        players_nicks = list(self.player.aes_player_keys_dec.keys())

        if len(self.player.encrypted_hand) < self.player.npieces:
            if random.random() < 0.05:
                print(Colors.Green + "Selecting a piece" + Colors.Color_Off)
                random.shuffle(pseudo_deck)
                padding.append(os.urandom(sys.getsizeof(pseudo_deck[-1])))
                self.player.encrypted_hand.append(pseudo_deck.pop())
            elif random.random() < 0.50 and len(self.player.encrypted_hand) > 0:
                # Substitute already selected pieces
                number_of_pieces_to_sub = random.randint(1, len(self.player.encrypted_hand))
                print(Colors.Green + "Substituting", number_of_pieces_to_sub, "pieces" + Colors.Color_Off)

                # For a certain number of pieces, take a new piece from deck and add one from hand.
                for piece in range(0, number_of_pieces_to_sub):
                    piece_to_put_in_deck = self.player.encrypted_hand.pop()
                    self.player.encrypted_hand.append(pseudo_deck.pop())
                    pseudo_deck.append(piece_to_put_in_deck)
                    random.shuffle(pseudo_deck)
            else:
                print(Colors.Green + "Passing" + Colors.Color_Off)
                random.shuffle(pseudo_deck)

        if len(pseudo_deck) > self.response.get("stock_low"):
            player_to_send_deck = random.choice(players_nicks)

            encrypted_message = pickle.dumps({'action': "selection_stage", "deck": pseudo_deck,
                                              'pieces_per_player': self.response.get("pieces_per_player"),
                                              'stock_low': self.response.get('stock_low'), "padding": padding})

            encrypted_tuple = self.player.aes_player_keys_dec[player_to_send_deck].encrypt_aes_gcm(encrypted_message)

            msg = {'action': 'send_to_player', 'sender': self.player.name, 'rec': player_to_send_deck,
                   'to_send': encrypted_tuple}
        else:
            print(Colors.Green + "Stock has reached low level. Stopping selection" + Colors.Color_Off)
            msg = {'action': 'selection_over', "deck": pseudo_deck}

        return msg

    def _handle_commit_hand(self):
        print(Colors.Yellow, "Generating hand commitment with starting encrypted deck", Colors.Color_Off)
        self.player.hand_commit = HandCommit(self.player.encrypted_hand.copy())

        signed_commit = self.keychain.sign(pickle.dumps(self.player.hand_commit.publishCommit()))

        print(Colors.Yellow, "Sending hand commitment", Colors.Color_Off)

        msg = {"action": 'send_commit', "commit": (self.player.hand_commit.publishCommit(), signed_commit)}

        return msg

    def _handle_validate_selection(self):
        self.player.players_commits = self.response.get("commits")
        print(self.player.player_pub_keys)
        for player in self.player.players_commits:
            if not self.keychain.verify_sign(pickle.dumps(self.player.players_commits[player][0]),
                                             self.player.players_commits[player][1],
                                             readPublicKeyFromPEM(self.player.player_pub_keys[player])):
                print(Colors.BRed + "GAME NOT VALID" + Colors.Color_Off)
                exit(1)

        self.player.pseudo_starting_stock = self.response.get('stock')
        return {"action": "hands_validated"}

    def _handle_reveal_keys(self):
        print(self.response.get("msg"))

        key_tuple_dict = {}

        keys_to_send = [keys for keys in self.player.randomized_tuple_mapping.keys() if
                        self.player.randomized_tuple_mapping[keys][0] not in self.player.pseudo_starting_stock]

        for key in keys_to_send:
            key_tuple_dict[self.player.randomized_tuple_mapping[key]] = key

        aux_encrypted_hand = []

        for piece in self.player.encrypted_hand:
            for tuple_piece in key_tuple_dict:
                if piece == tuple_piece[0]:
                    decipher = AESCipher(key_tuple_dict[tuple_piece])
                    deciphered_piece = pickle.loads(decipher.decrypt_aes_gcm(tuple_piece))
                    aux_encrypted_hand.append(deciphered_piece)

        self.player.encrypted_hand = aux_encrypted_hand

        print('size', sys.getsizeof(key_tuple_dict))
        print('dict', key_tuple_dict)

        return {'action': 'revealed_keys', 'keys_dict': key_tuple_dict}

    def _handle_keys_to_reveal(self):
        key_tuple_dict = self.response.get("keys_dict")
        aux_encrypted_hand = []

        print(Colors.BYellow + "Revealing Pieces" + Colors.Color_Off)

        for piece in self.player.encrypted_hand:
            for tuple_piece in key_tuple_dict:
                if piece == tuple_piece[0]:
                    decipher = AESCipher(key_tuple_dict[tuple_piece])
                    deciphered_piece = pickle.loads(decipher.decrypt_aes_gcm(tuple_piece))
                    aux_encrypted_hand.append(deciphered_piece)

        self.player.encrypted_hand = aux_encrypted_hand

        return {"action": "waiting_for_keys"}

    def _handle_piece_key_to_reveal(self):
        key_tuple_dict = self.response.get("key_dict")

        print(Colors.BYellow + "Revealing Piece" + Colors.Color_Off)

        for tuple_piece in key_tuple_dict:
            if tuple_piece[0] == self.player.new_piece:
                decipher = AESCipher(key_tuple_dict[tuple_piece])
                deciphered_piece = pickle.loads(decipher.decrypt_aes_gcm(tuple_piece))

        self.player.new_piece = deciphered_piece
        self.player.collected_keys[deciphered_piece] = key_tuple_dict

        if self.response.get("more"):
            msg = {"action": "request_piece_reveal", 'new_piece': self.player.new_piece,
                   'new_stock': self.player.pseudo_starting_stock}
        else:
            # Maybe needs to go ciphered for safety reasons?
            msg = {"action": "request_piece_deanon", "piece": deciphered_piece}

        return msg

    def _handle_keys_sent(self):
        return {'action': 'waiting_for_keys'}

    def _handle_start_deanon_stage(self):
        pub_key_list = self.response.get("pub_key_list")
        padding = self.response.get("padding")
        self.player.npieces = self.response.get("pieces_per_player")

        if random.random() < 0.05:
            print(Colors.BGreen + "Adding Public Key To Array" + Colors.Color_Off)
            padding.pop()
            padding.insert(0, None)
            tuple_to_add = self.player.encrypted_hand.pop()
            new_key = RSAKeychain(2048)
            self.player.tuple_keychains[tuple_to_add] = new_key
            pub_key_list[tuple_to_add[0]] = new_key.exportPubKey()
        else:
            print(Colors.BGreen + "Passing" + Colors.Color_Off)

        players_nicks = list(self.player.aes_player_keys_dec.keys())
        player_to_send_deck = random.choice(players_nicks)

        encrypted_message = pickle.dumps({'action': "deanon_stage", "pub_key_list": pub_key_list,
                                          'pieces_per_player': self.response.get("pieces_per_player"),
                                          "max_pieces": self.response.get("max_pieces"), "padding": padding})

        encrypted_tuple = self.player.aes_player_keys_dec[player_to_send_deck].encrypt_aes_gcm(encrypted_message)

        msg = {'action': 'send_to_player', 'sender': self.player.name, 'rec': player_to_send_deck,
               'to_send': encrypted_tuple}

        return msg

    def _handle_deanon_stage(self):
        pub_key_list = self.response.get("pub_key_list")
        padding = self.response.get("padding")
        self.player.npieces = self.response.get("pieces_per_player")

        players_nicks = list(self.player.aes_player_keys_dec.keys())

        if len(self.player.encrypted_hand) > 0:
            if random.random() < 0.05:
                print(Colors.BGreen + "Adding Public Key To Array" + Colors.Color_Off)
                padding.pop()
                padding.insert(0, None)
                new_key = RSAKeychain(2048)
                tuple_to_add = self.player.encrypted_hand.pop()
                self.player.tuple_keychains[tuple_to_add] = new_key
                pub_key_list[tuple_to_add[0]] = new_key.exportPubKey()
            else:
                print(Colors.BGreen + "Passing" + Colors.Color_Off)

        sum_check_done = sum(item is not None for item in pub_key_list)
        if sum_check_done < self.response.get('max_pieces'):
            player_to_send_deck = random.choice(players_nicks)

            encrypted_message = pickle.dumps({'action': "deanon_stage", "pub_key_list": pub_key_list,
                                              'pieces_per_player': self.response.get("pieces_per_player"),
                                              'max_pieces': self.response.get('max_pieces'), "padding": padding})

            encrypted_tuple = self.player.aes_player_keys_dec[player_to_send_deck].encrypt_aes_gcm(encrypted_message)

            msg = {'action': 'send_to_player', 'sender': self.player.name, 'rec': player_to_send_deck,
                   'to_send': encrypted_tuple}
        else:
            msg = {'action': 'deanon_prep_over', "pub_key_list": pub_key_list}

        return msg

    def _handle_decipher_tiles(self):
        tiles_to_decipher = self.response.get("ciphered_tiles")

        for tuple_piece in self.player.tuple_keychains:
            tile_to_decipher = tiles_to_decipher[tuple_piece[0]]
            tile_index = tuple_piece[0]
            cipher = self.player.tuple_keychains[tuple_piece]
            tile, tile_key = pickle.loads(cipher.decrypt(tile_to_decipher))
            if not hashFunctions.check_sha256_digest_from_list(tuple_piece[1], [str.encode(str(tile_index)), tile_key,
                                                                                str.encode(str(tile))]):
                print(Colors.Red + "SERVER IS CHEATING!" + Colors.Color_Off)
                exit(-1)
            self.player.insertInHand(tile)

        print(Colors.Green + "Hand has been de-anonymized!" + Colors.Color_Off)
        print("Hand -> " + ' '.join(map(str, self.player.hand)))
        return {"action": "ready_to_play"}

    def _handle_reveal_piece_key(self):
        key_tuple_dict = {}
        self.player.pseudo_starting_stock = self.response.get("new_stock")

        key_to_send = [keys for keys in self.player.randomized_tuple_mapping.keys() if
                       self.player.randomized_tuple_mapping[keys][0] in self.response.get("new_piece")]

        for key in key_to_send:
            key_tuple_dict[self.player.randomized_tuple_mapping[key]] = key

        print('size', sys.getsizeof(key_tuple_dict))
        print('dict', key_tuple_dict)

        return {'action': 'revealed_key_for_piece', 'key_dict': key_tuple_dict}

    def _handle_secret_message(self):
        if self.response.get('sender') == "server":
            cipher = self.player.server_aes_cipher
        else:
            cipher = self.player.aes_player_keys_dec[self.response.get('sender')]

        encrypted_tuple = self.response.get('msg')

        deciphered_msg = pickle.loads(cipher.decrypt_aes_gcm(encrypted_tuple))

        return deciphered_msg

    def _handle_insert_in_hand(self):
        tile, key = pickle.loads(self.response.get("new_tile"))
        print(Colors.Yellow + "Checking if server is not cheating" + Colors.Color_Off)
        if not hashFunctions.check_sha256_digest_from_list(self.player.new_piece[1],
                                                           [str.encode(str(self.player.new_piece[0])), key,
                                                            str.encode(str(tile))]):
            print(Colors.Red + "SERVER IS CHEATING!" + Colors.Color_Off)
            exit(-1)

        print(Colors.Green + "Tile is valid. Inserting in hand" + Colors.Color_Off, tile)
        self.player.insertInHand(tile)

        msg = self.player.play()
        if msg.get("action") == 'play_piece':
            piece_signature = self.keychain.sign(pickle.dumps(msg.get("piece")))
            msg.update({"signed_piece": piece_signature})
        return msg

    def _handle_validate_this_play(self):
        msg = {"action": "play_piece_ep"}
        self.player.nplayers = self.response.get("nplayers")
        self.player.npieces = self.response.get("npieces")
        self.player.pieces_per_player = self.response.get("pieces_per_player")
        self.player.in_table = self.response.get("in_table")
        last_table = self.request.get("last_table")

        if self.response.get("last_player") != self.player.name:
            print(Colors.Yellow + "Validating last played piece signature" + Colors.Color_Off)
            signed_piece = self.response.get("signed_piece")
            last_piece_played = self.response.get("last_piece")
            last_player = self.response.get("last_player")
            if not self.keychain.verify_sign(pickle.dumps(last_piece_played), signed_piece,
                                             readPublicKeyFromPEM(self.player.player_pub_keys[last_player])):
                print(Colors.Red + "This signature is not valid" + Colors.Color_Off)
                exit(-1)
            print(Colors.Green + "Last play signature is valid!" + Colors.Color_Off)

            if (self.player.validate(last_piece_played, last_table)):
                print("I don't know if the player cheated!")
                msg.update({"player_cheated": False})
            else:
                print("Cheated!")
                msg.update({"player_cheated": True})

            msg.update({"signed_player_cheated": self.keychain.sign(pickle.dumps(msg.get("player_cheated")))})

        return msg

    def _handle_rcv_game_properties(self):
        self.player.nplayers = self.response.get("nplayers")
        self.player.npieces = self.response.get("npieces")
        self.player.pieces_per_player = self.response.get("pieces_per_player")
        self.player.in_table = self.response.get("in_table")
        player_name = self.response.get("next_player")

        if self.response.get("next_player") == self.player.name:
            player_name = Colors.BRed + "YOU" + Colors.Color_Off
        print("deck -> ", len(self.player.pseudo_starting_stock), "pieces remaining")
        print("hand -> " + ' '.join(map(str, self.player.hand)))
        print("in table -> " + ' '.join(map(str, self.response.get("in_table"))) + "\n")
        print("Current player ->", player_name)
        print("next Action ->", self.response.get("next_action"))
        if self.player.name == self.response.get("next_player"):

            if self.response.get("next_action") == "get_piece":
                if not self.player.ready_to_play:
                    # input("Press ENter \n\n")
                    random.shuffle(self.player.deck)
                    piece = self.player.deck.pop()
                    self.player.insertInHand(piece)
                    msg = {"action": "get_piece", "deck": self.player.deck}
                    return msg
            if self.response.get("next_action") == "play":
                # input(Colors.BGreen+"Press ENter \n\n"+Colors.Color_Off)
                if self.player.isCheater:
                    msg = self.player.cheat_play()
                else:
                    msg = self.player.play()
                if msg.get("action") == 'play_piece':
                    piece_signature = self.keychain.sign(pickle.dumps(msg.get("piece")))
                    msg.update({"signed_piece": piece_signature})
                return msg

    def _handle_report_score(self):
        hand_commits_confirmations = self.response.get("hand_commits_confirmation")
        print(Colors.Yellow + "Validating all hand commits" + Colors.Color_Off)

        signed_msg = self.response.pop("signed_msg", None)

        print(Colors.Yellow, "Checking if Report Score message from server was compromised", Colors.Color_Off)
        if not self.keychain.verify_sign(pickle.dumps(self.response), signed_msg, self.player.server_pub_key):
                print(Colors.Red, "Report Score has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
        print(Colors.BGreen, "Report Score action integrity not compromised", Colors.Color_Off)

        for player_name in self.player.players_commits:

            if not verifyHandCommit(self.player.players_commits[player_name][0],
                                    hand_commits_confirmations[player_name]):
                print(Colors.Red + player_name + " Sent an Invalid Hand Commit" + Colors.Color_Off)
            else:
                print(Colors.Green + player_name + " Sent a valid Hand Commit" + Colors.Color_Off)

        print(Colors.Yellow + "Calculating score for this game" + Colors.Color_Off)

        remaining_hands = self.response.get("remaining_hands")
        score_history = {}
        score = 0

        if self.response.get("winner") == "TIE":
            winner = None
            for player_name in remaining_hands:
                score = 0
                for piece in remaining_hands[player_name]:
                    score += piece.values[0].value + piece.values[1].value
                score_history[player_name] = score
            for player in score_history:
                if winner is None:
                    winner = player
                elif score_history[winner] > score_history[player]:
                    winner = player
            score = 0
            for player_name in score_history:
                if player_name != winner:
                    score += score_history[player_name]
        else:
            winner = self.response.get("winner")
            for player_name in remaining_hands:
                if player_name != winner:
                    for piece in remaining_hands[player_name]:
                        score += piece.values[0].value + piece.values[1].value

        print(Colors.Green, "I expect the winner to be ", winner, Colors.Color_Off)
        print(Colors.Green, "I expect the score to be ", score, Colors.Color_Off)
        self.player.calculated_score = score
        self.player.expected_winner = winner
        msg = {"action": "score_report", "score": self.player.calculated_score,
               "possible_winner": self.player.expected_winner}
        msg.update({"signed_msg": self.keychain.sign(pickle.dumps(msg))})
        return msg

    def _handle_reveal_everything(self):
        print(Colors.Yellow, "Checking if reveal everything message was compromised", Colors.Color_Off)
        if not self.keychain.verify_sign(pickle.dumps(self.response.get("action")), self.response.get("signed_action"), self.player.server_pub_key):
                print(Colors.Red, "Reveal action has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
        print(Colors.BGreen, "Reveal action integrity not compromised", Colors.Color_Off)
        next_action = self.response.get("next_act")
        if next_action == "validate_protest":
            print(Colors.Red + "There has been a protest!\n" + Colors.Color_Off)
        else:
            print(Colors.Yellow + "Game Ended!\n" + Colors.Color_Off)
            print("hand -> " + ' '.join(map(str, self.player.hand)))
            print("end table -> " + ' '.join(map(str, self.response.get("in_table"))) + "\n")
        print(Colors.Green + "Revealing everything to server!" + Colors.Color_Off)
        msg = {"action": next_action, "tile_keys": self.player.randomized_tuple_mapping,
               'hand_commit_confirmation': self.player.hand_commit.publishConfirmation(),
               "remaining_hand": self.player.hand, "collected_keys": self.player.collected_keys}
        msg.update({"signed_msg": self.keychain.sign(pickle.dumps(msg))})
        return msg

    def _handle_end_game(self):

        signed_msg = self.response.pop("signed_msg", None)

        print(Colors.Yellow, "Checking if End Game message from server was compromised", Colors.Color_Off)
        if not self.keychain.verify_sign(pickle.dumps(self.response), signed_msg, self.player.server_pub_key):
                print(Colors.Red, "End Game has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
        print(Colors.BGreen, "End Game action integrity not compromised", Colors.Color_Off)

        winner = self.response.get("winner")
        score = self.response.get("score")
        print(Colors.Yellow, "Checking server score and winner", Colors.Color_Off)
        if winner != self.player.expected_winner:
            print(Colors.Red, "I don't agree with winner", Colors.Color_Off)
            exit(-1)
        if score != self.player.calculated_score:
            print(Colors.Red, "I don't agree with score", Colors.Color_Off)
            exit(-1)
        print(Colors.Green, "Agreeing with score and winner!", Colors.Color_Off)
        if self.response.get("winner") == self.player.name:
            winner = Colors.BRed + "YOU" + Colors.Color_Off
            print(Colors.BGreen + "End GAME, THE WINNER IS: " + winner)
            print(Colors.ICyan + "Your Score: " + str(score) + Colors.Color_Off)
            signature, data = self.cc.signData(pickle.dumps(str(score)))
            msg = {"action": "assign_score", "signed_score": signature, "data": data, "player": self.player.name}
            return msg
        else:
            winner = Colors.BBlue + winner + Colors.Color_Off
            print(Colors.BGreen + "End GAME, THE WINNER IS: " + winner)
            print(("{} {} {} earned {} points {}".format(Colors.ICyan, winner, Colors.ICyan, str(score),
                                                         Colors.Color_Off)))

    def _handle_wait(self):
        print(self.response.get("msg"))

    def _handle_disconnect(self):
        self.close()
        input("PRESS ANY KEY TO EXIT ")
        print(self.response.get("msg"))
        sys.exit(0)

    def _process_response_json_content(self):
        # ADD CLIENT ACTIONS TO MESSAGES HERE
        content = self.response
        action = content.get("action")
        if action == "secret_message":
            deciphered = self._handle_secret_message()
            action = deciphered.get("action")
            self.response = deciphered
        if action == "login":
            response = self._handle_login()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "you_host":
            self._handle_you_host()
        elif action == "new_player":
            self._handle_new_player()
        elif action == "send_pub_keys":
            print(self.response.get("msg"))
            print(Colors.Yellow, "Checking if server message was compromised", Colors.Color_Off)
            if not self.keychain.verify_sign(pickle.dumps(self.response.get("pub_keys")), self.response.get("signed_pub_keys"), self.player.server_pub_key):
                print(Colors.Red, "Public Keys Exchange has been compromised. Shutting Down!", Colors.Color_Off)
                exit(-1)
            print(Colors.BGreen, "Public Keys Exchange integrity not compromised", Colors.Color_Off)
            self.player.player_pub_keys = self.response.get('pub_keys')
        elif action == "key_exchange":
            response = self._handle_key_exchange()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "receiving_aes":
            self._handle_receiving_aes()
        elif action == "keys_exchanged":
            response = self._handle_keys_exchanged()
            if response is not None:
                message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                                  self.aes_cipher)
                self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "waiting_for_host":
            if self.player.host:
                response = self._handle_waiting_for_host_as_host()
                message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                                  self.aes_cipher)
                self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
            else:
                self._handle_waiting_for_host_as_player()
        elif action == "host_start_game":
            response = self._handle_host_start_game()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "randomization_stage":
            response = self._handle_randomization_stage()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "start_selection_stage":
            response = self._handle_start_selection_stage()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "selection_stage":
            response = self._handle_selection_stage()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "commit_hand":
            response = self._handle_commit_hand()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "validate_selection":
            response = self._handle_validate_selection()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "reveal_keys":
            response = self._handle_reveal_keys()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "keys_to_reveal":
            response = self._handle_keys_to_reveal()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "keys_sent":
            response = self._handle_keys_sent()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "start_deanon_stage":
            response = self._handle_start_deanon_stage()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "deanon_stage":
            response = self._handle_deanon_stage()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "decipher_tiles":
            response = self._handle_decipher_tiles()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "reveal_piece_key":
            response = self._handle_reveal_piece_key()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "piece_key_to_reveal":
            response = self._handle_piece_key_to_reveal()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "insert_in_hand":
            response = self._handle_insert_in_hand()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "validate_this_play":
            response = self._handle_validate_this_play()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "rcv_game_properties":
            response = self._handle_rcv_game_properties()
            if response is not None:
                message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                                  self.aes_cipher)
                self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "report_score":
            response = self._handle_report_score()
            if response is not None:
                message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                                  self.aes_cipher)
                self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "reveal_everything":
            response = self._handle_reveal_everything()
            message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                              self.aes_cipher)
            self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "end_game":
            response = self._handle_end_game()
            if response is not None:
                message = Message(self.selector, self.sock, self.addr, response, self.player, self.keychain, self.cc,
                                  self.aes_cipher)
                self.selector.modify(self.sock, selectors.EVENT_WRITE, data=message)
        elif action == "wait":
            self._handle_wait()
        elif action == "disconnect":
            self._handle_disconnect()


import os
import sys
import subprocess
import requests
import json
from pathlib import Path

folders = []
for entry in os.scandir("./"):
    if entry.is_dir() and (entry.name != '[cfxserver]' and entry.name != 'sql_scripts'):
        folders.append(entry.name)

while True:
    new_folder_name = input("Kérem, adjon meg egy mappanevet: ")
    if new_folder_name in folders:
        print("Ez a mappanév már szerepel a listában. Kérem, adjon meg másikat.")
    else:
        break

registry_url = "https://registry.hub.docker.com/v2/repositories/traskin/fxserver/tags/"
params = {
    "ordering": "last_updated",
    "page_size": 10
}

response = requests.get(registry_url, params=params)

if response.status_code == 200:
    tags = response.json()['results']
    for tag in tags:
        print(f"Tag: {tag['name']}, Last Updated: {tag['last_updated']}")
else:
    print("Failed to fetch tags:", response.status_code, response.text)

os.environ['VAR'] = new_folder_name
selected_artifact_input = input("Milyen artifact verziót szeretnél használni add meg vagy hagyd üresen: ")
os.environ['ARTIFACT'] = selected_artifact_input

path = Path.cwd() / new_folder_name

if not path.exists():
    path.mkdir(parents=True)
    print(f"Folder '{new_folder_name}' created successfully at {path}")
else:
    print(f"Folder '{new_folder_name}' already exists at {path}")

if selected_artifact_input:
    with open(f'./{new_folder_name}/artifact_version.txt', 'w') as file:
        file.write(os.environ['ARTIFACT'])
    docker_build_command = f"docker-compose build"
    subprocess.run(docker_build_command.split(), check=True)
docker_command = f" docker-compose --project-name {new_folder_name} up"
try:
    process = subprocess.run(docker_command.split(), check=True)
except KeyboardInterrupt:
    subprocess.run("docker-compose down".split())
    print("A program futása megszakadt!")


from application import app
from flask import render_template, flash, redirect, url_for, request
from application.forms import LoginForm

@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html')

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            flash("Login Successful")
            return redirect('/index')
    else:
        return render_template(
            'login.html',
            title='Login to Action-Central',
            form=form)


import h5py
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_svmlight_file
from sklearn.preprocessing import MinMaxScaler

def rescale(X_train, X_test):
    scaler = MinMaxScaler()
    X_train = scaler.fit_transform(X_train)
    X_test = scaler.transform(X_test)
    return X_train, X_test

def print_shapes(X_train, X_test, y_train, y_test):
    print('X_train.shape:\t', X_train.shape)
    print('y_train.shape:\t', y_train.shape)
    print('X_test.shape:\t', X_test.shape)
    print('y_test.shape:\t', y_test.shape)

def load_usps():
    print('Loading usps...')

    with h5py.File('data/usps.h5', 'r') as hf:
        train = hf.get('train')
        test = hf.get('test')

        X_train = train.get('data')[:]
        y_train = train.get('target')[:]
        X_test = test.get('data')[:]
        y_test = test.get('target')[:]

    X_train, X_test = rescale(X_train, X_test)
    print_shapes(X_train, X_test, y_train, y_test)
    return X_train, X_test, y_train, y_test

def load_letter():
    print('Loading letter...')
    dataset = pd.read_csv('data/letter-recognition.data', header=None)

    data = dataset.loc[:, 1:]
    data = np.array(data)

    target = dataset[0]
    target = target.apply(lambda x: ord(x) - ord('A'))
    target = np.array(target)

    X_train, X_test, y_train, y_test = train_test_split(data, target)

    X_train, X_test = rescale(X_train, X_test)
    print_shapes(X_train, X_test, y_train, y_test)
    return X_train, X_test, y_train, y_test

def load_satim():
    print('Loading satim...')
    train = pd.read_csv('data/sat.trn', sep=' ', header=None)
    test = pd.read_csv('data/sat.tst', sep=' ', header=None)

    train = np.array(train)
    X_train = train[:, :36]
    y_train = train[:, 36]

    test = np.array(test)
    X_test = test[:, :36]
    y_test = test[:, 36]

    X_train, X_test = rescale(X_train, X_test)
    print_shapes(X_train, X_test, y_train, y_test)
    return X_train, X_test, y_train, y_test

def load_dna():
    print('Loading dna...')
    X_train, y_train = load_svmlight_file('data/dna.scale.tr')
    X_train = X_train.A
    X_test, y_test = load_svmlight_file('data/dna.scale.t')
    X_test = X_test.A

    X_train, X_test = rescale(X_train, X_test)
    print_shapes(X_train, X_test, y_train, y_test)
    return X_train, X_test, y_train, y_test


import os

class NodoAVL:
    def __init__(self,carne,nombre):
        self.carne=carne
        self.nombre=nombre
        self.altura=0
        self.izquierdo=None
        self.derecho=None
class ArbolAVL:
    def __init__(self):
        self.raiz=None

    def ObtenerFe(self,nodo):
        if nodo == None:
            return -1
        return nodo.altura

    def max(self,a,b):
        if a > b:
            return a
        else:
            return b

    def rotarizquierda(self,nodo):
        avlnodo=nodo.izquierdo
        nodo.izquierdo=avlnodo.derecho
        avlnodo.derecho=nodo
        nodo.altura = self.max(self.ObtenerFe(nodo.izquierdo), self.ObtenerFe(nodo.derecho) + 1)
        avlnodo.altura = self.max(self.ObtenerFe(avlnodo.izquierdo), self.ObtenerFe(avlnodo.derecho) + 1)
        return avlnodo

    def rotarderecha(self,nodo):
        avlnodo=nodo.derecho
        nodo.derecho=avlnodo.izquierdo
        avlnodo.izquierdo=nodo
        nodo.altura = self.max(self.ObtenerFe(nodo.izquierdo), self.ObtenerFe(nodo.derecho) + 1)
        avlnodo.altura = self.max(self.ObtenerFe(avlnodo.izquierdo), self.ObtenerFe(avlnodo.derecho) + 1)
        return avlnodo

    def rotarDobleIzq(self,nodo):
        avl=None
        nodo.izquierdo=self.rotarderecha(nodo.izquierdo)
        avl=self.rotarizquierda(nodo)
        return  avl

    def rotarDobleDer(self,nodo):
        avl=None
        nodo.derecho=self.rotarizquierda(nodo.derecho)
        avl=self.rotarderecha(nodo)
        return avl

    def insertar(self,nuevo,subA):
        nuevopadre=subA
        if nuevo.carne < subA.carne:
            if subA.izquierdo == None:
                subA.izquierdo = nuevo
            else:
                subA.izquierdo=self.insertar(nuevo,subA.izquierdo)
                if (self.ObtenerFe(subA.izquierdo) - self.ObtenerFe(subA.derecho)) == 2:
                    if nuevo.carne < subA.izquierdo.carne:
                        nuevopadre=self.rotarizquierda(subA)
                    else:
                        nuevopadre=self.rotarDobleIzq(subA)
        elif nuevo.carne > subA.carne:
            if subA.derecho == None:
                subA.derecho = nuevo
            else:
                subA.derecho=self.insertar(nuevo,subA.derecho)
                if (self.ObtenerFe(subA.derecho) - self.ObtenerFe(subA.izquierdo)) == 2:
                    if nuevo.carne > subA.derecho.carne:
                        nuevopadre=self.rotarderecha(subA)
                    else:
                        nuevopadre=self.rotarDobleDer(subA)

        if subA.izquierdo == None and subA.derecho != None:
            subA.altura=subA.derecho.altura+1
        elif subA.derecho == None and subA.izquierdo != None:
            subA.altura=subA.izquierdo.altura+1
        else:
            subA.altura=self.max(self.ObtenerFe(subA.izquierdo),self.ObtenerFe(subA.derecho))+1

        return  nuevopadre

    def insertartodo(self,carne,nombre):
        nuevo=NodoAVL(carne,nombre)
        if self.raiz == None:
            self.raiz=nuevo
        else:
            self.raiz=self.insertar(nuevo,self.raiz)

    def GraficarAVL(self):
        file = open("ArbolAVL.dot", "w")
        file.write("digraph ArbolAVL{\n rankdir=TB;\n")
        file.write(" graph [splines=compound, nodesep=0.5];\n")
        file.write("node [shape = record, style=filled, fillcolor=seashell2,width=0.7,height=0.2];\n")
        self.CodigoInterno(self.raiz,file)
        file.write("}\n")
        file.close()
        os.system("dot -Tpng ArbolAVL.dot -o ArbolAVL.png")
        os.system("ArbolAVL.png")

    def CodigoInterno(self, raiz,file):
        if raiz != None:
            self.CodigoInterno(raiz.izquierdo,file)
            Fe=self.ObtenerFe(raiz.izquierdo)-self.ObtenerFe(raiz.derecho)
            file.write(str(raiz.carne)+"[label=\"<C0>|Carne: "+str(raiz.carne)+"\n Nombre: "+raiz.nombre + "\n Altura: "+ str(raiz.altura)+"\n FE: "+ str(Fe)+"|<C1>\"];\n")
            if raiz.derecho != None:
                file.write(str(raiz.carne) + "->" + str(raiz.derecho.carne)+"\n")
            if raiz.izquierdo != None:
                file.write(str(raiz.carne) + "->" + str(raiz.izquierdo.carne) + "\n")
            self.CodigoInterno(raiz.derecho,file)

    def InOrden(self,nodo):
        if nodo != None:
            self.InOrden(nodo.izquierdo)
            print(nodo.carne)
            self.InOrden(nodo.derecho)

    def PostOrden(self,raiz):
        if raiz != None:
            self.PostOrden(raiz.izquierdo)
            self.PostOrden(raiz.derecho)
            print(str(raiz.carne)+"-"+raiz.nombre+"->", end=" ")

    def PreOrden(self,raiz):
        if raiz != None:
            print(raiz.carne)
            self.PostOrden(raiz.izquierdo)
            self.PostOrden(raiz.derecho)

    def Graph_Transversal(self,lista,name):
        file=open('{}.dot'.format(name),"w")
        file.write("digraph inorden {\nrankdir = LR;\n")
        i=0
        for i in range(len(lista)):
            file.write(str(i)+"[shape=record, style=filled, fillcolor=seashell2,label=\"Carnet:"+str(lista[i])+"\"];\n")
            file.write(str(i)+"->"+str(i+1)+"\n")

        file.write(str(i+1)+"[shape=record, style=filled, fillcolor=seashell2,label=\"Fin\"];\n")
        file.write("}\n")
        file.close()
        os.system('dot -Tpng {}.dot -o {}.png'.format(name,name))
        os.system('{}.png'.format(name))

    def Graph_Inorden(self, lista, raiz):
        if raiz != None:
            carnet=str(raiz.carne)
            name=raiz.nombre
            total='{}\\n{}'.format(carnet,name)
            self.Graph_Inorden(lista,raiz.izquierdo)
            lista.append(total)
            self.Graph_Inorden(lista,raiz.derecho)

    def Graph_PostOrden(self, lista, raiz):
        if raiz != None:
            carnet=str(raiz.carne)
            name=raiz.nombre
            total='{}\\n{}'.format(carnet,name)
            self.Graph_Inorden(lista,raiz.izquierdo)
            self.Graph_Inorden(lista,raiz.derecho)
            lista.append(total)

    def Graph_PreOrden(self, lista, raiz):
        if raiz != None:
            carnet=str(raiz.carne)
            name=raiz.nombre
            total='{}\\n{}'.format(carnet,name)
            lista.append(total)
            self.Graph_Inorden(lista,raiz.izquierdo)
            self.Graph_Inorden(lista,raiz.derecho)

if __name__=="__main__":
    arbol=ArbolAVL()
    lista=[]
    arbol.insertartodo(201403525,"Nery")
    arbol.insertartodo(201212963, "Andres")
    arbol.insertartodo(201005874, "Estudiante1")
    arbol.insertartodo(201313526, "Alan")
    arbol.insertartodo(201403819, "Anne")
    arbol.insertartodo(201403624, "Fernando")
    arbol.insertartodo(201602255, "Estudiante2")
    arbol.PostOrden(arbol.raiz)
    arbol.GraficarAVL()
    arbol.Graph_Inorden(lista,arbol.raiz)
    arbol.Graph_Transversal(lista,'INORDEN')


"""Defines the PrePostProcessor class."""

import re
from ambre.settings import Settings
from ambre.strings import compress_string, decompress_string

class PrePostProcessor:
    """Pre- and post-processes in- and outgoing data."""

    def __init__(self, settings: Settings):
        """Init."""
        self.settings = settings
        # note: need to sort here on our own here because other provided sorting functions rely on the list computed
        #       here
        self.normalized_consequents = sorted(
            self.normalize_uncompressed_itemset(self.settings.consequents, sort_result=False)
        )
        self.compressed_consequents = self.compress_itemset(self.normalized_consequents)

    def normalize_uncompressed_itemset(self, itemset, sort_result=True):
        """Normalize the given itemset (any iterable)."""
        if self.settings.normalize_whitespace:
            itemset = (re.sub(r"\s+", " ", item).strip() for item in itemset)
        if self.settings.case_insensitive:
            itemset = (item.lower() for item in itemset)
        itemset = set(itemset)
        if sort_result:
            itemset = self.sort_and_move_consequents_first(itemset)
        return itemset

    def sort_and_move_consequents_first(self, itemset):
        """Sort the given itemset so we have the consequents first, followed by the antecedents."""
        consequents, antecedents = self.extract_consequents_antecedents_compressed_from_uncompressed(
            itemset, sort_result=True
        )
        return consequents + antecedents

    def extract_consequents_antecedents_compressed_from_uncompressed(self, itemset, sort_result=True):
        """Extract consequents and antecedents from the given itemset."""
        result_consequents, result_antecedents = [], []
        for item in itemset:
            if item in self.normalized_consequents:
                result_consequents.append(item)
            else:
                result_antecedents.append(item)
        if sort_result:
            result_consequents.sort(key=str.casefold)
            result_antecedents.sort(key=str.casefold)
        return result_consequents, result_antecedents

    def remove_column_names_from_uncompressed_itemset(self, uncompressed_itemset):
        """Remove column names from items in given uncompressed itemset."""
        return [
            re.sub(f"^.+?{re.escape(self.settings.column_value_separator)}", "", item) for item in uncompressed_itemset
        ]

    def compress_item(self, decompressed_item):
        """Compress the given item string."""
        return compress_string(decompressed_item, input_alphabet=self.settings.item_alphabet)

    def compress_itemset(self, decompressed_items):
        """Compress the given itemset."""
        return list(self.compress_item(uncompressed_item) for uncompressed_item in decompressed_items)

    def decompress_item(self, compressed_item):
        """Uncompress the given compressed item string."""
        return decompress_string(compressed_item, original_input_alphabet=self.settings.item_alphabet)

    def decompress_itemset(self, compressed_items):
        """Decompress the given compressed itemset."""
        return list(
            decompress_string(compressed_item, original_input_alphabet=self.settings.item_alphabet)
            for compressed_item in compressed_items
        )


#  This software and supporting documentation are distributed by
#      Institut Federatif de Recherche 49
#      CEA/NeuroSpin, Batiment 145,
#      91191 Gif-sur-Yvette cedex
#      France
#
# This software is governed by the CeCILL license version 2 under
# French law and abiding by the rules of distribution of free software.
# You can  use, modify and/or redistribute the software under the
# terms of the CeCILL license version 2 as circulated by CEA, CNRS
# and INRIA at the following URL "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license version 2 and that you accept its terms.

from __future__ import absolute_import
from brainvisa.processes import *
from soma import aims
import numpy as np

name = 'Sulcal Pits Labelization From Sulci'

userLevel = 2

signature = Signature(
    'sulci_voronoi',ReadDiskItem( 'sulci voronoi texture', 'aims Texture formats'),
    'basins_texture',ReadDiskItem( 'basins texture',  'Aims texture formats' ),
    'pits_texture',ReadDiskItem( 'pits texture',  'Aims texture formats' ),
    'white_mesh',ReadDiskItem( 'Hemisphere White Mesh' , 'Aims mesh formats' ),
    'labeled_basins_texture',WriteDiskItem( 'labeled basins texture',  'Aims texture formats' ),
    'labeled_pits_texture',WriteDiskItem( 'labeled pits texture',  'Aims texture formats' ),

)

def initialization( self ):
    self.linkParameters( 'white_mesh', 'sulci_voronoi' )
    self.linkParameters( 'basins_texture', 'sulci_voronoi' )
    self.linkParameters( 'pits_texture', 'sulci_voronoi' )
    self.linkParameters( 'labeled_basins_texture', 'sulci_voronoi' )
    self.linkParameters( 'labeled_pits_texture', 'sulci_voronoi' )

def execution( self, context ):
    def valCount(vect,vals):
        count = np.zeros(vals.shape)
        for i,v in enumerate(vals):
            count[i] = np.sum(vect == v)
        return count

    mesh = aims.read(self.white_mesh.fullPath())
    voronoi_tex = aims.read( self.sulci_voronoi.fullPath() )
    voronoi = np.array( voronoi_tex[0] )
    basins_tex = aims.read( self.basins_texture.fullPath() )
    basins = np.array( basins_tex[0] )
    pits_tex = aims.read( self.pits_texture.fullPath() )

    basins_labeled = np.zeros(basins.shape)
    basins_labels = np.unique(basins)
    context.write(basins_labels)
    context.write('labelling the basins based on majority voting')
    for b_lab in basins_labels:
        b_inds = np.where(basins == b_lab)[0]
        b_sulci_lab = voronoi[b_inds]
        u_b_sulci_lab =np.unique(b_sulci_lab)
        context.write(u_b_sulci_lab)
        bins = valCount(b_sulci_lab,u_b_sulci_lab)
        #bins = np.bincount(b_sulci_lab)
        #context.write(bins)
        perc = 100.0 * bins / len(b_inds)
        context.write(perc)
        basins_labeled[b_inds] = u_b_sulci_lab[np.argmax(perc)]
        #for sulci_lab in u_b_sulci_lab:

    tex_out = aims.TimeTexture_S16()
    tex_out[0].assign(basins_labeled)
    aims.write(tex_out, self.labeled_basins_texture.fullPath())

    context.write('labelling the pits')
    pits = np.array( pits_tex[0] )
    pits_ind = np.where(pits)[0]
    pits[pits_ind] = np.uint16(basins_labeled[pits_ind])
    tex_out = aims.TimeTexture_S16()
    tex_out[0].assign(pits)
    aims.write(tex_out, self.labeled_pits_texture.fullPath())

    # context.write('Done')


from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django import forms
from .import models
from django.contrib.auth import get_user_model
User=get_user_model()
class PostForm(forms.ModelForm):
    class Meta():
        fields=('group','position','to','massage','stars')
        model=models.Post
        widgits={
        'group':forms.TextInput(attrs={"class":'select'}),
        'position':forms.TextInput(attrs={"class":"position"}),
        'massage':forms.Textarea(attrs={"class":"massage"}),
        }
    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.fields['massage'].label = "leave a masssage"
        self.fields['position'].label = "if you are in ieee what is your position?"
        self.fields['group'].label = "you are a "
        self.fields['stars'].label = "rate your IEEE day exporince"
        self.fields['to'].label = "what socity are/were you in"


num = list(map(int, input().split()))

d = {1:"c", 2:"d", 3:"e", 4:"f", 5:"g", 6:"a", 7:"b", 8:"C"}

asc = sorted(d, )
dsc = sorted(d, reverse=True)

if num == asc:
  print('ascending')

elif num == dsc:
  print('descending')

else: print('mixed')


from dotenv import load_dotenv
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from io import StringIO
from pydantic import BaseModel
from typing import Optional
from uuid import UUID

from llama_index import (
    StorageContext,
    load_index_from_storage,
    get_response_synthesizer,
)
from llama_index.agent import OpenAIAgent
from llama_index.indices.postprocessor import SimilarityPostprocessor
from llama_index.indices.vector_store import VectorStoreIndex
from llama_index.llms import OpenAI
from llama_index.prompts import PromptTemplate
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.retrievers import VectorIndexRetriever
from llama_index.tools import FunctionTool, QueryEngineTool, ToolMetadata
from llama_index.vector_stores import SupabaseVectorStore

from supabase import create_client, Client

import csv
import nest_asyncio
import os
import random
import requests
import uvicorn
import uuid

# import logging
# import sys

# logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
# logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))

class UserInput(BaseModel):
    id: Optional[UUID] = None
    query: str

load_dotenv()
nest_asyncio.apply()

ENV = os.environ.get("APP_ENV")
DB_CONNECTION = os.environ.get("DB_CONNECTION")

url: str = os.environ.get("SUPABASE_URL")
key: str = os.environ.get("SUPABASE_KEY")
supabase: Client = create_client(url, key)

os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")

llm = OpenAI(model="gpt-3.5-turbo", temperature=0)

SYSTEM_PROMPT = """\
You are acting as a chatbot that only answers questions about \
companies or estate planning. If the user asks about something else, \
you should respond with a message saying you can only answer questions \
about those topics.
"""

app = FastAPI()

def get_company_sector_industry_market_cap(ticker: str) -> dict[str, str]:
    """Get company sector, industry, or market cap from a ticker"""
    url = (
        "https://raw.githubusercontent.com/axc20/companies/main/universe_2023-10-29.csv"
    )
    response = requests.get(url)
    if response.status_code == 200:
        csv_reader = csv.DictReader(StringIO(response.text))
        data = [row for row in csv_reader]
        try:
            result = [
                {
                    "ticker": item["ticker"],
                    "company": item["company"],
                    "sector": item["sector"],
                    "industry": item["industry"],
                    "marketCap": item["marketCap"],
                }
                for item in data
                if item["ticker"] == ticker
            ][0]
        except IndexError:
            result = {}
    else:
        result = {}

    return result

def get_technology_companies(count: int) -> list(dict[str, str]):
    """Get a random list of technology companies (max 20)"""
    url = (
        "https://raw.githubusercontent.com/axc20/companies/main/universe_2023-10-29.csv"
    )
    response = requests.get(url)
    if response.status_code == 200:
        csv_reader = csv.DictReader(StringIO(response.text))
        data = [row for row in csv_reader]
        result = [item for item in data if item["sector"] == "Technology"]
    else:
        result = []

    companies_list = [
        {
            "ticker": item["ticker"],
            "company": item["company"],
            "industry": item["industry"],
        }
        for item in result
    ]

    return {"result": random.sample(companies_list, min(count, 20)), "source": url}

async def chatbot_event_generator(session_id: str):
    chat_session = (
        supabase.table("chat_sessions")
        .select("*")
        .eq("session_id", session_id)
        .limit(1)
        .single()
        .execute()
    )
    current_messages = chat_session.data["messages"]
    last_user_message = None
    for message in reversed(current_messages):
        if message["type"] == "user":
            last_user_message = message["message"]
            break

    companies_vector_store = SupabaseVectorStore(
        postgres_connection_string=DB_CONNECTION,
        collection_name="ticker_descriptions_demo",
    )
    companies_index = VectorStoreIndex.from_vector_store(
        vector_store=companies_vector_store
    )
    companies_query_engine_tool = QueryEngineTool(
        query_engine=companies_index.as_query_engine(similarity_top_k=2),
        metadata=ToolMetadata(
            name="ticker_descriptions_demo",
            description="useful for when you want to answer questions related to company description",
        ),
    )

    get_company_sector_industry_market_cap_tool = FunctionTool.from_defaults(
        fn=get_company_sector_industry_market_cap
    )
    get_technology_companies_tool = FunctionTool.from_defaults(
        fn=get_technology_companies
    )

    storage_context = StorageContext.from_defaults(
        persist_dir="indices/estate-planning"
    )
    estate_planning_index = load_index_from_storage(storage_context)
    estate_planning_index.set_index_id("estate_planning_demo")
    retriever = VectorIndexRetriever(index=estate_planning_index, similarity_top_k=3)
    response_synthesizer = get_response_synthesizer()
    estate_planning_query_engine = RetrieverQueryEngine(
        retriever=retriever,
        response_synthesizer=response_synthesizer,
        node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.7)],
    )
    estate_planning_query_engine_tool = QueryEngineTool(
        query_engine=estate_planning_query_engine,
        metadata=ToolMetadata(
            name="estate_planning_demo",
            description=(
                "useful for when you want to answer questions anything related to "
                "estate planning (tax exemption sunset, revocable trust, pot trust, "
                "pour-over will, power of attorney)"
            ),
        ),
    )

    tools = [
        get_company_sector_industry_market_cap_tool,
        get_technology_companies_tool,
        companies_query_engine_tool,
        estate_planning_query_engine_tool,
    ]
    agent = OpenAIAgent.from_tools(
        tools, llm=llm, verbose=True, system_prompt=SYSTEM_PROMPT
    )

    response = agent.stream_chat(last_user_message)

    if response.sources[0]:
        function_message = f"CALLING_FUNCTION: {response.sources[0].tool_name}\n\n"
        current_messages = current_messages + [
            {"type": "function", "message": function_message}
        ]
        supabase.table("chat_sessions").update({"messages": current_messages}).eq(
            "session_id", session_id
        ).execute()
        yield function_message

    response_gen = response.response_gen
    chat_response = ""

    for token in response_gen:
        chat_response += token
        yield token

    supabase.table("chat_sessions").update(
        {"messages": current_messages + [{"type": "system", "message": chat_response}]}
    ).eq("session_id", session_id).execute()

@app.get("/")
async def root():
    return {"message": "Hello World"}

@app.post("/chat")
async def stream(user_input: UserInput):
    if user_input.id:
        chat_session = (
            supabase.table("chat_sessions")
            .select("*")
            .eq("session_id", user_input.id)
            .limit(1)
            .single()
            .execute()
        )
        current_messages = chat_session.data["messages"]
        supabase.table("chat_sessions").update(
            {
                "messages": current_messages
                + [{"type": "user", "message": user_input.query}]
            }
        ).eq("session_id", user_input.id).execute()
        return {"session_id": user_input.id}
    else:
        session_id = str(uuid.uuid4())
        supabase.table("chat_sessions").insert(
            {
                "session_id": session_id,
                "messages": [{"type": "user", "message": user_input.query}],
            }
        ).execute()
        return {"session_id": session_id}

@app.get("/stream/{session_id}")
async def stream(session_id: str):
    return StreamingResponse(
        chatbot_event_generator(session_id), media_type="text/event-stream"
    )

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=int(os.environ.get("PORT", "8080")),
        reload=True if ENV == "dev" else False,
    )


# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponseRedirect, Http404
from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
# Responsible for converting description into sharable text.
from urllib.parse import quote_plus
from tags.forms import TagForm
from tags.models import *
from .forms import TotoForm, AddTagsForm
from .models import Toto
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.db.models import Q
from comments.models import Comment
from comments.forms import CommentForm
from accounts.models import UserAccount

def toto_create(request):
	"""
		This makes sure that the form accpets a POST requests (of some data) or Nothing.
		Without this the form would even accept empty totos.
	"""
	if not request.user.is_authenticated():
		raise Http404

	form = TotoForm(request.POST or None, request.FILES or None)
	if form.is_valid():
		instance = form.save(commit=False)
		filter_content(instance.content)
		toto_tags = form.cleaned_data['tags']
		print(toto_tags)
		instance.user = request.user
		instance.save()
		for tag in toto_tags:
			if (len(tag.text.split(' '))>1):
				messages.error(request, "Tags cant contain spaces", extra_tags='')
				continue
			instance.tags.add(tag)
		tags = instance.tags.all()
		print(tags)
		messages.success(request, "Toto created!")
		return HttpResponseRedirect(instance.get_absolute_url())
	else:
		messages.error(request, "Something went wrong", extra_tags="")
	context = {
		'title': "Create",
		'form' : form,
	}
	return render(request, 'write/write.html', context)

def toto_detail(request, slug):
	instance 		= get_object_or_404(Toto, slug=slug)

	if instance.publish > timezone.now() or instance.draft:
		if not request.user == instance.user:
			raise Http404

	tags = instance.tags.all()
	share_string = quote_plus("Hey! I've just started learning from gitall.tech. It's cool. Check them out!!!")
	add_tags_form = AddTagsForm(request.POST or None)

	initial_data = {
			"content_type": instance.get_content_type,
			"object_id": instance.id
	 }

	comment_form = CommentForm(request.POST or None, initial=initial_data)

	if comment_form.is_valid():
		if request.user.is_authenticated:

			c_type = comment_form.cleaned_data.get("content_type")
			content_type = ContentType.objects.get(model=c_type)
			obj_id = comment_form.cleaned_data.get('object_id')
			content_data = comment_form.cleaned_data.get("content")
			parent_obj = None

			try:
				parent_id = int(request.POST.get("parent_id"))

			except:
				parent_id = None

			if parent_id:
				parent_qs = Comment.objects.filter(id=parent_id)
				if parent_qs.exists() and parent_qs.count() == 1:
					parent_obj = parent_qs.first()

			new_comment, created = Comment.objects.get_or_create(
								user = request.user,
								content_type= content_type,
								object_id = obj_id,
								content = content_data,
								parent = parent_obj,
									)
			return HttpResponseRedirect(new_comment.content_object.get_absolute_url())

		else:
			messages.error(request,"You must be logged in to comment!")

	comments = instance.comments

	context = {
		'instance'		: instance,
		'title'			: "Details",
		'share_string' 	: share_string,
		'comments' : comments,
		'comment_form' : comment_form,
		"tags":tags,
	}
	return render(request, 'write/detail.html', context)

def toto_edit(request, slug):
	# This retuns the data (for form) the particular toto
	instance = get_object_or_404(Toto, slug=slug)

	if not request.user == instance.user:
		raise Http404

	# if not request.user.is_superuser or not request.user.is_staff:
	# 	raise Http404

	"""
		without instance=instance part, the form would be an empty form
		instance=instance essentially adds value of the instance to the form
	"""
	form = TotoForm(request.POST or None, request.FILES or None, instance=instance)

	if form.is_valid():
		instance = form.save(commit=False)
		instance.save()
		messages.success(request,"Edited nicely!")
		return HttpResponseRedirect(instance.get_absolute_url())
	else:
		messages.error(request, "Something didn't edit.", extra_tags="")

	context = {
		'title': "Edit",
		'instance' : instance,
		'form': form,
	}
	return render(request, 'write/edit.html', context)

def toto_list(request):
	# queryset_list = Toto.objects.all().order_by("-timestamp")
	# queryset_list = Toto.objects.filter(draft=False).filter(publish__lte=timezone.now())
	# the above command is implemented by using
	queryset_list = Toto.objects.active()

	query = request.GET.get('query')
	if query:
		queryset_list = queryset_list.filter(
				Q(title__icontains=query) |
				Q(content__icontains=query) |
				Q(user__first_name__icontains=query)|
				Q(user__last_name__icontains=query)).distinct()

	paginator = Paginator(queryset_list, 1)

	page = request.GET.get('page')

	try:
		queryset_list = paginator.page(page)
	except PageNotAnInteger:
		queryset_list = paginator.page(1)
	except EmptyPage:
		queryset_list = paginator.page(paginator.num_pages)

	context = {
		"toto_list" : queryset_list,
		"title" : "List",
	}
	# if request.user.is_authenticated():
	# 	context = {
	# 		"tut_list" : queryset,
	# 		"title": "my list",
	# 	}
	# else:
	# 	context = {
	# 		'title' : "list"
	# 	}
	return render(request, 'write/list.html', context)

def toto_delete(request, slug):
	instance = get_object_or_404(Toto, slug=slug)
	if not request.user == instance.user:
		raise Http404
	instance.delete()
	context = {
		'title': "Delete",
	}
	# messages.success(request, "Deteted")
	return redirect("toto:list")

def toto_draft(request):
	query = Toto.objects.draft()

	context = {
		'draft_list' : query,
	}

	return render(request, 'write/draft_list.html', context)

def filter_content(content):
	print(content)

def add_tag(request, slug):
	toto = get_object_or_404(Toto, slug=slug)
	if not request.user.is_authenticated():
		raise Http404
	add_tags_form =  AddTagsForm(data=request.POST)
	if add_tags_form.is_valid():
		form_instance = add_tags_form.save(commit=False)
		toto_tags = add_tags_form.cleaned_data['tags']
		for tag in toto_tags:
			if (len(tag.text.split(' '))>1):
				continue
			toto.tags.add(tag)
	else:
		add_tags_form = AddTagsForm()

	form = TagForm(data=request.POST)
	text = form['text'].value()
	print(text)
	toto = get_object_or_404(Toto, slug=slug)
	all = Tag.objects.all()
	flag = 0
	for a in all:
		if (text==a.text):
			flag = 1
			break
	if form.is_valid():
		instance = form.save(commit=False)
		instance.user = request.user
		instance.save()
	else:
		form =  TagForm()
	context = {
		'title': "Create",
		'form' : form,
		'add_tags_form': add_tags_form,
		'instance': toto,
	}
	return render(request, 'tags/add.html', context)

def delete_tag(request, slug, text):
	instance = get_object_or_404(Toto, slug=slug)
	tag = get_object_or_404(Tag, text=text)
	print(instance.tags.all())
	instance.tags.remove(tag)
	print(instance.tags.all())
	instance.save()
	return redirect("toto:detail", slug=slug)


from django.contrib import admin
from django import forms

from . import models

class SellerAdminForm(forms.ModelForm):

    class Meta:
        model = models.Seller
        fields = "__all__"

class SellerAdmin(admin.ModelAdmin):
    form = SellerAdminForm
    list_display = [
        "url",
        "name",
        "slug",
        "platform",
    ]
    readonly_fields = [
        "url",
        "name",
        "slug",
        "platform",
    ]

class ProductBatchAdminForm(forms.ModelForm):

    class Meta:
        model = models.ProductBatch
        fields = "__all__"

class ProductBatchAdmin(admin.ModelAdmin):
    form = ProductBatchAdminForm
    list_display = [
        "purchase_price_usd",
        "purchase_date",
        "last_updated",
        "created",
        "quantity",
        "purchase_price_pln",
    ]
    readonly_fields = [
        "purchase_price_usd",
        "purchase_date",
        "last_updated",
        "created",
        "quantity",
        "purchase_price_pln",
    ]

admin.site.register(models.Seller, SellerAdmin)
admin.site.register(models.ProductBatch, ProductBatchAdmin)