import random
import matplotlib.pyplot as plt
import numpy as np
import math
import copy
from typing import List
from matplotlib.patches import Arc
from plotly_util import PlotlyUtil

tolerancia = 10 ** -13
lista_colores = ["r", "b", "g", "c", "m", "y", "k"]
lista_espesores = [x / 5 for x in range(10, 20)]

class Nodo(object):
    def __init__(self, x: (int, float), y: (int, float)):
        self.x = x
        self.y = y

    def __eq__(self, otro_nodo):  # Determinar si dos puntos son iguales, bajo el valor de tolerancia
        return self.x - tolerancia <= otro_nodo.x <= self.x + tolerancia and self.y - tolerancia <= otro_nodo.y <= self.y + tolerancia

class ListaDeNodos(object):
    """Objeto que alberga una lista de elementos de clase Nodo."""

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

    def eliminar_duplicados(self):
        resultado = [self.lista_nodos[0]]
        for nodo in self.lista_nodos[1:]:
            if not any(nodo == nodo_b for nodo_b in resultado):
                resultado.append(nodo)
        return resultado

class Segmento(object):
    """Segmento en dos dimensiones definido a partir de 2 Nodos de paso."""

    def __init__(self, nodo_1: Nodo, nodo_2: Nodo):
        self.nodo_1 = nodo_1
        self.nodo_2 = nodo_2
        self.recta_segmento = Recta(nodo_1, nodo_2)
        self.extremos_x, self.extremos_y = self.extremos_segmento()

    def obtener_parametros_ecuacion_recta(self):
        x1, y1 = self.nodo_1.x, self.nodo_1.y
        x2, y2 = self.nodo_2.x, self.nodo_2.y
        a = y1 - y2
        b = x2 - x1
        c = y2 * (x1 - x2) + (y2 - y1) * x2
        return a, b, c

    def determinar_si_nodo_esta_en_rango(self, nodo):
        xmin, xmax = self.extremos_x
        ymin, ymax = self.extremos_y
        return xmin - tolerancia <= nodo.x <= xmax + tolerancia and ymin - tolerancia <= nodo.y <= ymax + tolerancia

    def determinar_si_nodo_pertenece_a_segmento(self, nodo):
        return self.recta_segmento.ecuacion_recta(nodo) == 0 and self.determinar_si_nodo_esta_en_rango(nodo)

    def extremos_segmento(self):
        extramos_x = (min(self.nodo_1.x, self.nodo_2.x), max(self.nodo_1.x, self.nodo_2.x))
        extramos_y = (min(self.nodo_1.y, self.nodo_2.y), max(self.nodo_1.y, self.nodo_2.y))
        return extramos_x, extramos_y

    def obtener_interseccion_recta(self, recta):
        nodo_interseccion_rectas = recta & self.recta_segmento
        if not nodo_interseccion_rectas:  # Paralelas
            return None
        resultado = nodo_interseccion_rectas if self.determinar_si_nodo_esta_en_rango(
            nodo_interseccion_rectas) else None
        return resultado

    def plot(self, ax=None, **kwargs):
        if ax is None:
            plt.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y], **kwargs)
        else:
            ax.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y], **kwargs)

    def __and__(self, otro_segmento):
        result = self.recta_segmento & otro_segmento.recta_segmento  # Buscando interseccion
        if not result:
            return None
        return result if self.determinar_si_nodo_esta_en_rango(
            result) and otro_segmento.determinar_si_nodo_esta_en_rango(result) else None

class Recta(object):
    def __init__(self, nodo_1: Nodo, nodo_2: Nodo):
        self.nodo_1 = nodo_1
        self.nodo_2 = nodo_2
        a, b, c = self.obtener_parametros_ecuacion_implicita(nodo_1, nodo_2)
        self.ecuacion_recta = lambda nodo: a * nodo.x + b * nodo.y + c

        # Definición de métodos a ser usados.
        self.distancia_a_nodo = lambda nodo: (a * nodo.x + b * nodo.y + c) / ((a ** 2 + b ** 2) ** 0.5)
        self.distancia_a_nodo_v = lambda nodo: abs(nodo.y + (c + a * nodo.x) / b) if b != 0 else None
        self.distancia_a_nodo_h = lambda nodo: abs(nodo.x + (c + b * nodo.y) / a) if a != 0 else None

        # Distancia en Vertical y Horizontal, respectivamente.
        self.y = lambda x: -c / b - a / b * x
        self.x = lambda y: -c / a - b / a * y

    @staticmethod
    def obtener_parametros_ecuacion_implicita(nodo_1, nodo_2):
        """Obtiene los parámetros a, b y c de una recta, de manera de describir
        la ecuación de la misma en el plano de la forma a*x+b*y*c (tipo cartesiana o implícita)"""
        x1, y1 = nodo_1.x, nodo_1.y
        x2, y2 = nodo_2.x, nodo_2.y
        a = y1 - y2
        b = x2 - x1
        c = y2 * (x1 - x2) + (y2 - y1) * x2
        return a, b, c

    def mostrar_recta(self):
        plt.plot([self.nodo_1.x, self.nodo_2.x], [self.nodo_1.y, self.nodo_2.y])

    def __and__(self, otra_recta) -> Nodo or None:
        """Realiza la intersección de la recta con otra provista, en el plano.
        :param otra_recta: recta a intersectar.
        :return Nodo de intersección or None si no se encuentran"""
        x1, y1, x2, y2 = self.nodo_1.x, self.nodo_1.y, self.nodo_2.x, self.nodo_2.y
        x3, y3, x4, y4 = otra_recta.nodo_1.x, otra_recta.nodo_1.y, otra_recta.nodo_2.x, otra_recta.nodo_2.y
        det = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)  # Determinante de la matriz de coordenadas
        if -1 * tolerancia <= det <= tolerancia:  # Cuando den tiende a 0, las rectas son paralelas, no hay intersección.
            return None
        # Se aplica la fórmula de Cramer para resolver el sistema de ecuaciones lineal.
        x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / det
        y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / det
        return Nodo(x, y)

class Poligono(object):
    """Polígono CONVEXO en el plano, formado por una serie de nodos."""

    def __init__(self, nodos: List[Nodo], ordenar: bool = False):
        """
        :param nodos: lista de nodos que definen el contorno del polígono.
        :param ordenar: define si la lista de nodos anterior debe ser ordenada en sentido antihorario, siendo dicho
         sentido el convencional para el programa."""

        self.nodos_extremos = nodos if not ordenar else self.ordenar_nodos_poligono_convexo_antihorario(nodos)
        self.x = [nodo.x for nodo in self.nodos_extremos]
        self.y = [nodo.y for nodo in self.nodos_extremos]
        self.total_de_nodos = len(nodos)
        self.segmentos_borde = self.obtener_segmentos_borde()
        valor_area = self.determinar_area_poligono()
        self.area = valor_area
        self.nodo_centroide = self.determinar_centroide()
        self.xg, self.yg = self.nodo_centroide.x, self.nodo_centroide.y
        # Se definen estos atributos para determinar si el elemento fue modificado por una intersección (ver método)
        self.nodos_interseccion_lista = []
        self.numero_de_modificaciones = 0
        self.nodo_centroide_original = copy.copy(self.nodo_centroide)
        self.area_original = valor_area

    @staticmethod
    def ordenar_nodos_poligono_convexo_antihorario(nodos=None):
        """Ordena los nodos en sentido antihorario, cuando el polígono SEA CONVEXO."""
        if nodos is None:
            return None
        x_array = np.array([nodo.x for nodo in nodos])
        y_array = np.array([nodo.y for nodo in nodos])

        # Coordenadas del centroide
        x0 = np.mean(x_array)
        y0 = np.mean(y_array)

        # Calcular las distancias desde el centroide a cada nodo
        r = np.sqrt((x_array - x0) ** 2 + (y_array - y0) ** 2)
        # Calcula el ángulo antihorario con respecto al eje x de cada punto, y ordena en base al mismo.
        angulos = np.where((y_array - y0) > 0, np.arccos((x_array - x0) / r), 2 * np.pi - np.arccos((x_array - x0) / r))
        mask = np.argsort(angulos)
        x_sorted = x_array[mask]
        y_sorted = y_array[mask]

        nodos_ordenados = []
        for i in range(len(x_sorted)):
            nodos_ordenados.append(Nodo(x_sorted[i], y_sorted[i]))
        return nodos_ordenados

    def obtener_segmentos_borde(self):
        """A partir de los nodos que fueron ingresados, obtiene la lista de segmentos de borde."""
        i = 0
        imax = len(self.nodos_extremos)
        lista_de_segmentos = []
        while i < imax:
            nodo_1 = self.nodos_extremos[i]
            nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
            lista_de_segmentos.append(Segmento(nodo_1, nodo_2))
            i = i + 1
        return lista_de_segmentos

    def determinar_area_poligono(self):
        i = 0
        imax = len(self.nodos_extremos)
        area = 0
        while i < imax:
            nodo_1 = self.nodos_extremos[i]
            nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
            area = area + (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
            i = i + 1
        return abs(area / 2)

    def determinar_centroide(self):
        try:
            i = 0
            imax = len(self.nodos_extremos)
            cx, cy = 0, 0
            while i < imax:
                nodo_1 = self.nodos_extremos[i]
                nodo_2 = self.nodos_extremos[i + 1] if i + 1 < imax else self.nodos_extremos[0]
                cx = cx + (nodo_1.x + nodo_2.x) * (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
                cy = cy + (nodo_1.y + nodo_2.y) * (nodo_1.x * nodo_2.y - nodo_2.x * nodo_1.y)
                i = i + 1
            return Nodo(cx / (6 * self.area), cy / (6 * self.area))
        except RuntimeWarning as e:
            print(e)

    def determinar_si_nodo_pertence_a_contorno(self, nodo_a_buscar: Nodo):
        """Devuelve verdadero si el nodo se encuentra dentro del contorno, sin incluir los bordes del mismo."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y nodo 2.
            # La ecuación de la recta devolverá valores del mismo signo para puntos del mismo semiplano.
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception(f"Error de Ingreso de datos: Ingresar puntos en el contorno que no estén alineados", )
            signo_semiplano_elemento = recta.ecuacion_recta(nodo_a_buscar)
            if signo_semiplano_contorno * signo_semiplano_elemento < 0:  # Si son de distinto signo, no pertenece.
                return False
        return True

    def determinar_si_nodo_pertence_a_contorno_sin_borde(self, nodo_a_buscar: Nodo):
        """Devuelve verdadero si el nodo se encuentra dentro del contorno, incluyendo los bordes del mismo."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y nodo 2.
            # La ecuación de la recta devolverá valores del mismo signo para puntos del mismo semiplano.
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception("Error de Ingreso de datos: Ingresar puntos en el contorno que no estén alineados.")
            signo_semiplano_elemento = recta.ecuacion_recta(nodo_a_buscar)
            if signo_semiplano_contorno * signo_semiplano_elemento <= 0:  # Si son de distinto signo, no pertenece.
                return False
        return True

    def obtener_3_nodos_x_indice(self, i_punto_1):
        """
        Obtiene el nodo en cuestión y los 2 nodos subsiguientes, basado en el índice del primer nodo.
        Al quedar algún índice fuera de rango, se obtiene el primer o segundo elemento según corresponda."""
        i_punto_2 = self.obtener_indice(i_punto_1, 1)
        i_punto_3 = self.obtener_indice(i_punto_1, 2)
        return self.nodos_extremos[i_punto_1], self.nodos_extremos[i_punto_2], self.nodos_extremos[i_punto_3]

    def obtener_indice(self, indice_punto_1, valor_a_sumar: int):
        """Obtiene el índice del próximo elemento, dependiendo de valor_a_sumar"""
        i_punto = indice_punto_1 + valor_a_sumar
        return i_punto if i_punto < self.total_de_nodos else i_punto - self.total_de_nodos

    def plot(self, indice_color=None,
             espesor=None,
             mostrar_centroide=False,
             texto_a_mostrar=None,
             tridimensional=False,
             ec_plano_3d=None,
             plt_3d=None,
             transparencia=1,
             ax=None):

        color = random.choice(lista_colores) if indice_color is None else lista_colores[indice_color]
        espesor = random.choice(lista_espesores) if espesor is None else espesor
        x = []
        y = []
        for segmento in self.segmentos_borde:
            x.extend([segmento.nodo_1.x, segmento.nodo_2.x])
            y.extend([segmento.nodo_1.y, segmento.nodo_2.y])

        if not tridimensional:
            plt.plot(x, y, c=color, alpha=transparencia, linewidth=espesor, zorder=0)
        else:  # Figura 3d
            z1 = [0] * len(x)
            z2 = [ec_plano_3d(x[i], y[i]) for i in range(len(x))]
            x = x * 2
            y = y * 2
            z = z1 + z2
            for i in range(len(x) - 1):
                x0, y0, z0 = [x[i], x[i + 1]], [y[i], y[i + 1]], [z[i], z[i + 1]]
                plt_3d.plot(z0, x0, y0,
                            c=color if i != len(x) / 2 - 1 else "w",
                            alpha=transparencia, linewidth=espesor, zorder=0)

        if mostrar_centroide:
            plt.scatter(self.xg, self.yg, c=color, marker=".", zorder=0)
        if texto_a_mostrar:
            plt.text(self.xg, self.yg, texto_a_mostrar)

    def obtener_poligono_interseccion(self, otro_poligono):
        """Obtiene el poligono que resulta de intersectar self con otro_poligono"""
        lista_nodos_interseccion = self & otro_poligono
        if not lista_nodos_interseccion:
            return self
        # Determinar que nodos de otro_poligono pertenecen a self
        for nodo in otro_poligono.nodos_extremos:
            if self.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo):
                lista_nodos_interseccion.append(nodo)
        for nodo_pos in self.nodos_extremos:  # Determinar que nodos de self pertenecen a otro_poligono
            if otro_poligono.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo_pos):
                lista_nodos_interseccion.append(nodo_pos)
        if len(lista_nodos_interseccion) <= 2:
            return self
        lista_nodos_interseccion = ListaDeNodos(lista_nodos_interseccion).eliminar_duplicados()
        return Poligono(lista_nodos_interseccion, ordenar=True)

    def restar_con_otro_poligono(self, otro_poligono):
        """Resta al elemento self el complemento con otro_poligono.
        :param otro_poligono: polígono que se le restará a self.
        :return el mismo elemento self, pero modificado por la resta."""

        lista_de_nodos_de_interseccion = self.obtener_nodos_compartidos_entre_poligonos(self, otro_poligono)

        if len(lista_de_nodos_de_interseccion) <= 2:
            return self

        poligono_interno = Poligono(lista_de_nodos_de_interseccion, ordenar=True)

        if self.area_y_centro_son_iguales(poligono_1=self, poligono_2=poligono_interno):
            # Si la interseccion es igual al mismo poligono, será eliminado.
            return None

        nueva_area = self.area - poligono_interno.area
        nueva_x = (self.area * self.xg - poligono_interno.area * poligono_interno.xg) / nueva_area
        nueva_y = (self.area * self.yg - poligono_interno.area * poligono_interno.yg) / nueva_area

        if not self.nuevo_poligono_es_valido(nueva_area, nueva_x, nueva_y):
            return None
        self.numero_de_modificaciones = self.numero_de_modificaciones + 1

        self.area = nueva_area
        self.xg, self.yg = nueva_x, nueva_y
        self.nodo_centroide = Nodo(nueva_x, nueva_y)
        self.nodos_interseccion_lista = lista_de_nodos_de_interseccion + self.nodos_interseccion_lista
        return self  # self modificado

    @staticmethod
    def nuevo_poligono_es_valido(nueva_area, nueva_x, nueva_y):  # Criterio: área=0|nodo en infinito numérico.
        return not (-tolerancia <= nueva_area <= tolerancia or nueva_x == float("inf") or nueva_y == float("inf"))

    @staticmethod
    def obtener_nodos_compartidos_entre_poligonos(poligono_1, poligono_2):
        """Devuelve una lista de:
        Aquellos nodos que intersectan alguno de los bordes entre los polígonos
        Los nodos de poligono_1 que se encuentran dentro de poligono_2, y viceversa."""

        lista_nodos_interseccion = poligono_1 & poligono_2  # Intersección de bordes
        if not lista_nodos_interseccion:  # no hay intersección
            return []

        for nodo in poligono_2.nodos_extremos:  # Determinar qué nodos de otro_poligono pertenecen a self
            if poligono_1.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo):
                lista_nodos_interseccion.append(nodo)
        for nodo_pos in poligono_1.nodos_extremos:  # Determinar qué nodos de self pertenecen a otro_poligono
            if poligono_2.determinar_si_nodo_pertence_a_contorno_sin_borde(nodo_pos):
                lista_nodos_interseccion.append(nodo_pos)
        nodos_interseccion = ListaDeNodos(lista_nodos_interseccion).eliminar_duplicados()
        return nodos_interseccion

    def __and__(self, otro_poligono):
        """Devuelve los nodos de interseccion de dos poligonos"""
        lista_nodos_interseccion = []
        for segmento_contorno in self.segmentos_borde:
            for segmento_rectangulo in otro_poligono.segmentos_borde:
                interseccion = segmento_contorno & segmento_rectangulo
                if interseccion:
                    nodo_interseccion = Nodo(interseccion.x, interseccion.y)
                    if not (any(nodo_interseccion == nodo_x for nodo_x in lista_nodos_interseccion)):
                        lista_nodos_interseccion.append(nodo_interseccion)
        return lista_nodos_interseccion or None

    def __lt__(self, poligono_mayor):
        """Determina si self esta contenido dentro de poligono mayor"""
        centro_pertenece_a_contorno = poligono_mayor.determinar_si_nodo_pertence_a_contorno(self.nodo_centroide)
        if not centro_pertenece_a_contorno:
            return False
        intersecciones = poligono_mayor & self
        return True if not intersecciones else False

    @staticmethod
    def area_y_centro_son_iguales(poligono_1, poligono_2):
        """Valida si dos poligonos son iguales"""
        return poligono_2.area - tolerancia <= poligono_1.area <= poligono_2.area + tolerancia and \
            poligono_2.xg - tolerancia <= poligono_1.xg <= poligono_2.xg + tolerancia and \
            poligono_2.yg - tolerancia <= poligono_1.yg <= poligono_2.yg + tolerancia

    def desplazar_sistema_de_referencia(self, desp_x, desp_y):
        self.xg = self.xg + desp_x
        self.yg = self.yg + desp_y
        # Para que se muestre la discretización correctamente
        for nodo_extremo in self.nodos_extremos:
            nodo_extremo.x = nodo_extremo.x + desp_x
            nodo_extremo.y = nodo_extremo.y + desp_y
        self.segmentos_borde = self.obtener_segmentos_borde()

class ElementoRectangular(Poligono):
    """El elemento finito que compone a un Polígono mayor.
     Al estar formado por 4 nodos compone en sí un Polígono."""

    def __init__(self, ubicacion_centro: Nodo, medidas: tuple):
        a, b = medidas
        self.lado_x, self.lado_y = a, b
        self.x_centro = ubicacion_centro.x
        self.y_centro = ubicacion_centro.y
        self.ubicacion_centro = ubicacion_centro
        self.medidas = medidas
        self.perimetro = 2 * a + 2 * b
        super().__init__(self.obtener_nodos_extremos())

    def determinar_area_poligono(self):
        return self.lado_x * self.lado_y

    def determinar_centroide(self):
        return self.ubicacion_centro

    def obtener_nodos_extremos(self):
        x, y = self.x_centro, self.y_centro
        a, b = self.lado_x, self.lado_y
        return Nodo(x + a / 2, y + b / 2), Nodo(x + a / 2, y - b / 2), Nodo(x - a / 2, y - b / 2), Nodo(x - a / 2,
                                                                                y + b / 2)

    def obtener_segmentos_borde(self):
        return (
            Segmento(self.nodos_extremos[0], self.nodos_extremos[1]),
            Segmento(self.nodos_extremos[1], self.nodos_extremos[2]),
            Segmento(self.nodos_extremos[2], self.nodos_extremos[3]),
            Segmento(self.nodos_extremos[3], self.nodos_extremos[0])
        )

class ElementoTrapecioCircular():
    def __init__(self, nodo_centro: Nodo, radios: tuple, angulos):
        self.area = math.radians(angulos[1] - angulos[0]) * (radios[1] ** 2 - radios[0] ** 2) / 2
        angulo_medio = math.radians(angulos[1] + angulos[0]) / 2

        self.angulo_inicial, self.angulo_final = min(angulos), max(angulos)
        self.radio_interno, self.radio_externo = min(radios), max(radios)
        self.nodo_centro = nodo_centro
        self.xc = nodo_centro.x
        self.yc = nodo_centro.y

        theta = math.radians(self.angulo_final - self.angulo_inicial) / 2

        # El centroide de un sector circular se define como 2R sin(θ)/(3θ); siendo R el radio y θ la apertura del sector
        area_1 = math.radians(self.angulo_final - self.angulo_inicial) * (self.radio_externo ** 2) / 2
        area_2 = math.radians(self.angulo_final - self.angulo_inicial) * (self.radio_interno ** 2) / 2
        radio_al_centroide = 2 * math.sin(theta) / (3 * theta) * (
                    self.radio_externo * area_1 - self.radio_interno * area_2) / self.area
        self.xg = math.cos(angulo_medio) * radio_al_centroide + self.xc
        self.yg = math.sin(angulo_medio) * radio_al_centroide + self.yc

        self.nodos_extremos = self.obtener_nodos_extremos()
        self.segmentos_rectos = self.obtener_segmentos_rectos()

    def obtener_nodos_extremos(self):
        resultado = []
        if self.radio_interno == 0:  # Circulo
            return []
        for theta in [self.angulo_inicial, self.angulo_final]:
            for r in [self.radio_interno, self.radio_externo]:
                resultado.append(
                    Nodo(self.xc + r * math.cos(math.radians(theta)), self.yc + r * math.sin(math.radians(theta))))
        return resultado

    def obtener_segmentos_rectos(self):
        if len(self.nodos_extremos) == 0:
            return None
        return (
            Segmento(self.nodos_extremos[0], self.nodos_extremos[1]),
            Segmento(self.nodos_extremos[2], self.nodos_extremos[3])
        )

    def nodo_en_elemento(self, nodo: Nodo):
        xc, yc = nodo.x - self.nodo_centro.x, nodo.y - self.nodo_centro.y
        angulo = math.atan2(yc, xc)
        angulo = angulo + 360 if angulo < 0 else angulo  # Sumar 360 para convertir a ángulo con respecto a x.
        radio = math.sqrt(xc ** 2 + yc ** 2)
        return self.angulo_inicial <= angulo <= self.angulo_final and self.radio_interno <= radio <= self.radio_externo

    def desplazar_sistema_de_referencia(self, desp_x, desp_y):
        self.xg = self.xg + desp_x
        self.xc = self.xc + desp_x
        self.yg = self.yg + desp_y
        self.yc = self.yc + desp_y
        self.nodo_centro = Nodo(self.xc, self.yc)
        self.nodos_extremos = self.obtener_nodos_extremos()
        self.segmentos_rectos = self.obtener_segmentos_rectos()

    def plot(self, indice_color, espesor, ax, mostrar_centroide=False, transparencia=1):
        # Coordenadas de los puntos del trapecio circular
        color = random.choice(lista_colores) if indice_color is None else lista_colores[indice_color]
        espesor = random.choice(lista_espesores) if espesor is None else espesor
        style_data = {"color": color, "linewidth": espesor, "alpha": transparencia}

        arco_externo = Arc((self.xc, self.yc),
                           2 * self.radio_externo,
                           2 * self.radio_externo,
                           theta1=self.angulo_inicial,
                           theta2=self.angulo_final,
                           **style_data)
        if self.radio_interno > 0:
            arco_interno = Arc((self.xc, self.yc),
                               2 * self.radio_interno,
                               2 * self.radio_interno,
                               theta1=self.angulo_inicial,
                               theta2=self.angulo_final,
                               **style_data)
            ax.add_patch(arco_interno)

        ax.add_patch(arco_externo)
        if self.segmentos_rectos is not None:
            self.segmentos_rectos[0].plot(ax, **style_data)
            self.segmentos_rectos[1].plot(ax, **style_data)
        if mostrar_centroide:
            plt.scatter(self.xg, self.yg, c=color, marker=".", zorder=0)
        return ax
        # Crear la figura y el gráfico

class ContornoCircular(ElementoTrapecioCircular):
    tipo = "Circular"

    def __init__(self, nodo_centro: Nodo, indice, radios: tuple, angulos: tuple = (0, 360), signo=1):
        self.signo = signo
        super().__init__(nodo_centro, radios, angulos)

    def discretizar_contorno(self, discretizacion_angulo, discretizacion_radio):
        """Define la lista de elementos tipo ElementoRectangular que se encuentran dentro del contorno."""
        n = discretizacion_radio
        funcion_radios = lambda i: self.radio_interno + (self.radio_externo - self.radio_interno) * (
                    1 - (1 - i / n) ** 1.25)

        resultado = []
        radio = self.radio_interno
        radio_final = funcion_radios(1)
        i_inicial = 0

        if self.radio_interno == 0:  # Sector central circular
            resultado.append(ElementoTrapecioCircular(
                nodo_centro=self.nodo_centro,
                angulos=(self.angulo_inicial, self.angulo_final),
                radios=(radio, radio_final / 2)))
            angulo = self.angulo_inicial
            while angulo <= self.angulo_final - discretizacion_angulo:
                resultado.append(ElementoTrapecioCircular(
                    nodo_centro=self.nodo_centro,
                    angulos=(angulo, angulo + discretizacion_angulo),
                    radios=(radio_final / 2, radio_final)))
                angulo = angulo + discretizacion_angulo
            i_inicial = i_inicial + 1

        for i_radios in range(i_inicial, n):
            radio = funcion_radios(i_radios)
            radio_final = funcion_radios(i_radios + 1)
            angulo = self.angulo_inicial
            while angulo <= self.angulo_final - discretizacion_angulo:
                resultado.append(ElementoTrapecioCircular(
                    nodo_centro=self.nodo_centro,
                    angulos=(angulo, angulo + discretizacion_angulo),
                    radios=(radio, radio_final)))
                angulo = angulo + discretizacion_angulo
        return resultado

    def eliminar_elementos_fuera_de_contorno(self, lista_de_elementos):
        """Elimina aquellos elementos definidos preliminarmente que finalmente se encuentran por fuera del contorno."""
        for elemento_diferencial in lista_de_elementos.copy():
            if not self.nodo_en_elemento(elemento_diferencial.nodo_centroide):  # Fuera del semiplano del contorno.
                lista_de_elementos.remove(elemento_diferencial)
        return lista_de_elementos

    def mostrar_contorno_y_discretizacion(self, lista_elementos):
        # Contorno
        fig, ax = plt.subplots()
        for elemento in lista_elementos:
            elemento.plot(ax)
        plt.show()

class Contorno(Poligono):
    """Un contorno es un polígono el cual podrá ser discretizado en elementos tipo ElementoRectangular.
    Además, el atributo signo lleva asignado un significado:
    signo = -1; negativo implica que el contorno es un agujero en la sección final.
    signo = 1; por el contrario, positivo es un contorno con elementos que aportarán a la solución final."""
    tipo = "Poligonal"

    def __init__(self, nodos: list, signo: int, indice, ordenar=False):
        self.signo = signo
        self.indice = indice
        super().__init__(nodos, ordenar=ordenar)

    def discretizar_contorno(self, dx, dy):
        """Define la lista de elementos tipo ElementoRectangular que se encuentran dentro del contorno."""
        lista_de_elementos = self.obtener_lista_de_elementos_preliminar(dx, dy)
        lista_de_elementos.sort(key=lambda elemento: (elemento.y, elemento.x))
        # Elementos con coordenadas no válidas deben ser limpiados
        lista_elem_validos = [elem for elem in lista_de_elementos if not (math.isnan(elem.xg) or math.isnan(elem.yg))]
        return self.eliminar_elementos_fuera_de_contorno(
            lista_elem_validos)  # Limpieza adicional de elementos no validos.

    def obtener_lista_de_elementos_preliminar(self, dx, dy):
        """Obtiene la lista de elementos preliminarmente del rectángulo mayor que contiene al contorno, para luego
         eliminar los restantes."""
        lista_de_elementos = []
        lista_de_direcciones = [(1, 1), (1, -1), (-1, -1), (-1, 1)]
        for direccion in lista_de_direcciones:
            x_partida = self.xg + direccion[0] * dx / 2
            y_partida = self.yg + direccion[1] * dy / 2
            lista_de_elementos = lista_de_elementos + self.elementos_segun_direccion(x_partida, y_partida, dx, dy,
                                                                                direccion)
        return lista_de_elementos

    def elementos_segun_direccion(self, x_partida, y_partida, dx, dy, direccion: tuple):
        """La direccion de avance viene dada por una tupla tipo (±1; ±1), el primer término indicando si es positivo
        en X y el segundo si lo es en Y."""
        x_max, y_max = max(self.x), max(self.y)
        x_min, y_min = min(self.x), min(self.y)
        lista_de_elementos = []
        condicion_y = lambda yp: yp - dy / 2 < y_max if direccion[1] == 1 else yp + dy / 2 > y_min
        condicion_x = lambda xp: xp - dx / 2 < x_max if direccion[0] == 1 else xp + dx / 2 > x_min
        x = x_partida
        y = y_partida
        while condicion_y(y):
            while condicion_x(x):
                rectangulo_diferencial = ElementoRectangular(ubicacion_centro=Nodo(x, y), medidas=(dx, dy))
                rectangulo_diferencial = rectangulo_diferencial.obtener_poligono_interseccion(
                    self)  # recortando con bordes de contorno
                lista_de_elementos.append(rectangulo_diferencial)
                x = x + direccion[0] * dx
            y = y + direccion[1] * dy
            x = x_partida
        return lista_de_elementos

    def eliminar_elementos_fuera_de_contorno(self, lista_de_elementos):
        """Elimina aquellos elementos definidos preliminarmente que finalmente se encuentran por fuera del contorno."""
        for i, nodo in enumerate(self.nodos_extremos):
            nodo_1, nodo_2, nodo_3 = self.obtener_3_nodos_x_indice(i)
            recta = Recta(nodo_1, nodo_2)  # Recta definida por el nodo 1 y 2.
            # La ecuación de la recta devuelve números del mismo signos para puntos en el mismo semiplano
            signo_semiplano_contorno = recta.ecuacion_recta(nodo_3)
            if signo_semiplano_contorno == 0:
                raise Exception(
                    f"Por favor, ingresar puntos en el contorno que no estén alineados. Contorno: {self.indice}")
            for elemento_diferencial in lista_de_elementos.copy():
                signo_semiplano_elemento = recta.ecuacion_recta(elemento_diferencial.nodo_centroide)
                if signo_semiplano_contorno * signo_semiplano_elemento < 0:  # Fuera del semiplano del contorno.
                    lista_de_elementos.remove(elemento_diferencial)
        return lista_de_elementos

    def mostrar_contorno_y_discretizacion(self, lista_elementos):
        # Contorno
        X = [nodo.x for nodo in self.nodos_extremos]
        Y = [nodo.y for nodo in self.nodos_extremos]
        plt.plot(X, Y)
        for elemento in lista_elementos:
            elemento.plot(indice_color=0, espesor=0.5)
        self.plot(indice_color=1)
        plt.title("Contorno y Discretizacion - pre eliminar elementos negativos")
        plt.show()

class SeccionArbitraria(object):
    """Una Sección Genérica será la combinación de objetos tipo Contorno, dando el resultante dependiendo del signo,
    de los mismos."""

    def __init__(self, contornos: dict, dx, dy):
        self.dx, self.dy = dx, dy
        self.contornos_negativos = [contorno for indice_contorno, contorno in contornos.items() if contorno.signo == -1]
        self.contornos_positivos = [contorno for indice_contorno, contorno in contornos.items() if contorno.signo == 1]
        self.elementos = self.obtener_matriz_elementos_positivos()
        self.area, self.xg, self.yg = self.obtener_baricentro_y_area()
        self.cambiar_coordenadas_a_baricentro()
        self.x_min = min(x.xg for x in self.elementos)
        self.x_max = max(x.xg for x in self.elementos)
        self.y_min = min(x.yg for x in self.elementos)
        self.y_max = max(x.yg for x in self.elementos)

    def obtener_matriz_elementos_positivos(self):
        result = []
        for contorno_positivo in self.contornos_positivos:
            if contorno_positivo.tipo == "Poligonal":
                lista_elementos_positivos = contorno_positivo.discretizar_contorno(self.dx, self.dy)
            else:
                lista_elementos_positivos = contorno_positivo.discretizar_contorno(discretizacion_angulo=5,
                                                                                discretizacion_radio=50)
            for elemento_positivo in lista_elementos_positivos:
                if self.elemento_pertenece_a_contorno_negativo(elemento_positivo):
                    continue  # Descartar elemento
                elemento_intersectado = self.obtener_interseccion_elemento_positivo_con_negativo(elemento_positivo)
                if elemento_intersectado:
                    result.append(elemento_intersectado)
        return result

    def elemento_pertenece_a_contorno_negativo(self, elemento_positivo: Poligono):
        for contorno_negativo in self.contornos_negativos:
            if elemento_positivo < contorno_negativo:  # Pertenece COMPLETAMENTE a elemento
                return True
        return False

    def obtener_interseccion_elemento_positivo_con_negativo(self, elemento_positivo):
        for contorno_neg in self.contornos_negativos:
            elemento_positivo = elemento_positivo.restar_con_otro_poligono(contorno_neg)
            if elemento_positivo is None:
                return None
        return elemento_positivo

    def mostrar_contornos_2d(self, ax):
        for contorno_negativo in self.contornos_negativos:
            contorno_negativo.plot(indice_color=2, espesor=2, ax=ax)
        for contorno_positivo in self.contornos_positivos:
            contorno_positivo.plot(ax=ax, indice_color=2, espesor=2)

    def mostrar_contornos_2d_plotly(self, fig):
        plotly_util = PlotlyUtil()
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.contornos_negativos, color="Red", espesor=3, transparencia=1)
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.contornos_positivos, color="Cyan", espesor=4, transparencia=1)

    def mostrar_discretizacion_2d(self, ax):
        for elemento in self.elementos:
            elemento.plot(indice_color=3, espesor=0.2, mostrar_centroide=True, ax=ax, transparencia=0.1)

    def mostrar_discretizacion_2d_plotly(self, fig):
        plotly_util = PlotlyUtil()
        plotly_util.plot_poligono(fig=fig, lista_elementos=self.elementos, color="Cyan", transparencia=0.2, mostrar_centroide=True)

    def mostrar_contornos_3d(self, ecuacion_plano_a_desplazar=None):
        fig = plt.figure()
        ax = fig.add_subplot(projection='3d')
        for contorno_negativo in self.contornos_negativos:
            contorno_negativo.plot(
                indice_color=2, espesor=2, tridimensional=True, ec_plano_3d=ecuacion_plano_a_desplazar,
                plt_3d=ax)
        for contorno_positivo in self.contornos_positivos:
            contorno_positivo.plot(
                indice_color=1, espesor=2, tridimensional=True, ec_plano_3d=ecuacion_plano_a_desplazar,
                plt_3d=ax)

    def mostrar_discretizacion_3d_desplazada(self):
        for elemento in self.elementos:
            elemento.cargar_poligono_para_mostrar(indice_color=3, espesor=2, mostrar_centroide=True)

    def obtener_baricentro_y_area(self):
        area_total = 0
        sx = 0
        sy = 0
        for elemento in self.elementos:
            area_total = area_total + elemento.area
            sx = sx + elemento.area * elemento.xg
            sy = sy + elemento.area * elemento.yg
        return round(area_total, 10), round(sx / area_total, 10), round(sy / area_total, 10)

    def cambiar_coordenadas_a_baricentro(self):
        for elemento in self.elementos:
            elemento.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)
        for contorno_pos in self.contornos_positivos:
            contorno_pos.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)
        for contorno_neg in self.contornos_negativos:
            contorno_neg.desplazar_sistema_de_referencia(desp_x=-self.xg, desp_y=-self.yg)

    @staticmethod
    def plot_to_buf(lista_de_elementos, height=2800, width=2800, inc=0.3):
        data = np.array([(elemento.xg, elemento.yg) for elemento in lista_de_elementos])
        xlims = (data[:, 0].min(), data[:, 0].max())
        ylims = (data[:, 1].min(), data[:, 1].max())
        dxl = xlims[1] - xlims[0]
        dyl = ylims[1] - ylims[0]

        print('xlims: (%f, %f)' % xlims)
        print('ylims: (%f, %f)' % ylims)

        buffer = np.zeros((height + 1, width + 1))
        for i, p in enumerate(data):
            print('\rloading: %03d' % (float(i) / data.shape[0] * 100), end=' ')
            x0 = int(round(((p[0] - xlims[0]) / dxl) * width))
            y0 = int(round((1 - (p[1] - ylims[0]) / dyl) * height))
            buffer[y0, x0] += inc
            if buffer[y0, x0] > 1.0:
                buffer[y0, x0] = 1.0
        return xlims, ylims, buffer

    def obtener_rango_valores_extremos(self):
        lista_x = []
        lista_y = []
        for contorno in self.contornos_positivos:
            for nodo in contorno.nodos_extremos:
                lista_x.append(nodo.x)
                lista_y.append(nodo.y)
        return ((min(lista_x), max(lista_x)), (min(lista_y), max(lista_y)))


import serial
import time
import constants
import random
import chess
import chess.pgn
import chess.svg
import pyttsx3
# import opencv
import threading
# import voice
from command import Command
from collections import deque
from stockfish import Stockfish
from game import Game
from graph import Graph, Node
from enum import Enum
from connection import SerialConnection

class GameMode(Enum):
    VS_AI = 1
    DATABASE = 2
    SANDBOX = 3

gameMode = GameMode.DATABASE
NARRATION = False
move_str = ""
def main():
    players = dict()

    if gameMode == GameMode.VS_AI:
        game = Game()
        stockfish = Stockfish('./stockfish_15_linux_x64_ssse')
        userMove = ''
        userName = ''
            # ------------------------------------------------
        # move_str = input("Black(0) or White(1)?: ")
        move_str = "1"
        # if userName == '':
        #     userName = input("Your Name: ")
        userName = "Dhwani"
        move = None
        # sayStr = userName + ", You've choosen to play white. You are ready to make your move once you hear the beep sound."

        if move_str == '1':

            players['White'] = userName
            players['Black'] = 'Stockfish'
            while True:
                cell1, cell2 = opencv.getUserMoveFromCamera(game.board)
                # print(cell1,cell2)
                move1 = chess.Move.from_uci(cell1+cell2)
                move2 = chess.Move.from_uci(cell2+cell1)
                if move1 in game.board.legal_moves:
                    move = move1
                elif move2 in game.board.legal_moves:
                    move = move2
                else:
                    pass
                    # print("Illegal move", cell1,cell2)
                    # voice.say("Its an illegal move. Try again.")
                if move in game.board.legal_moves:
                    fromPosition = game.graph.chessPositions[chess.SQUARE_NAMES[move.from_square]]
                    toPosition = game.graph.chessPositions[chess.SQUARE_NAMES[move.to_square]]
                    game.updatePiecePosition(fromPosition,toPosition)
                    game.makeMove(move)
                    # movePiecesInBoard(game,move)
                    break
                else:
                    # voice.say("error")
                    pass
        while not game.board.is_game_over():
            stockfish.set_fen_position(game.board.fen())
            move = chess.Move.from_uci(stockfish.get_best_move())
            is_capture = game.board.is_capture(move)

            # print(game.board)
            # if is_capture:
            #     voice.say("This is a capture move")

            movePiecesInBoard(game,move,players)

           # game.makeMove(move)
            is_check = game.board.is_check()
            is_checkmate = game.board.is_checkmate()
            # print(is_check, is_checkmate)
            # if is_checkmate:
            #     voice.say("Checkmate!!")
            # elif is_check:
            #     voice.say("Check!")

            fromSquare = chess.SQUARE_NAMES[move.from_square]
            toSquare = chess.SQUARE_NAMES[move.to_square]

            while not game.board.is_game_over():
                cell1, cell2 = opencv.getUserMoveFromCamera(game.board)
                move1 = chess.Move.from_uci(cell1+cell2)
                move2 = chess.Move.from_uci(cell2+cell1)
                if move1 in game.board.legal_moves:
                    move = move1
                elif move2 in game.board.legal_moves:
                    move = move2
                else:
                    pass
                is_captureMove = game.board.is_capture(move)

                fs = game.graph.chessPositions[chess.SQUARE_NAMES[move.from_square]]
                ts = game.graph.chessPositions[chess.SQUARE_NAMES[move.to_square]]
                if move in game.board.legal_moves:
                    clr = game.board.turn
                    is_qsCastling = game.board.is_queenside_castling(move)
                    is_ksCastling = game.board.is_kingside_castling(move)
                    game.makeMove(move)

                    if is_captureMove:
                        game.updateCaptureMove(fs,ts)
                    elif is_qsCastling:
                        game.updatePiecePosition(fs,ts)
                        game.updateCastling("qs",clr)
                    elif is_ksCastling:
                        game.updatePiecePosition(fs,ts)
                        game.updateCastling("ks",clr)
                    else:
                        game.updatePiecePosition(fs,ts)

                    break
        voice.say("Thanks for playing. Resetting board.")
        game.resetBoard()

    elif gameMode == GameMode.DATABASE:
        pgn = open('Carlsen.pgn')
        while pgn:
            gamePGN = chess.pgn.read_game(pgn)
            game = Game()
            blackPlayer = gamePGN.headers['Black'].split(',')[0]
            whitePlayer = gamePGN.headers['White'].split(',')[0]
            if NARRATION:
                voice.say("Lets look at a game between " + \
                    gamePGN.headers['White'] + " playing white and " + \
                    gamePGN.headers['Black'] + " playing black in " + \
                    gamePGN.headers['Date'] + " at " + gamePGN.headers['Site'])
            moveCount = 0

            player = whitePlayer
            for move in gamePGN.mainline_moves():
                movePiecesInBoard(game,move, gamePGN.headers)
                moveCount += 1
                game.graph.updateGraph(game.board)
            if NARRATION:
                voice.say("That was the final move in the game.")
            game.resetBoard()
            game.probeBoard()
    elif gameMode == GameMode.SANDBOX:
        game = Game()

        while True:
            txt = input()
            print(txt)
            movePiecesInBoard(game,chess.Move.from_uci(txt),None)
            if txt == '1':
                break;

def movePiecesInBoard(game, move, headers=None):
        if NARRATION:
            piecesName = dict()
            piecesName['P'] = 'Pawn'
            piecesName['N'] = 'Knight'
            piecesName['B'] = 'Bishop'
            piecesName['R'] = 'Rook'
            piecesName['Q'] = 'Queen'
            piecesName['K'] = 'King'

        is_capture = game.board.is_capture(move)
        is_kingside_castling = game.board.is_kingside_castling(move)
        is_queenside_castling = game.board.is_queenside_castling(move)
        thisTurn = game.board.turn
        player = ''
        movingCellName = chess.SQUARE_NAMES[move.from_square]
        movingToCellName = chess.SQUARE_NAMES[move.to_square]
        if headers:
            if thisTurn == chess.BLACK:
                player = headers['Black'].split(',')[0]
            else:
                player = headers['White'].split(',')[0]
        else:
            player = move_str
        if NARRATION:
            thingToSay = ''
            if is_capture:
                movingPieceName = piecesName[str.upper((game.board.piece_at(move.from_square)).symbol())]
                movingToPieceName = piecesName[str.upper((game.board.piece_at(move.to_square)).symbol())]
                thingToSay = player + "'s" + movingPieceName + " at " + movingCellName + " captures " + movingToPieceName + " at " + movingToCellName
            elif is_kingside_castling:
                thingToSay = player + " castles kingside"
            elif is_queenside_castling:
                thingToSay = player + " castles queenside"
            else:
                movingPieceName = piecesName[str.upper((game.board.piece_at(move.from_square)).symbol())]
                if movingPieceName == "Pawn":
                    thingToSay = player + " advances " + movingCellName +" pawn to " + movingToCellName
                else:
                    thingToSay = player + " moves " + movingPieceName + " to " + movingToCellName
            threading.Thread(target=voice.say, args=(thingToSay,)).start()

        fromSquare = chess.SQUARE_NAMES[move.from_square]
        toSquare = chess.SQUARE_NAMES[move.to_square]
        game.makeMove(move)

        if is_capture:
            game.captureMove(game.graph.chessPositions[fromSquare], game.graph.chessPositions[toSquare])
        else:
            game.movePieceFromTo(game.graph.chessPositions[fromSquare],game.graph.chessPositions[toSquare])
        if is_kingside_castling:
            if thisTurn == chess.BLACK:
                game.movePieceFromTo(game.graph.chessPositions['h8'], game.graph.chessPositions['f8'])
            elif thisTurn == chess.WHITE:
                game.movePieceFromTo(game.graph.chessPositions['h1'], game.graph.chessPositions['f1'])
        if is_queenside_castling:
            if thisTurn == chess.BLACK:
                game.movePieceFromTo(game.graph.chessPositions['a8'], game.graph.chessPositions['d8'])
            elif thisTurn == chess.WHITE:
                game.movePieceFromTo(game.graph.chessPositions['a1'], game.graph.chessPositions['d1'])

if __name__ == "__main__":
    main()


from django.conf.urls import patterns, include, url
from django.contrib import admin

urlpatterns = patterns('',
                       # Examples:
                       # url(r'^blog/', include('blog.urls')),
                       url(r'^$', 'login.views.landing', name='landing'),
                       url(r'^home/$', 'login.views.home', name='home'),
                       url(r'^account/$', 'login.views.account', name='account'),
                       url(r'^goals/$', 'login.views.goals', name='goals'),
                       url(r'^signup/$', 'login.views.signup', name='signup'),
                       url(r'^guide/$', 'login.views.guide', name='guide'),
                       # Purposefully redirecting to home
                       url(r'^profile/$', 'login.views.home', name='profile'),
                       url(r'^ATMs/$', 'login.views.ATMs', name='ATMs'),
                       url(r'^collection/$', 'login.views.collection', name='collection'),
                       url(r'^.*/?logout[/w]?$', 'login.views.logout', name = 'logout'),
                       url(r'^parent/$', 'login.views.parent', name='parent'),
                       url(r'^references/$', 'login.views.references', name='references'),
                       url(r'^404/$', 'login.views.http404', name='404'),
                       url(r'^admin/', include(admin.site.urls)),

)
# Default 404 and 403 code override
handler404 = 'login.views.http404'
handler403 = 'login.views.http403'


"""New Migration

Revision ID: fd38be600b40
Revises: 7d96ebd19353
Create Date: 2023-04-09 16:19:00.475743

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'fd38be600b40'
down_revision = '7d96ebd19353'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('advertisements', schema=None) as batch_op:
        batch_op.add_column(sa.Column('file_path', sa.String(length=200), nullable=False))
        batch_op.drop_column('adFilePath')

    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    with op.batch_alter_table('advertisements', schema=None) as batch_op:
        batch_op.add_column(sa.Column('adFilePath', sa.VARCHAR(length=200), nullable=False))
        batch_op.drop_column('file_path')

    # ### end Alembic commands ###


import numpy as np
import wave
import sys
import argparse
from contextlib import closing
from struct import pack
from pylab import *
# from itertools import *

nchannels = 0
framerate = 0
nframes = 0
width = 0

def pcm2float(sig, dtype=np.float64):
    """
    Convert PCM signal to floating point with a range from -1 to 1.

    Use dtype=np.float32 for single precision.

    Parameters
    ----------
    sig : array_like
        Input array, must have (signed) integral type.
    dtype : data-type, optional
        Desired (floating point) data type.

    Returns
    -------
    ndarray
        normalized floating point data.
    """

    sig = np.asarray(sig)  # make sure it's a NumPy array
    assert sig.dtype.kind == 'i', "'sig' must be an array of signed integers!"
    dtype = np.dtype(dtype)  # allow string input (e.g. 'f')

    # Note that 'min' has a greater (by 1) absolute value than 'max'!
    # Therefore, we use 'min' here to avoid clipping.
    return sig.astype(dtype) / dtype.type(-np.iinfo(sig.dtype).min)

def read_data(f, verbose, plotf):
    global nchannels, framerate, nframes, width
    with closing(wave.open(f)) as w:
        nchannels = w.getnchannels()
        framerate = w.getframerate()
        nframes = w.getnframes()
        width = w.getsampwidth()

        print("sampling rate = {0} Hz, length = {1} samples, channels = {2}, sample \
            width = {3} bytes".format(framerate, nframes, nchannels, width))
        data = w.readframes(nframes)

    sig = np.frombuffer(data, dtype='<i2')
    sig = sig.reshape(-1, nchannels)
    if (verbose):
        print(sig)

    if (plotf):
        normalized = pcm2float(sig, float32)
        plot(normalized)
        show()

    return sig

def string2dec(str):
    return [ord(c) for c in str]

def padd_message(msg, nframes):
    pad_amount = nframes - len(msg)
    if (pad_amount > 0):
        msg+=[0]*(pad_amount)
    return msg

def normalize_data(data):
    normalized = []
    for arr in data:
        mean = sum(arr)//len(arr)
        normalized.append(mean)
    return normalized

def rot13(msg, normalized):
    for i in range(len(normalized)):
        if normalized[i]+255 < sys.maxsize:
            msg[i]+= normalized[i]
        else:
            msg[i]+= (normalized[i]-255)
    return msg

# TODO: grouper to chunk large files for more efficient writing
# def grouper(n, iterable, fillvalue=None):
#     "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
#     args = [iter(iterable)] * n
#     return zip_longest(fillvalue=fillvalue, *args)

def main():
    global plot_flag
    parser = argparse.ArgumentParser()
    parser.add_argument('-o', '--output', help="Name of file to ouput")
    parser.add_argument('-p', '--plot', action='store_true', help="Show Plot of Audio Channels")
    parser.add_argument('-t', '--text', help="Text to encode" )
    parser.add_argument('-v', '--verbose', action='store_true')
    parser.add_argument('filename', help="The file containing message or in which we wish to encode one.")
    args = parser.parse_args()

    if args.output == None:
        output = 'new.wav'
    else:
        output = args.output

    print('output file: ', output)

    data = read_data(args.filename, args.verbose, args.plot)

    if args.text == None:
        print('frames: {frames}'.format(frames=nframes))
        while True:
            word = input("> ")
            if len(word) <= nframes:
                break
            print('Error: Length of input is greater than {0}'.format(nframes))
    else:
        word = args.text
        if len(word) > nframes:
            print('Error: Length of input is greater than {0}, the max size of a message for this file'.format(nframes))
            return

    msg = padd_message(string2dec(word), nframes)

    msg = rot13(msg, normalize_data(data))

    if(args.verbose):
        print('encode: ', msg)

    transform = np.hstack((data, np.atleast_2d(msg).T))
    transform = np.reshape(transform, (np.product(transform.shape),))

    with closing(wave.open(output, 'w')) as w:
        w.setparams((nchannels+1, width, framerate, nframes, 'NONE', 'not compressed'))
        frames = b''.join(pack('h', int(sample)) for sample in transform)
        w.writeframes(frames)

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


from flask import Flask, request, render_template, send_file, url_for, jsonify, flash, redirect
from werkzeug.utils import secure_filename
import os

# imported custom modules
from Modules import merge_pdfs, watermark_pdfs, image_editor, split_pdf, file_compression, perform_ocr, crop_image, file_conversion, feedback_support

app = Flask(__name__)
app.secret_key = 'Aditya@123    '

# create directories if they don't exist
if not os.path.exists('uploads'):
    os.makedirs('uploads')
if not os.path.exists('downloads'):
    os.makedirs('downloads')

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

# link to function section from navbar
@app.route('/functions')
def functions():
    return render_template('main.html')

# merge pdf route
@app.route('/merge', methods=['GET', 'POST'])
def merge_pdfs_route():
    if request.method == 'POST':
        pdf_files = request.files.getlist('pdfs')
        merger = merge_pdfs.merge_pdfs(pdf_files)

        try:
            merged_filename = 'merged_pdf.pdf'
            merged_path = os.path.join('downloads', merged_filename)
            merger.write(merged_path)
            merger.close()

            # Flash a success message
            flash("PDFs merged successfully!", 'success')

            # Open a window to save the merged PDF file
            return send_file(merged_path, as_attachment=True)
        except Exception as e:
            # Flash an error message
            flash(f"Error: {str(e)}", 'error')
            return redirect(url_for('merge_pdfs_route'))

    return render_template('merge.html', merged_pdf=None)

# Watermark PDFs route
@app.route('/watermark', methods=['POST', 'GET'])
def watermark_pdfs_route():
    if request.method == 'POST':
        watermark_file = request.files['watermark']
        input_pdf = request.files['input_pdf']

        if watermark_file and input_pdf:
            watermark_path = 'uploads/' + watermark_file.filename
            input_pdf_path = 'uploads/' + input_pdf.filename

            watermark_file.save(watermark_path)
            input_pdf.save(input_pdf_path)

            output_filename = f'watermarked_{input_pdf.filename}'
            output_path = os.path.join('downloads', output_filename)

            try:
                watermark_pdfs.apply_watermark(
                    input_pdf_path, watermark_path, output_path)

                # Flash a success message
                flash("PDFs watermarked successfully!", 'success')

                # Open a window to save the watermarked PDF file
                return send_file(output_path, as_attachment=True)

            except Exception as e:
                # Flash an error message if there's an exception
                flash(f"Error: {str(e)}", 'error')

    return render_template('watermark.html', watermark_pdfs=None)

# PDF Splitting route
@app.route('/split_pdf', methods=['POST', 'GET'])
def pdf_split_route():
    if request.method == 'POST':
        if 'pdf_file' in request.files and 'page_range' in request.form:
            pdf_file = request.files['pdf_file']
            page_range = request.form['page_range']

            if pdf_file and page_range:
                try:
                    # Save the uploaded PDF file
                    filename = secure_filename(pdf_file.filename)
                    pdf_path = os.path.join('uploads', filename)
                    pdf_file.save(pdf_path)

                    # Calling the split_pdf function
                    output_path = split_pdf.split_pdf(pdf_path, page_range)

                    if output_path:
                        # Flash a success message
                        flash("PDF split successfully!", 'success')

                        # Open a window to save the split PDF file
                        return send_file(output_path, as_attachment=True)
                    else:
                        # Flash an error message for invalid page range
                        flash("Invalid page range", 'error')
                        return redirect(url_for('pdf_split_route'))
                except Exception as e:
                    # Flash an error message if there's an exception
                    flash(f"Error: {str(e)}", 'error')
                    return redirect(url_for('pdf_split_route'))

    return render_template('split_pdf.html')

# File Compression route
@app.route('/compress', methods=['GET', 'POST'])
def compress_file():
    if request.method == 'POST':
        input_file = request.files['input_file']
        compression_level = request.form['compression_level']

        if input_file:
            # Save the uploaded file to the 'uploads' directory
            filename = secure_filename(input_file.filename)
            file_path = os.path.join('uploads', filename)
            input_file.save(file_path)

            # Call the file_compression function to compress the file
            success, compressed_file_path = file_compression.compress(
                file_path, compression_level)

            if success:
                # Flash a success message and provide the compressed file for download
                flash("File compressed successfully!", 'success')
                return send_file(compressed_file_path, as_attachment=True)

            # If compression fails, flash an error message
            flash("File compression failed. Please try again.", 'error')

    return render_template('compress.html')

# Image Editor route
@app.route('/image_editor', methods=['POST', 'GET'])
def image_editor_route():
    if request.method == 'POST':
        if 'image_file' in request.files:
            image_file = request.files['image_file']
            if image_file.filename != '':
                input_image_path = os.path.join('uploads', image_file.filename)
                image_file.save(input_image_path)

                operation = request.form['operation']
                parameters = {}

                if operation == "resize":
                    parameters['size'] = (800, 600)
                elif operation == "rotate":
                    # Default rotation degree 90 degrees
                    parameters['degrees'] = 90
                elif operation == "sharpen":
                    pass
                elif operation == "brightness":
                    parameters['factor'] = 1.5
                elif operation == "contrast":
                    parameters['factor'] = 1.5
                elif operation == "grayscale":
                    pass
                elif operation == "horizontal_flip":
                    pass
                elif operation == "vertical_flip":
                    pass
                elif operation == "sepia_filter":
                    pass
                elif operation == "grayscale_with_custom_threshold":
                    parameters['threshold'] = 128
                elif operation == "invert_colors":
                    pass
                else:
                    return "Invalid operation. Supported operations: resize, rotate, sharpen, brightness, contrast, grayscale, horizontal_flip, vertical_flip, sepia_filter, custom_threshold, invert_colors"

                edited_image_path = image_editor.edit_image(
                    input_image_path, operation, parameters)

                # Flash a success message
                flash("Image editing successful!", 'success')

                # After applying edits to the image, open a window to save it
                return send_file(edited_image_path, as_attachment=True)

    return render_template('image_editor.html')

# OCR route (Optical Character Recognition)
@app.route('/perform_ocr', methods=['GET', 'POST'])
def perform_ocr_route():
    if request.method == 'POST':
        if 'image_file' in request.files:
            image_file = request.files['image_file']
            if image_file.filename != '':
                # Save the uploaded image
                image_path = os.path.join('uploads', image_file.filename)
                image_file.save(image_path)

                language = request.form.get('language', 'eng')
                threshold = int(request.form.get('threshold', 128))
                segmentation_mode = int(
                    request.form.get('segmentation_mode', 6))

                # Perform OCR and store the result
                ocr_result = perform_ocr.perform_ocr(
                    image_path, language, threshold, segmentation_mode)

                return render_template('ocr.html', ocr_result=ocr_result)

    return render_template('ocr.html', ocr_result=None)

# Image Cropping route
@app.route('/crop_image', methods=['POST', 'GET'])
def crop_image_route():
    if 'image_file' in request.files:
        input_file = request.files['image_file']
        if input_file.filename != '':
            try:
                # Save the uploaded image
                filename = secure_filename(input_file.filename)
                input_path = os.path.join('uploads', filename)
                input_file.save(input_path)

                x = int(request.form.get('x', 0))
                y = int(request.form.get('y', 0))
                width = int(request.form.get('width', 0))
                height = int(request.form.get('height', 0))

                coordinates = (x, y, x + width, y + height)

                # Modify the output path using the uploaded file's name
                output_path = os.path.join('uploads', 'cropped_' + filename)

                if crop_image.crop_image(input_path, output_path, coordinates):
                    # Flash a success message
                    flash("Image cropping successful!", 'success')
                    return send_file(output_path, as_attachment=True)
                else:
                    # Flash an error message for failed cropping
                    flash("Image cropping failed.", 'error')
            except Exception as e:
                # Flash an error message if there's an exception
                flash(f"Error: {str(e)}", 'error')

    return render_template('crop_image.html')

# File Conversion route
@app.route('/convert_file', methods=['POST', 'GET'])
def convert_file_route():
    if 'input_file' in request.files:
        input_file = request.files['input_file']
        if input_file.filename != '':
            try:
                # Save the uploaded file with a secure filename in the 'uploads' folder
                filename = secure_filename(input_file.filename)
                input_path = os.path.join('uploads', filename)
                input_file.save(input_path)

                # Get input and output formats
                input_format = request.form.get('input_format', 'png')
                output_format = request.form.get('output_format', 'png')

                # Check if the conversion is supported
                if (input_format, output_format) in file_conversion.convert_file:
                    # Generate output filename
                    output_filename = f'converted_file.{output_format}'

                    # Output file path in the 'uploads' folder
                    output_path = os.path.join('uploads', output_filename)

                    # Call the conversion function using the dictionary
                    conversion_function = file_conversion.convert_file[(
                        input_format, output_format)]
                    success = conversion_function(input_path, output_path)

                    if success:
                        # Flash a success message
                        flash("File conversion successful!", 'success')

                        # Open a window to save the converted file
                        return send_file(output_path, as_attachment=True)
                    else:
                        # Flash an error message if the conversion fails
                        flash("File conversion failed", 'error')
                else:
                    flash("Unsupported conversion: {} to {}".format(
                        input_format, output_format), 'error')
            except Exception as e:
                # Flash an error message if there's an exception
                flash(f"Error: {str(e)}", 'error')

    return render_template('conversion.html', ocr_result=None)

# Feedback Route
@app.route('/feedback', methods=['GET', 'POST'])
def feedback_route():
    if request.method == 'POST':
        feedback = request.form['feedback']
        feedback_support.store_feedback(feedback)
        flash("Feedback submitted successfully!", "success")
        return redirect(url_for('feedback_route'))

    return render_template('feedback_form.html')

# feedback submit

@app.route('/submit_feedback', methods=['POST'])
def submit_feedback():
    if request.method == 'POST':
        feedback = request.form['feedback']
        feedback_support.store_feedback(feedback)
        flash("Feedback submitted successfully!", "success")
        return redirect(url_for('feedback_route'))
    else:
        return redirect(url_for('feedback_route'))

# 404 Error Page
@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

if __name__ == '__main__':
    app.run(debug=True)


from collections import deque
N = int(input())
graph = [list(map(int, input().split())) for _ in range(N)]

# 방문 유무 확인
visited = [[False]*3 for _ in range(N)]

# 오른쪽만 가능
dx = [1,0]

# 아래만 가능
dy = [0,1]

def bfs(x, y):

  q = deque()
  q.append([x,y])

  while q:
    x, y = q.popleft()
    # 쪨리가 현재 밟고 있는 칸의 숫자를 인출
    step = graph[x][y]

    # 끝점이 -1이므로 -1로 도달하면 성공
    if graph[x][y] == -1:
      return True
    for move in range(2):
      # 쩰리가 현재 밟고 있는 숫자를 인출하고 이동수에 곱해서 이동
      nx = x+dx[move]*step
      ny = y+dy[move]*step

       # 주어진 범위를 벗어나는 경우 무시
  	   # x, y의 범위가 0,0보다 작아지거나, 주어진 matrix의 크기를 초과하는 경우
      if nx < 0 or nx >= N or ny < 0 or ny >= N:
        continue

      # 방문하지 않았을경우만
      if not visited[nx][ny]:
      	# 방문으로 수정
        visited[nx][ny] = True
        # 아직 우리가 찾는 조건(graph[x][y] == -1)이 아니므로 q에 담아주고 다시 반복
        q.append([nx, ny])

if bfs(0,0):
    print('HaruHaru')
else:
    print('Hing')


import discord
from discord.ext import commands
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
from text_Finder import Finder
import time

client = commands.Bot(command_prefix='!')
options = webdriver.ChromeOptions()
options.add_argument("headless")
options.add_argument("log-level=3")
driver=webdriver.Chrome(r"C:/Users/athul/Downloads/chromedriver_win32/chromedriver.exe", chrome_options=options)

@client.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))

@client.command()
async def search(ctx, arg):
    counter = 370
    status = True
    while status:
        driver.get("https://api.bestbuy.com/v1/products((search=" + str(arg) +"))?apiKey=fbHpo1XU31MYqyDvPNeczIzQ&sort=onlineAvailability.asc&show=orderable,name,addToCartUrl&pageSize=24&format=json")

        avail = []
        names = []
        url = []
        stock = []
        o = 0
        ns = 0
        us = 0
        page=driver.page_source
        page = Finder.noEnd(str(page), '"products":[')
        for c, v in enumerate(str(page)):
            if page[c:c+9] == 'orderable':
                o = c + 12
            if page[c:c+4] == 'Sold':
                avail.append("Sold Out")
            elif page[c:c+9] == 'Available':
                avail.append("In Stock")

        for c, v in enumerate(str(page)):
            if page[c:c+4] == "name":
                ns = c + 7
            if page[c:c+5] == "addTo":
                names.append(str(page)[ns:c-3])

        for c, v in enumerate(str(page)):
            if page[c:c+12] == "addToCartUrl":
                us = c + 15
            if page[c:c+4] == "cart":
                url.append(str(page)[us:c+4])

        for c, v in enumerate(avail):
            stock.append("\nNAME: " + names[c] + "\nSTOCK: " + avail[c] + "\nLINK: " + url[c])

        time.sleep(2)

        if counter > 360:
            embed=discord.Embed(title="STOCK CHECKER", color=0xEC5252)
            #description="These are probably not in stock"
            for i in stock:
                embed.add_field(name="-", value=i, inline=False)
            embed.set_footer(text="We are not associated with Bestbuy")
            await ctx.send(embed=embed)
            counter = 0

        for c, v in enumerate(avail):
            if v == "In Stock":
                embed=discord.Embed(title="STOCK CHECKER", description="BUY NOW", color=0xEC5252)
                embed.add_field(name="ITEM: ", value=names[c], inline=False)
                embed.add_field(name="IN STOCK: ", value=url[c], inline=False)
                #embed.set_footer(text="If we are still pinging, the cards are still in stock")
                await ctx.send(embed=embed)
                await ctx.channel.send("@everyone")
                #check = False
                status = False

        counter += 1

client.run('ODQyMTM0Mzk3NTE1MDcxNTE4.YJw4xg.lBgTauBk_UdQowWZEgo-U9Pq6dQ')


"""

Classes
^^^^^^^

"""

import sys

import numpy as np

import matplotlib.pyplot as plt

from NAMDAnalyzer.helpersFunctions.objectConverters import fromSliceToArange

class ResidenceTime:
    """ This class defines methods to compute retention time of atoms
        in a certain region. This determines how fast atoms in sel2 can
        leave the vicinity of sel1.

        Some plotting methods are also available to quicly check the results.

        :arg data:       a :class:`Dataset` class instance containing
                         trajectories data
        :arg sel:        selection corresponding to analysis region
                         (with 'within' keyword')
        :arg tMax:       maximum number of frames to be used
        :arg step:       time interval between each computed frame
        :arg nbrTimeOri: number of time origins to be averaged over
                         (optional, default 20)

    """
    def __init__(self, data, sel, tMax=25, step=1, nbrTimeOri=20, frames=slice(0, None)):

        self.data       = data
        self.sel        = sel
        self.tMax       = tMax
        self.step       = step
        self.nbrTimeOri = nbrTimeOri
        self.frames     = fromSliceToArange(frames, data.nbrFrames)

        self.resTime     = None
        self.times       = None
        self.residueWise = None
        self.residues    = None

# --------------------------------------------
# Computation methods
# --------------------------------------------
    def compResidenceTime(self):
        """ For each frame in the range of tMax with given step,
            the number of atoms in selected region is computed and divided
            by the number of atoms at time origin. The result is averaged over
            multiple time origins.

            The result is stored in *resTime* attribute.

        """
        self.times = (np.arange(0, self.tMax, self.step, dtype=int)
                      * self.data.dcdFreq[0] * self.data.timestep * 1e12)
        corr       = np.zeros_like(self.times)

        deltaTime = self.frames[-1] - self.frames[0]
        oriList = self.frames[0] + ((deltaTime - self.tMax)
                   * np.random.random(self.nbrTimeOri)).astype(int)

        for idx, frame in enumerate(oriList):

            print("Processing time origin %i of %i with %i frames..."
                  % (idx + 1, oriList.size, self.tMax / self.step), end='\r')

            sel = self.data.selection(
                self.sel + " frame %i:%i:%i"
                % (frame, frame + self.tMax, self.step))

            for tIdx, keepIdx in enumerate(sel):
                corr[tIdx] += np.intersect1d(sel[0], keepIdx).size

        self.resTime = corr / corr[0]

    def compResidueWiseResidenceTime(self, dt=25):
        """ Computes, for a given time dt, the residence time for
            selected atoms around each residue in the protein.

            By default all atoms pertaining to protein are selected.
            If different proteins are present, the *segName* argument
            can be used in selection text.

            :arg dt: time step to compute residence time. Basically,
                     the number of selected molecules wihtin the given
                     region at initial time divides the number that stayed
                     within the region after a time dt (in number of frames).

            The result is stored in *residueWise* attribute

        """

        # Gets residue number list
        resSel = self.sel[self.sel.find('of ') + 3:]
        resSel = self.data.selection(resSel).getUniqueResidues()
        resSel = np.array([val for val in resSel if val.isdecimal()])

        corr = np.zeros_like(resSel, dtype=float)

        deltaTime = self.frames[-1] - self.frames[0] + 1
        oriList = self.frames[0] + ((deltaTime - self.tMax)
                   * np.random.random(self.nbrTimeOri)).astype(int)

        for resId, residue in enumerate(resSel):
            for idx, frame in enumerate(oriList):
                print("Processing time origin %i of %i for "
                      "residue %i of %i...        "
                      % (idx + 1, oriList.size, resId + 1,
                         int(resSel.size)), end='\r')

                # Processing selection text
                selText = self.sel

                selText += ' and resid %s' % residue

                sel = self.data.selection(
                    selText + " frame %i %i" % (frame, frame + dt))

                corr[resId] += np.intersect1d(sel[0], sel[1]).size

        self.residueWise    = corr / np.max(corr)
        self.residues       = resSel.astype(int)
        self.residueWise_dt = dt

# --------------------------------------------
# Plotting methods
# --------------------------------------------
    def plotResidenceTime(self):
        """ Used to quickly plot residence time. """

        fig, ax = plt.subplots()

        ax.plot(self.times, self.resTime, marker='o')
        ax.set_xlabel('Time [ps]')
        ax.set_ylabel('P(t)')

        fig.show()

    def plotResidueWiseResidenceTime(self):
        """ Used to quickly plot residue wise residence time. """

        fig, ax = plt.subplots()

        ax.bar(self.residues, self.residueWise)
        ax.set_xlabel('Residue')
        ax.set_ylabel('P(dt=%i ps)'
                      % np.ceil(self.residueWise_dt
                                * self.data.timestep
                                * self.data.dcdFreq[0] * 1e12))

        fig.show()


# *-* coding=utf-8
from xml.etree.ElementTree import ElementTree
from xml.etree.ElementTree import Element
from xml.etree.ElementTree import SubElement

import xml.etree.ElementTree as etree

def createNode(nodeRoot,id, tagname,x,y,w,h,role,ref=''):
    node= Element('node',{'id':id,'tagname':tagname,'x':x,'y':y,'w':w,'h':h,'role':role,'ref':ref})
    data =Element('data')
    node.append(data)
    # SubElement(node,'', attrib)
    nodeRoot.append(node)

def createPValue(pValueRoot,pvid,name,adressPV,accesstypePV,datatypePV,description='empty'):
    pValue = Element('pvalue',{'pvid':pvid,'name':name,'description':description})
    SubElement(pValue,'adress').text=adressPV
    SubElement(pValue, 'accsesstype').text=accesstypePV
    SubElement(pValue,'datatype').text=datatypePV
    pValueRoot.append(pValue)

def createPC(ePCRoot,type,server):
    pConnect=Element('pconnection')
    pConnect.set('type',type)
    pConnect.set('server',server)
    ePCRoot.append(pConnect)
    createPValue(pConnect,'54564218', 'testi','sdf54652434235644','READWRITE','REAL')

def createGraph(graphRoot,id,name,width, height):
    graph=Element('graph')
    graph.set('id',id)
    graph.set('name',name)
    graph.set('w',width)
    graph.set('h',height)
    graphRoot.append(graph)
    createNode(graph, 'sd5s65d', 'L001', '5', '56', '66', '56', 'pump')

def indent(elem,level=0):
    i ="\n"+level*"    "
    print (elem);
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = i + "    "
        for e in elem:
            print (e)
            indent(e,level+1)
        if not e.tail or not e.tail.strip():
            e.tail =i
    if level and (not elem.tail or not elem.tail.strip()):
        elem.tail =i
    return elem

filename='hmi.xml'

root=Element('hmi')
root.set('version', '1.0')
tree=ElementTree(root)

createPC(root,'OPCUA','localhost')
createGraph(root,'5454746484','filter','1920','1024')

#root.set('id','123')
indent(root)
print(etree.tostring(root))
tree.write(open(filename,'wb+'),xml_declaration=True, encoding='UTF-8')

"""

filename="hmi.xml"

def CreateXml():

    hmiXML=ElementTree()
    purOrder =Element("hmi")

    book._setroot(purOrder)

    generated_on = str(datetime.datetime.now())

    list = Element("account",{'id':'2390094'})
    purOrder.append(list)
    item = Element("item1",{"Adresse":"abcd","Attribute":"4"})
    SubElement(item,"Name").text="Ventil1"
    SubElement(item,"Description").text="Wasserleitung1"
    purOrder.append(item)

    item = Element("item2",{"Adresse":"gfhi","attribute":"40"})
    SubElement(item,"Name").text="Ventil2"
    SubElement(item,"Description").text="Wasserleitung2"
    purOrder.append(item)

    indent(purOrder)
    return hmiXML

if __name__ == '__main__':
    hmiXML =CreateXml()
    hmiXML.write(filename,"utf-8")
    #book.write("book2.xml","utf-8",True) #true is with xml declaration
"""


from decimal import Decimal

from django.db import models
from django.db.models import Sum

# Create your models here.
from users.models import User
from generic.models import GenericModel
from django.contrib.postgres.fields import JSONField
from django.utils.translation import ugettext_lazy as _

class Payment(GenericModel):
    TESTING = 0
    STRIPE = 1
    BRAINTREE = 2
    PAYPAL = 3

    PLATFORMS = [
        (TESTING, _('Testing')),
        (STRIPE, _('Stripe')),
        (BRAINTREE, _('Braintree')),
        (PAYPAL, _('Paypal'))
    ]

    TEST = 0
    CARD = 1
    SEPA = 2
    ALIPAY = 3
    WECHAT = 4
    AMEX = 5
    GIROPAY = 6
    SOFORT = 7

    PROVIDERS = [
        (TEST, _('Test')),
        (CARD, _('Card')),
        (SEPA, _('SEPA Direct Debit')),
        (ALIPAY, _('Alipay')),
        (WECHAT, _('WeChat')),
        (AMEX, _('Amex Express Checkout')),
        (GIROPAY, _('Giropay')),
        (SOFORT, _('SOFORT')),
    ]

    platform = models.PositiveSmallIntegerField(
        PLATFORMS, default=STRIPE)
    provider = models.PositiveSmallIntegerField(
        PROVIDERS, default=CARD
    )
    paid = models.NullBooleanField(
        default=None
    )

    request = JSONField()
    response = JSONField(blank=True, null=True)

    owner = models.ForeignKey(
        User,
        related_name='user_payments',
        blank=False,
        null=False
    )
    topic = models.ForeignKey(
        'core.Topic',
        blank=True,
        null=True
    )

    def __str__(self):
        PROVIDER = None
        for provider in self.PROVIDERS:
            if provider[0] == self.provider:
                PROVIDER = provider[1]
        PLATFORM = None
        for platform in self.PLATFORMS:
            if platform[0] == self.platform:
                PLATFORM = platform[1]
        return "[{}] {} ({}) payment by {}".format(
            self.created_date,
            PLATFORM, PROVIDER, self.owner.username)

class Reserve(GenericModel):
    """
    Defines a purchase and expenses of infinity hours credit
    reserve by a user from the managing organization, that runs
    an infinity server, as a credit institution.

    1. If it is a purchase of infinity hours, then payment__not=None.

    And the filled out fields are:
        payment
        hours (+) (positive)
        hour_price,
        currency_price,
        currency,
        amount
        user

    2. If it is an expense of infinity hours, then transaction__not=None.

    And the filled out fields are:
        transaction
        hours (-) (negative)
        user

    The reserve is used (2.), if the daily credit balance is insufficient
    to cover the investment (exceeds the daily limit of free credit).

    The purchased hours are going to be added to the daily limit of
    the user who purchased it. The purchased hours reserve is treated
    as an extra reserve in addition to the daily credit limit

    The user can use the extra hours anytime, when daily limit is not
    enough. When this extra reserve is used up, the user remains to
    have just daily limit.

    The below fields define the number of hours
    purchased by the user with given payment
    at the price of hour_price and currency_price.
    """

    hours = models.DecimalField(
        default=0.,
        decimal_places=8,
        max_digits=20,
        blank=False,
        null=False
    )

    user = models.ForeignKey(
        User,
        related_name='buyer',
        blank=False,
        null=False
    )

    payment = models.OneToOneField(
        Payment,
        blank=True,
        null=True,
    )

    hour_price = models.ForeignKey(
        'transactions.HourPriceSnapshot',
        related_name='hour_prices',
        blank=True,
        null=True
    )
    currency_price = models.ForeignKey(
        'transactions.CurrencyPriceSnapshot',
        related_name='currency_prices',
        blank=True,
        null=True
    )

    '''
    These below facts are derived from the
    details of the Payment model's fields.
    '''

    currency = models.ForeignKey(
        'transactions.Currency',
        related_name='currencies',
        blank=True,
        null=True
    )

    amount = models.DecimalField(
        default=0.,
        decimal_places=8,
        max_digits=20,
        blank=True,
        null=True
    )

    transaction = models.OneToOneField(
        'transactions.Transaction',
        related_name='transactions',
        blank=True,
        null=True,
    )

    topic = models.ForeignKey(
        'core.Topic',
        blank=True,
        null=True
    )

    #TODO: Later remove with better tests
    is_test = models.BooleanField(
        default=True
    )

    @classmethod
    def user_purchased(cls, user):
        """
        Returns amount of reserve hours that a given user has accumulated.
        """
        return Decimal(
            cls.objects.filter(
                user=user, payment__isnull=False).aggregate(total=Sum('hours')).get('total')
            or 0)

    @classmethod
    def user_expended(cls, user):
        """
        Returns amount of reserve hours that a given user has expended.
        """
        return Decimal(
            cls.objects.filter(
                user=user, transaction__isnull=False).aggregate(total=Sum('hours')).get('total')
            or 0)

    @classmethod
    def user_reserve_remains(cls, user):
        return Reserve.user_purchased(user) + Reserve.user_expended(user)


# Definition for singly-linked list.
from typing import Optional

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if not head:
            return False

        slow = head
        fast = head.next

        while slow != fast:
            if fast is None or fast.next is None:
                return False
            fast = fast.next.next
            slow = slow.next
        return True