from marshmallow import Schema, fields, EXCLUDE, post_load
from marshmallow.exceptions import ValidationError
from typing import Any, Mapping, Optional

from eddn.commodity_v3.model import Commodity, CommodityV3, Economy, Header, Message

class BaseSchema(Schema):
    class Meta:
        unknown = EXCLUDE

class HeaderSchema(BaseSchema):
    uploader_id = fields.String(required=True, data_key="uploaderID")
    software_name = fields.String(required=True, data_key="softwareName")
    software_version = fields.String(required=True, data_key="softwareVersion")
    gateway_timestamp = fields.String(allow_none=True, data_key="gatewayTimestamp")

    @post_load
    def to_domain(self, data, **kwargs) -> Header:
        return Header(**data)

class Bracket(fields.Field):
    """
    Field to accept bracket 0, 1, 2, 3 or "" info
    """

    def _deserialize(
        self,
        value: Any,
        attr: Optional[str],
        data: Optional[Mapping[str, Any]],
        **kwargs
    ):
        if isinstance(value, int):
            value = str(value)
        if isinstance(value, str):
            return super()._deserialize(value, attr, data, **kwargs)
        else:
            raise ValidationError('Bracket should be int or ""')

class CommoditySchema(BaseSchema):
    name = fields.String(required=True)
    mean_price = fields.Integer(required=True, data_key="meanPrice")
    buy_price = fields.Integer(required=True, data_key="buyPrice")
    stock = fields.Integer(required=True)
    stock_bracket = Bracket(required=True, data_key="stockBracket")
    sell_price = fields.Integer(required=True, data_key="sellPrice")
    demand = fields.Integer(required=True)
    demand_bracket = Bracket(required=True, data_key="demandBracket")
    status_flags = fields.List(
        fields.String(required=True), allow_none=True, data_key="statusFlags"
    )

    @post_load
    def to_domain(self, data, **kwargs) -> Commodity:
        return Commodity(**data)

class EconomySchema(BaseSchema):
    name = fields.String(required=True)
    proportion = fields.Decimal(required=True)

    @post_load
    def to_domain(self, data, **kwargs) -> Economy:
        return Economy(**data)

class MessageSchema(BaseSchema):
    system_name = fields.String(required=True, data_key="systemName")
    station_name = fields.String(required=True, data_key="stationName")
    market_id = fields.Integer(required=True, data_key="marketId")
    timestamp = fields.String(required=True)
    commodities = fields.List(fields.Nested(CommoditySchema), required=True)
    economies = fields.List(fields.Nested(EconomySchema), allow_none=True)
    prohibited = fields.List(fields.String(), allow_none=True)
    horizons = fields.Bool(allow_none=True)
    odyssey = fields.Bool(allow_none=True)

    @post_load
    def to_domain(self, data, **kwargs) -> Message:
        return Message(**data)

class CommodityV3Schema(BaseSchema):
    header = fields.Nested(HeaderSchema, required=True)
    message = fields.Nested(MessageSchema, required=True)

    @post_load
    def to_domain(self, data, **kwargs) -> CommodityV3:
        return CommodityV3(**data)


import datetime as datetime
import math
import os
import warnings

import numpy as np
import pandas as pd
import statsmodels.api as sm
import yfinance as yf
from arch import arch_model
from scipy.optimize import minimize
from scipy.special import gamma, kv
from scipy.stats import multivariate_normal, norm, t
from sklearn.decomposition import PCA
from sklearn.neighbors import KernelDensity

from gig import (
    lnw,
    log_gamma,
    loglike_ig,
    moment_bessel,
    moment_gamma_x,
    moment_gamma_xinv,
)
from skewtpdf import multskewtpdf

warnings.filterwarnings("ignore")

os.chdir("/home/sahil/diss")
k = pd.read_pickle("returns_pca.pkl")

def mpPDF(var, q, pts):
    # Marcenko-Pastur pdf
    # q=T/N
    eMin, eMax = (
        var * (1 - (1.0 / q) ** 0.5) ** 2,
        var * (1 + (1.0 / q) ** 0.5) ** 2,
    )
    eVal = np.linspace(eMin, eMax, pts)
    pdf = q / (2 * np.pi * var * eVal) * ((eMax - eVal) * (eVal - eMin)) ** 0.5
    pdf = pd.Series(pdf, index=eVal)
    return pdf

def getPCA(matrix):
    # Get eVal,eVec from a Hermitian matrix
    eVal, eVec = np.linalg.eigh(matrix)
    indices = eVal.argsort()[::-1]  # arguments for sorting eVal desc
    eVal, eVec = eVal[indices], eVec[:, indices]
    eVal = np.diagflat(eVal)
    return eVal, eVec

def cov2corr(cov):
    # Derive the correlation matrix from a covariance matrix
    std = np.sqrt(np.diag(cov))
    corr = cov / np.outer(std, std)
    corr[corr < -1], corr[corr > 1] = -1, 1  # numerical error
    return corr

def fitKDE(obs, bWidth=0.25, kernel="gaussian", x=None):
    # Fit kernel to a series of obs, and derive the prob of obs
    # x is the array of values on which the fit KDE will be evaluated
    if len(obs.shape) == 1:
        obs = obs.reshape(-1, 1)
    kde = KernelDensity(kernel=kernel, bandwidth=bWidth).fit(obs)
    if x is None:
        x = np.unique(obs).reshape(-1, 1)
    if len(x.shape) == 1:
        x = x.reshape(-1, 1)
    logProb = kde.score_samples(x)  # log(density)
    pdf = pd.Series(np.exp(logProb), index=x.flatten())
    return pdf

def errPDFs(var, eVal, q, bWidth, pts=1000):
    # Fit error
    var = var[0]
    pdf0 = mpPDF(var, q, pts)  # theoretical pdf
    pdf1 = fitKDE(eVal, bWidth, x=pdf0.index.values)  # empirical pdf
    sse = np.sum((pdf1 - pdf0) ** 2)
    return sse

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def findMaxEval(eVal, q, bWidth):
    # Find max random eVal by fitting Marcenko’s dist
    out = minimize(
        lambda *x: errPDFs(*x),
        0.5,
        args=(eVal, q, bWidth),
        bounds=((1e-5, 1 - 1e-5),),
    )
    if out["success"]:
        var = out["x"][0]
    else:
        var = 1
    eMax = var * (1 + (1.0 / q) ** 0.5) ** 2
    return eMax, var
    # - - - - - - - - - - - - - - - - - - - -

def denoisedCorr(eVal, eVec, nFacts):
    # Remove noise from corr by fixing random eigenvalues
    eVal_ = np.diag(eVal).copy()
    eVal_[nFacts:] = eVal_[nFacts:].sum() / float(eVal_.shape[0] - nFacts)
    eVal_ = np.diag(eVal_)
    corr1 = np.dot(eVec, eVal_).dot(eVec.T)
    corr1 = cov2corr(corr1)
    return corr1
    # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# corr1=denoisedCorr(eVal0,eVec0,nFacts0)
# eVal1,eVec1=getPCA(corr1)
# ret_col = [i for i in k.columns if "_logret" in i]
# cov1 = k[ret_col].corr()
# eVal0, evec = getPCA(cov1)
# x = np.matrix(k[ret_col])
# q = x.shape[0] / float(x.shape[1])
# eMax0, var0 = findMaxEval(np.diag(eVal0), q=q, bWidth=0.01)
# nFacts0 = eVal0.shape[0] - np.diag(eVal0)[::-1].searchsorted(eMax0)
# print(nFacts0)


import sys
import sqlite3
from PyQt5.QtWidgets import QTableWidgetItem, QApplication, QWidget, QMainWindow, QTableWidget, QTableWidgetItem, QPushButton, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel
from models.Voiture import Voiture
from models.Locataire import Locataire

DB_PATH = ".\\location_voiture.db"

class DBConnector():
    def __init__(self):
        self.connection = sqlite3.connect(DB_PATH)
        print(self.connection.total_changes)

    def getVoitures(self):
        cursor = self.connection.cursor()
        rows = cursor.execute("SELECT * FROM voiture").fetchall()
        return rows

    def addVoiture(self, voiture):
        cursor = self.connection.cursor()
        cursor.execute(f"INSERT INTO voiture VALUES ('{voiture.num_imma}', '{voiture.marque}', '{voiture.modele}', '{voiture.kilometrage}', '{voiture.etat}', '{voiture.prix_location}')")
        self.connection.commit()

    def updateVoiture(self, voiture):
        cursor = self.connection.cursor()
        cursor.execute(
            "UPDATE voiture SET marque = ? , modele = ? , kilometrage = ? , etat = ? , prix_location = ? WHERE num_imma = ?",
            (voiture.marque, voiture.modele, voiture.kilometrage, voiture.etat, voiture.prix_location, voiture.num_imma)
        )
        self.connection.commit()

    def deleteVoiture(self, num_imma):
        cursor = self.connection.cursor()
        cursor.execute(f"DELETE FROM voiture WHERE num_imma = '{num_imma}'")
        self.connection.commit()

    def getLocataires(self):
        cursor = self.connection.cursor()
        rows = cursor.execute("SELECT * FROM locataire").fetchall()
        return rows

    def addLocataire(self, locataire):
        cursor = self.connection.cursor()
        cursor.execute(f"INSERT INTO locataire VALUES ('{locataire.id_loc}', '{locataire.nom}', '{locataire.prenom}', '{locataire.adresse}')")
        self.connection.commit()

    def updateLocataire(self, locataire):
        cursor = self.connection.cursor()
        cursor.execute(
            "UPDATE locataire SET nom = ? , prenom = ? , adresse = ? WHERE id_loc = ?",
            (locataire.nom, locataire.prenom, locataire.adresse, locataire.id_loc)
        )
        self.connection.commit()

    def deleteLocataire(self, id_loc):
        cursor = self.connection.cursor()
        cursor.execute(f"DELETE FROM locataire WHERE id_loc = '{id_loc}'")
        self.connection.commit()

class MainWindow(QMainWindow):
    def __init__(self, dbConnector):
        super().__init__()
        self.setWindowTitle("Gestion de location de voitures")
        self.setGeometry(100, 100, 800, 600)

        # connecteur de la bd
        self.dbConnector = dbConnector

        # collections d'objets de voitures et locataires
        self.voitures = []
        self.locataires = []

        # widgets pour la gestion des voitures
        self.voitures_table = QTableWidget()
        self.voitures_table.setColumnCount(6)
        self.voitures_table.setHorizontalHeaderLabels(["Num_imma", "Marque", "Modele", "Kilometrage", "Etat", "Prix_location"])
        self.voitures_table.cellChanged.connect(self.modify_voiture)
        self.delete_voiture_btn = QPushButton("Supprimer une voiture")
        self.delete_voiture_btn.clicked.connect(self.delete_voiture)

        # widgets pour la formulaire d'ajout de voiture
        self.add_voiture_form = QWidget()
        self.add_voiture_form_layout = QVBoxLayout()
        self.add_voiture_form.setLayout(self.add_voiture_form_layout)
        self.add_voiture_form_label = QLabel("Ajouter une voiture")
        self.add_voiture_form_num_imma_input = QLineEdit()
        self.add_voiture_form_num_imma_input.setPlaceholderText("Numéro d'immatriculation")
        self.add_voiture_form_marque_input = QLineEdit()
        self.add_voiture_form_marque_input.setPlaceholderText("Marque")
        self.add_voiture_form_modele_input = QLineEdit()
        self.add_voiture_form_modele_input.setPlaceholderText("Modèle")
        self.add_voiture_form_kilometrage_input = QLineEdit()
        self.add_voiture_form_kilometrage_input.setPlaceholderText("Kilométrage")
        self.add_voiture_form_etat_input = QLineEdit()
        self.add_voiture_form_etat_input.setPlaceholderText("Etat")
        self.add_voiture_form_prix_location_input = QLineEdit()
        self.add_voiture_form_prix_location_input.setPlaceholderText("Prix de location")
        self.add_voiture_form_submit_btn = QPushButton("Ajouter")
        self.add_voiture_form_submit_btn.clicked.connect(self.add_voiture)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_label)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_num_imma_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_marque_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_modele_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_kilometrage_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_etat_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_prix_location_input)
        self.add_voiture_form_layout.addWidget(self.add_voiture_form_submit_btn)

        # widgets pour la gestion des locataires
        self.locataires_table = QTableWidget()
        self.locataires_table.setColumnCount(4)
        self.locataires_table.setHorizontalHeaderLabels(["Id_loc", "Nom", "Prenom", "Adresse"])
        self.locataires_table.cellChanged.connect(self.modify_locataire)
        self.delete_locataire_btn = QPushButton("Supprimer un locataire")
        self.delete_locataire_btn.clicked.connect(self.delete_locataire)
        self.search_locataire_btn = QPushButton("Rechercher un locataire")
        self.search_locataire_btn.clicked.connect(self.search_locataire)
        self.locataire_search_input = QLineEdit()
        self.locataire_search_input.setPlaceholderText("Entrez l'ID ou le nom du locataire")

        # widgets pour la formulaire d'ajout d'un locataire
        self.add_locataire_form = QWidget()
        self.add_locataire_form_layout = QVBoxLayout()
        self.add_locataire_form.setLayout(self.add_locataire_form_layout)
        self.add_locataire_form_label = QLabel("Ajouter un locataire")
        self.add_locataire_form_id_loc_input = QLineEdit()
        self.add_locataire_form_id_loc_input.setPlaceholderText("ID du locataire")
        self.add_locataire_form_nom_input = QLineEdit()
        self.add_locataire_form_nom_input.setPlaceholderText("Nom")
        self.add_locataire_form_prenom_input = QLineEdit()
        self.add_locataire_form_prenom_input.setPlaceholderText("Prénom")
        self.add_locataire_form_adresse_input = QLineEdit()
        self.add_locataire_form_adresse_input.setPlaceholderText("Adresse")
        self.add_locataire_form_submit_btn = QPushButton("Ajouter")
        self.add_locataire_form_submit_btn.clicked.connect(self.add_locataire)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_label)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_id_loc_input)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_nom_input)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_prenom_input)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_adresse_input)
        self.add_locataire_form_layout.addWidget(self.add_locataire_form_submit_btn)

        # widgets pour la gestion des locations
        self.louer_voiture_btn = QPushButton("Louer une voiture")
        self.louer_voiture_btn.clicked.connect(self.louer_voiture)
        self.rendre_voiture_btn = QPushButton("Rendre une voiture")
        self.rendre_voiture_btn.clicked.connect(self.rendre_voiture)
        self.location_table = QTableWidget()
        self.location_table.setColumnCount(7)
        self.location_table.setHorizontalHeaderLabels(["Num_imma", "Marque", "Modele", "Kilometrage", "Etat", "Prix_location", "Locataire"])

        # layout principal
        self.mainWidget = QWidget()
        self.main_layout = QVBoxLayout()
        self.mainWidget.setLayout(self.main_layout)
        self.setCentralWidget(self.mainWidget)

        # layout pour la gestion des voitures
        self.voitures_layout = QHBoxLayout()
        self.voitures_layout.addWidget(self.add_voiture_form, 2)
        self.voitures_layout.addWidget(self.voitures_table, 10)
        self.voitures_layout.addWidget(self.delete_voiture_btn, 1)

        # layout pour la gestion des locataires
        self.locataires_layout = QHBoxLayout()
        self.locataires_layout.addWidget(self.add_locataire_form, 2)
        self.locataires_layout.addWidget(self.locataires_table, 15)
        self.locataires_layout.addWidget(self.delete_locataire_btn, 1)
        self.locataires_layout.addWidget(self.search_locataire_btn, 1)
        self.locataires_layout.addWidget(self.locataire_search_input, 1)

        # layout pour la gestion des locations
        self.location_layout = QHBoxLayout()
        self.location_layout.addWidget(self.location_table)
        self.location_layout.addWidget(self.louer_voiture_btn)
        self.location_layout.addWidget(self.rendre_voiture_btn)

        # ajout des layouts à la fenêtre principale
        self.main_layout.addLayout(self.voitures_layout)
        self.main_layout.addLayout(self.locataires_layout)
        self.main_layout.addLayout(self.location_layout)

        # initializer les données
        self.init_voitures(dbConnector.getVoitures())
        self.init_locataires(dbConnector.getLocataires())

    def init_voitures(self, rows):
        for row in rows:
            self.voitures.append(
                Voiture(
                    row[0],
                    row[1],
                    row[2],
                    row[3],
                    row[4],
                    row[5]
                )
            )

            rowPosition = self.voitures_table.rowCount()
            self.voitures_table.insertRow(rowPosition)
            self.voitures_table.setItem(rowPosition , 0, QTableWidgetItem(self.voitures[-1].num_imma))
            self.voitures_table.setItem(rowPosition , 1, QTableWidgetItem(self.voitures[-1].marque))
            self.voitures_table.setItem(rowPosition , 2, QTableWidgetItem(self.voitures[-1].modele))
            self.voitures_table.setItem(rowPosition , 3, QTableWidgetItem(self.voitures[-1].kilometrage))
            self.voitures_table.setItem(rowPosition , 4, QTableWidgetItem(self.voitures[-1].etat))
            self.voitures_table.setItem(rowPosition , 5, QTableWidgetItem(self.voitures[-1].prix_location))

    def add_voiture(self):
        self.voitures.append(
            Voiture(
                self.add_voiture_form_num_imma_input.text(),
                self.add_voiture_form_marque_input.text(),
                self.add_voiture_form_modele_input.text(),
                self.add_voiture_form_kilometrage_input.text(),
                self.add_voiture_form_etat_input.text(),
                self.add_voiture_form_prix_location_input.text()
            )
        )

        rowPosition = self.voitures_table.rowCount()
        self.voitures_table.insertRow(rowPosition)
        self.voitures_table.setItem(rowPosition , 0, QTableWidgetItem(self.voitures[-1].num_imma))
        self.voitures_table.setItem(rowPosition , 1, QTableWidgetItem(self.voitures[-1].marque))
        self.voitures_table.setItem(rowPosition , 2, QTableWidgetItem(self.voitures[-1].modele))
        self.voitures_table.setItem(rowPosition , 3, QTableWidgetItem(self.voitures[-1].kilometrage))
        self.voitures_table.setItem(rowPosition , 4, QTableWidgetItem(self.voitures[-1].etat))
        self.voitures_table.setItem(rowPosition , 5, QTableWidgetItem(self.voitures[-1].prix_location))

        self.dbConnector.addVoiture(self.voitures[-1])

    def modify_voiture(self, row, column):
        voiture = self.voitures[row]
        voiture.num_imma = self.voitures_table.item(row, 0).text() if self.voitures_table.item(row, 0) is not None else voiture.num_imma
        voiture.marque = self.voitures_table.item(row, 1).text() if self.voitures_table.item(row, 1) is not None else voiture.marque
        voiture.modele = self.voitures_table.item(row, 2).text() if self.voitures_table.item(row, 2) is not None else voiture.modele
        voiture.kilometrage = self.voitures_table.item(row, 3).text() if self.voitures_table.item(row, 3) is not None else voiture.kilometrage
        voiture.etat = self.voitures_table.item(row, 4).text() if self.voitures_table.item(row, 4) is not None else voiture.etat
        voiture.prix_location = self.voitures_table.item(row, 5).text() if self.voitures_table.item(row, 5) is not None else voiture.prix_location

        self.dbConnector.updateVoiture(voiture)

        print(self.voitures[0], voiture)

    def delete_voiture(self):
        indices = self.voitures_table.selectionModel().selectedRows()
        for index in sorted(indices):
            num_immat_voiture = self.voitures_table.item(index.row(), 0).text()
            self.voitures = [voiture for voiture in self.voitures if voiture.num_imma != num_immat_voiture]
            self.voitures_table.removeRow(index.row())
            self.dbConnector.deleteVoiture(num_immat_voiture)

        print(self.voitures)
        return

    def init_locataires(self, rows):
        for row in rows:
            self.locataires.append(
                Locataire(
                    row[0],
                    row[1],
                    row[2],
                    row[3]
                )
            )

            rowPosition = self.locataires_table.rowCount()
            self.locataires_table.insertRow(rowPosition)
            self.locataires_table.setItem(rowPosition , 0, QTableWidgetItem(self.locataires[-1].id_loc))
            self.locataires_table.setItem(rowPosition , 1, QTableWidgetItem(self.locataires[-1].nom))
            self.locataires_table.setItem(rowPosition , 2, QTableWidgetItem(self.locataires[-1].prenom))
            self.locataires_table.setItem(rowPosition , 3, QTableWidgetItem(self.locataires[-1].adresse))

    def add_locataire(self):
        self.locataires.append(
            Locataire(
                self.add_locataire_form_id_loc_input.text(),
                self.add_locataire_form_nom_input.text(),
                self.add_locataire_form_prenom_input.text(),
                self.add_locataire_form_adresse_input.text(),
            )
        )

        rowPosition = self.locataires_table.rowCount()
        self.locataires_table.insertRow(rowPosition)
        self.locataires_table.setItem(rowPosition , 0, QTableWidgetItem(self.locataires[-1].id_loc))
        self.locataires_table.setItem(rowPosition , 1, QTableWidgetItem(self.locataires[-1].nom))
        self.locataires_table.setItem(rowPosition , 2, QTableWidgetItem(self.locataires[-1].prenom))
        self.locataires_table.setItem(rowPosition , 3, QTableWidgetItem(self.locataires[-1].adresse))

        self.dbConnector.addLocataire(self.locataires[-1])
        return

    def modify_locataire(self, row, column):
        locataire = self.locataires[row]

        locataire.id_loc = self.locataires_table.item(row, 0).text() if self.locataires_table.item(row, 0) is not None else locataire.id_loc
        locataire.nom = self.locataires_table.item(row, 1).text() if self.locataires_table.item(row, 1) is not None else locataire.nom
        locataire.prenom = self.locataires_table.item(row, 2).text() if self.locataires_table.item(row, 2) is not None else locataire.prenom
        locataire.adresse = self.locataires_table.item(row, 3).text() if self.locataires_table.item(row, 3) is not None else locataire.adresse

        self.dbConnector.updateLocataire(locataire)

        print(self.locataires[0], locataire)

    def delete_locataire(self):
        indices = self.locataires_table.selectionModel().selectedRows()
        for index in sorted(indices):
            id_loc = self.locataires_table.item(index.row(), 0).text()
            self.locataires = [locataire for locataire in self.locataires if locataire.id_loc != id_loc]
            self.locataires_table.removeRow(index.row())

        # TODO : delete locataire in db
        self.dbConnector.deleteLocataire(id_loc)

        print(self.locataires)
        return

    def search_locataire(self):
        res = [index for (index, locataire) in enumerate(self.locataires) if locataire.id_loc == self.locataire_search_input.text()]
        if len(res) == 0:
            return

        index = self.locataires_table.model().index(res[0], 0)
        self.locataires_table.scrollTo(index)
        self.locataires_table.selectRow(index.row())

    def louer_voiture(self):
        pass

    def rendre_voiture(self):
        pass

def main():
    connector = DBConnector()
    app = QApplication(sys.argv)

    # Create a Qt widget, which will be our window.
    window = MainWindow(connector)
    window.show()  # IMPORTANT!!!!! Windows are hidden by default.

    # Start the event loop.
    app.exec()

if __name__ == '__main__':
    main()


"""
https://leetcode-cn.com/problems/subsets/

给你一个整数数组 nums ，数组中的元素 互不相同 。返回该数组所有可能的子集（幂集）。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

示例 1：
    输入：nums = [1,2,3]
    输出：[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2：
    输入：nums = [0]
    输出：[[],[0]]

提示：
    1 <= nums.length <= 10
    -10 <= nums[i] <= 10
    nums 中的所有元素 互不相同

"""

from typing import List
from  itertools import combinations

""" 1. 使用combinations实现排列组合 """
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        for i in range(len(nums) + 1):
            for combo in combinations(nums, i):   # combinations(nums, i) 返回 nums 中所有长度为 i 的子序列
                res.append(combo)
        return res

""" 2. 迭代"""
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = [[]]
        for i in nums:
            res = res + [[i] + num for num in res]
        return res

""" 3. 递归(回溯算法)"""
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        n = len(nums)

        def helper(i, combo):
            res.append(combo)
            for j in range(i, n):
                helper(j + 1, combo + [nums[j]] )

        helper(0, [])
        return res

if __name__ == "__main__":
    nums = [1,2,3]
    sol = Solution()
    result = sol.subsets(nums)
    print(result)


"""update

Revision ID: db693f817b3f
Revises: a3d902eeacd1
Create Date: 2022-11-19 18:47:36.205568

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'db693f817b3f'
down_revision = 'a3d902eeacd1'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('posts', 'likes')
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column('posts', sa.Column('likes', sa.BOOLEAN(), autoincrement=False, nullable=True))
    # ### end Alembic commands ###


try:
  from couchdb.schema import Document, TextField, IntegerField, LongField, DateTimeField, DictField, ListField, Schema, FloatField
except ImportError:
  from couchdb.mapping import Document, TextField, IntegerField, LongField, DateTimeField, DictField, ListField, FloatField
  from couchdb.mapping import Mapping as Schema

import core.model
import database

"""
            1                         n
group     <----------------------------> atividade
-----                                    ------
group=[list_tesk]

"""

class Group(core.model.ActivDB):
    #_id           = <couchdb_id>

    observacoes         = TextField()

    @classmethod
    def countObjectsByGroup(self, id):
        return super(Group, self).countObjectsByGroup(id)

    def GroupIsFilled(self, registry_id, finalized = None):
        dict_temp = {}
        if finalized == None:
            for row in core.database.ACTIVDB.view('activity/by_group', startkey=[registry_id], endkey=[registry_id,{}], group=True, group_level=2):

                if row.value > 0:
                    dict_temp.update({row.key[1]: True})

        else:

            for row in core.database.ACTIVDB.view('activity/by_group', startkey=[registry_id], endkey=[registry_id,{}], group=True, group_level=3):

                if row.key[2]=="finalizado" and row.value > 0:
                    dict_temp.update({row.key[1]: True})

        return dict_temp

    def retrieve(self, id, db=core.database.ACTIVDB):
        _tmp = super(Group, self).retrieve(id,db)
        if _tmp: _tmp.__class__ = Group
        return _tmp

    def save(self, id=None, db=core.database.ACTIVDB):
            if not self.id and id: self.id = id
            self.store(db)

class Activity(core.model.ActivDB):
    #_id                = <couchdb_id>
    observacao          = TextField()
    data_start          = TextField()
    data_end            = TextField()
    data_conclusion     = TextField()
    prioritario         = TextField()
    status              = TextField()
    encarregados        = ListField(TextField())

    @classmethod
    def listfinalizedByRegistry_id(self, registry_id):
        list_tmp=[]

        for row in core.database.ACTIVDB.view('activity/finalized_and_groups', startkey=[registry_id], endkey=[registry_id,{}]):
            list_tmp.append(row)
        return list_tmp

    @classmethod
    def listPendentActivities(self, registry_id):
        list_tmp=[]

        for row in core.database.ACTIVDB.view('activity/pendent', startkey=[registry_id], endkey=[registry_id,{}]):
                list_tmp.append(row)
        return list_tmp

    @classmethod
    def listActivities(self, registry_id):
        list_tmp=[]
        n=1
        for row in core.database.ACTIVDB.view('activity/list_by_registry', startkey=[registry_id], endkey=[registry_id,{}]):

            if row.key[3] and row.value['status'] =="finalizado" and n <= 10:
                list_tmp.append(row)
                n = n+1
            elif row.key[3]== 0 or (row.key[3]==1 and row.value['status'] != "finalizado"):
                list_tmp.append(row)

        return list_tmp

    def retrieve(self, id, db=core.database.ACTIVDB):
        # chamada:
        #    _task = model.Task().retrieve(id)
        _tmp = super(Activity, self).retrieve(id,db)
        if _tmp: _tmp.__class__ = Activity
        return _tmp

    def save(self, id=None, db=core.database.ACTIVDB):
            if not self.id and id: self.id = id
            self.store(db)


import sys
sys.path.append('L2-UniFrac')
sys.path.append('L2-UniFrac/src')
sys.path.append('src')
sys.path.append('scripts')
import L2UniFrac as L2U
import partition_predict_16s as pp
from extract_data import extract_biom, extract_samples, extract_sample_metadata, parse_tree_file, parse_envs, extract_biom_samples, extract_metadata
import partition_predict_wgs as pp2
from copy import deepcopy
import os
import clustering_16s as clustering
from sklearn_extra.cluster import KMedoids
import pandas as pd

def test_push_up_from_wgs_profile():
    print(L2U.push_up_from_wgs_profile('wgs-env0-sample24-reads.profile'))

def test_merge_efficiency():
    return

def test_merge_profiles_by_dir():
    L2U.merge_profiles_by_dir('profile_test')

def test_partition_sample():
    train_percentage=80
    biom_file = 'data/biom/47422_otu_table.biom'
    tree_file = 'data/trees/gg_13_5_otus_99_annotated.tree'
    metadata_file = 'data/metadata/P_1928_65684500_raw_meta.txt'
    metadata_key = 'body_site'
    train_dict, test_dict = pp.partition_samples(train_percentage, biom_file, tree_file, metadata_file, metadata_key)
    print(train_dict.keys())
    print(train_dict['skin'])

def test_decipher_label():
    prediction = [0,0,0,1,1,1,0,1,0,1,0,1,0,1,0,1]
    group_name = 1
    training = ['a','b','c','d','e','f','g','h','i','j']
    meta_dict = {'a':{'body_site':'gut'},
                 'b':{'body_site':'gut'},
                 'c':{'body_site': 'gut'},
                 'd': {'body_site': 'gut'},
                 'e': {'body_site': 'skin'},
                 'f': {'body_site': 'skin'},
                 'g': {'body_site': 'gut'},
                 'h': {'body_site': 'skin'},
                 'i': {'body_site': 'skin'},
                 'j': {'body_site': 'skin'},
                 'k': {'body_site': 'gut'},
                 'l': {'body_site': 'skin'},
                 }
    sample_dict = {'a':0, 'b':1, 'c':2, 'd':3, 'e':4, 'f':5,'g':6,'h':7,'i':8,'j':9,'k':10,'l':11}
    label = pp.decipher_label_by_vote(prediction, training, group_name, meta_dict, sample_dict)
    print(label)

def test_get_sample_id_from_dict():
    t_dict = {'skin': {'sample1':[1,2,3], 'sample2':[3,4,5]}, 'gut':{'sample4':[4,5,6], 'sample5':[6,8,9]}}
    sample_lst = pp.get_sample_id_from_dict(t_dict)
    print(sample_lst)

def test_get_L2UniFrac_results():
    biom_file = 'data/biom/47422_otu_table.biom'
    tree_file = 'data/trees/gg_13_5_otus_99_annotated.tree'
    Tint, lint, nodes_in_order = parse_tree_file(tree_file)
    print("len  of Tint:", len(Tint))
    print("len of nodes in order:", len(nodes_in_order))
    metadata_file = 'data/metadata/P_1928_65684500_raw_meta.txt'
    train_dict, test_dict = pp.partition_samples(80, biom_file, tree_file, metadata_file, "body_site")
    meta_dict = extract_metadata(metadata_file)
    results = pp.get_L2UniFrac_accuracy_results(train_dict, test_dict, Tint, lint, nodes_in_order)
    print(results)

def test_get_wgs_metadict():
    meta_file = 'data/hmgdb_adenoma_bioproject266076.csv'
    meta_dict = pp2.get_metadata_dict(meta_file)
    print(meta_dict)

def test_train_test_split():
    meta_dict = pp2.get_metadata_dict('data/hmgdb_adenoma_bioproject266076.csv')
    samples_train, samples_test, targets_train, targets_test = pp2.partition_sample(meta_dict)
    print(len(samples_train))
    print(len(samples_test))
    print(len(targets_train))
    print(len(targets_test))
    print(targets_test)
    print(samples_test)

def test_merge_profile():
    profile_path1 = 'tests/profile_test/wgs-env0-sample0-reads.profile'
    profile_path2 = 'tests/profile_test/wgs-env1-sample10-reads.profile'
    profile_list1 = L2U.open_profile_from_tsv(profile_path1, False)
    name, metadata, profile = profile_list1[0]
    #print(profile)
    profile1 = L2U.Profile(sample_metadata=metadata, profile=profile)
    profile_list2 = L2U.open_profile_from_tsv(profile_path2, False)
    name, metadata, profile = profile_list2[0]
    profile2 = L2U.Profile(sample_metadata=metadata, profile=profile)
    (Tint, lint, nodes_in_order, nodes_to_index, P) = profile1.make_unifrac_input_and_normalize()

    print(len(profile1._data))

    profile3 = deepcopy(profile1)
    profile3.merge(profile2)
    print(len(profile1._data))
    print(len(profile3._data))

def test_get_rep_sample_from_profiles():
    profile_path1 = 'tests/profile_test/wgs-env0-sample0-reads.profile'
    profile_path2 = 'tests/profile_test/wgs-env1-sample10-reads.profile'
    profile_path3 = 'tests/profile_test/wgs-env1-sample4-reads.profile'
    profile_path4 = 'tests/profile_test/wgs-env0-sample21-reads.profile'
    #profile_lst = [profile_path1, profile_path2, profile_path3, profile_path4]
    profile_lst = [profile_path1, profile_path2, profile_path3]
    rep_vector, Tint, lint, nodes_in_order = L2U.get_representative_sample_wgs(profile_lst)
    print(rep_vector)

def test_wgs_L2UniFrac():
    profile_path1 = 'tests/profile_test/test_profile1.profile'
    profile_path2 = 'tests/profile_test/test_profile2.profile'
    profile_list = [profile_path1, profile_path2]
    profile_list1 = L2U.open_profile_from_tsv(profile_path1, False)
    name1, metadata1, profile1 = profile_list1[0]
    real_profile1 = L2U.Profile(sample_metadata=metadata1, profile=profile1)
    (Tint1, lint1, nodes_in_order1, nodes_to_index1, P1) = real_profile1.make_unifrac_input_and_normalize()
    pushed_1 = L2U.push_up(P1, Tint1, lint1, nodes_in_order1)
    #print(pushed_1)
    profile_list2 = L2U.open_profile_from_tsv(profile_path2, False)
    name2, metadata2, profile2 = profile_list2[0]
    real_profile2 = L2U.Profile(sample_metadata=metadata2, profile=profile2)
    (Tint2, lint2, nodes_in_order2, nodes_to_index2, P2) = real_profile2.make_unifrac_input_and_normalize()
    pushed_2 = L2U.push_up(P2, Tint2, lint2, nodes_in_order2)
    #print(pushed_2)
    real_profile1.merge(real_profile2)
    (Tint, lint, nodes_in_order, nodes_to_index, P) = real_profile1.make_unifrac_input_and_normalize()
    print(nodes_to_index)
    #print(Tint)
    #print(lint)
    #print(nodes_in_order)
    #print(nodes_to_index)
    #print(P)
    rep_vector, Tint, lint, nodes_in_order, nodes_to_index = L2U.get_representative_sample_wgs(profile_list)
    print(rep_vector)
    print(nodes_to_index)
    #print(all_taxids_list)
    #print(nodes_to_index)

def test_get_wgs_clustering_results():
    meta_dict = pp2.get_metadata_dict('data/hmgdb_adenoma_bioproject266076.csv')
    samples_train, samples_test, targets_train, targets_test = pp2.partition_sample(meta_dict, 0)
    print(targets_train)
    kmedoids_prediction, sample_ids = pp2.get_KMedoids_prediction('data/adenoma_266076/adenoma_pairwise_L1UniFrac.txt',2)
    sample_index_dict = pp2.get_index_dict(sample_ids)
    print(kmedoids_prediction)
    results = pp2.get_clustering_scores(kmedoids_prediction, samples_test, meta_dict, sample_index_dict)
    print(results)

def test_get_KMedoids_prediction():
    dmatrix_file = 'data/adenoma_266076/adenoma_pairwise_L1UniFrac.txt'
    kmedoids_prediction, sample_ids = pp2.get_KMedoids_prediction(dmatrix_file,3)
    print(kmedoids_prediction[:10])
    print(sample_ids[:10])
    meta_dict = pp2.get_metadata_dict('data/hmgdb_adenoma_bioproject266076.csv')

    merged_prediction, updated_group_label_dict, sample_ids = pp2.try_cluster(2, 30, 3, 'kmedoids', dmatrix_file, meta_dict)
    print(meta_dict)
    print('merged prediction', merged_prediction)

def test_get_merged_clusters():
    dmatrix_file = 'data/adenoma_266076/adenoma_pairwise_L1UniFrac.txt'
    profile_dir = 'data/adenoma_266076/profiles'
    profile_path_lst = [os.path.join(profile_dir, file) for file in os.listdir(profile_dir)]
    Tint, lint, nodes_in_order, nodes_to_index = L2U.get_wgs_tree(profile_path_lst)

    meta_dict = pp2.get_metadata_dict('data/hmgdb_adenoma_bioproject266076.csv')
    #merged_prediction, updated_group_label_dict, sample_ids = pp2.try_cluster(2, 20, 3, "kmedoids", dmatrix_file, meta_dict)
    #print(updated_group_label_dict)
    all_samples = sample_id = list(meta_dict.keys())
    all_samples_paths = [profile_dir + '/' + sample + '.profile' for sample in all_samples]
    sample_vector_dict = L2U.merge_profiles_by_dir(all_samples_paths, nodes_to_index)
    merged_prediction, updated_group_label_dict, sample_ids = pp2.try_cluster(2, 20, 3, "kmeans", sample_vector_dict, meta_dict)
    print(updated_group_label_dict)

def test_16s_cluster():
    tree_file = 'data/trees/gg_13_5_otus_99_annotated.tree'
    Tint, lint, nodes_in_order = parse_tree_file(tree_file)
    meta_file = 'data/metadata/P_1928_65684500_raw_meta.txt'
    biom_file = 'data/biom/47422_otu_table.biom'
    dmatrix_file = 'data/L1-UniFrac-Out.csv'
    sample_vector = extract_biom_samples(biom_file)
    #print(list(sample_vector.keys())[0])
    #print(list(sample_vector.values())[0])

    for k in sample_vector:
        L2U.push_up(sample_vector[k], Tint, lint, nodes_in_order)

def test_combine_train_test():
    tree_file = 'data/trees/gg_13_5_otus_99_annotated.tree'
    Tint, lint, nodes_in_order = parse_tree_file(tree_file)
    meta_file = 'data/metadata/P_1928_65684500_raw_meta.txt'
    biom_file = 'data/biom/47422_otu_table.biom'
    dmatrix_file = 'data/L1-UniFrac-Out.csv'
    train_dict, test_dict = pp.partition_samples(80, biom_file, tree_file, meta_file, 'body_site')
    sample_dict = clustering.combine_train_test(train_dict, test_dict)
    print(len(sample_dict))
    print(len(list(sample_dict.values())[0]))

def test_get_KMedoids_clustering_score():
    dmatrix_file = 'data/L1-UniFrac-Out.csv'
    meta_file = 'data/metadata/P_1928_65684500_raw_meta.txt'
    biom_file = 'data/biom/47422_otu_table.biom'
    meta_dict = extract_metadata(meta_file)
    sample_ids = extract_samples(biom_file)
    distance_matrix = pd.read_csv(dmatrix_file, header=None)
    kmedoids_prediction = KMedoids(n_clusters=5, metric='precomputed', method='pam',
                                   init='heuristic').fit_predict(distance_matrix)
    score = clustering.get_KMedoids_clustering_score(dmatrix_file, 5, sample_ids, meta_dict)
    print(score)

if __name__ == '__main__':
	#test_partition_sample()
    #test_decipher_label()
    #test_get_L2UniFrac_results()
    #test_get_wgs_metadict()
    #test_train_test_split()
    #test_merge_profile()
    #test_get_rep_sample_from_profiles()
    #test_wgs_L2UniFrac()
    #test_get_KMedoids_prediction()
    #test_get_wgs_clustering_results()
    #test_get_merged_clusters()
    #test_16s_cluster()
    #test_combine_train_test()
    test_get_KMedoids_clustering_score()


# coding: utf-8
####################################################
# Trade Decision
# trade timing: at 13, 14, 15, 20, 21 o'clock
# if endprice over bollinger band 2sigma 2 times
# if oshime and modori at ema 20, execute entry
# stoploss rate: 20pips
# takeprofit rate: 50pips
####################################################

from super_algo import SuperAlgo
from common import instrument_init, account_init, decideMarket, getSlope, getEWMA
from get_indicator import getBollingerWrapper, getVolatilityPriceWrapper, getHighlowPriceWrapper, getLastPriceWrapper, getWeekStartPrice
from trade_calculator import decideLowExceedPrice, decideLowSurplusPrice, decideHighExceedPrice, decideHighSurplusPrice, decideVolatility, decideDailyVolatilityPrice
from mysql_connector import MysqlConnector
from datetime import datetime, timedelta
from logging import getLogger, FileHandler, DEBUG

class MultiAlgo(SuperAlgo):
    def __init__(self, instrument, base_path, config_name, base_time):
        super(MultiAlgo, self).__init__(instrument, base_path, config_name, base_time)
        self.base_price = 0
        self.setPrice(base_time)
        self.debug_logger = getLogger("debug")
        self.result_logger = getLogger("result")
        self.mysql_connector = MysqlConnector()
        self.first_flag = self.config_data["first_trail_mode"]
        self.second_flag = self.config_data["second_trail_mode"]
        self.most_high_price = 0
        self.most_low_price = 0
        self.mode = ""
        self.buy_count = 0
        self.buy_count_price = 0
        self.sell_count = 0
        self.sell_count_price = 0
        self.original_stoploss_rate = 0
        self.week_start_price = 0
        self.count_threshold = 1
        self.stoploss_flag = False
        self.algorithm = ""
        self.log_max_price = 0
        self.log_min_price = 0
        self.first_flag = "pass"
        self.second_flag = "pass"
        self.first_flag_time = None
        self.second_flag_time = None
        self.stl_first_flag = False
        self.setExpantionIndicator(base_time)
        #self.setVolatilityIndicator(base_time)
        self.setDailyIndicator(base_time)

    # decide trade entry timing
    def decideTrade(self, base_time):
        trade_flag = "pass"
        try:
            if self.order_flag:
                pass
            else:
#            if 1 == 1:
                weekday = base_time.weekday()
                hour = base_time.hour
                minutes = base_time.minute
                seconds = base_time.second
                current_price = self.getCurrentPrice()

                if hour == 7 and minutes == 0 and seconds < 10:
                    self.setDailyIndicator(base_time)

                # if weekday == Saturday, we will have no entry.
                if weekday == 5 and hour >= 5:
                    trade_flag = "pass"
                    self.buy_count = 0
                    self.sell_count = 0

                else:
                    # if spread rate is greater than 0.5, we will have no entry
                    if (self.ask_price - self.bid_price) >= 0.5:
                        pass

                    else:
                        if 1==1:
                            trade_flag = self.decideExpantionTrade(trade_flag, current_price, base_time)

                if trade_flag != "pass" and self.order_flag:
                    if trade_flag == "buy" and self.order_kind == "buy":
                        trade_flag = "pass"
                    elif trade_flag == "sell" and self.order_kind == "sell":
                        trade_flag = "pass"
                    else:
                        self.result_logger.info("# execute all over the world at MultiAlgo")

                self.writeDebugLog(base_time, mode="trade")

            return trade_flag
        except:
            raise

    # settlement logic
    def decideStl(self, base_time):
        try:
            stl_flag = False
            ex_stlmode = self.config_data["ex_stlmode"]
            if self.order_flag:

                if ex_stlmode == "on":
                    weekday = base_time.weekday()
                    hour = base_time.hour
                    minutes = base_time.minute
                    seconds = base_time.second
                    current_price = self.getCurrentPrice()

                    self.updatePrice(current_price)

                    if hour == 7 and minutes == 0 and seconds < 10:
                        self.setDailyIndicator(base_time)

                    # if weekday == Saturday, we will settle one's position.
                    if weekday == 5 and hour >= 5:
                        self.result_logger.info("# weekend stl logic")
                        stl_flag = True

                    else:
                        stl_flag = self.decideCommonStoploss(stl_flag, current_price, base_time)
                        stl_flag = self.decideExpantionStoploss(stl_flag, current_price, base_time)
                        stl_flag = self.decideTrailLogic(stl_flag, self.ask_price, self.bid_price, base_time)

            else:
                pass

            self.writeDebugLog(base_time, mode="stl")

            return stl_flag
        except:
            raise

    def decideHighLowPrice(self, current_price, exceed_th, surplus_th, high_price, low_price, mode):
        flag = False

        if mode == "buy":
            if current_price  > (high_price + exceed_th):
                flag = True
            elif current_price < (high_price - surplus_th):
                flag = True
        elif mode == "sell":
            if current_price < (low_price - exceed_th):
                flag = True
            elif current_price > (low_price + surplus_th):
                flag = True

        return flag

    # cannot allovertheworld
    def decideExpantionTrade(self, trade_flag, current_price, base_time):
        if trade_flag == "pass":
            hour = base_time.hour
            minutes = base_time.minute
            seconds = base_time.second

            expantion_timelimit = 3    # 3hours
            #if (hour == 8 or hour == 9 or hour == 10 or hour == 12 or hour == 13 or hour == 14 or hour == 15 or hour == 16 or hour == 17 or hour == 19 or hour == 20 or hour == 21) and (minutes == 59) and seconds < 10:
            if (hour == 12 or hour == 13 or hour == 14 or hour == 19) and (minutes == 59) and seconds < 10:
                self.setExpantionIndicator(base_time)
                if self.sma20_1h > self.sma40_1h > self.sma80_1h:
                    if self.max_price_1h > self.upper_sigma_1h2:
                        trade_flag = "sell"
                        self.result_logger.info("trendreverse trade")
                        self.algorithm = "trendreverse"

                    else:
                        trade_flag  = "buy"
                        self.result_logger.info("trendfollow trade")
                        self.algorithm = "trendfollow"

                    self.entry_time = base_time

                elif self.sma20_1h < self.sma40_1h < self.sma80_1h:
                    if self.min_price_1h < self.lower_sigma_1h2:
                        trade_flag = "buy"
                        self.result_logger.info("trendreverse trade")
                        self.algorithm = "trendreverse"
                    else:
                        trade_flag = "sell"
                        self.result_logger.info("trendfollow trade")
                        self.algorithm = "trendfollow"

                    self.entry_time = base_time

        self.setExpantionStoploss(trade_flag)
        return trade_flag

    def setExpantionStoploss(self, trade_flag):
#        if trade_flag != "pass" and self.algorithm == "expantion":
        if trade_flag != "pass":
            if trade_flag == "buy" and self.daily_slope > 0:
                self.original_stoploss_rate = 0.1
            elif trade_flag == "buy" and self.daily_slope < 0:
                self.original_stoploss_rate = 0.1
            elif trade_flag == "sell" and self.daily_slope < 0:
                self.original_stoploss_rate = 0.1
            elif trade_flag == "sell" and self.daily_slope > 0:
                self.original_stoploss_rate = 0.1

    def decideCommonStoploss(self, stl_flag, current_price, base_time):
#        if self.algorithm == "expantion" or self.algorithm == "volatility" or self.algorithm == "reverse":
        if 1 == 1:
            minutes = base_time.minute
            seconds = base_time.second

            if minutes % 5 == 0 and seconds < 10:
                if self.order_kind == "buy":
                    if (self.order_price - self.bid_price) > self.original_stoploss_rate:
                        self.result_logger.info("# execute common stoploss")
                        stl_flag = True

                elif self.order_kind == "sell":
                    if (self.ask_price - self.order_price) > self.original_stoploss_rate:
                        self.result_logger.info("# execute common stoploss")
                        stl_flag = True

        return stl_flag

    def decideTouchBollinger(self, mode):
        flag = False
        if mode == "upper":
            for i in range(0, len(self.upper_sigma_5m2_list)):
                if self.end_price_5m_list[i] > self.upper_sigma_5m2_list[i]:
                    flag = True

        elif mode == "lower":
            for i in range(0, len(self.lower_sigma_5m2_list)):
                if self.end_price_5m_list[i] < self.lower_sigma_5m2_list[i]:
                    flag = True

        return flag

    def decideExpantionStoploss(self, stl_flag, current_price, base_time):
        target_time = self.entry_time + timedelta(minutes=16)
        base_ftime = base_time.strftime("%Y-%m-%d %H:%M:00")
        target_ftime = target_time.strftime("%Y-%m-%d %H:%M:00")

        seconds = base_time.second

        if target_ftime == base_ftime and seconds < 10:
            self.debug_logger.info("target_time=%s" % target_time)
            self.debug_logger.info("base_time=%s" % base_time)
            self.setExpantionIndicator(base_time)
            if self.order_kind == "buy" and (self.end_price_5m_list[-1] - self.start_price_5m_list[0]) < 0.1:
#                self.stl_first_flag = True
                stl_flag = True
            elif self.order_kind == "sell" and (self.start_price_5m_list[0] - self.end_price_5m_list[-1]) < 0.1:
#                self.stl_first_flag = True
                stl_flag = True
            else:
                self.original_stoploss_rate = 0.2

#            if self.order_kind == "buy" and self.decideTouchBollinger("upper") == False:
#                self.result_logger.info("bollinger stoploss")
#                self.stl_first_flag = True
##                stl_flag = True
#
#            elif self.order_kind == "sell" and self.decideTouchBollinger("lower") == False:
#                self.result_logger.info("bollinger stoploss")
#                self.stl_first_flag = True
##                stl_flag = True

        if self.stl_first_flag and  self.order_kind == "buy":
            if self.order_price < self.bid_price or (self.order_price - 0.1) > self.bid_price:
                stl_flag = True
        elif self.stl_first_flag and self.order_kind == "sell":
            if self.order_price > self.ask_price or (self.order_price + 0.1) < self.ask_price:
                stl_flag = True

#        target_time = self.entry_time + timedelta(hours=3)
#
#        if base_time > target_time:
#            if self.order_kind == "buy" and self.sma20_1h > current_price:
#                self.result_logger.info("sma20 stoploss")
#                stl_flag = True
#            elif self.order_kind == "sell" and self.sma20_1h < current_price:
#                self.result_logger.info("sma20 stoploss")
#                stl_flag = True

        return stl_flag

    def updatePrice(self, current_price):
        if self.log_max_price == 0:
            self.log_max_price = current_price
        elif self.log_max_price < current_price:
            self.log_max_price = current_price
        if self.log_min_price == 0:
            self.log_min_price = current_price
        elif self.log_min_price > current_price:
            self.log_min_price = current_price

# trail settlement function
    def decideTrailLogic(self, stl_flag, current_ask_price, current_bid_price, base_time):
        minutes = base_time.minute
        seconds = base_time.second

        if minutes % 5 == 0 and seconds < 10:
            order_price = self.getOrderPrice()
            first_take_profit = 0.5
            second_take_profit = 1.0

            # update the most high and low price
            if self.most_high_price == 0 and self.most_low_price == 0:
                self.most_high_price = order_price
                self.most_low_price = order_price

            if self.most_high_price < current_bid_price:
                self.most_high_price = current_bid_price
            if self.most_low_price > current_ask_price:
                self.most_low_price = current_ask_price

            # first trailing stop logic
            if self.order_kind == "buy":
                if (current_bid_price - order_price) > first_take_profit:
                    self.trail_flag = True
            elif self.order_kind == "sell":
                if (order_price - current_ask_price) > first_take_profit:
                    self.trail_flag = True

            if self.trail_flag == True and self.order_kind == "buy":
                if (self.most_high_price - 0.3) > current_bid_price:
                    self.result_logger.info("# Execute FirstTrail Stop")
                    stl_flag = True
            elif self.trail_flag == True and self.order_kind == "sell":
                if (self.most_low_price + 0.3) < current_ask_price :
                    self.result_logger.info("# Execute FirstTrail Stop")
                    stl_flag = True

            # second trailing stop logic
            if self.order_kind == "buy":
                if (current_bid_price - order_price) > second_take_profit:
                    self.trail_second_flag = True
            elif self.order_kind == "sell":
                if (order_price - current_ask_price) > second_take_profit:
                    self.trail_second_flag = True
            if self.trail_second_flag == True and self.order_kind == "buy":
                if (self.most_high_price - 0.3) > current_bid_price:
                    self.result_logger.info("# Execute SecondTrail Stop")
                    stl_flag = True
            elif self.trail_second_flag == True and self.order_kind == "sell":
                if (self.most_low_price + 0.3) < current_ask_price :
                    self.result_logger.info("# Execute SecondTrail Stop")
                    stl_flag = True

        return stl_flag

# reset flag and valiables function after settlement
    def resetFlag(self):
        self.most_high_price = 0
        self.most_low_price = 0
        self.stoploss_flag = False
        self.algorithm = ""
        self.log_max_price = 0
        self.log_min_price = 0
        self.first_flag = "pass"
        self.second_flag = "pass"
        self.first_flag_time = None
        self.second_flag_time = None
        self.stl_first_flag = False
        super(MultiAlgo, self).resetFlag()

# set Indicator dataset function
    def getDailySlope(self, instrument, target_time, span, connector):
        table_type = "day"
        sql = "select end_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit %s" % (instrument, table_type, target_time, span)
        response = connector.select_sql(sql)

        price_list = []
        for res in response:
            price_list.append(res[0])

        price_list.reverse()
        slope = getSlope(price_list)

        return slope

    def setExpantionIndicator(self, base_time):
        # set dataset 5minutes
        target_time = base_time - timedelta(minutes=5)

        # set 5m 2sigma bollinger band
        dataset = getBollingerWrapper(target_time, self.instrument, table_type="5m", window_size=28, connector=self.mysql_connector, sigma_valiable=2, length=5)
        self.upper_sigma_5m2_list = dataset["upper_sigmas"][-5:]
        self.lower_sigma_5m2_list = dataset["lower_sigmas"][-5:]
        self.base_line_5m2_list = dataset["base_lines"][-5:]

        # set 5m 3sigma bollinger band
        dataset = getBollingerWrapper(target_time, self.instrument, table_type="5m", window_size=28, connector=self.mysql_connector, sigma_valiable=3, length=5)
        self.upper_sigma_5m3_list = dataset["upper_sigmas"][-5:]
        self.lower_sigma_5m3_list = dataset["lower_sigmas"][-5:]
        self.base_line_5m3_list = dataset["base_lines"][-5:]

        # set 5m end price list
        sql = "select start_price, end_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit 6" % (self.instrument, "5m", target_time)
        response = self.mysql_connector.select_sql(sql)
        start_tmp = []
        end_tmp = []
        for res in response:
            start_tmp.append(res[0])
            end_tmp.append(res[1])

        start_tmp.reverse()
        end_tmp.reverse()

        self.start_price_5m_list = start_tmp
        self.end_price_5m_list = end_tmp

        # set dataset 1hour
        target_time = base_time - timedelta(hours=1)

        # set 1h end price list
        sql = "select end_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit 80" % (self.instrument, "1h", target_time)
        response = self.mysql_connector.select_sql(sql)
        tmp = []
        for res in response:
            tmp.append(res[0])

        tmp.reverse()
        self.end_price_1h_list = tmp

        self.sma20_1h = sum(self.end_price_1h_list[-20:])/len(self.end_price_1h_list[-20:])
        self.sma40_1h = sum(self.end_price_1h_list[-40:])/len(self.end_price_1h_list[-40:])
        self.sma80_1h = sum(self.end_price_1h_list[-80:])/len(self.end_price_1h_list[-80:])

        # set 1h 2sigma bollinger band
        dataset = getBollingerWrapper(target_time, self.instrument, table_type="1h", window_size=28, connector=self.mysql_connector, sigma_valiable=2, length=0)
        self.upper_sigma_1h2 = dataset["upper_sigmas"][-1]
        self.lower_sigma_1h2 = dataset["lower_sigmas"][-1]
        self.base_line_1h2 = dataset["base_lines"][-1]

        sql = "select max_price, min_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit 1" % (self.instrument, "1h", target_time)
        response = self.mysql_connector.select_sql(sql)
        self.max_price_1h = response[0][0]
        self.min_price_1h = response[0][1]

    def setVolatilityIndicator(self, base_time):
        target_time = base_time - timedelta(minutes=5)
        sql = "select start_price, end_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit 1" % (self.instrument, "5m", target_time)
        response = self.mysql_connector.select_sql(sql)
        self.start_price_1m = response[0][0]
        self.end_price_1m = response[0][1]

    def setDailyIndicator(self, base_time):
        target_time = base_time - timedelta(days=1)
        self.daily_slope = self.getDailySlope(self.instrument, target_time, span=10, connector=self.mysql_connector)
        sql = "select max_price, min_price from %s_%s_TABLE where insert_time < \'%s\' order by insert_time desc limit 1" % (self.instrument, "day", target_time)
        response = self.mysql_connector.select_sql(sql)
        self.high_price = response[0][0]
        self.low_price = response[0][1]

# write log function
    def writeDebugLog(self, base_time, mode):
        pass
#        self.debug_logger.info("%s: %s Logic START" % (base_time, mode))
#        self.debug_logger.info("# self.buy_count=%s" % self.buy_count)
#        self.debug_logger.info("# self.sell_count=%s" % self.sell_count)
#        self.debug_logger.info("# self.daily_slope=%s" % self.daily_slope)
#        self.debug_logger.info("# self.upper_sigma_1h3=%s" % self.upper_sigma_1h3)
#        self.debug_logger.info("# self.lower_sigma_1h3=%s" % self.lower_sigma_1h3)
#        self.debug_logger.info("# self.upper_sigma_5m3=%s" % self.upper_sigma_5m3)
#        self.debug_logger.info("# self.lower_sigma_5m3=%s" % self.lower_sigma_5m3)
#        self.debug_logger.info("# self.start_price_5m=%s" % self.start_price_5m)
#        self.debug_logger.info("# self.end_price_5m=%s" % self.end_price_5m)
#        self.debug_logger.info("# self.start_price_1m=%s" % self.start_price_1m)
#        self.debug_logger.info("# self.end_price_1m=%s" % self.end_price_1m)
#        self.debug_logger.info("#############################################")

    def entryLogWrite(self, base_time):
        self.result_logger.info("#######################################################")
        self.result_logger.info("# in %s Algorithm" % self.algorithm)
        self.result_logger.info("# EXECUTE ORDER at %s" % base_time)
        self.result_logger.info("# ORDER_PRICE=%s, TRADE_FLAG=%s" % (self.order_price, self.order_kind))
        self.result_logger.info("# self.daily_slope=%s" % self.daily_slope)
        self.result_logger.info("# self.upper_sigma_5m2=%s" % self.upper_sigma_5m2_list[-1])
        self.result_logger.info("# self.lower_sigma_5m2=%s" % self.lower_sigma_5m2_list[-1])
        self.result_logger.info("# self.sma20_1h=%s" % self.sma20_1h)
        self.result_logger.info("# self.sma40_1h=%s" % self.sma40_1h)
        self.result_logger.info("# self.sma80_1h=%s" % self.sma80_1h)

    def settlementLogWrite(self, profit, base_time, stl_price, stl_method):
        self.result_logger.info("# self.log_max_price=%s" % self.log_max_price)
        self.result_logger.info("# self.log_min_price=%s" % self.log_min_price)
        self.result_logger.info("# EXECUTE SETTLEMENT at %s" % base_time)
        self.result_logger.info("# STL_PRICE=%s" % stl_price)
        self.result_logger.info("# PROFIT=%s" % profit)


import traceback
from datetime import date
from http.server import SimpleHTTPRequestHandler
from urllib import parse
from typing import Optional

from jinja2 import Template
#импортим, чтобы в html не хватить много скобок на стилях

from const import CSS_CLASS_ERROR
from custom_types import HttpRequest, User
from errors import MethodNotAllowed
from errors import NotFound
from utils import to_str, generate_new_session, build_session_header, \
    load_theme, switch_theme, store_theme, load_profile, store_profile, drop_profile
from utils import to_bytes
from utils import read_static

class MyHttp(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.dispatch("get")

    def do_POST(self):
        self.dispatch("post")

    def dispatch(self, http_method):
        req = HttpRequest.build(self.path, method=http_method, headers=self.headers)

        endpoints = {
            "/": [self.handle_static, ["index.html", "text/html"]],
            "/0/": [self.handle_zde, []],
            "/hello/": [self.handle_hello, [req]],
            "/hello-update/": [self.handle_hello_update, [req]],
            "/hello-reset/": [self.handle_hello_reset, [req]],
            "/i/": [self.handle_static, [f"images/{req.file_name}", req.content_type]],
            "/s/": [self.handle_static, [f"styles/{req.file_name}", req.content_type]],
            "/theme/": [self.handle_theme, [req]]
        }

        try:
            try:
                handler, args = endpoints[req.normal]
            except KeyError:
                raise NotFound

            handler(*args)
        except (NotFound, KeyError):
            self.handle_404()
        except MethodNotAllowed:
            self.handle_405()
        except Exception:
            self.handle_500()

    # def get_session(self) -> str:
    #     cookie = self.headers.get("Cookie", "")
    #     qs = parse.qs(cookie)
    #     session = qs.get("session", "")
    #     return session

    def handle_theme(self, request: HttpRequest) -> None:
        if request.method != "post":
            raise MethodNotAllowed

        response_kwargs = {}
        session = request.session
        if not session:
            session = generate_new_session()
            response_kwargs["session"] = session

        current_theme = load_theme(session)
        new_theme = switch_theme(current_theme)
        store_theme(session, new_theme)

        self.redirect("/hello", **response_kwargs)

    def handle_hello(self, request: HttpRequest) -> None:
        if request.method != "get":
            raise MethodNotAllowed

        profile = load_profile(request.session)
        user = User.build(profile)

        content = self.render_hello_page(request, user, user)
        # session = self.get_session()
        # if not session:
        #     session = self.generate_new_session
        #     user = User.build("")
        #     content = self.render_hello_page(user, user)
        #     self.respond(content, session=session)
        #
        # query = self.load_user_data(session)
        # user = User.build(query)
        #
        # content = self.render_hello_page(user, user)
        self.respond(content)

    def handle_hello_update(self, request: HttpRequest) -> None:
        if request.method != "post":
            raise MethodNotAllowed

        form_data = self.get_form_data()
        new_user = User.build(form_data)

        response_kwargs = {}
        session = request.session
        if not session:
            session = generate_new_session()
            response_kwargs["session"] = session

        if new_user.errors:
            saved_data = load_profile(session)
            saved_user = User.build(saved_data)
            html = self.render_hello_page(request, new_user, saved_user)
            self.respond(html, **response_kwargs)
        else:
            store_profile(session, form_data)
            self.redirect("/hello", **response_kwargs)

    def handle_hello_reset(self, request: HttpRequest) -> None:
        if request.method != "post":
            raise MethodNotAllowed

        drop_profile(request.session)
        self.redirect("/hello", session="")

    def render_hello_page(self,
        request: HttpRequest,
        new_user: User,
        saved_user: User,
    ) -> str:
        css_class_for_name = css_class_for_age = ""
        label_for_name = "Your name: "
        label_for_age = "Your age: "

        age_new = age_saved = saved_user.age
        name_new = name_saved = saved_user.name

        year = date.today().year - age_saved

        if new_user.errors:
            if "name" in new_user.errors:
                error = new_user.errors["name"]
                label_for_name = f"ERROR: {error}"
                css_class_for_name = CSS_CLASS_ERROR

            if "age" in new_user.errors:
                error = new_user.errors["age"]
                label_for_age = f"ERROR: {error}"
                css_class_for_age = CSS_CLASS_ERROR

            name_new = new_user.name
            age_new = new_user.age

        theme = load_theme(request.session)

        html = read_static("hello.html").decode()
        template = Template(html)

        context = {
            "age_new": age_new or "",
            "label_for_age": label_for_age,
            "label_for_name": label_for_name,
            "name_new": name_new or "",
            "name_saved": name_saved or "",
            "class_for_age": css_class_for_age,
            "class_for_name": css_class_for_name,
            "year": year,
            "theme": theme,
        }

        content = template.render(**context)

        return content

    def handle_static(self, file_path, content_type):
        content = read_static(file_path)
        self.respond(content, content_type=content_type)

    def handle_zde(self):
        x = 1 / 0
        print(x)

    def handle_404(self):
        msg = """PAGE NOT FOUND!!!!!!"""

        self.respond(msg, code=404, content_type="text/plain")

    def handle_405(self):
        self.respond("", code=405, content_type="text/plain")

    def handle_500(self):
        msg = traceback.format_exc()
        self.respond(msg, code=500, content_type="text/plain")

    def respond(self, message, code=200, content_type="text/html", session: Optional[str] = None) -> None:
        payload = to_bytes(message)

        self.send_response(code)
        self.send_header("Content-type", content_type)
        self.send_header("Content-length", str(len(payload)))
        self.send_header_session(session)
        self.end_headers()
        self.wfile.write(payload)

    def redirect(self, to, session: Optional[str] = None) -> None:
        self.send_response(302)
        self.send_header("Location", to)
        self.send_header_session(session)
        self.end_headers()

    def send_header_session(self, session: Optional[str]) -> None:
        if session is None:
            return

        if session:
            header = build_session_header(session)
        else:
            header = build_session_header("expires", expires=True)

        self.send_header("Set-Cookie", header)

    def get_form_data(self) -> str:
        content_length_as_str = self.headers.get("content-length", 0)
        content_length = int(content_length_as_str)

        if not content_length:
            return ""

        payload_as_bytes = self.rfile.read(content_length)
        payload = to_str(payload_as_bytes)

        return payload


import json
from pathlib import Path
from typing import List

import pandas as pd
from gpao.job import Job

from coclico.metrics.commons import bounded_affine_function
from coclico.metrics.metric import Metric
from coclico.version import __version__

class MALT0(Metric):
    """Metric MALT0 (for "Métrique altimétrique 0")
    Comparison of 2d height maps (digital surface model for one class only) for each class between the
    classification and the reference. The comparison is done only for the pixels of the map that contain
    points in the reference tile (based on an occupancy map similar to the one used in MPLA0).

    - metric_intrinsic:
        - for each input file, generate a tif file with one layer by class in the class_weight dictionary
    for each class, the corresponding layer contains a height map of the class, created the same way as a digital
    surface model:
            - generate a 2D delaunay triangulation of the points of the given class
            - interpolate the values on a raster with the desired pixel size (pdal faceraster filter)

        - for the tiles of the reference folder, generate also an addditional "occupancy map":
    a tif file with one layer by class in the class_weight dictionary for each class, the corresponding layer contains
    a kind of 2d occupancy map for the class (ie. if any point of this class belongs to the pixel, the pixel has a
    value of 1, the value is 0 everywhere else)

    - metric relative:
        Compare the Z values between the classification and the reference, then compute the mean, max and standard
        deviation of the difference. (only where the occupancy map of the reference is True)

    - note:
        Linear combination of notes:
        - a bounded affine function of the max dfference
        - a bounded affine function of the mean dfference
        - a bounded affine function of the standard deviation of the dfference
    """

    # Pixel size for MNx
    pixel_size = 0.5
    metric_name = "malt0"

    def create_metric_intrinsic_one_job(self, name: str, input: Path, output: Path, is_ref: bool):
        job_name = f"{self.metric_name}_intrinsic_{name}_{input.stem}"
        occupancy_map_arg = ""
        mnx_out = output / "mnx"
        mnx_out.mkdir(exist_ok=True, parents=True)
        if is_ref:
            occ_out = output / "occupancy"
            occ_out.mkdir(exist_ok=True, parents=True)
            occupancy_map_arg = f"--output-occupancy-file /output/occupancy/{input.stem}.tif"

        command = f"""
docker run -t --rm --userns=host --shm-size=2gb
-v {self.store.to_unix(input)}:/input
-v {self.store.to_unix(output)}:/output
ignimagelidar/coclico:{__version__}
python -m coclico.malt0.malt0_intrinsic
--input-file /input
--output-mnx-file /output/mnx/{input.stem}.tif
{occupancy_map_arg}
--class-weights '{json.dumps(self.class_weights)}'
--pixel-size {self.pixel_size}
"""

        job = Job(job_name, command, tags=["docker"])
        return job

    def create_metric_relative_to_ref_jobs(
        self, name: str, out_c1: Path, out_ref: Path, output: Path, c1_jobs: List[Job], ref_jobs: List[Job]
    ) -> Job:
        job_name = f"{self.metric_name}_{name}_relative_to_ref"
        command = f"""
docker run -t --rm --userns=host --shm-size=2gb
-v {self.store.to_unix(out_c1) / "mnx"}:/input
-v {self.store.to_unix(out_ref) / "mnx"}:/ref
-v {self.store.to_unix(out_ref) / "occupancy"}:/occupancy
-v {self.store.to_unix(output)}:/output
ignimagelidar/coclico:{__version__}
python -m coclico.malt0.malt0_relative
--input-dir /input
--ref-dir /ref
--occupancy-dir /occupancy
--output-csv-tile /output/result_tile.csv
--output-csv /output/result.csv
--class-weights '{json.dumps(self.class_weights)}'
"""

        job = Job(job_name, command, tags=["docker"])
        for c1_job in c1_jobs:
            job.add_dependency(c1_job)
        for ref_job in ref_jobs:
            job.add_dependency(ref_job)

        return [job]

    @staticmethod
    def compute_note(metric_df: pd.DataFrame) -> pd.DataFrame:
        """Compute malt0 note from malt0_relative results.
        This method expects a pandas dataframe with columns:
            - max_diff
            - mean_diff
            - std_diff
        (these columns are described in the malt0_relative function docstring)

        Args:
            metric_df (pd.DataFrame): malt0 relative results as a pandas dataframe

        Returns:
            metric_df: the updated metric_df input with notes instead of metrics
        """

        max_note = bounded_affine_function((0.1, 1), (4, 0), metric_df["max_diff"])  # 0 <= max_note <= 1
        mean_note = bounded_affine_function((0.01, 2), (0.5, 0), metric_df["mean_diff"])  # 0 <= mean_note <= 2
        std_note = bounded_affine_function((0.01, 2), (0.5, 0), metric_df["std_diff"])  # 0 <= std_note <= 2

        metric_df[MALT0.metric_name] = (max_note + mean_note + std_note) / 5

        metric_df.drop(columns=["max_diff", "mean_diff", "std_diff"], inplace=True)

        return metric_df


"""
Given a set with distinct elements, find all of its distinct subsets.

Example 1:

Input: [1, 3]
Output: [], [1], [3], [1,3]
Example 2:

Input: [1, 5, 3]
Output: [], [1], [5], [3], [1,5], [1,3], [5,3], [1,5,3]
"""

def find_subsets(nums):
    subsets = [[]]
    for each in nums:
        for idx in range(len(subsets)):
            set = list(subsets[idx])
            set.append(each)
            subsets.append(set)

    return subsets

def main():
    print("Here is the list of subsets: " + str(find_subsets([1, 3])))
    print("Here is the list of subsets: " + str(find_subsets([1, 5, 3])))

main()