"""Module contains chess board logic"""
import tkinter as tk
import json

from utils.logger import LOGGER
from utils.bases import Square
from utils.pieces import Pawn, Rook, Knight, Bishop, Queen, King

# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-public-methods

logger = LOGGER.get_logger('board')

class Board:
    """Contains logic to render and resize board"""

    turn = 0
    board_size = 600

    @classmethod
    def inc_turn(cls):
        """Increments turn"""

        cls.turn += 1

    @classmethod
    def update_board_size(cls, size):
        """Updates board size based on size input"""

        cls.board_size = size

    def __init__(self, master):
        self.master = master
        self.canvas = tk.Canvas(self.master,
            width=self.board_size, height=self.board_size)
        Square.update_length(self.board_size / 8)
        self.squares = self.init_squares()
        self.pieces = self.init_pieces()
        self.selected_piece = None
        self.square = None
        self.piece = None
        self.draw_squares()
        self.draw_pieces()
        self.canvas.bind("<Button-1>", self.left_click)
        self.canvas.bind("<Configure>", self.on_resize)
        self.canvas.pack()
        # self.label = tk.Label(self.master, text=self.turn_color.title(), fg="red")
        # self.label.pack()

    @property
    def selected_piece(self):
        """Return private copy of selected piece"""

        return self.__selected_piece

    @selected_piece.setter
    def selected_piece(self, piece):
        """Sets private copy of selected piece to current piece"""

        if piece:
            self.square.is_selected = True
            self.__selected_piece = piece
            self.valid_moves = self.get_valid_moves()
        else:
            self.__selected_piece = None
            self.valid_moves = []

    @property
    def turn_color(self):
        """Returns team color based on turn index"""

        return "white" if not self.turn % 2 else "black"

    def left_click(self, event):
        """Event handler for left click"""

        self.reset_squares()

        row = event.y // Square.length
        col = event.x // Square.length

        self.square = self.find_square(row, col)
        self.piece = self.find_piece(row, col)

        self.display()

        # no piece selected previously
        if not self.selected_piece:
            if self.piece:
                # a piece was clicked, selecting
                if self.turn_color == self.piece.color:
                    # correct color chosen based on turn color
                    self.selected_piece = self.piece
                else:
                    # incorrect color chosen based on turn color
                    logger.warning(f"It's {self.turn_color}'s turn!")
            else:
                # square clicked, deselecting
                self.selected_piece = None
        else:
            # piece selected previously
            if self.piece:
                # a piece was clicked
                if self.selected_piece.color == self.piece.color:
                    # another piece on same team selected (reselect)
                    self.selected_piece = self.piece
                else:
                    # enemy piece was clicked
                    if (row, col) in self.valid_moves:
                        # enemy piece is captured
                        self.move_piece(row, col)
                        self.remove_piece(self.piece)
                        Board.inc_turn()
                    else:
                        # enemy piece is clicked, deselect
                        self.selected_piece = None
            else:
                # a square was clicked
                if (row, col) in self.valid_moves:
                    # square is valid, moving piece
                    self.move_piece(row, col)
                    Board.inc_turn()
                else:
                    # square is invalid, deselect
                    self.selected_piece = None

        # redraw updated board
        self.draw_squares()
        self.draw_pieces()
        # self.label.destroy()
        # self.label = tk.Label(self.master, text=self.turn_color.title(), fg="red")
        # self.label.pack()

    def on_resize(self, _):
        """Event handler for window resize"""

        width = self.master.winfo_width()
        height = self.master.winfo_height()
        new_size = width if width < height else height
        self.board_size = new_size - (new_size % 8)
        self.canvas.config(width=self.board_size, height=self.board_size)
        self.master.geometry(f"{self.board_size}x{self.board_size}")
        Square.update_length(self.board_size / 8)
        self.draw_squares()
        self.draw_pieces()

    def draw_squares(self):
        """Draws squares to board"""

        for square in self.squares:
            square.draw(self.canvas)

    def draw_pieces(self):
        """Draws pieces to board"""

        for piece in self.pieces:
            piece.draw(self.canvas)

    def find_square(self, row, col):
        """Locates a square on the board based on input"""

        found_square = None
        for square in self.squares:
            if square.row == row and square.col == col:
                found_square = square
                break

        return found_square

    def find_piece(self, row, col):
        """Locates a piece on the board based on input"""

        found_piece = None
        for piece in self.pieces:
            if piece.row == row and piece.col == col:
                found_piece = piece
                break

        return found_piece

    def reset_squares(self):
        """Resets state of squares"""

        for square in self.squares:
            square.reset_state()

    def get_valid_moves(self):
        """Retrieves valid moves for selected piece"""

        if self.selected_piece.name == "Knight":
            validate_func = self.validate_knight
        elif self.selected_piece.name == "King":
            validate_func = self.validate_king
        elif self.selected_piece.name == "Pawn":
            validate_func = self.validate_pawn
        elif self.selected_piece.name == "Queen":
            validate_func = self.validate_queen
        elif self.selected_piece.name == "Rook":
            validate_func = self.validate_rook
        elif self.selected_piece.name == "Bishop":
            validate_func = self.validate_bishop
        else:
            raise Exception("Unidentified piece.")

        valid_moves = validate_func()
        return valid_moves

    def remove_piece(self, piece):
        """Removes piece from board"""

        piece_id = self.pieces.index(piece)
        self.pieces.pop(piece_id)

    def move_piece(self, row, col):
        """Moves selected piece to coordinates"""

        if self.selected_piece.name == "Pawn":
            self.selected_piece.has_not_moved = False
        self.selected_piece.row = row
        self.selected_piece.col = col
        self.selected_piece = None
        self.valid_moves = []

    def validate_pawn(self):
        """Returns valid moves for pawn"""

        (diagonals, straight) = self.selected_piece.get_possible_moves()

        valid_moves = []
        for move in diagonals:
            pos_square = self.find_square(*move)
            pos_piece = self.find_piece(*move)
            if pos_piece:
                if pos_piece.color != self.selected_piece.color:
                    # enemy piece, attack
                    pos_square.is_threat = True
                    valid_moves.append(move)

        for move in straight:
            pos_square = self.find_square(*move)
            pos_piece = self.find_piece(*move)
            if not pos_piece:
                pos_square.is_possible = True
                valid_moves.append(move)
            else:
                break

        return valid_moves

    def validate_rook(self):
        """Returns valid moves for rook"""

        moves = self.selected_piece.get_possible_moves()

        valid_moves = []
        for _moves in moves:
            for move in _moves:
                pos_square = self.find_square(*move)
                pos_piece = self.find_piece(*move)
                if pos_piece:
                    if pos_piece.color != self.selected_piece.color:
                        # enemy piece
                        pos_square.is_threat = True
                        valid_moves.append(move)
                    break
                pos_square.is_possible = True
                valid_moves.append(move)

        return valid_moves

    def validate_knight(self):
        """Returns valid moves for knight"""

        moves = self.selected_piece.get_possible_moves()

        valid_moves = []
        for move in moves:
            pos_square = self.find_square(*move)
            pos_piece = self.find_piece(*move)
            if pos_piece:
                if pos_piece.color != self.selected_piece.color:
                    # enemy piece, attack
                    pos_square.is_threat = True
                    valid_moves.append(move)
            else:
                pos_square.is_possible = True
                valid_moves.append(move)

        return valid_moves

    def validate_bishop(self):
        """Returns valid moves for bishop"""

        moves = self.selected_piece.get_possible_moves()

        valid_moves = []
        for _moves in moves:
            for move in _moves:
                pos_square = self.find_square(*move)
                pos_piece = self.find_piece(*move)
                if pos_piece:
                    if pos_piece.color != self.selected_piece.color:
                        # enemy piece
                        pos_square.is_threat = True
                        valid_moves.append(move)
                    break
                pos_square.is_possible = True
                valid_moves.append(move)

        return valid_moves

    def validate_queen(self):
        """Returns valid moves for queen"""

        moves = self.selected_piece.get_possible_moves()

        valid_moves = []
        for _moves in moves:
            for move in _moves:
                pos_square = self.find_square(*move)
                pos_piece = self.find_piece(*move)
                if pos_piece:
                    if pos_piece.color != self.selected_piece.color:
                        # enemy piece
                        pos_square.is_threat = True
                        valid_moves.append(move)
                    break
                pos_square.is_possible = True
                valid_moves.append(move)

        return valid_moves

    def validate_king(self):
        """Returns valid moves for king"""

        moves = self.selected_piece.get_possible_moves()

        valid_moves = []
        for move in moves:
            pos_square = self.find_square(*move)
            pos_piece = self.find_piece(*move)
            if pos_piece:
                if pos_piece.color != self.selected_piece.color:
                    # enemy piece, attack
                    pos_square.is_threat = True
                    valid_moves.append(move)
            else:
                pos_square.is_possible = True
                valid_moves.append(move)

        return valid_moves

    def display(self):
        """Prints game data"""

        logger.info(json.dumps({
            "square": str(self.square),
            "piece": str(self.piece),
            "selected_piece": str(self.selected_piece),
            "validMoves": str(self.valid_moves),
            "turn": str(self.turn),
            "squareLength": self.square.length if self.square else None,
            "pieceSize": self.piece.get_font_size() if self.piece else None
        }, indent=2))

    @staticmethod
    def init_squares():
        """Initializes board squares"""

        squares = []
        for row in range(8):
            for col in range(8):
                square = Square(row, col)
                squares.append(square)
        return squares

    @staticmethod
    def init_pieces():
        """Initializes board pieces"""

        pieces = []
        for row in range(8):
            for col in range(8):
                if row in (0, 1, 6, 7):
                    color = "black" if row in (0, 1) else "white"

                    piece = None
                    if row in (1, 6):
                        piece = Pawn(row, col, color)
                    elif row in (0, 7):
                        if col in (0, 7):
                            piece = Rook(row, col, color)
                        elif col in (1, 6):
                            piece = Knight(row, col, color)
                        elif col in (2, 5):
                            piece = Bishop(row, col, color)
                        elif col == 3:
                            piece = Queen(row, color)
                        elif col == 4:
                            piece = King(row, color)

                    pieces.append(piece)
        return pieces


import numpy as np
from mpmath import mp, findroot
from scipy.integrate import odeint
from ase.thermochemistry import HarmonicThermo as HT
from ase import units

mp.dps = 50
# mp.pretty = True

# Physical constants and conversion factors
J2eV = 6.24150974E18            # eV/J
Na = 6.0221415E23             # mol-1
h = 6.626068E-34 * J2eV      # in eV*s
kb = 1.3806503E-23 * J2eV     # in eV/K

kJ_mol2eV = 0.01036427
wave2eV = 0.00012398

# Standard entropy of gas phase species
SN2g = 191.61e-3 * kJ_mol2eV
SH2g = 130.68e-3 * kJ_mol2eV
SNH3g = 192.77e-3 * kJ_mol2eV

# Use gas phase N2 and H2 energies as reference
EN2g = 0
EH2g = 0

# From NIST
ZPEN2g = 0.144
ZPEH2g = 0.267
ZPENH3g = 0.903

class NH3mkm:

    def __init__(self,
                 T,
                 EN,
                 Evib=0.0,
                 p0=1.,
                 N2_frac=0.25,
                 dps=25,
                 X=0.01,
                 model='effective',
                 alkali_promotion=False,
                 use_HT=False,
                 nlevels=1,
                 Tv=3000,
                 dist='Treanor',
                 use_alpha=False,
                 site='step'):

        if dps is not None:
            mp.dps = dps

        # From NIST
        self.ZPEN2g = ZPEN2g
        self.ZPEH2g = ZPEH2g
        self.ZPENH3g = ZPENH3g

        self.model = model
        self.nlevels = nlevels
        self.T = T
        self.Tv = Tv
        self.use_alpha = use_alpha

        if dist == 'Treanor':
            self.Elevels, self.plevels = self.treanor_dist()
        elif dist == 'Boltzmann':
            self.Elevels, self.plevels = self.boltzmann_dist()

        self.EN = EN
        self.Evib = Evib
        self.p0 = p0
        self.N2_frac = N2_frac
        self.pN2_0 = N2_frac * p0
        self.pH2_0 = (1 - N2_frac) * p0
        self.X = X
        self.alkali_promotion = alkali_promotion

        pN2, pH2, pNH3 = self.get_pressures()
        self.pN2 = pN2
        self.pH2 = pH2
        self.pNH3 = pNH3

        self.use_HT = use_HT
        self.site = site

        # Vib freq in cm^-1 from vojvodic cpl
        freqN = np.array([434, 465, 508]) * wave2eV
        freqNH = np.array([372, 404, 511, 686, 714, 3430]) * wave2eV
        freqNH2 = np.array([94, 310, 454, 612,
                            661, 674, 1480, 3466, 3568]) * wave2eV
        freqNN = np.array([378, 408, 444, 479, 527]) * wave2eV

        # The values are in meV. These are h\mu. From Honakala Science 2005
        freqH = np.array([44, 121, 161]) / 1000.

        self.freqN = freqN
        self.freqNN = freqNN
        self.freqNH2 = freqNH2
        self.freqNH = freqNH
        self.freqH = freqH

        self.ZPEN = sum(freqN) * 0.5
        self.ZPENH = sum(freqNH) * 0.5
        self.ZPENH2 = sum(freqNH2) * 0.5
        self.ZPENN = sum(freqNN) * 0.5
        self.ZPEH = sum(freqH) * 0.5
        # We can set this later
        self.Xrc_calc = False

    def get_pressures(self):
        N2_frac = self.N2_frac
        H2_frac = 1 - N2_frac
        X = self.X

        # N is limiting
        nN2 = N2_frac - X * N2_frac
        nH2 = H2_frac - 3 * X * N2_frac
        nNH3 = 2 * X * N2_frac

        ntot = nN2 + nH2 + nNH3

        pN2 = self.p0 * nN2 / ntot
        pH2 = self.p0 * nH2 / ntot
        pNH3 = self.p0 * nNH3 / ntot

        return pN2, pH2, pNH3

    def boltzmann_dist(self):
        Tv = self.Tv

        ws = np.array([2372.45, 18.1017])
        cm1eV = 0.00012398426
        EN_vibs = cm1eV * ws

        nlevels = self.nlevels
        levels = np.arange(nlevels)
        Ei_harmonic = levels * EN_vibs[0]  # energy of ith vibrational state
        Ei_anharmonic = EN_vibs[0] * levels - EN_vibs[1] * levels ** 2
        P_B = np.exp(-Ei_harmonic / units.kB / Tv)
        P_B = P_B / sum(P_B)
        return Ei_anharmonic, P_B

    def treanor_dist(self):
        Tv = self.Tv
        Tg = self.T
        nlevels = self.nlevels

        ws = np.array([2372.45, 18.1017])
        cm1eV = 0.00012398426
        EN_vibs = cm1eV * ws

        levels = np.arange(nlevels)
        Ei_harmonic = levels * EN_vibs[0]  # energy of ith vibrational state
        Ei_anharmonic = EN_vibs[0] * levels - EN_vibs[1] * levels ** 2

        P_T = np.exp(-Ei_harmonic / units.kB / Tv
                     + (Ei_harmonic - Ei_anharmonic) / units.kB / Tg)

        P_T = P_T / sum(P_T)
        return Ei_anharmonic, P_T

    def get_effective_Evib(self):
        pi = self.plevels
        Ei = self.Elevels
        return np.dot(Ei, pi)

    def get_N2_pressures(self, use_conversion=True):
        if use_conversion:
            return self.plevels * self.pN2
        else:
            return self.plevels * self.pN2_0

    def get_rxn_energies(self):

        EN = self.EN
        Evib = self.Evib

        if self.model == 'effective':
            dE = np.zeros(5)          # array initialization
            if self.site is 'step':
                dE[0] = 2 * (EN) - Evib    # N adsorption
                dE[1] = 2 * (0.10 * EN - 0.44)  # H adsorption
                dE[2] = -0.19 * EN - 0.16  # NH formation
                dE[3] = -0.39 * EN - 0.37  # NH2 formation
                dE[4] = -0.72 * EN + 1.01  # NH3 formation

            elif self.site is 'terrace':
                dE[0] = 2 * EN - Evib
                dE[1] = 2 * (0.0 * EN - 0.32)
                dE[2] = -0.20 * EN - 0.33
                dE[3] = -0.35 * EN + 0.24
                dE[4] = -0.45 * EN + 0.21

        elif self.model == 'explicit':
            nlevels = self.nlevels
            Elevels = self.Elevels
            dE = np.zeros(nlevels + 4)

            for level in range(nlevels):
                dE[level] = 2 * (EN) - Elevels[level]  # N adsorption

            # Index is now level
            if self.site is 'step':
                dE[level + 1] = 2 * (0.10 * EN - 0.44)  # H adsorption
                dE[level + 2] = -0.19 * EN - 0.16  # NH formation
                dE[level + 3] = -0.39 * EN - 0.37  # NH2 formation
                dE[level + 4] = -0.72 * EN + 1.01  # NH3 formation

            elif self.site is 'terrace':
                dE[level + 1] = 2 * (0.00 * EN - 0.32)
                dE[level + 2] = -0.20 * EN - 0.33
                dE[level + 3] = -0.35 * EN + 0.24
                dE[level + 4] = -0.45 * EN + 0.21
        return dE

    def get_rxn_entropies(self):
        # Surface entropies are assumed to be zero
        # SN = SH = SNH = SNH2 = SNH3 = 0
        # Frozen adsorbate approximation

        # Entropy changes
        # dS = np.zeros(5)
        nlevels = self.nlevels
        dS = np.zeros(nlevels + 4)

        if not self.use_HT:
            for level in range(nlevels):
                dS[level] = -SN2g

            dS[level + 1] = -SH2g
            dS[level + 4] = SNH3g
        else:
            SN = HT(2 * self.freqN).get_entropy(self.T, verbose=False)
            SH = HT(2 * self.freqH).get_entropy(self.T, verbose=False)
            SNH = HT(2 * self.freqNH).get_entropy(self.T, verbose=False)
            SNH2 = HT(2 * self.freqNH2).get_entropy(self.T, verbose=False)

            for level in range(nlevels):
                dS[level] = 2 * SN - SN2g
            dS[level + 1] = 2 * SH - SH2g
            dS[level + 2] = SNH - SN - SH
            dS[level + 3] = SNH2 - SNH - SH
            dS[level + 4] = SNH3g - SNH2 - SH

        return dS

    def get_Eacts(self):
        dE = self.get_rxn_energies()
        # Activation energy barriers
        Ea = np.zeros(5)          # array initialization

        EN = self.EN

        if self.model == 'effective':
            Evib = self.Evib
            if self.site is 'step':
                Ea[0] = 1.57 * EN + 1.56  # N dissoc
                alpha = self.calculate_alpha(Ea[0])
                Ea[0] -= alpha * Evib
                Ea[2] = 0.14 * dE[2] + 1.38  # NH
                Ea[3] = 0.85 * dE[3] + 1.74  # NH2
                Ea[4] = 0.24 * dE[4] + 1.48  # NH3

            elif self.site is 'terrace':
                Ea[0] = 1.58 * EN + 2.33
                alpha = self.calculate_alpha(Ea[0])
                Ea[0] -= alpha * Evib
                Ea[2] = 0.56 * dE[2] + 1.23
                Ea[3] = 0.44 * dE[3] + 1.16
                Ea[4] = 0.92 * dE[4] + 0.79

        elif self.model == 'explicit':
            nlevels = self.nlevels
            Elevels = self.Elevels
            Ea = np.zeros(nlevels + 4)

            if self.site is 'step':
                for level in range(nlevels):
                    Ea0 = 1.57 * EN + 1.56
                    alpha = self.calculate_alpha(Ea0)
                    Ea[level] = Ea0 - alpha * Elevels[level]
                Ea[level + 2] = 0.14 * dE[level + 2] + 1.38  # NH
                Ea[level + 3] = 0.85 * dE[level + 3] + 1.74  # NH2
                Ea[level + 4] = 0.24 * dE[level + 4] + 1.48  # NH3

            elif self.site is 'terrace':
                for level in range(nlevels):
                    Ea0 = 1.58 * EN + 2.33
                    alpha = self.calculate_alpha(Ea0)
                    Ea[level] = Ea0 - alpha * Elevels[level]
                Ea[level + 2] = 0.56 * dE[level + 2] + 1.23
                Ea[level + 3] = 0.44 * dE[level + 3] + 1.16
                Ea[level + 4] = 0.92 * dE[level + 4] + 0.79
        return Ea

    def get_TS_entropies(self):

        SN2g = 192.77 / Na * J2eV    # eV/K
        SH2g = 130.0 / Na * J2eV
        SNH3g = 191.0 / Na * J2eV

        nlevels = self.nlevels

        # Entropy changes to the transition state
        STS = np.zeros(nlevels + 4)
        for level in range(nlevels):
            STS[level] = -SN2g
        STS[level + 1] = -SH2g
        STS[level + 4] = SNH3g
        return STS

    def get_ZPEs_rxn(self):
        # Vib freq in cm^-1

        nlevels = self.nlevels
        ZPEs = np.zeros(nlevels + 4)
        for level in range(nlevels):
            ZPEs[level] = 2 * self.ZPEN - ZPEN2g
        ZPEs[level + 1] = 2 * self.ZPEH - ZPEH2g
        ZPEs[level + 2] = self.ZPENH - self.ZPEH - self.ZPEN
        ZPEs[level + 3] = self.ZPENH2 - self.ZPENH - self.ZPEH
        ZPEs[level + 4] = ZPENH3g - self.ZPENH2 - self.ZPEH
        return ZPEs

    def get_ZPEs_act(self, TSstate='initial'):

        nlevels = self.nlevels

        # Vib freq in cm^-1
        ZPEs = np.zeros(nlevels + 4)
        for level in range(nlevels):
            ZPEs[level] = self.ZPENN - ZPEN2g

        if TSstate is 'final':
            # Full ZPE change
            ZPEs[level + 1] = 2 * self.ZPEH - ZPEH2g
            ZPEs[level + 2] = self.ZPENH - self.ZPEH - self.ZPEN
            ZPEs[level + 3] = self.ZPENH2 - self.ZPENH - self.ZPEH
            ZPEs[level + 4] = ZPENH3g - self.ZPENH2 - self.ZPEH

        elif TSstate is 'average':
            ZPEs[level + 1] = (2 * self.ZPEH + ZPEH2g) / 2. - ZPEH2g
            ZPEs[level + 2] = self.ZPENH - (self.ZPEH + self.ZPEN) / 2.
            ZPEs[level + 3] = self.ZPENH2 - (self.ZPENH + self.ZPEH) / 2.
            ZPEs[level + 4] = ZPENH3g - (self.ZPENH2 + self.ZPEH) / 2.
        return ZPEs

    def get_rate_constants(self):
        """This function calculates all rate constants based on
        the DFT energies for each species.
        No coverage dependence is included.
        """

        # Gas phase entropies
        kbT = kb * self.T                   # in eV

        dE = self.get_rxn_energies()
        dS = self.get_rxn_entropies()
        Ea = self.get_Eacts()
        STS = self.get_TS_entropies()
        ZPEs = self.get_ZPEs_rxn()
        TSZPEs = self.get_ZPEs_act()

        dE += ZPEs
        Ea += TSZPEs
        nlevels = self.nlevels
        if self.alkali_promotion:
            for level in range(nlevels):
                dE[level] += 0.02  # N2 inc by 0.01
                Ea[level] -= 0.15  # Barrier lowered by 0.15
            dE[level + 2] += 0.24 - 0.01  # NH inc by 0.24
            dE[level + 3] += 0.27 - 0.24  # NH2 inc by 0.27
            dE[level + 4] += 0 - 0.27  # NH3 inc by 0.54, but not adsorbed

        self.dE = dE
        self.dS = dS
        self.Ea = Ea
        self.STS = STS
        self.ZPEs = ZPEs
        self.TSZPEs = TSZPEs

        # Calculate equilibrium and rate constants
        K = np.zeros(len(dE))           # equilibrium constants
        kf = np.zeros(len(dE))                # forward rate constants
        kr = np.zeros(len(dE))             # reverse rate constants

        for i in range(len(dE)):
            dG = dE[i] - self.T*dS[i]
            K[i] = mp.exp(-dG / kbT)
            Ea[i] = max([0, dE[i], Ea[i]])   # Enforce Ea > 0, and Ea > dE
            kf[i] = kbT/h * mp.exp(STS[i]/kb) * mp.exp(-Ea[i]/kbT)
            kr[i] = kf[i]/K[i]       # enforce thermodynamic consistency

        self.kf = kf
        self.kr = kr
        return (kf, kr)

    def get_sabatier_rate(self, use_conversion=False,
                          N2_only=False, non_N2=False):

        kf, kr = self.get_rate_constants()
        sabatier_rates = np.zeros(5)

        if not use_conversion:
            pN2 = self.pN2_0
            pH2 = self.pH2_0

        else:
            pN2 = self.pN2
            pH2 = self.pH2

        if self.model == 'effective':
            sabatier_rates[0] = kf[0] * pN2 * 1
            sabatier_rates[1] = kf[1] * pH2 * 1
            sabatier_rates[2] = 0.5 * kf[2] * 0.5 * 0.5
            sabatier_rates[3] = 0.5 * kf[3] * 0.5 * 0.5
            sabatier_rates[4] = 0.5 * kf[4] * 0.5 * 0.5

        elif self.model == 'explicit':
            sabatier_rates[0] = 0
            nlevels = self.nlevels
            pressures = self.get_N2_pressures(use_conversion=use_conversion)

            for level in range(nlevels):
                p = pressures[level]
                sabatier_rates[0] += kf[level] * p

            sabatier_rates[1] = kf[level + 1] * pH2 * 1
            sabatier_rates[2] = 0.5 * kf[level + 2] * 0.5 * 0.5
            sabatier_rates[3] = 0.5 * kf[level + 3] * 0.5 * 0.5
            sabatier_rates[4] = 0.5 * kf[level + 4] * 0.5 * 0.5

        if N2_only:
            sabatier_rates = np.array([sabatier_rates[0]])
        elif non_N2:
            sabatier_rates = np.array(sabatier_rates[1:])
        rds = np.argmin(sabatier_rates)
        sabatier_rate = sabatier_rates[rds]

        self.sabatier_rates = sabatier_rates
        self.rds = rds
        self.sabatier_rate = sabatier_rate

        return rds, sabatier_rate

    def get_rates(self, theta):
        if not self.Xrc_calc:
            kf, kr = self.get_rate_constants()
        else:
            kf = self.kf
            kr = self.kr

        tN = theta[0]
        tH = theta[1]        # theta of H
        tNH = theta[2]             # theta of NH
        tNH2 = theta[3]             # theta of NH2
        tstar = 1.0 - tN - tH - tNH - tNH2   # site balance for tstar

        rate = np.zeros(5)
        # Rates
        if self.model == 'effective':
            rate[0] = kf[0] * self.pN2 * tstar ** 2 - kr[0] * tN ** 2
            rate[1] = kf[1] * self.pH2 * tstar ** 2 - kr[1] * tH ** 2
            rate[2] = kf[2] * tN * tH - kr[2] * tNH * tstar
            rate[3] = kf[3] * tNH * tH - kr[3] * tNH2 * tstar
            rate[4] = kf[4] * tNH2 * tH - kr[4] * self.pNH3 * tstar ** 2

        elif self.model == 'explicit':
            nlevels = self.nlevels
            pressures = self.get_N2_pressures()
            # Could use vector algebra here
            rate[0] = 0
            for level in range(nlevels):
                p = pressures[level]
                rate[0] += kf[level] * p * tstar ** 2 - kr[level] * tN ** 2

            rate[1] += kf[level + 1] * self.pH2 * tstar ** 2 \
                       - kr[level + 1] * tH ** 2
            rate[2] = kf[level + 2] * tN * tH - kr[level + 2] * tNH * tstar
            rate[3] = kf[level + 3] * tNH * tH - kr[level + 3] * tNH2 * tstar
            rate[4] = kf[level + 4] * tNH2 * tH \
                      - kr[level + 4] * self.pNH3 * tstar ** 2

        return rate

    @staticmethod
    def get_odes(theta, t, self):
        """This needs to be a staticmethod to work well
        with odeint"""

        rate = self.get_rates(theta)

        # Time derivatives of theta
        dt = [0] * 4
        dt[0] = 2 * rate[0] - rate[2]                          # d(tN)/dt
        dt[1] = 2 * rate[1] - rate[2] - rate[3] - rate[4]      # d(tH)/dt
        dt[2] = rate[2] - rate[3]                              # d(tNH)/dt
        dt[3] = rate[3] - rate[4]                              # d(tNH2)/dt
        return dt

    @staticmethod
    def check_site_type(site_type, theta0):
        if site_type == 'single':
            func = NH3mkm.get_odes
            if not len(theta0) == 4:
                theta0 = mp.zeros(1, 4)

        elif site_type == 'two-site':
            func = NH3mkm.get_odes_two_site
            if not len(theta0) == 6:
                theta0 = mp.zeros(1, 6)
        return func, theta0

    def integrate_odes(self,
                       site_type='single',
                       theta0=mp.zeros(1, 4),
                       timespan=[0, 1e8],
                       h0=1e-20,
                       mxstep=200000,          # maximum number of steps
                       rtol=1E-12,            # relative tolerance
                       atol=1E-15,           # Absolute tolerance
                       full_output=1):

        func, theta0 = NH3mkm.check_site_type(site_type, theta0)
        self.theta0 = theta0
        args = self,

        # Integrate the ODEs for 1E10 sec (enough to reach steady-state)
        theta, out = odeint(func,         # system of ODEs
                            theta0,                  # initial guess
                            timespan,                 # time span
                            args=args,
                            h0=h0,              # initial time step
                            mxstep=mxstep,          # maximum number of steps
                            rtol=rtol,            # relative tolerance
                            atol=atol,           # Absolute tolerance
                            full_output=full_output)
        self.integrated_thetas = theta
        self.ode_output = out

        # Return the value of theta at the last timestep
        return theta[-1, :]

    def find_steady_state_roots(self,
                                theta0=[0., 0., 0., 0.],
                                tol=1e-22,
                                solver='secant'):
        mp.dps = 25
        mp.pretty = True

        def get_findroot_eqns(*args):
            return self.get_odes(args, 0, self)

        theta = findroot(get_findroot_eqns,
                         tuple(theta0),
                         solver=solver,
                         tol=tol,
                         multidimensional=True)

        self.steady_state_theta = theta
        return theta

    def get_rates_two_site(self, theta):
        '''
        Compute reaction rates based on the two-site model
        used by Vojvodic et al, Chem Phys Lett, 2014, 598, 108
        '''
        kf, kr = self.get_rate_constants()

        tstepN = theta[0]
        tstepH = theta[1]        # theta of H
        tstepNH = theta[2]             # theta of NH
        tstepNH2 = theta[3]             # theta of NH2
        tstep = 0.5 - tstepN - tstepH - tstepNH - tstepNH2  # steps

        tstarN = theta[4]
        tstarNH = theta[5]
        tstar = 0.5 - tstarN - tstarNH

        # Rates
        rate = np.zeros(7)
        rate[0] = kf[0] * self.pN2 * tstep * tstar - kr[0] * tstepN * tstarN
        rate[1] = kf[1] * self.pH2 * tstep ** 2 - kr[1] * tstepH ** 2
        rate[2] = kf[2] * tstarN * tstepH - kr[2] * tstarNH * tstep
        rate[3] = kf[2] * tstepN * tstepH - kr[2] * tstepNH * tstep
        rate[4] = kf[3] * tstarNH * tstepH - kr[3] * tstepNH2 * tstar
        rate[5] = kf[3] * tstepNH * tstepH - kr[3] * tstepNH2 * tstep
        rate[6] = kf[4] * tstepNH2 * tstepH - kr[4] * self.pNH3 * tstep ** 2
        return rate

    @staticmethod
    def get_odes_two_site(theta, t, self):
        """This needs to be a staticmethod to work well
        with odeint"""

        rate = self.get_rates_two_site(theta)

        # Time derivatives of theta
        dt = np.zeros(6)
        # d(tstepN)/dt
        dt[0] = rate[0] - rate[3]
        # d(tstepH)/dt
        dt[1] = 2 * rate[1] - rate[2] - rate[3] - rate[4] - rate[5] - rate[6]
        # d(tstepNH)/dt
        dt[2] = rate[3] - rate[5]
        # d(tstepNH2)/dt
        dt[3] = rate[4] + rate[5] - rate[6]
        # d(tstarN)/dt
        dt[4] = rate[0] - rate[2]
        # d(tstarNH)/dt
        dt[5] = rate[2] - rate[4]
        return dt

    def get_N2_rds_rate(self, site_type='single'):
        '''
        Compute rates assuming N2 dissociation is rate
        limiting
        '''
        kf, kr = self.get_rate_constants()
        K = kf / kr
        self.K = K

        lambdaH = np.sqrt(K[1] * self.pH2)
        lambdaNH2 = self.pNH3 / K[4] / lambdaH
        lambdaNH = lambdaNH2 / K[3] / lambdaH
        lambdaN = lambdaNH / K[2] / lambdaH

        if site_type is 'single':

            theta_star = 1 / (1 + lambdaN + lambdaH
                              + lambdaNH + lambdaNH2)

            thetaH = lambdaH * theta_star
            thetaN = lambdaN * theta_star
            thetaNH = lambdaNH * theta_star
            thetaNH2 = lambdaNH2 * theta_star

            theta_N2_rds = [thetaN, thetaH, thetaNH, thetaNH2]
            rates_N2_rds = self.get_rates(theta_N2_rds)

        elif site_type == 'two-site':
            theta_star = 0.5 / (1 + lambdaN + lambdaNH)
            theta_step = 0.5 / (1 + lambdaN + lambdaH
                                + lambdaNH + lambdaNH2)

            thetaH_step = lambdaH * theta_step
            thetaN_step = lambdaN * theta_step
            thetaNH_step = lambdaNH * theta_step
            thetaNH2_step = lambdaNH2 * theta_step

            thetaN_star = lambdaN * theta_star
            thetaNH_star = lambdaNH * theta_star

            theta_N2_rds = [thetaN_step,
                            thetaH_step,
                            thetaNH_step,
                            thetaNH2_step,
                            thetaN_star,
                            thetaNH_star]

            rates_N2_rds = self.get_rates_two_site(theta_N2_rds)

        self.theta_N2_rds = theta_N2_rds
        self.rates_N2_rds = rates_N2_rds

        return theta_N2_rds, rates_N2_rds

    def get_NH_rds_rate(self, site_type='single'):
        '''
        Compute rates assuming NH formation is rate
        limiting
        '''
        kf, kr = self.get_rate_constants()
        K = kf / kr
        self.K = K

        lambdaN = np.sqrt(K[0] * self.pN2)
        lambdaH = np.sqrt(K[1] * self.pH2)
        lambdaNH2 = self.pNH3 / K[4] / lambdaH
        lambdaNH = lambdaNH2 / K[3] / lambdaH

        if site_type is 'single':

            theta_star = 1 / (1 + lambdaN + lambdaH
                              + lambdaNH + lambdaNH2)

            thetaH = lambdaH * theta_star
            thetaN = lambdaN * theta_star
            thetaNH = lambdaNH * theta_star
            thetaNH2 = lambdaNH2 * theta_star

            theta_NH_rds = [thetaN, thetaH, thetaNH, thetaNH2]
            rates_NH_rds = self.get_rates(theta_NH_rds)

        self.theta_NH_rds = theta_NH_rds
        self.rates_NH_rds = rates_NH_rds

        return theta_NH_rds, rates_NH_rds

    def calculate_Xrc(self):
        '''
        Degree of rate control
        '''
        kf0, kr0 = self.get_rate_constants()
        theta0 = self.integrate_odes()
        r0 = self.get_rates(theta0)[0]

        # Currently only implemented for single site
        # and no excitation
        delta = 0.1  # change of 10%
        Xrc_rates = np.zeros(5)

        self.kf0 = kf0
        self.kr0 = kr0

        for s in range(len(Xrc_rates)):
            kf = kf0.copy()
            kr = kr0.copy()

            kf[s] = (1 + delta) * kf0[s]
            kr[s] = (1 + delta) * kr0[s]

            self.kf = kf
            self.kr = kr
            self.Xrc_calc = True

            theta = self.integrate_odes(theta0=theta0)
            rates = self.get_rates(theta)
            Xrc_rates[s] = rates[0]

        Xrc = (Xrc_rates - r0) / (delta * r0)
        self.Xrc_rates = Xrc_rates
        self.Xrc = Xrc
        return Xrc

    def calculate_alpha(self, Ea):
        '''
        Calculate Fridman alpha
        '''
        if not self.use_alpha:
            alpha = 1
        else:
            Eaf = Ea
            Eab = Eaf - 2 * self.EN
            alpha = Eaf / (Eaf + Eab)
        alpha = max(0, alpha)
        self.alpha = alpha
        return alpha


"""
Harris 角点探测
角点是一幅图像上最明显与重要的特征，对于一阶导数而言，角点在各个方向的变化是最大的，而边缘区域在只是某一方向有明显变化
"""

import numpy as np
import cv2 as cv

def find_conner():
    filename = '../resources/chessboard.png'
    img = cv.imread(filename)
    img = cv.resize(img, (300, 300), interpolation=cv.INTER_CUBIC)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    gray = np.float32(gray)
    # img - Input image, it should be grayscale and float32 type.
    # blockSize - It is the size of neighbourhood considered for corner detection
    # ksize - Aperture parameter of Sobel derivative used.
    # k - Harris detector free parameter in the equation.
    dst = cv.cornerHarris(gray, 2, 3, 0.04)

    # result is dilated for marking the corners, not important
    dst = cv.dilate(dst, None)
    # Threshold for an optimal value, it may vary depending on the image.
    img[dst > 0.01*dst.max()] = [0, 0, 255]
    cv.imshow('dst', img)
    if cv.waitKey(0) & 0xff == 27:
        cv.destroyAllWindows()

def find_corner_with_subpixel():
    filename = '../resources/chessboard.png'
    img = cv.imread(filename)
    img = cv.resize(img, (400, 400), interpolation=cv.INTER_CUBIC)
    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
    # find Harris corners
    gray = np.float32(gray)
    dst = cv.cornerHarris(gray, 2, 3, 0.04)
    dst = cv.dilate(dst, None)
    ret, dst = cv.threshold(dst, 0.01 * dst.max(), 255, 0)
    dst = np.uint8(dst)
    # find centroids
    ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
    # define the criteria to stop and refine the corners
    criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
    corners = cv.cornerSubPix(gray, np.float32(centroids), (5, 5), (-1, -1), criteria)
    # Now draw them
    res = np.hstack((centroids, corners))
    res = np.int0(res)
    img[res[:, 1], res[:, 0]] = [0, 0, 255]
    img[res[:, 3], res[:, 2]] = [0, 255, 0]
    # 放大才可以看到
    cv.imshow('dst', img)
    if cv.waitKey(0) & 0xff == 27:
        cv.destroyAllWindows()

#find_conner()
find_corner_with_subpixel()


#!/usr/bin/env python

import json
import os
import sys

from jsonpath_ng import parse
from lxml import etree

SPEC_DIR = f"{os.path.dirname(os.path.realpath(__file__))}/../specification"

def main(file: str):
    with open(file, 'r') as f:
        spec = json.load(f)

        req_path = parse("$.paths.['/$convert'].post.requestBody.content.*.examples.*.['$ref']")
        req_examples = req_path.find(spec)
        inline_examples(spec, req_examples)

        res_path = parse("$.paths.['/$convert'].post.responses.*.content.*.examples.*.['$ref']")
        res_examples = res_path.find(spec)
        inline_examples(spec, res_examples)

        print(json.dumps(spec))

def inline_examples(spec, examples_path):
    for example in examples_path:
        ref = example.full_path
        example_file_content = read_example_from_component(spec, ref)

        example_path = ref.left
        example_path.update(spec, {"value": example_file_content})

def read_example_from_component(spec: dict, path):
    component = path.find(spec)[0].value
    com_path = component.replace("#/", "").replace("/", ".")
    example_path = parse(f"$.{com_path}.value.['$ref']")
    match = example_path.find(spec)[0].value

    if match.endswith(".json") or match.endswith(".xml"):
        with open(f"{SPEC_DIR}/{match}", "r") as example_content:
            if match.endswith(".xml"):
                return pretty_print_xml(example_content)
            elif match.endswith(".json"):
                return example_content.read()

def pretty_print_xml(content):
    x = etree.parse(content)
    return etree.tostring(x, pretty_print=True, encoding=str)

if __name__ == '__main__':
    main(sys.argv[1])


from rest_framework import generics
from rest_framework.views import APIView, Response, Request, status
from .models import Book
from .serializers import BookSerializer, BookFollowingSerializer
from rest_framework_simplejwt.authentication import JWTAuthentication
from permissions.isAdminOrReadOnly import IsAdminOrReadOnly

from django.shortcuts import get_object_or_404
from rest_framework.exceptions import NotAcceptable

class BookView(generics.ListCreateAPIView):
    authentication_classes = [JWTAuthentication]
    permission_classes = [IsAdminOrReadOnly]

    queryset = Book.objects.all()
    serializer_class = BookSerializer

    def perform_create(self, serializer):
        data_book = self.request.data
        title = data_book["title"]
        author = data_book["author"]
        book = Book.objects.filter(
            title=title,
            author=author,
        ).first()
        if book:
            raise NotAcceptable("This book is already registered")

        serializer.save()

class BookDetailView(generics.RetrieveAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializer

    lookup_url_kwarg = "book_id"

class BookFollowingView(APIView):
    authentication_classes = [JWTAuthentication]

    def post(self, request: Request, book_id):
        book = get_object_or_404(Book, id=book_id)

        user = request.user
        serializer = BookFollowingSerializer(data=dict())
        serializer.is_valid()
        serializer.save(book=book, user=user)

        return Response(serializer.data, status.HTTP_201_CREATED)


import multiprocessing as mp
import queue
from collections.abc import Iterable
from os import PathLike, cpu_count
from pathlib import Path
from timeit import default_timer as timer
from typing import Any, Generator, Optional, Union

import numpy as np
import pandas as pd
import xarray as xr
from pydantic import BaseModel
from skimage.transform import ProjectiveTransform

from ..io import write_scores
from ..matching.algorithms import PointsMatchingAlgorithm
from ..plugins import plugin_manager

class RunConfig(BaseModel):
    info: dict[str, Any]
    algorithm_name: str
    algorithm_kwargs: dict[str, Any]
    match_points_kwargs: dict[str, Any]
    algorithm_is_directed: bool = False

def run(
    run_config: RunConfig,
    scores_file: Union[str, PathLike],
    reverse_scores_file: Union[str, PathLike, None] = None,
) -> tuple[dict[str, Any], Optional[xr.DataArray], Optional[xr.DataArray]]:
    scores_file = Path(scores_file)
    if reverse_scores_file is not None:
        reverse_scores_file = Path(reverse_scores_file)
    scores_info = run_config.info.copy()
    algorithm = None
    try:
        algorithm_types: list[
            type[PointsMatchingAlgorithm]
        ] = plugin_manager.hook.spellmatch_get_mask_matching_algorithm(
            name=run_config.algorithm_name
        )
        assert len(algorithm_types) == 1
        algorithm_type = algorithm_types[0]
        algorithm = algorithm_type(**run_config.algorithm_kwargs)
    except Exception as e:
        scores_info["error"] = str(e)
    info = None
    scores = None
    reverse_info = None
    reverse_scores = None
    if algorithm is not None:
        try:
            start = timer()
            info, scores = algorithm.match_points(**run_config.match_points_kwargs)
            if run_config.algorithm_is_directed:
                inv_prior_transform = None
                if "prior_transform" in run_config.match_points_kwargs:
                    inv_prior_transform = ProjectiveTransform(
                        matrix=np.linalg.inv(
                            run_config.match_points_kwargs["prior_transform"].params
                        )
                    )
                reverse_info, reverse_scores = algorithm.match_points(
                    run_config.match_points_kwargs["target_name"],
                    run_config.match_points_kwargs["source_name"],
                    run_config.match_points_kwargs["target_points"],
                    run_config.match_points_kwargs["source_points"],
                    source_bbox=run_config.match_points_kwargs.get("target_bbox"),
                    target_bbox=run_config.match_points_kwargs.get("source_bbox"),
                    source_intensities=run_config.match_points_kwargs.get(
                        "target_intensities"
                    ),
                    target_intensities=run_config.match_points_kwargs.get(
                        "source_intensities"
                    ),
                    prior_transform=inv_prior_transform,
                )
                reverse_scores = reverse_scores.transpose()
            end = timer()
            scores_info["seconds"] = end - start
            write_scores(scores_file, scores)
            scores_info["scores_file"] = scores_file.name
            scores_info.update({f"algorithm_{k}": v for k, v in info.items()})
            if reverse_info is not None and reverse_scores is not None:
                scores_info.update(
                    {f"reverse_algorithm_{k}": v for k, v in reverse_info.items()}
                )
                if reverse_scores_file is not None:
                    write_scores(reverse_scores_file, reverse_scores)
                    scores_info["reverse_scores_file"] = reverse_scores_file.name
        except Exception as e:
            scores_info["error"] = str(e)
    return scores_info, scores, reverse_scores

def run_sequential(
    run_configs: Iterable[RunConfig],
    scores_dir: Union[str, PathLike],
    offset: int = 0,
) -> Generator[
    tuple[dict[str, Any], Optional[xr.DataArray], Optional[xr.DataArray]],
    None,
    pd.DataFrame,
]:
    scores_dir = Path(scores_dir)
    scores_dir.mkdir(exist_ok=True)
    scores_infos = []
    for i, run_config in enumerate(run_configs, start=offset):
        scores_info, scores, reverse_scores = run(
            run_config,
            scores_dir / f"scores{i:06d}.nc",
            reverse_scores_file=scores_dir / f"scores{i:06d}_reverse.nc",
        )
        scores_infos.append(scores_info)
        yield scores_info, scores, reverse_scores
    return pd.DataFrame(data=scores_infos)

def run_parallel(
    run_configs: Iterable[RunConfig],
    scores_dir: Union[str, PathLike],
    offset: int = 0,
    n_processes: Optional[int] = None,
    queue_size: int = None,
    worker_timeout: int = 1,
) -> Generator[RunConfig, None, pd.DataFrame]:
    class WorkerProcess(mp.Process):
        def __init__(
            self,
            run_config_queue: mp.Queue,
            scores_info_queue: mp.Queue,
            scores_dir: Path,
            timeout: int,
            **kwargs: Any,
        ) -> None:
            super(WorkerProcess, self).__init__(**kwargs)
            self.run_config_queue = run_config_queue
            self.scores_info_queue = scores_info_queue
            self.scores_dir = scores_dir
            self.timeout = timeout
            self.stop_event = mp.Event()

        def run(self) -> None:
            while not self.stop_event.is_set() or not self.run_config_queue.empty():
                try:
                    i, run_config = self.run_config_queue.get(timeout=self.timeout)
                    scores_info, scores, reverse_scores = run(
                        run_config,
                        self.scores_dir / f"scores{i:06d}.nc",
                        reverse_scores_file=self.scores_dir
                        / f"scores{i:06d}_reverse.nc",
                    )
                    self.scores_info_queue.put(scores_info)
                except queue.Empty:
                    pass

    scores_dir = Path(scores_dir)
    scores_dir.mkdir(exist_ok=True)
    if n_processes is None:
        n_processes = cpu_count()
    if queue_size is None:
        queue_size = n_processes
    run_config_queue = mp.Queue(maxsize=queue_size)
    scores_info_queue = mp.Queue()
    workers = [
        WorkerProcess(
            run_config_queue,
            scores_info_queue,
            scores_dir,
            worker_timeout,
            daemon=True,
            name=f"W{worker_number:03d}",
        )
        for worker_number in range(n_processes)
    ]
    for worker in workers:
        worker.start()
    n = 0
    for i, run_config in enumerate(run_configs, start=offset):
        run_config_queue.put((i, run_config))
        yield run_config
        n += 1
    for worker in workers:
        worker.stop_event.set()
    for worker in workers:
        worker.join()
    return pd.DataFrame(data=[scores_info_queue.get() for _ in range(n)])


import urllib.request
import urllib.error
from lxml import etree
import numpy as np
import requests
import datetime
from bs4 import BeautifulSoup
from selenium import webdriver
import json
import random
from tqdm import tqdm
import unicodedata

def get_model_list(filter_val):

    filtered_results = []

    hugging_face_models = "https://huggingface.co/models"
    page = urllib.request.urlopen(hugging_face_models)
    soup = BeautifulSoup(page, 'lxml')

    data = soup.findAll('ul',attrs={'class':'models-list'})
    for el in data:
        links = el.findAll('a')
        for a in links:
            if filter_val in a.text:
                result = "https://huggingface.co" + a['href']
                print(result)

                filtered_results.append(result)
    return filtered_results

def extract_model_info(mt_url):

    languages = mt_url.split('-')[-2:]

    page = urllib.request.urlopen(mt_url)
    soup = BeautifulSoup(page, 'lxml')

    data = soup.findAll('table')
    for el in data:
        if 'BLEU' in el.text:
            benchmark = el.findAll('tr')[-1].text.split()

            name = benchmark[0]
            bleu = float(benchmark[1])
            chr_f = float(benchmark[2])

            return (languages[0],languages[1],name,bleu,chr_f)

    return (languages[0],languages[1],None,None,None)

def find_best_path(pair,score_dict,aggregation_func=lambda x: np.mean(x)):
	start = pair.split('-')[0]
	end = pair.split('-')[1]

	all_possible_start = set([k.split('-')[1]  for k in score_dict.keys() if start == k.split('-')[0] and score_dict[k] is not None])
	all_possible_end = set([k.split('-')[0] for k in score_dict.keys() if end == k.split('-')[1] and score_dict[k] is not None])

	candidates = all_possible_start.intersection(all_possible_end)

	candidate_aggre_bleu = [(b,aggregation_func([score_dict["{}-{}".format(start,b)],score_dict["{}-{}".format(b,end)]])) for b in  candidates]

	if len(candidate_aggre_bleu) == 0: return (None,None)

	return max(candidate_aggre_bleu,key=lambda x: x[1])

if __name__ == "__main__":

    results = get_model_list("Helsinki-NLP/opus-mt")

    language_pairs = {}

    for r in results:
        lang_A,lang_B, _ , bleu, _ = extract_model_info(r)
        pair = "{}-{}".format(lang_A,lang_B)
        language_pairs[pair] = bleu
        print(pair,bleu)

    for pair in language_pairs.keys():
    	if language_pairs[pair] == None: continue
    	print("Original:",(pair,language_pairs[pair]), "Other:",find_best_path(pair,language_pairs))


##!/usr/bin/env python
'''
    @author [mst]
    @file   733_flood_fill.py
    @brief  leetcode problems series
    733. Flood Fill

    An image is represented by an m x n integer grid image where image[i][j] represents the pixel value of the image.
    You are also given three integers sr, sc, and color. You should perform a flood fill on the
    image starting from the pixel image[sr][sc].
    To perform a flood fill, consider the starting pixel, plus any pixels connected 4-directionally to
    the starting pixel of the same color as the starting pixel, plus any pixels connected 4-directionally
    to those pixels (also with the same color), and so on. Replace the color of all of the aforementioned pixels with color.
    Return the modified image after performing the flood fill.

    Gains:
    -py matrix operation
    -bfs/dfs recursion methodology

    features, changelog:
    -2022.12: -submission

    @version 0.1 2022.12
'''

# Recursive approach: recurse over all valid (adjacent, same color)
# neighbors while coloring the tile if needed
#
# sub: 92%T 66%T
class Solution:

    # the original color. -1 meaning 'not set'
    orig = -1

    def floodFill(self, image: [[int]], sr: int, sc: int, color: int) -> [[int]]:

        # basic cases
        if (sr >= len(image)) or (sr < 0): return image
        if (sc >= len(image[0])) or (sc < 0): return image

        # note the color to fill over
        if self.orig == -1:
            self.orig = image[sr][sc]

        # the actual fill
        if (image[sr][sc] != self.orig) or (image[sr][sc] == color):
            return image
        else:
            image[sr][sc] = color

        self.floodFill(image, sr, sc + 1, color)
        self.floodFill(image, sr + 1, sc, color)
        self.floodFill(image, sr, sc - 1, color)
        self.floodFill(image, sr - 1, sc, color)
        return image

# helper method: print as table
def print_matrix(image):
    if image is None:
        return

    for row in image:
        for col in row:
            print(f"{col} ", end='')
        print("")

################## DRIVER
def main():
    print ("[mst]")

    sol = Solution()

    # image = [[1,1,1],[1,1,0],[1,0,1]]
    image = [[1,1,1,1],[1,1,0,1],[1,1,1,1],[1,1,1,1]]
    print(f"before")
    print_matrix(image)
    sr = 1
    sc = 1
    color = 3
    res = sol.floodFill(image, sr, sc, color)
    print(f"after fill at [{sr},{sc}]")
    print_matrix(res)

    image = [[1,1,1],[1,1,0],[1,0,1]]
    sr = 1
    sc = 1
    color = 2
    res = sol.floodFill(image, sr, sc, color)
    print(f"after fill at [{sr},{sc}]")
    print_matrix(res)

if __name__ == ("__main__"):
    main()


from flask import Flask

app = Flask(__name__)

@app.route('/play/<color>/<int:num>')
def play(color, num):
    if num < 1:
        return 'El número debe ser mayor que cero'

    html = ''
    for i in range(num):
        html += f'

'

    return html

if __name__ == "__main__":
    app.run(debug=True)    # Ejecuta la aplicación en modo de depuración


from concurrent.futures import ProcessPoolExecutor
from concurrent.futures.process import _sendback_result, _global_shutdown

# codes from https://github.com/python/cpython/blob/v3.7.13/Lib/concurrent/futures/process.py

# only the lines with "extended" code comment is modified from the original codes.

import atexit
import os
from concurrent.futures import _base
import queue
from queue import Full
import multiprocessing as mp
from multiprocessing.connection import wait
from multiprocessing.queues import Queue
import threading
import weakref
from functools import partial
import itertools
import sys
import traceback

_threads_wakeups = weakref.WeakKeyDictionary()
_global_shutdown = False

class _WorkItem(object):
    def __init__(self, stateful, future, fn, args, kwargs):
        self.stateful = stateful  # extended
        self.future = future
        self.fn = fn
        self.args = args
        self.kwargs = kwargs

class _CallItem(object):
    def __init__(self, stateful, work_id, fn, args, kwargs):
        self.stateful = stateful  # extended
        self.work_id = work_id
        self.fn = fn
        self.args = args
        self.kwargs = kwargs

def _process_worker(self, call_queue, result_queue, initializer, initargs):
    """Evaluates calls from call_queue and places the results in result_queue.
    This worker is run in a separate process.
    Args:
        call_queue: A ctx.Queue of _CallItems that will be read and
            evaluated by the worker.
        result_queue: A ctx.Queue of _ResultItems that will written
            to by the worker.
        initializer: A callable initializer, or None
        initargs: A tuple of args for the initializer
    """
    if initializer is not None:
        try:
            initializer(*initargs)
        except BaseException:
            _base.LOGGER.critical('Exception in initializer:', exc_info=True)
            # The parent will notice that the process stopped and
            # mark the pool broken
            return
    while True:
        call_item = call_queue.get(block=True)
        if call_item is None:
            # Wake up queue management thread
            result_queue.put(os.getpid())
            return
        try:
            if call_item.stateful:  # extended
                r = call_item.fn(self, *call_item.args, **call_item.kwargs)
            else:
                r = call_item.fn(*call_item.args, **call_item.kwargs)
        except BaseException as e:
            exc = _ExceptionWithTraceback(e, e.__traceback__)
            _sendback_result(result_queue, call_item.work_id, exception=exc)
        else:
            _sendback_result(result_queue, call_item.work_id, result=r)

        # Liberate the resource as soon as possible, to avoid holding onto
        # open files or shared memory that is not needed anymore
        del call_item

def _add_call_item_to_queue(pending_work_items,
                            work_ids,
                            call_queue):
    """Fills call_queue with _WorkItems from pending_work_items.
    This function never blocks.
    Args:
        pending_work_items: A dict mapping work ids to _WorkItems e.g.
            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
        work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
            are consumed and the corresponding _WorkItems from
            pending_work_items are transformed into _CallItems and put in
            call_queue.
        call_queue: A multiprocessing.Queue that will be filled with _CallItems
            derived from _WorkItems.
    """
    while True:
        if call_queue.full():
            return
        try:
            work_id = work_ids.get(block=False)
        except queue.Empty:
            return
        else:
            work_item = pending_work_items[work_id]

            if work_item.future.set_running_or_notify_cancel():
                call_queue.put(_CallItem(work_item.stateful,  # extended
                                         work_id,
                                         work_item.fn,
                                         work_item.args,
                                         work_item.kwargs),
                               block=True)
            else:
                del pending_work_items[work_id]
                continue

def _queue_management_worker(executor_reference,
                             processes,
                             pending_work_items,
                             work_ids_queue,
                             call_queue,
                             result_queue,
                             thread_wakeup):
    """Manages the communication between this process and the worker processes.
    This function is run in a local thread.
    Args:
        executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
            this thread. Used to determine if the ProcessPoolExecutor has been
            garbage collected and that this function can exit.
        process: A list of the ctx.Process instances used as
            workers.
        pending_work_items: A dict mapping work ids to _WorkItems e.g.
            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
        work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
        call_queue: A ctx.Queue that will be filled with _CallItems
            derived from _WorkItems for processing by the process workers.
        result_queue: A ctx.SimpleQueue of _ResultItems generated by the
            process workers.
        thread_wakeup: A _ThreadWakeup to allow waking up the
            queue_manager_thread from the main Thread and avoid deadlocks
            caused by permanently locked queues.
    """
    executor = None

    def shutting_down():
        return (_global_shutdown or executor is None
                or executor._shutdown_thread)

    def shutdown_worker():
        # This is an upper bound on the number of children alive.
        n_children_alive = sum(p.is_alive() for p in processes.values())
        n_children_to_stop = n_children_alive
        n_sentinels_sent = 0
        # Send the right number of sentinels, to make sure all children are
        # properly terminated.
        while n_sentinels_sent < n_children_to_stop and n_children_alive > 0:
            for i in range(n_children_to_stop - n_sentinels_sent):
                try:
                    call_queue.put_nowait(None)
                    n_sentinels_sent += 1
                except Full:
                    break
            n_children_alive = sum(p.is_alive() for p in processes.values())

        # Release the queue's resources as soon as possible.
        call_queue.close()
        # If .join() is not called on the created processes then
        # some ctx.Queue methods may deadlock on Mac OS X.
        for p in processes.values():
            p.join()

    result_reader = result_queue._reader
    wakeup_reader = thread_wakeup._reader
    readers = [result_reader, wakeup_reader]

    while True:
        _add_call_item_to_queue(pending_work_items,
                                work_ids_queue,
                                call_queue)

        # Wait for a result to be ready in the result_queue while checking
        # that all worker processes are still running, or for a wake up
        # signal send. The wake up signals come either from new tasks being
        # submitted, from the executor being shutdown/gc-ed, or from the
        # shutdown of the python interpreter.
        worker_sentinels = [p.sentinel for p in processes.values()]
        ready = wait(readers + worker_sentinels)

        cause = None
        is_broken = True
        if result_reader in ready:
            try:
                result_item = result_reader.recv()
                is_broken = False
            except BaseException as e:
                cause = traceback.format_exception(type(e), e, e.__traceback__)

        elif wakeup_reader in ready:
            is_broken = False
            result_item = None
        thread_wakeup.clear()
        if is_broken:
            # Mark the process pool broken so that submits fail right now.
            executor = executor_reference()
            if executor is not None:
                executor._broken = ('A child process terminated '
                                    'abruptly, the process pool is not '
                                    'usable anymore')
                executor._shutdown_thread = True
                executor = None
            bpe = BrokenProcessPool("A process in the process pool was "
                                    "terminated abruptly while the future was "
                                    "running or pending.")
            if cause is not None:
                bpe.__cause__ = _RemoteTraceback(
                    f"\n'''\n{''.join(cause)}'''")
            # All futures in flight must be marked failed
            for work_id, work_item in pending_work_items.items():
                work_item.future.set_exception(bpe)
                # Delete references to object. See issue16284
                del work_item
            pending_work_items.clear()
            # Terminate remaining workers forcibly: the queues or their
            # locks may be in a dirty state and block forever.
            for p in processes.values():
                p.terminate()
            shutdown_worker()
            return
        if isinstance(result_item, int):
            # Clean shutdown of a worker using its PID
            # (avoids marking the executor broken)
            assert shutting_down()
            p = processes.pop(result_item)
            p.join()
            if not processes:
                shutdown_worker()
                return
        elif result_item is not None:
            work_item = pending_work_items.pop(result_item.work_id, None)
            # work_item can be None if another process terminated (see above)
            if work_item is not None:
                if result_item.exception:
                    work_item.future.set_exception(result_item.exception)
                else:
                    work_item.future.set_result(result_item.result)
                # Delete references to object. See issue16284
                del work_item
            # Delete reference to result_item
            del result_item

        # Check whether we should start shutting down.
        executor = executor_reference()
        # No more work items can be added if:
        #   - The interpreter is shutting down OR
        #   - The executor that owns this worker has been collected OR
        #   - The executor that owns this worker has been shutdown.
        if shutting_down():
            try:
                # Flag the executor as shutting down as early as possible if it
                # is not gc-ed yet.
                if executor is not None:
                    executor._shutdown_thread = True
                # Since no new work items can be added, it is safe to shutdown
                # this thread if there are no pending work items.
                if not pending_work_items:
                    shutdown_worker()
                    return
            except Full:
                # This is not a problem: we will eventually be woken up (in
                # result_queue.get()) and be able to send a sentinel again.
                pass
        executor = None

class BrokenProcessPool(_base.BrokenExecutor):
    """
    Raised when a process in a ProcessPoolExecutor terminated abruptly
    while a future was in the running state.
    """

class ProcessPoolExecutorExtended(ProcessPoolExecutor):

    def _start_queue_management_thread(self):
        if self._queue_management_thread is None:
            # When the executor gets garbarge collected, the weakref callback
            # will wake up the queue management thread so that it can terminate
            # if there is no pending work item.
            def weakref_cb(_,
                           thread_wakeup=self._queue_management_thread_wakeup):
                mp.util.debug('Executor collected: triggering callback for'
                              ' QueueManager wakeup')
                thread_wakeup.wakeup()
            # Start the processes so that their sentinels are known.
            self._adjust_process_count()
            self._queue_management_thread = threading.Thread(
                target=_queue_management_worker,
                args=(weakref.ref(self, weakref_cb),
                      self._processes,
                      self._pending_work_items,
                      self._work_ids,
                      self._call_queue,
                      self._result_queue,
                      self._queue_management_thread_wakeup),
                name="QueueManagerThread")
            self._queue_management_thread.daemon = True
            self._queue_management_thread.start()
            _threads_wakeups[self._queue_management_thread] = \
                self._queue_management_thread_wakeup

    def submit(*args, **kwargs):
        if len(args) >= 2:
            self, fn, *args = args
        elif not args:
            raise TypeError("descriptor 'submit' of 'ProcessPoolExecutor' object "
                            "needs an argument")
        elif 'fn' in kwargs:
            fn = kwargs.pop('fn')
            self, *args = args
        else:
            raise TypeError('submit expected at least 1 positional argument, '
                            'got %d' % (len(args)-1))

        stateful = kwargs.pop('__stateful__')

        with self._shutdown_lock:
            if self._broken:
                raise BrokenProcessPool(self._broken)
            if self._shutdown_thread:
                raise RuntimeError('cannot schedule new futures after shutdown')
            if _global_shutdown:
                raise RuntimeError('cannot schedule new futures after '
                                   'interpreter shutdown')

            f = _base.Future()
            w = _WorkItem(stateful, f, fn, args, kwargs)

            self._pending_work_items[self._queue_count] = w
            self._work_ids.put(self._queue_count)
            self._queue_count += 1
            # Wake up queue management thread
            self._queue_management_thread_wakeup.wakeup()

            self._start_queue_management_thread()
            return f

    def _adjust_process_count(self):
        for _ in range(len(self._processes), self._max_workers):
            p = self._mp_context.Process(
                target=_process_worker,
                args=(self,
                      self._call_queue,
                      self._result_queue,
                      self._initializer,
                      self._initargs))
            p.start()
            self._processes[p.pid] = p


"""
Module to interact with Keycloak Admin API
"""
from typing import Dict, List

from keycloak.exceptions import KeycloakAuthenticationError, KeycloakGetError
from keycloak.keycloak_admin import KeycloakAdmin

from django_keycloak.config import settings
from django_keycloak.errors import (
    KeycloakMissingServiceAccountRolesError,
    KeycloakNoServiceAccountRolesError,
)

_args: List
_kwargs: Dict
_initialized: bool = False

class LazyKeycloakAdmin(KeycloakAdmin):
    """
    Overrides `KeycloakAdmin` from `python-keycloak`,
    to provide a mechanism to lazy load the connection to
    Keycloak's server and re-use the same connection on
    subsequent requests, thus eliminating the connection overhead.
    """

    def __init__(self, *args, **kwargs):
        global _args, _kwargs
        _args, _kwargs = args, kwargs

    def __getattribute__(self, item):
        """
        Intercepts a method call for `KeycloakAdmin`.
        If no instance has been previously initialized, we
        call the parent constructor to create a new one, and
        save a flag to re-use the same instance on following requests.
        """
        global _initialized, _args, _kwargs
        if not _initialized:
            _initialized = True
            self.handle_keycloak_init(_args, _kwargs)
        # Calling the super class to avoid recursion
        return super().__getattribute__(item)

    def handle_keycloak_init(self, args, kwargs):
        """
        Calls the parent constructor to initialize a
        Keycloak connection.
        """
        try:
            super().__init__(*args, **kwargs)
            # Try to call a users method
            # if error occurs a required role is missing
            # https://github.com/marcospereirampj/python-keycloak/issues/87
            try:
                self.users_count()
            except KeycloakGetError as error:
                if "unknown_error" in str(error):
                    raise KeycloakMissingServiceAccountRolesError from error
                else:
                    raise error
        except KeycloakAuthenticationError as error:
            # Check if the error is due to service account not being enabled
            if "Client not enabled to retrieve service account" in str(error):
                raise KeycloakNoServiceAccountRolesError from error

            # Otherwise re-throw the original error
            else:
                raise error

# The exported module variable
lazy_keycloak_admin = LazyKeycloakAdmin(
    server_url=settings.KEYCLOAK_URL,
    client_id=settings.CLIENT_ID,
    realm_name=settings.REALM,
    client_secret_key=settings.CLIENT_SECRET_KEY,
)


import numpy as np
import imageio
import math

filename = str(input()).rstrip()
I = imageio.imread(filename)
method = int(input())

def fn1():
    t = int(input())
    while(True):
        ti = t
        g1 = []
        g2 = []
        for i in range(len(I)):
            for k in range(len(I[i])):
                if(I[i][k] > ti):
                    g1.append(I[i][k])
                else:
                    g2.append(I[i][k])
        u1 = sum(g1) / len(g1)
        u2 = sum(g2) / len(g2)
        t = (u1 + u2) / 2
        if abs(t - ti) < 0.5:
            break
    Ip = np.zeros((len(I), len(I[0])), np.uint8)
    for i in range(len(I)):
        for k in range(len(I[i])):
            Ip[i][k] = np.uint8(I[i][k] > t)
    return Ip

def fn2():
    n = int(input())
    weightsStrings = input().split(' ', n)
    weights = np.array([float(weightsStrings[i]) for i in range(len(weightsStrings))])
    center = n >> 1
    Ia = I.flatten()
    Iaaux = np.pad(Ia, center, "wrap")
    Ipa = np.zeros(len(Ia))
    for i in range(center, len(Ipa) - center):
        Ipa[i - center] = sum(weights * Iaaux[i - center: i + center + 1])
    return np.reshape(Ipa, (len(I), len(I)))

def fn3():
    n = int(input())
    weights = []
    for i in range(n):
        weightsStrings = input().split(' ', n)
        weights.append([float(weightsStrings[i]) for i in range(len(weightsStrings))])
    t = int(input())
    Ipf = np.zeros((len(I), len(I[0])))
    center = len(weights) >> 1
    Iaux = np.pad(I, center, "edge")
    for i in range(center, len(Iaux) - center):
        for k in range(center, len(Iaux[i]) - center):
            Ipf[i - center][k - center] = sum((weights * Iaux[i - center:i + center + 1, k - center:k + center + 1]).flatten())
    while(True):
        ti = t
        g1 = []
        g2 = []
        for i in range(len(Ipf)):
            for k in range(len(Ipf[i])):
                if(Ipf[i][k] > ti):
                    g1.append(Ipf[i][k])
                else:
                    g2.append(Ipf[i][k])
        u1 = sum(g1) / len(g1)
        u2 = sum(g2) / len(g2)
        t = (u1 + u2) / 2
        if abs(t - ti) < 0.5:
            break
    Ip = np.zeros((len(Ipf), len(Ipf[0])), np.uint8)
    for i in range(len(Ipf)):
        for k in range(len(Ipf[i])):
            Ip[i][k] = np.uint8(Ipf[i][k] > t)
    return Ip

def fn4():
    n = int(input())
    center = n >> 1
    Iaux = np.pad(I, center, "constant", constant_values=0)
    Ip = np.zeros((len(I), len(I[0])), int)
    for i in range(center, len(Iaux) - center):
        for k in range(center, len(Iaux[i]) - center):
            neighbours = Iaux[i - center:i + center + 1, k - center:k + center + 1].flatten()
            neighbours.sort()
            Ip[i - center][k - center] = neighbours[(n * n) >> 1]
    return Ip

Ip = [fn1, fn2, fn3, fn4][method - 1]()
Ip = (((Ip - np.min(Ip)) / (np.max(Ip) - np.min(Ip)) * 255).astype(np.uint8))

sums = 0
for i in range(len(I)):
    for k in range(len(I[i])):
        sums += (I[i][k] - float(Ip[i][k])) ** 2
rse = math.sqrt(sums / (len(I) * len(I[0])))
print('{:.4f}'.format(rse))


#!/usr/bin/python3
"""The base model for the clone"""
from datetime import datetime
import uuid
from models import storage

class BaseModel(object):
    def __init__(self, *args, **kwargs):
        """Initilizing the attributes"""
        if kwargs:
            dateform = "%Y-%m-%dT%H:%M:%S.%f"
            k_dict = kwargs.copy()
            del k_dict["__class__"]
            for key in k_dict:
                if (key == "created_at" or key == "updated_at"):
                    k_dict[key] = datetime.strptime(k_dict[key], dateform)
            self.__dict__ = k_dict
        else:
            self.id = str(uuid.uuid4())
            self.created_at = datetime.today()
            self.updated_at = datetime.today()
            storage.new(self)

    def __str__(self):
        """Return formated string"""
        return "[{}] ({}) {}".format(self.__class__.__name__,
                         self.id, self.__dict__)
    def save(self):
        """Updates update_at with current time"""
        self.updated_at = datetime.today()
        storage.save()

    def to_dict(self):
        """Dict of all keys and values"""
        newdict = self.__dict__.copy()
        newdict["created_at"] = self.created_at.isoformat()
        newdict["updated_at"] = self.updated_at.isoformat()
        newdict["__class__"] = self.__class__.__name__
        return newdict


import json
import asyncio
import lambda_multiprocessing
from url_methods import *
from constants import *

def get_from_schema(schema, item):
    """
    Get the data value asociated with an specific field from a data item

    Params:
      schema: The schema or field name
      item: the data record
    Return:
        The field value from the record if the field exists in the data item.
    """
    if schema is None:
        return None
    fields=schema.split(".")
    while len(fields) > 0:
        field = fields.pop(0)
        if field not in item:
            return None
        item = item.get(field)
        if item == "":
            return None
    return item

def get_from_table(table_params, field, item):
    """
    Get the data value asociated with an specific field from a table inside the
    model

    Params:
      table_params:
        tables: The lookup tables for specific fields in schema
        lang: The lang for the search
      field: the nested fields to access the code
      item: the data record
    Return:
        The string corresponding to that table, code, language
    """
    tables, lang = table_params
    code = get_from_schema(field, item)
    table_name = field.split('.')[0]
    return tables.get(table_name).get(code).get(lang)

def get_from_array(schema, lookup, item):
    """
    Get the data value asociated with an specific field from an array of items

    Params:
      schema: The schema or field name
      lookup: the section of schema that defines the rules to extract the value
              from item
      item: the data record
    Return:
        The field value from the array
    """
    item_array = get_from_schema(schema, item)
    ndx = int(lookup.get("field"))
    return item_array[ndx]

def get_from_search(field, search_field, contains, return_field, item):
    """
    Get the data value asociated with an specific field from an array of items
    where the item has the searched value

    Params:
      field: The field where the list is placed in the data item
      search_field: the field to search on
      contains. The value to match the search with
      return_field. the field containg the returning value
      item: the data record
    Return:
        The 'field' value from the sub-item containing the matching string in
        the search field.
    """
    sub_items = get_from_schema(field, item)
    for sub_item in sub_items:
        if contains in get_from_schema(search_field, sub_item):
            return sub_item.get(return_field)
    return None

def get_average(schema, index_list, item):
    """
    Get the average of a set of values from a numeric array

    Params:
      schema: The schema or field name
      indexes: A list with positions of each value for the addition
      item: the data record
    Return:
        The average of the specified values inside the list
    """
    total = 0.0
    addends = len(index_list)
    if addends < 1:
        return total
    item_array = get_from_schema(schema, item)
    for ndx in index_list:
        total += item_array[ndx]
    return total/addends

def function_error():
    """
    Return:
        Error string when the schema doesn't matches
        any expected type
    """
    return ERR_UNEXPECTED_SCHEMA_TYPE

def function_null():
    """
    Return:
        Null value
    """
    return NULL

def get_function_from_schema(schema, item):
    """
    Provide the specific function based on the schema section provided

    Params:
        schema: The section of the schema with the definition of the field
        item: The data item to be used along with schema to identify which
              function required

    Return: The function that matches the schema for that specific item field
    """
    field = schema.get("field")
    lookup = schema.get("lookup")
    if field == "":
        return function_null
    if not lookup:
        return get_from_schema
    schema_type = lookup.get("type")
    if schema_type == "table":
        return get_from_table
    elif schema_type == "array":
        return get_from_array
    elif schema_type == "search":
        return get_from_search
    elif schema_type == "average":
        return get_average
    elif schema_type == "url":
        return get_from_url
    else:
        return function_error

def get_results(table_params, function_field, item):
    """
    Apply the function asociated for each field to the item to
    extract the return value

    Params:
      table_params:
        tables: The lookup tables for specific fields in schema
        lang: The lang for the search
      function_field: Contains the function and the section of the schema
                      with the rules to obtain the value for that field
      item: the data item to obtain the value from
    Return: The return value for the piece of data either original or altered
             and an error string where None or empty means no error
    """
    function, item_schema = function_field
    if "get_from_schema" in function.__name__:
        return function(item_schema.get("field"), item)
    elif "get_from_table" in function.__name__:
        return function(table_params, item_schema.get("field"), item)
    elif "get_from_array" in function.__name__:
        field = item_schema.get("field")
        lookup = item_schema.get("lookup")
        return function(field, lookup, item)
    elif "get_from_search" in function.__name__:
        field = item_schema.get("field")
        search_field = item_schema.get("lookup").get("search_field")
        contains = item_schema.get("lookup").get("contains")
        return_field = item_schema.get("lookup").get("return_field")
        return function(field,
                        search_field,
                        contains,
                        return_field,
                        item)
    elif "get_average" in function.__name__:
        field = item_schema.get("field")
        ndx_list = item_schema.get("lookup").get("at")
        return function(field, ndx_list, item)
    elif "function_null" in function.__name__:
        return function()
    else:
        return function_error()

def validate_against_schema(value, definition):
    """
    Validate a piece of data with its asociated section in the schema

    Params:
      value: The data value to be evaluated
      definition: The specific schema section to validatethe value

    Return: The return value for the piece of data either original or altered
             and an error string where None or empty means no error
    """
    if value is not None and value != NULL:
        data_type = definition.get("type")
        if data_type == "string":
            if not isinstance(value, str):
                if not isinstance(value, int):
                    return value, ERR_INVALID_STRING
                else:
                    value = str(value)
        elif data_type == "number":
            try:
                val = float(value)
            except:
                return value, ERR_INVALID_NUMBER
            minimum = definition.get("minimum")
            maximum = definition.get("maximum")
            if (val < minimum) or (val > maximum):
                return val, ERR_NUMBER_OUT_OF_RANGE
            return val, ""
        elif data_type == "array":
            if not isinstance(value,list):
                if isinstance(value, int):
                    value = str(value)
                value = value.split(",")
            min_items = definition.get("minItems")
            items = definition.get("items")
            try:
                m_items = int(min_items)
            except:
                m_items = 0
            # Match each item in the data list against the list in schema
            if isinstance(items, list):
                for i in range(m_items):
                    val, error = validate_against_schema(value[i], items[i])
                    if error != "":
                        value[i] = f"{value[i]} - {error}"
                    else:
                        value[i] = val
            else:
                # Match the data list against the type of list in
                for i in range(len(value)):
                    val, error = validate_against_schema(value[i], items)
                    if error != "":
                        value[i] = f"{value[i]} - {error}"
                    else:
                        value[i] = val
        else:
            error = ERR_UNEXPECTED_SCHEMA_TYPE

    return value, ""

def get_data_layer(schema, data):
    """
    Go down through the layers of data to reach the data level
    based on the schema on each service

    Params:
      schema: The schema with the data structure needed
      data: The raw data read from the service

    Return: The the section of the schema for the data transformation and
             the layer of data where is the required information
    """
    schema_out = schema.get("lookup").get("out")
    structure = schema_out.get("structure")
    if structure.get("type")=="dict":
        key = structure.get("key")
        data_fields_layer = data.get(key)
    else:
        data_fields_layer = data
    schema_fields_layer = schema_out.get("data")
    return schema_fields_layer, data_fields_layer

def get_functions(schema, item):
    """
    Based on the schema and one item from the load, get the set of
    functions to be applied to each field

    Params:
      schema: The schema (or part of it) that applies to the field
      item: An item to match the schema with, to identify the function that
            applies for each field

    Return: the set of functions and schemas to apply on each field
    """
    #  in schema from first data item
    functions_by_field = {}
    for key in schema:
        schema_field = schema.get(key)
        if isinstance(schema_field, dict):
            schema_function = get_function_from_schema(schema_field, item)
            functions_by_field[key] = [(schema_function, schema_field)]
        else:
            functions_by_field[key] = []
            for item_schema in schema_field:
                schema_function = get_function_from_schema(item_schema, item)
                functions_by_field[key].append((schema_function, item_schema))
    return functions_by_field

async def apply_service_schema(service,
                               table_params,
                               functions_by_field,
                               data_item):
    """
    Extract the required information from each item based on the service model

    Params:
      service: The name of the service to be added at the begging of each item
      table_params:
        tables: The lookup tables for specific fields in schema
        lang: The lang for the search
      function_by_field: the list of field functions
      data_item: The item to be affected by the functions

    Return: A restructured new item matching the output requirements
    """
    item = {'key': service}
    for key in functions_by_field:
        functions = functions_by_field.get(key)
        if len(functions) == 1:
            item[key] = get_results(table_params, functions[0], data_item)
        else:
            result_list = []
            for function_field in functions:
                result_list.append(get_results(table_params,
                                               function_field,
                                               data_item))
            item[key] = result_list
    return item

def apply_out_schema(parameters_tuple):
    """
    Validate the data item against the output-api-schema

    Params:
      parameters_tuple:
        schema_items: list with all the output items
        schema_required: list with all required items
        item: the item item data to be validated

    Return: The item affected by the model where either absent default values
             or data errors are point out
    """
    output_schema, item = parameters_tuple
    output_fields = output_schema.get("properties")
    output_required = output_schema.get("required")
    for key in output_fields:
        value = item.get(key)
        # Validate required parameters
        if (key in output_required) and (value is None):
            try:
                if key == "lat":
                    item[key] = get_average("bbox", [1, 3], item)
                if key == "lng":
                    item[key] = get_average("bbox", [0, 2], item)
            except:
                item[key] = ERR_ATTRIBUTE_NOT_FOUND
        else:
            # Validate value against schema
            key_definition = output_fields.get(key)
            val, schema_error = validate_against_schema(value,
                                                        key_definition)
            if schema_error:
                item[key] = f"{value} - {schema_error}"
            else:
                item[key] = val
    return item

def items_from_service(service,
                       table_params,
                       service_schema,
                       output_schema,
                       load):
    """
    Based on the output schema and api-out schema, return the formated
    data using multiprocessing to accelerate the task

    Params:
      service: The name of the service to put it ahead of the item's data
      table_params:
        tables: The lookup tables for specific fields in schema
        lang: The lang for the search
      schema_items: the section of the out-api schema to process the data layer
      schema_required: The section of the out-api schema to validate the
                       prescence of 'required' fields in the data layer
      load: The input set of data items

    Return: A set of output items standarized and validated
    """
    list_to_process = []
    loads = []
    #schema = model.get_schema()
    schema_layer, data_layer = get_data_layer(service_schema, load)
    # Identify the process by field
    if len(data_layer) > 0:
        functions_by_field = get_functions(schema_layer, data_layer[0])
        # Readjust the item's data based on the service's schema
        for data_item in data_layer:
            item = asyncio.run(
                apply_service_schema(service,
                                     table_params,
                                     functions_by_field,
                                     data_item))
            # Add the item to the list for the next process
            list_to_process.append((output_schema, item))
        num =len(list_to_process)
        # Apply the 'generic' out-api-schema to the item
        with lambda_multiprocessing.Pool(num) as process:
            loads = process.map(apply_out_schema, list_to_process)
    return loads


"""
 Topic: prime algorithms.
 Here is an example output of this script:

 Totally free. No warranty.

Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit (Intel)]

 [calc_slower]
 seeking primes between (0..100000) ...
 ...profiling of 'create_primes_1': tooks 6.974707 seconds
 ...profiling of 'create_primes_2': tooks 0.195253 seconds
 ...profiling of 'create_primes_3': tooks 0.090312 seconds
 ...profiling of 'create_primes_4': tooks 0.062241 seconds
 ...profiling of 'create_primes_5': tooks 0.046717 seconds
 ...profiling of 'create_primes_6': tooks 0.024859 seconds
 done - 9592 primes found.

 [calc_medium]
 seeking primes between (0..1000000) ...
 ...profiling of 'create_primes_2': tooks 4.662721 seconds
 ...profiling of 'create_primes_3': tooks 1.133545 seconds
 done - 78498 primes found.

 [calc_faster]
 seeking primes between (0..10000000) ...
 ...profiling of 'create_primes_3': tooks 13.872371 seconds
 ...profiling of 'create_primes_4': tooks 7.766646 seconds
 ...profiling of 'create_primes_5': tooks 6.069688 seconds
 done - 664579 primes found.

Are there no better solutions? ... smile ... Of course there are many!
But unfortunately some of them require a much better understanding in
math and I'm afraid (for myself) that some explanations are not simple
enough so I can present them here.
"""
import sys
import time
import math

def profile(function):
    """ intended to be used as decorator for a function or a method to check
        the execution time """

    def decorate(args):
        """ concrete decorator measuring the execution time of
            given function or method """
        start = time.clock()
        result = function(args)
        print("...profiling of '%s': tooks %f seconds" \
              % (function.__name__, time.clock()-start))
        #print("  -> %s" % (function.__doc__))
        return result
    return decorate

def is_prime(val):
    """ simple check for a prime value.
        Note: as a function called several time it is expensive. """
    if val <= 1:
        return False
    if val == 2:
        return True
    if val % 2 == 0:
        return False

    for divisor in range(3, int(math.sqrt(val))+1, 2):
        if val % divisor == 0:
            return False

    return True

@profile
def create_primes_1(max_n):
    """ creating primes up to a maximum value. The even numbers are not
        touched. The primes itself are used to check for possible division. """
    primes = []
    for val in range(3, max_n+1, 2):
        found = False
        for divisor in primes:
            if val % divisor == 0:
                found = True
                break

        if not found:
            primes.append(val)
    return [2] + primes

@profile
def create_primes_2(max_n):
    """ creating primes up to a max. value calling 'is_prime' for each value. """
    primes = [2]+[val for val in range(3, max_n+1, 2) if is_prime(val)]
    return primes

@profile
def create_primes_3(max_n):
    """ using sieve of sundaram:
        http://en.wikipedia.org/wiki/Sieve_of_Sundaram
        The description was really understandable   """
    limit    = max_n + 1
    sieve    = [True] * (limit)
    sieve[1] = False

    for i in range(1, limit//2):
        f = 2 * i + 1
        for j in range(i, limit//2):
            k = i + j * f
            if k <= max_n:
                sieve[k] = False
            else:
                break

    return [2,3]+[2*k+1 for k in range(1, int(max_n/2)) if sieve[k]]

@profile
def create_primes_4(max_n):
    """ creating primes up to a maximum value using a sieve algorithm. All
        multiples of a prime are flagged as 'no prime'. In addition there
        is an optimization by ignoring values flagged as none prime when
        proceeding to next value."""
    sieve = [True] * (max_n+1) # default: all are primes
    sieve[0] = False           # init:    0 is not a prime
    sieve[1] = False           # init:    1 is not a prime

    val = 2
    while val <= max_n:
        factor = val
        # strike out values not being a prime
        noprime = val * factor
        while noprime <= max_n:
            sieve[noprime] = False
            factor += 1
            noprime = val * factor
        # next value
        val += 1
        while val <= max_n and sieve[val] == False:
            val += 1

    return [n for n in range(max_n) if sieve[n] == True]

@profile
def create_primes_5(max_n):
    """ creating primes up to a maximum value using a sieve algorithm. All
        multiples of a prime are flagged as 'no prime'. In addition there
        is an optimization by ignoring values flagged as none prime when
        proceeding to next value."""
    sieve  = [False, True] * (int(max_n / 2))
    sieve += [True]

    if not max_n % 2 == 0:
        sieve += [False]

    sieve[1] = False
    sieve[2] = True
    primes   = [2]

    val = 3
    while val <= max_n:
        # now we have one prime
        primes.append(val)
        # strike out values not being a prime
        noprime = val + val
        while noprime <= max_n:
            sieve[noprime] = False
            noprime += val
        # next value
        val += 1
        while val <= max_n and sieve[val] == False:
            val += 1

    return primes

@profile
def create_primes_6(max_n):
    """ creating primes up to a maximum value using a sieve algorithm. All
        multiples of a prime are flagged as 'no prime'. In addition there
        is an optimization by ignoring values flagged as none prime when
        proceeding to next value."""
    sieve  = [False, True] * (max_n // 2)
    sieve += [False]

    sieve[1] = False
    sieve[2] = True
    primes   = [2]

    val = 3
    while val <= max_n:
        # now we have one prime
        primes.append(val)
        # strike out values not being a prime
        offset  = val * 2
        noprime = val + offset
        while noprime <= max_n:
            sieve[noprime] = False
            noprime += offset
        # next value
        val += 2
        while val <= max_n:
            if not sieve[val]:
                val += 2
            else:
                break

    return primes

def calc_slower(max_n):
    """ scenario I - displaying relation and validate results """
    print("\n[%s]" % (sys._getframe().f_code.co_name))
    print("seeking primes between (0..%d) ..." % (max_n))
    primes1 = create_primes_1(max_n)
    primes2 = create_primes_2(max_n)
    primes3 = create_primes_3(max_n)
    primes4 = create_primes_4(max_n)
    primes5 = create_primes_5(max_n)
    primes6 = create_primes_6(max_n)

    assert primes1 == primes2 == primes3 == primes4 == primes5 == primes6
    print("done - %d primes found." % (len(primes4)))

def calc_medium(max_n):
    """ scenario II - testing 'is_prime' alone """
    print("\n[%s]" % (sys._getframe().f_code.co_name))
    print("seeking primes between (0..%d) ..." % (max_n))
    primes2 = create_primes_2(max_n)
    primes3 = create_primes_3(max_n)
    print("done - %d primes found." % (len(primes2)))

def calc_faster(max_n):
    """ scenario III - testing sieve alone """
    print("\n[%s]" % (sys._getframe().f_code.co_name))
    print("seeking primes between (0..%d) ..." % (max_n))
    primes3 = create_primes_3(max_n)
    primes4 = create_primes_4(max_n)
    primes5 = create_primes_5(max_n)
    print("done - %d primes found." % (len(primes4)))

if __name__ == "__main__":
    print("Python %s" % (sys.version))
    max_n = 100000
    calc_slower(max_n      )
    calc_medium(max_n *  10)
    calc_faster(max_n * 100)