import sqlite3
from sqlite3 import Error

class Key:
    def __init__(self, key,content,info, database_path):
     if database_path!="":
      try:
        self.key = key
        self.database_path =database_path

        if not self.check_key_exists():
          if len(self.get_all__key(key))==0:
           if key!="":
            self.create_key(key,content,info)
        else:
            self.update_key(key,content,info)
      except Error as e:
          print(e)

    def check_key_exists(self):
        conn = sqlite3.connect(self.database_path)
        cursor = conn.cursor()

        cursor.execute("SELECT id FROM os WHERE key like '%'+?+'%'", (self.key,))
        exists = cursor.fetchone()

        conn.close()

        if exists is None:
            return False
        else:
            return True
    def get_all__key(self, key):
     a=[]
     if self.database_path!="":
      conn = sqlite3.connect(self.database_path)
      if key!="":
       cur = conn.cursor()
       cur.execute("SELECT * FROM os", () )

       rows = cur.fetchall()

       for row in rows:
        #print("s",row[1])
        if key in row[1]:
         a.append(row[1])
      return a
     return a;
    def find_key_content(self, key):
     conn = sqlite3.connect(self.database_path)
     a=[]
     if key!='':
      cur = conn.cursor()
      cur.execute("SELECT * FROM os WHERE key=?", (key,) )
      rows = cur.fetchall()
      for row in rows:
        #print("s",row[2])
        return row[2]
        if key in row[2]:
         a.append(row[2])
     return a
    def delete_key(self,key):
        try:
         conn = sqlite3.connect(self.database_path)
         cursor = conn.cursor()

         cursor.execute("DELETE FROM os WHERE key=?", (key,))

         conn.commit()
         conn.close()
        except Error as e:
         print(e)
    def create_key(self,key,content,info):
      try:
        conn = sqlite3.connect(self.database_path)
        cursor = conn.cursor()
        print(str(len(self.get_all__key(key))))
        if len(self.get_all__key(key))==0:
         cursor.execute("INSERT INTO os (key,content,info) VALUES (?,?,?)", (key,content,info))

        conn.commit()
        conn.close()
      except Error as e:
        print(e)

    def update_key(self,key,content,info):
      try:
        conn = sqlite3.connect(self.database_path)
        cursor = conn.cursor()
        cursor.execute("UPDATE os SET content=?,info=? WHERE key=?", (content,info,key))
        conn.commit()
        conn.close()
      except Error as e:
        print(e)

    def get_key(self):
        conn = sqlite3.connect(self.database_path)
        cursor = conn.cursor()

        cursor.execute("SELECT key FROM os WHERE key=?", (self.key,))


from PyQt5.QtWidgets import QTableWidgetItem, QLabel, QFileDialog
from PyQt5.QtCore import  Qt
from pandas.tests.io.excel.test_xlrd import xlwt

from UI.resultWinUI import *
from algorithm import *
from UI.mainWinUI import *

class BrokerWin(Ui_MainWindow, QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.setupUi(self)
        self.setFixedSize(1100, 540)
        self.fizButton.clicked.connect(self.sendInputFiz)
        self.bizButton.clicked.connect(self.sendInputBiz)
        self.selectAll.clicked.connect(self.selectAllFiz)
        self.selectAll_2.clicked.connect(self.selectAllBiz)

    def sendInputFiz(self):
        banks.clear()
        black_list.clear()
        optional_fiz['Осуществление автоплатежей'] = self.autoPayments.isChecked()
        optional_fiz['Перевод за рубеж'] = self.foreignTransfer.isChecked()
        optional_fiz['Создание автоперевода'] = self.createAutoPayments.isChecked()
        optional_fiz['Новости системы банка онлайн'] = self.news.isChecked()
        optional_fiz['Автострахование'] = self.insuranceAuto.isChecked()
        optional_fiz['Страхование недвижимости'] = self.insuranceEstate.isChecked()
        optional_fiz['Страхование путешественников'] = self.insuranceTravellers.isChecked()
        optional_fiz['Страхование пассажиров'] = self.insurancePassangers.isChecked()
        optional_fiz['Наличие мобильного приложения'] = self.mobileApp.isChecked()
        optional_fiz['Открытие брокерского счета'] = self.brokerAccount.isChecked()
        ranked_fiz['Переводы на карту'] = self.transferToClient_fiz_SpinBox.value()
        ranked_fiz['Минимальная сумма вклада'] = self.depositSum_fiz_SpinBox.value()
        ranked_fiz['Процент по вкладу '] = self.persentDepozit_fiz_SpinBox.value()
        ranked_fiz['Сумма кредита'] = self.creditSum_fiz_SpinBox.value()
        ranked_fiz['Ставка кредита'] = self.percentCredit_fiz_SpinBox.value()
        ranked_fiz['Переводы на карты по номеру телефона'] = self.transferNumber_fiz_SpinBox.value()

        choose_necessary('fiz')
        choose_ranked('fiz')
        kind_of_sort = self.sort_fiz.currentText()
        # self.close()
        if kind_of_sort == "Пользовательскому рейтингу":
            self.Open = ResultWin("По рейтингу")
        elif kind_of_sort == "Кредитным условиям":
            self.Open = ResultWin("по кредиту")
        elif kind_of_sort == "Условиям по вкладам":
            self.Open = ResultWin("по вкладу")
        self.Open.show()

        # print(special_sort('По рейтингу'))

    def sendInputBiz(self):
        banks.clear()
        black_list.clear()
        optional_biz['Мобильное приложение'] = self.mobileApp_biz.isChecked()
        optional_biz['Торговый эквайринг'] = self.trade_biz.isChecked()
        optional_biz['Мобильный эквайринг'] = self.mobileTrade_biz.isChecked()
        optional_biz['Онлайн-бухгалтерия'] = self.onlineAccounting_biz.isChecked()
        optional_biz['Проверка контрагентов'] = self.checkAgents_biz.isChecked()
        optional_biz['Управление корпоративными картами'] = self.cards_biz.isChecked()
        optional_biz['Финансовая аналитика'] = self.analitics_biz.isChecked()
        optional_biz['Техподдержка клиентов 24/7'] = self.clientSupport_biz.isChecked()
        optional_biz['Персональный менеджер'] = self.personalManager_biz.isChecked()
        ranked_biz['Стоимость обслуживания'] = self.mounthPayment_biz_SpinBox.value()
        ranked_biz['% за снятие наличных'] = self.cashComission_biz_SpinBox.value()
        ranked_biz['% за внесение наличных'] = self.cashInputComission_biz_SpinBox.value()
        ranked_biz['Лимит перевода на карту физ.лица'] = self.transfer_biz_SpinBox.value()

        choose_necessary('biz')
        choose_ranked('biz')
        kind_of_sort = self.sort_biz.currentText()
        # self.close()
        if kind_of_sort == "Пользовательскому рейтингу":
            self.Open = ResultWin("По рейтингу")
        elif kind_of_sort == "Стоимости обслуживания":
            self.Open = ResultWin("По обслуживанию в месяц")
        self.Open.show()

    def selectAllFiz(self):
        self.autoPayments.setChecked(True)
        self.foreignTransfer.setChecked(True)
        self.createAutoPayments.setChecked(True)
        self.news.setChecked(True)
        self.insuranceAuto.setChecked(True)
        self.insuranceEstate.setChecked(True)
        self.insuranceTravellers.setChecked(True)
        self.insurancePassangers.setChecked(True)
        self.mobileApp.setChecked(True)
        self.brokerAccount.setChecked(True)

    def selectAllBiz(self):
        self.mobileApp_biz.setChecked(True)
        self.trade_biz.setChecked(True)
        self.mobileTrade_biz.setChecked(True)
        self.onlineAccounting_biz.setChecked(True)
        self.checkAgents_biz.setChecked(True)
        self.cards_biz.setChecked(True)
        self.analitics_biz.setChecked(True)
        self.clientSupport_biz.setChecked(True)
        self.personalManager_biz.setChecked(True)

class ResultWin(Ui_ResultWindow, QtWidgets.QMainWindow):

    def __init__(self, type_of_sort, parent=None):
        QtWidgets.QWidget.__init__(self, parent)
        self.setupUi(self)
        self.setFixedSize(930, 900)
        self.type_of_sort = type_of_sort
        self.showResult()
    def showResult(self):
        result = special_sort(self.type_of_sort)
        i = 0
        self.sites=[]
        information = pd.read_csv("files/banks_info.csv", encoding="cp1251", sep=";")
        for key in result.keys():
            for bank in result[key]:
                self.tableWidget.insertRow(i)
                label = QLabel()
                item = QTableWidgetItem(str(key))
                item.setTextAlignment(Qt.AlignHCenter)
                self.tableWidget.setItem(i, 0, item)
                self.tableWidget.setItem(i, 1, QTableWidgetItem(bank))
                self.sites.append(information[bank][0])
                label.setText(''+information[bank][0]+'')
                label.setOpenExternalLinks(True)
                self.tableWidget.setCellWidget(i, 2, label)
                self.tableWidget.setItem(i, 3, QTableWidgetItem(information[bank][1]))
                item=QTableWidgetItem(information[bank][2])
                item.setTextAlignment(Qt.AlignHCenter)
                self.tableWidget.setItem(i, 4, item)
                item = QTableWidgetItem(information[bank][3])
                item.setTextAlignment(Qt.AlignHCenter)
                self.tableWidget.setItem(i, 5, item)
                i += 1
        self.tableWidget.resizeColumnsToContents()
        self.importButton.clicked.connect(self.savefile)
        style = "::section {""background-color: #ffc02b; font:10pt; }"
        self.tableWidget.horizontalHeader().setStyleSheet(style)

    def savefile(self):
        filename, _ = QFileDialog.getSaveFileName(self, 'Save File', '', ".xls(*.xls)")
        wbk = xlwt.Workbook()
        sheet = wbk.add_sheet("sheet", cell_overwrite_ok=True)
        style = xlwt.XFStyle()

        model = self.tableWidget.model()
        for c in range(model.columnCount()):
            text = model.headerData(c, QtCore.Qt.Horizontal)
            sheet.write(0, c , text, style=style)

        for c in range(model.columnCount()):
            for r in range(model.rowCount()):
                text = model.data(model.index(r, c))
                sheet.write(r + 1, c, text)
        for r in range(model.rowCount()):
            text = self.sites[r]
            sheet.write(r + 1, 2, text)

        wbk.save(filename)


from unittest import TestCase
from A3.SUD import fight_or_run
from unittest.mock import patch

class TestFightOrRun(TestCase):
    @patch('builtins.input', side_effect=[0])
    def test_fight_or_run_zero(self, mock_input):
        actual = fight_or_run()
        expected = 0
        self.assertEqual(actual, expected)

    @patch('builtins.input', side_effect=[1])
    def test_fight_or_run_one(self, mock_input):
        actual = fight_or_run()
        expected = 1
        self.assertEqual(actual, expected)

    @patch('builtins.input', side_effect=[10])
    def test_fight_or_run_10(self, mock_input):
        actual = fight_or_run()
        expected = 10
        self.assertEqual(actual, expected)

    @patch('builtins.input', side_effect=[100])
    def test_fight_or_run_100(self, mock_input):
        actual = fight_or_run()
        expected = 100
        self.assertEqual(actual, expected)


import logging
import numpy as np
import sys
import warnings
import affine6p
import geopandas
from typing import List, Optional
from shapely.geometry import Polygon

import geoCosiCorr3D.georoutines.geo_utils as geoRT
import geoCosiCorr3D.geoErrorsWarning.geoErrors as geoErrors
from geoCosiCorr3D.geoCore.core_RFM import RawRFM

class ReadRFM(RawRFM):
    def __init__(self, rfm_file):
        super().__init__()
        self.rfm_file = rfm_file
        self._ingest()

    def _ingest(self):

        if self.rfm_file.endswith('xml') or self.rfm_file.endswith('XML'):
            logging.info("RFM file format: xml")
            self.RFM_Read_fromXML(self.rfm_file)
        elif self.rfm_file.lower().endswith('RPB'):
            logging.info("RFM file format: RPB")
            self.RFM_Read_fromRPB(self.rfm_file)
        elif self.rfm_file.lower().endswith(tuple(("txt", "TXT", "rpc"))):
            # print("RFM file format: txt")
            self.RFM_Read_fromTXT(self.rfm_file)
        elif self.rfm_file.endswith(tuple(('TIF', 'NTF', "tif", "ntf", "JP2"))):
            logging.info("RFM file format: Raster")
            self.RFM_Read_fromRaster(self.rfm_file)
        else:
            try:
                self.RFM_Read_fromTXT(self.rfm_file)
            except:
                raise IOError(f'RFM file:{self.rfm_file}  is not valid')

    def parse_file(self, param, lines):
        from re import search
        val = None
        # print(param)
        for line_ in lines:
            if search(param, line_):
                val = float(line_.split(":")[1].split()[0])

        if val == None:
            msg = "ERROR in reading " + param + " from RFM txt file!"
            sys.exit(msg)
        return val

    def RFM_Read_fromTXT(self, rfm_txt_file):

        with open(rfm_txt_file) as f:
            fileContent = f.read()
        lines = fileContent.split('\n')
        self.linOff = self.parse_file(param="LINE_OFF", lines=lines)
        self.colOff = self.parse_file(param="SAMP_OFF", lines=lines)
        self.latOff = self.parse_file(param="LAT_OFF", lines=lines)
        self.lonOff = self.parse_file(param="LONG_OFF", lines=lines)
        self.altOff = self.parse_file(param="HEIGHT_SCALE", lines=lines)
        self.linScale = self.parse_file(param="LINE_SCALE", lines=lines)
        self.colScale = self.parse_file(param="SAMP_SCALE", lines=lines)
        self.latScale = self.parse_file(param="LAT_SCALE", lines=lines)
        self.lonScale = self.parse_file(param="LONG_SCALE", lines=lines)
        self.altScale = self.parse_file(param="HEIGHT_SCALE", lines=lines)

        ### Inverse model
        for i in range(20):
            self.linNum[i] = self.parse_file(param="LINE_NUM_COEFF_" + str(i + 1) + ":", lines=lines)
            self.linDen[i] = self.parse_file(param="LINE_DEN_COEFF_" + str(i + 1) + ":", lines=lines)
            self.colNum[i] = self.parse_file(param="SAMP_NUM_COEFF_" + str(i + 1) + ":", lines=lines)
            self.colDen[i] = self.parse_file(param="SAMP_DEN_COEFF_" + str(i + 1) + ":", lines=lines)
        # print(self.linNum)
        # TODO: check for direct model
        return

    def RFM_Read_fromXML(self, rfm_xml_file):
        # TODO
        logging.info("--- Read RFM form xML ---")
        logging.info("--- Future work  ---")
        geoErrors.erNotImplemented(routineName="Read RFM from XML")
        return

    def RFM_Read_fromRPB(self, rpb_file):
        # TODO
        logging.info("--- Read RFM form RPB ---")
        logging.info("--- Future work  ---")
        geoErrors.erNotImplemented(routineName="Read RFM from RPB")
        return

    def RFM_Read_fromRaster(self, raster_file):
        ## Read the RPC coefficent from raster tag using GDAL and georoutines.
        rasterInfo = geoRT.cRasterInfo(raster_file)
        if rasterInfo.rpcs:
            rfmInfo = rasterInfo.rpcs
            # print("RFM info :", rfmInfo)
            ## Scale and offset
            self.altOff = float(rfmInfo["HEIGHT_OFF"])
            self.altScale = float(rfmInfo["HEIGHT_SCALE"])

            self.latOff = float(rfmInfo["LAT_OFF"])
            self.latScale = float(rfmInfo["LAT_SCALE"])
            self.lonOff = float(rfmInfo["LONG_OFF"])
            self.lonScale = float(rfmInfo["LONG_SCALE"])

            self.linOff = float(rfmInfo["LINE_OFF"])
            self.linScale = float(rfmInfo["LINE_SCALE"])
            self.colOff = float(rfmInfo["SAMP_OFF"])
            self.colScale = float(rfmInfo["SAMP_SCALE"])

            ## Inverse model
            self.linNum = list(map(float, rfmInfo['LINE_NUM_COEFF'].split()))
            self.linDen = list(map(float, rfmInfo['LINE_DEN_COEFF'].split()))
            self.colNum = list(map(float, rfmInfo['SAMP_NUM_COEFF'].split()))
            self.colDen = list(map(float, rfmInfo['SAMP_DEN_COEFF'].split()))

            ## Direct model
            if 'LON_NUM_COEFF' in rfmInfo:
                self.lonNum = list(map(float, rfmInfo['LON_NUM_COEFF'].split()))
                self.lonDen = list(map(float, rfmInfo['LON_DEN_COEFF'].split()))
                self.latNum = list(map(float, rfmInfo['LAT_NUM_COEFF'].split()))
                self.latDen = list(map(float, rfmInfo['LAT_DEN_COEFF'].split()))
        else:
            sys.exit(f'RPCs not found in the raster {raster_file} metadata')
        return

class RFM(ReadRFM):

    def __init__(self, rfm_file: Optional[str] = None, debug: bool = False):
        self.init_RFM()
        if rfm_file is not None:
            super().__init__(rfm_file)
        self.debug = debug
        if self.debug:
            logging.info(self.__repr__())

    def Ground2Img_RFM(self, lon, lat, alt: List = None, normalized=False, demInfo=None, corrModel=np.zeros((3, 3))):
        """
        Apply inverse RFM model to convert Ground coordinates to image coordinates

        Args:
            lon: longitude(s) of the input 3D point(s) : float or list
            lat: latitude(s) of the input 3D point(s) : float or list
            alt: altitude(s) of the input 3D point(s) : float or list
            corrModel

        Returns:

            float or list: horizontal image coordinate(s) (column index, ie x)
            float or list: vertical image coordinate(s) (row index, ie y)

        """
        if alt is None:
            alt = []
        lon = np.asarray(lon)
        lat = np.asarray(lat)

        if np.array(alt).any() == True:
            alt = np.asarray(alt)
        else:
            if demInfo is not None:
                warnings.warn("INTERPOLATE FROM DEM --> TODO")
                logging.warning("INTERPOLATE FROM DEM --> TODO")
            else:
                warnings.warn("NO alt  values and no DEM: alt will be set to:{}".format(self.altOff))
                logging.warning("NO alt  values and no DEM: alt will be set to:{}".format(self.altOff))
                alt = np.ones(lon.shape) * self.altOff

        lonN = (lon - self.lonOff) / self.lonScale
        latN = (lat - self.latOff) / self.latScale
        altN = (alt - self.altOff) / self.altScale
        colN = self.build_RFM(num=self.colNum, den=self.colDen, x=latN, y=lonN, z=altN)
        linN = self.build_RFM(num=self.linNum, den=self.linDen, x=latN, y=lonN, z=altN)
        if not np.all((corrModel == 0)):
            colN, linN = self.apply_correction(corrModel=corrModel, colN=colN, linN=linN)
        if normalized == True:
            return colN, linN
        else:
            col = colN * self.colScale + self.colOff
            row = linN * self.linScale + self.linOff

            return col, row

    def Img2Ground_RFM(self, col, lin,
                       altIni: Optional[List] = None,
                       demInfo: Optional[geoRT.cRasterInfo] = None,
                       corrModel=np.zeros((3, 3)),
                       normalized=False):
        """
        Apply direct RFM model to convert image coordinates to ground coordinates

        Args:
            col: x-image coordinate(s) of the input point(s) : float or list
            lin: y-image coordinate(s) of the input point(s) : float or list
            altIni: altitude(s) of the input point(s) : float or list
            normalized:

        Returns: float or list: longitude(s) && float or list: latitude(s)
        """
        if altIni is None:
            altIni = []
        if isinstance(altIni, list):
            if len(altIni) == 0:
                if isinstance(col, list) and isinstance(lin, list):
                    altIni = len(col) * [self.altOff]
                else:
                    altIni = self.altOff
            elif len(altIni) != len(col) or len(altIni) != len(lin):
                ValueError("Invalid Initial Altitude values !")

        col = np.asarray(col)
        lin = np.asarray(lin)
        altIni_ = np.asarray(altIni)

        # Normalize input image coordinates
        colN = (col - self.colOff) / self.colScale
        linN = (lin - self.linOff) / self.linScale
        altIniN = (altIni_ - self.altOff) / self.altScale
        if self.lonNum == [np.nan] * 20:
            if self.debug:
                logging.warning("Computing Direct model ....")
                # print("correction matrix:\n", corrModel)
            # print("colN,linN,altN", colN, linN, altN)
            lonN, latN = self.ComputeDirectModel(colN=colN, linN=linN, altN=altIniN, corrModel=corrModel)
        else:
            # print("Direct model provided in the RFM file will be used")
            lonN = self.build_RFM(num=self.lonNum, den=self.lonDen, x=linN, y=colN, z=altIniN)
            latN = self.build_RFM(num=self.latNum, den=self.latDen, x=linN, y=colN, z=altIniN)
        if not normalized:
            lon = lonN * self.lonScale + self.lonOff
            lat = latN * self.latScale + self.latOff
            # print(lon, lat, altIni)
            # ==== Apply correction if exist =====
            # if not np.all((modelCorr == 0)):
            #     lon, lat, altIni = ApplyCorrection(lon=lon, lat=lat, alt=altIni, col=col, lin=lin, modelCorr=modelCorr)

            if isinstance(altIni, list):
                alt = altIni
            else:
                alt = altIni

            ### Here we will use the computed lon & lat to interpolate the alt from the DEM if exist
            if demInfo is not None:
                alt = []

                # TODO: loop until convergence or no change in coordinates
                if isinstance(lon, np.ndarray) and isinstance(lat, np.ndarray):
                    for lonVal, latVal, altValIni in zip(lon, lat, altIni):
                        altVal = self.ExtractAlt(lonVal, latVal, demInfo)
                        if altVal == 0:
                            altVal = altValIni
                        alt.append(altVal)

                else:
                    altVal = self.ExtractAlt(lon, lat, demInfo)
                    if altVal == 0:
                        altVal = altIni
                    alt = altVal
                alt = np.asarray(alt)
                # Normalize input image coordinates
                colN = (col - self.colOff) / self.colScale
                linN = (lin - self.linOff) / self.linScale
                altN = (alt - self.altOff) / self.altScale
                if self.lonNum == [np.nan] * 20:
                    # print("Computing Direct model ....")
                    # print("colN,linN,altN", colN, linN, altN)
                    lonN, latN = self.ComputeDirectModel(colN=colN, linN=linN, altN=altN, corrModel=corrModel)
                else:
                    # print("Direct model provided in the RFM file will be used")
                    lonN = self.build_RFM(num=self.lonNum, den=self.lonDen, x=linN, y=colN, z=altN)
                    latN = self.build_RFM(num=self.latNum, den=self.latDen, x=linN, y=colN, z=altN)
                lon = lonN * self.lonScale + self.lonOff
                lat = latN * self.latScale + self.latOff
                # lon, lat, alt = ApplyCorrection(lon=lon, lat=lat, alt=alt, col=col, lin=lin, modelCorr=modelCorr)
            return lon, lat, alt

        else:
            return lonN, latN, None

    def get_geoTransform(self):

        h = int(self.linOff * 2)
        w = int(self.colOff * 2)
        BBoxPix = [[0, 0],
                   [0, h],
                   [w, h],
                   [w, 0],
                   [0, 0]]

        z = self.altOff
        lons, lats, _ = self.Img2Ground_RFM(col=[0, 0, w, w, 0],
                                            lin=[0, h, h, 0, 0],
                                            altIni=[z, z, z, z, z],
                                            normalized=False)
        BBoxMap = []
        for lon_, lat_ in zip(lons, lats):
            BBoxMap.append([lon_, lat_])

        trans = affine6p.estimate(origin=BBoxPix, convrt=BBoxMap)
        mat = trans.get_matrix()  ## Homogenious represention of the affine transformation
        geoTrans_h = np.array(mat)
        geo_transform = [mat[0][-1], mat[0][0], mat[0][1], mat[1][-1], mat[1][0], mat[1][1]]
        return geo_transform

    def compute_footprint(self, corr_model: Optional[np.ndarray] = None,
                          dem_info: Optional[geoRT.cRasterInfo] = None) -> [Polygon, geopandas.GeoDataFrame]:
        h = int(self.linOff * 2)
        w = int(self.colOff * 2)
        z = self.altOff
        if corr_model is None:
            corr_model = np.zeros((3, 3))
        lons, lats, _ = self.Img2Ground_RFM(col=[0, 0, w, w, 0],
                                            lin=[0, h, h, 0, 0],
                                            altIni=[z, z, z, z, z],
                                            normalized=False,
                                            corrModel=corr_model,
                                            demInfo=dem_info)

        fp_poly_geom = Polygon(zip(lons, lats))
        gpd_polygon = geopandas.GeoDataFrame(index=[0], crs='epsg:4326', geometry=[fp_poly_geom])

        return fp_poly_geom, gpd_polygon

    def get_GSD(self):

        h = self.linOff * 2
        w = self.colOff * 2

        ## Estimate GSD from RFM
        center = (int(h / 2), int(w / 2))
        center_plus = (center[0] + 1, center[1] + 1)
        prjCenter = self.Img2Ground_RFM(col=center[1], lin=center[0])
        prjCenter_plus = self.Img2Ground_RFM(col=center_plus[1], lin=center_plus[0])
        ## Estimate the UTM
        epsgCode = geoRT.ComputeEpsg(lon=prjCenter[0], lat=prjCenter[1])
        ## Convert tot UTM projection
        centerCoords = geoRT.ConvCoordMap1ToMap2_Batch(X=[prjCenter[1], prjCenter_plus[1]],
                                                       Y=[prjCenter[0], prjCenter_plus[0]],
                                                       targetEPSG=epsgCode)
        xGSD = np.abs(centerCoords[0][0] - centerCoords[0][1])
        yGSD = np.abs(centerCoords[1][0] - centerCoords[1][1])
        return (xGSD, yGSD)

    def get_altitude_range(self, scaleFactor=1):
        """

        Args:
            scaleFactor:

        Returns:

        """
        minAlt = self.altOff - scaleFactor * self.altScale
        maxAlt = self.altOff + scaleFactor * self.altScale
        return [minAlt, maxAlt]

if __name__ == '__main__':
    # TODO add to unit/functional tests
    img = '/home/cosicorr/0-WorkSpace/3-PycharmProjects/geoCosiCorr3D/geoCosiCorr3D/Tests/3-geoOrtho_Test/Sample/Sample1/SPOT2.TIF'
    rfm = RFM(img, debug=True)
    print(f'attitude range:{rfm.get_altitude_range()}')
    print(f'GSD:{rfm.get_GSD()}')
    print(f'geoTransform:{rfm.get_geoTransform()}')


import json

from bitbnspy import bitbns

# from bitbnspy import bitbns

import config
key = config.apiKey
secretKey = config.secret
bitbnsObj = bitbns(key, secretKey)
# print('APIstatus: =',  bitbnsObj.getApiUsageStatus)

# getPairTicker = bitbnsObj.getTickerApi('DOGE')
# print(' PairTicker : ', getPairTicker)
print('====================================')
# dumpBid = json.dumps(getPairTicker)
# loadBid = json.loads(dumpBid)
# getBid = loadBid['highest_buy_bid']
# print('highest buy: ', loadBid)
print('====================================')
# OpenOrders = bitbnsObj.listOpenOrders('DOGE')
# print(OpenOrders)

bitbnsObj = bitbns.publicEndpoints()
getTickers = bitbnsObj.fetchTickers()
dumpTickers = json.dumps(getTickers)
loadTickers = json.loads(dumpTickers)
print(loadTickers)


import numpy
from abstract_model import Model
from asc.core.time_series import TimeSeries

class BrownModel(Model):
    r"""
    Class representing Brown's exponential smoothing model.

    NOTES:

        Brown's model is described by moving average `\hat{m_t}` \ 	for
        `t=1,\dots, n`, which we can count with recursion:

        .. MATH::
            \hat{m_t} = a X_t + (1-a)\hat{m_{t-1}}
            \hat{m_1} = X_1
        for any `a \in [0,1].`
        Thus for `t \ge 2`
        .. MATH:
        \hat{m}_t = \sum\limits_{j=0}^{t-2} a (1 - a)^j x_{t-j} + (1

- a)^{t-1} X_1

        Paramet `a` we choose for trial and error method.

    """

    obligatory_parameters = ("alpha", )

    def __init__(self, data, alpha=0.3):
        r"""
        Initialize new instance of BronwModel class with given

parameters and
        data.

        :param data: data to constructed smoothened model.

        :type data: TimeSeries.

        :param alpha: the smoothing parameter of the model.

        :type alpha: float.
        """
        self._Model_forecast_offset = 1
        super(BrownModel, self).__init__({"alpha": alpha}, data)

    @property
    def alpha(self):
        r"""
        Get smoothing parameter of this model.

        :return: smoothing parameter of the model.

        :rtype: float.
        """
        return self.__alpha

    @alpha.setter
    def alpha(self, value):
        r"""
        Set new value of this model's smoothing parameter.

        :param value: new value of smoothing parameter. ``value``
            should lie in the interval [0,1].

        :type value: float.

        :raise: ValueError if value of alpha is greater than 1 or
            lesser than 0.
        """
        if 0 <= value <= 1:
            self.__alpha = value
        else:
            raise ValueError("alpha must be a number from the interval [0,1].")

    @property
    def estimated_series(self):
        r"""
        Get series estimated from this model using data from which it

was
        constructed.

        :return: sequence of estimated values, i.e. smoothened time

series
            given as the data parameter.

        :rtype: TimeSeries.
        """
        return self.__estimated_series

    @property
    def forecast_offset(self):
        r"""
        Get forecast offset of this model.

        :return: forecast offset of this model. Forecast offset is

the time
            after which model starts to estimate consequtive values

in
            initial data. For Brown's model this is always 1.

        :rtype: integer.

        NOTES:

            This method is included primarily to maintain

compatibility with
            abstract model framework.
        """
        return 1

    def get_parameter(self, param):
        r"""
        Get value of given parameter in this model.

        :param param: name of the parameter. The only valid value for
            BrownModel is "alpha".

        :type param: string.

        :return: value of parameter ``param``.

        :rtype: float.

        :raise: ValueError if ``param`` is anything different than

"apha".

        NOTES:

            This method is included primarily to maintain

compatibility with
            abstract model framework.
        """
        if param == "alpha":
            return self.alpha
        raise ValueError("Unknown parameter %s." % (param))

    def set_parameter(self, param, value):
        r"""
        Set value of a given parameter in this model.

        :param param: parameter for which value should be set. The

only
            valid value for Brown's model is "alpha".

        :type param: float.

        :param value: new value for the parameter. For parameter

alpha it
            should be float in range from the interval [0,1].

        :type value: float.

        :raise: ValueError if ``param`` is anything different than

"alpha"
            or if ``value`` doesn't lie in the interval [0,1].

        NOTES:
            This method is included primarily to maintain

compatibility with
            abstract model framework.
        """
        if param == "alpha":
            self.alpha = value
        else:
            raise ValueError("Unknown parameter %s." % (param))

    def recalculate_model(self):
        r"""
        Recalculate model. This method is used to calculate smoothed

values
        from model's empirical data.
        """
        sample_size = len(self.data)

        m_t = numpy.zeros(sample_size - 1)
        m_t[0] = self.data[0]

        for k in range(sample_size - 2):
            m_t[k + 1] = self.alpha * self.data[k + 1] + \
                        (1 - self.alpha) * m_t[k]

        self.components = {}
        self.components["smoothened"] = self.__estimated_series = \
            TimeSeries(m_t)
        self.components["residues"] = self.goodness_info.errors


# -*- coding: utf-8 -*-
import os
import sys
import xbmcgui
import xbmcplugin
import xbmcaddon

from urllib.parse import parse_qsl

from libs.utils import get_url, check_settings
from libs.session import Session
from libs.channels import Channels, manage_channels, list_channels_edit, list_channels_list_backups, edit_channel, delete_channel, change_channels_numbers
from libs.channels import list_channels_groups, add_channel_group, edit_channel_group, edit_channel_group_list_channels, edit_channel_group_add_channel, edit_channel_group_add_all_channels, edit_channel_group_delete_channel, select_channel_group, delete_channel_group
from libs.live import list_live
from libs.archive import list_archive, list_archive_days, list_program
from libs.stream import play_live, play_archive, play_catchup
from libs.settings import list_settings, list_devices, remove_device
from libs.iptvsc import generate_playlist, generate_epg

if len(sys.argv) > 1:
    _handle = int(sys.argv[1])

def main_menu():
    addon = xbmcaddon.Addon()
    icons_dir = os.path.join(addon.getAddonInfo('path'), 'resources','images')

    list_item = xbmcgui.ListItem(label = addon.getLocalizedString(300111))
    url = get_url(action='list_live', label = addon.getLocalizedString(300111))
    list_item.setArt({ 'thumb' : os.path.join(icons_dir , 'livetv.png'), 'icon' : os.path.join(icons_dir , 'livetv.png') })
    xbmcplugin.addDirectoryItem(_handle, url, list_item, True)

    list_item = xbmcgui.ListItem(label = addon.getLocalizedString(300112))
    url = get_url(action='list_archive', label = addon.getLocalizedString(300112))
    list_item.setArt({ 'thumb' : os.path.join(icons_dir , 'archive.png'), 'icon' : os.path.join(icons_dir , 'archive.png') })
    xbmcplugin.addDirectoryItem(_handle, url, list_item, True)

    if addon.getSetting('hide_settings') != 'true':
        list_item = xbmcgui.ListItem(label = addon.getLocalizedString(300100))
        url = get_url(action='list_settings', label = addon.getLocalizedString(300100))
        list_item.setArt({ 'thumb' : os.path.join(icons_dir , 'settings.png'), 'icon' : os.path.join(icons_dir , 'settings.png') })
        xbmcplugin.addDirectoryItem(_handle, url, list_item, True)

    xbmcplugin.endOfDirectory(_handle)

def router(paramstring):
    params = dict(parse_qsl(paramstring))
    check_settings()
    if params:
        if params['action'] == 'list_live':
            list_live(label = params['label'])
        elif params['action'] == 'play_live':
            play_live(id = params['id'])

        elif params['action'] == 'list_archive':
            list_archive(label = params['label'])
        elif params['action'] == 'list_archive_days':
            list_archive_days(id = params['id'], label = params['label'])
        elif params['action'] == 'list_program':
            list_program(id = params['id'], day_min = params['day_min'], label = params['label'])
        elif params['action'] == 'play_archive':
            play_archive(id = params['id'], channel_id = params['channel_id'])

        elif params['action'] == 'manage_channels':
            manage_channels(label = params['label'])
        elif params['action'] == 'reset_channels_list':
            channels = Channels()
            channels.reset_channels()
        elif params['action'] == 'restore_channels':
            channels = Channels()
            channels.restore_channels(backup = params['backup'])
        elif params['action'] == 'list_channels_list_backups':
            list_channels_list_backups(label = params['label'])

        elif params['action'] == 'list_channels_edit':
            list_channels_edit(label = params['label'])
        elif params['action'] == 'edit_channel':
            edit_channel(id = params['id'])
        elif params['action'] == 'delete_channel':
            delete_channel(id = params['id'])
        elif params['action'] == 'change_channels_numbers':
            change_channels_numbers(from_number =params['from_number'], direction = params['direction'])

        elif params['action'] == 'list_channels_groups':
            list_channels_groups(label = params['label'])
        elif params['action'] == 'add_channel_group':
            add_channel_group(label = params['label'])
        elif params['action'] == 'edit_channel_group':
            edit_channel_group(group = params['group'], label = params['label'])
        elif params['action'] == 'delete_channel_group':
            delete_channel_group(group = params['group'])
        elif params['action'] == 'select_channel_group':
            select_channel_group(group = params['group'])

        elif params['action'] == 'edit_channel_group_list_channels':
            edit_channel_group_list_channels(group = params['group'], label = params['label'])
        elif params['action'] == 'edit_channel_group_add_channel':
            edit_channel_group_add_channel(group = params['group'], channel = params['channel'])
        elif params['action'] == 'edit_channel_group_add_all_channels':
            edit_channel_group_add_all_channels(group = params['group'])
        elif params['action'] == 'edit_channel_group_delete_channel':
            edit_channel_group_delete_channel(group = params['group'], channel = params['channel'])

        elif params['action'] == 'list_devices':
            list_devices(label = params['label'])
        elif params['action'] == 'remove_device':
            remove_device(id = params['id'], title = params['title'], last_activity = params['last_activity'])

        elif params['action'] == 'list_settings':
            list_settings(label = params['label'])
        elif params['action'] == 'addon_settings':
            xbmcaddon.Addon().openSettings()

        elif params['action'] == 'reset_session':
           session = Session()
           session.remove_session()

        elif params['action'] == 'generate_playlist':
            if 'output_file' in params:
                generate_playlist(params['output_file'])
                xbmcplugin.addDirectoryItem(_handle, '1', xbmcgui.ListItem())
                xbmcplugin.endOfDirectory(_handle, succeeded = True)
            else:
                generate_playlist()
        elif params['action'] == 'generate_epg':
            if 'output_file' in params:
                generate_epg(params['output_file'])
                xbmcplugin.addDirectoryItem(_handle, '1', xbmcgui.ListItem())
                xbmcplugin.endOfDirectory(_handle, succeeded = True)
            else:
                generate_epg()
        elif params['action'] == 'iptsc_play_stream':
            if 'catchup_start_ts' in params and 'catchup_end_ts' in params:
                play_catchup(id = params['id'], start_ts = params['catchup_start_ts'], end_ts = params['catchup_end_ts'])
            else:
                play_live(params['id'])

        else:
            raise ValueError('Neznámý parametr: {0}!'.format(paramstring))
    else:
        main_menu()

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


# -*- coding: utf-8 -*-
"""
Created on Wed Aug  9 11:35:21 2023

@author: akava
"""

import tkinter as tk
from tkinter import ttk
from PIL import Image, ImageTk
import customtkinter, tkinter
from retinaface import RetinaFace
import cv2
from gender_classification.gender_classifier_window import GenderClassifierWindow

class SinglePhotoDetectionPage:
    def __init__(self, Load, App, App_window, image1, options):
        self.App = App
        self.App_window = App_window
        self.Load = Load
        self.root = customtkinter.CTkToplevel()
        self.root.title("Pagina de Deteccion de Rostros de una Sola Foto")
        self.root.geometry("800x600")  # Tamaño de la ventana
        self.root.resizable(False, False)
        self.checkbox_vars = []

        # Configurar el evento de cierre de la ventana secundaria
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        # Variable de control para rastrear si se ha borrado alguna imagen
        self.images_deleted = False
        self.detected_faces = RetinaFace.extract_faces(image1, align = True)
        num_personas= len(self.detected_faces)
        self.scaled_image=image1
        self.Load.withdraw()

        main_frame = customtkinter.CTkFrame(self.root, fg_color=("transparent"))
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Crear un Frame para el mensaje
        message_frame = customtkinter.CTkFrame(main_frame, fg_color=("transparent"))
        message_frame.pack(side=tk.TOP, fill=tk.X)

        # Agregar una etiqueta para el mensaje "Selecciona las fotos que deseas eliminar"
        message_label = customtkinter.CTkLabel(message_frame, text="Selecciona las fotos que deseas eliminar:", font=('Calibri', 15),  fg_color="transparent", width=110)
        message_label.pack(padx=10, pady=5, anchor=tk.W)

        # Crear un Frame para las imágenes
        images_frame = customtkinter.CTkFrame(main_frame, fg_color=("transparent"))
        images_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # Crear un Frame para los botones
        button_frame = customtkinter.CTkFrame(main_frame,  fg_color="transparent")
        button_frame.pack(side=tk.RIGHT, fill=tk.Y)

        face_count_image = customtkinter.CTkImage(Image.open("images/face.png"), size=(26, 26))
        self.face_count_label1 = customtkinter.CTkButton(button_frame, image=face_count_image, text_color="black", fg_color="transparent", text="", width=30)
        self.face_count_label1.pack(padx=10, pady=10, anchor=tk.W)

        # Botones para continuar y regresar en el Frame de los botones
        home_image = customtkinter.CTkImage(Image.open("images/home.png"), size=(26, 26))
        home_button = customtkinter.CTkButton(
            button_frame,
            image=home_image,
            fg_color="transparent",
            text_color= "black",
            text="Home", width=10,
            command=self.return_to_main_menu)
        home_button.pack(pady=10)
        home_button.pack(padx=10, pady=10, anchor=tk.W)

        continue_image = customtkinter.CTkImage(Image.open("images/aceptar.png"), size=(26, 26))
        continue_button = customtkinter.CTkButton(
            button_frame,
            text="Aceptar",
            width=20,
            command=self.continue_pressed,
            image=continue_image,
            text_color="black",
            fg_color="transparent"
        )
        continue_button.pack(padx=10, pady=10, anchor=tk.W)

        delete_image = customtkinter.CTkImage(Image.open("images/borrar.png"), size=(26, 26))
        delete_button = customtkinter.CTkButton(
            button_frame,
            text="Borrar",
            width=20,
            command=self.delete_selected,
            image=delete_image,
            text_color="black",
            fg_color="transparent"
        )
        delete_button.pack(padx=10, pady=10, anchor=tk.W)

        back_image  = customtkinter.CTkImage(Image.open("images/volver.png"), size=(26, 26))
        back_button = customtkinter.CTkButton(
            button_frame,
            text="Regresar",
            width=20,
            command=self.go_back,
            image=back_image,
            text_color="black",
            fg_color="transparent"
        )
        back_button.pack(padx=10, pady=10, anchor=tk.W)

        # Agregar una Scrollbar al Frame de las imágenes
        scroll_y = tk.Scrollbar(images_frame, orient=tk.VERTICAL)
        scroll_y.pack(side=tk.RIGHT, fill=tk.Y)

        # Crear un Canvas para mostrar las imágenes con scrollbar en el Frame de las imágenes
        canvas = tk.Canvas(images_frame, yscrollcommand=scroll_y.set)
        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scroll_y.config(command=canvas.yview)

        # Crear un Frame en el Canvas para mostrar las imágenes
        self.frame = customtkinter.CTkFrame(canvas, fg_color=("transparent"), width=650)
        canvas.create_window((0, 0), window=self.frame, anchor=tk.NW)

        self.display_detected_faces(self.frame , self.detected_faces, self.scaled_image )

        # Configurar el Canvas para que pueda desplazarse
        canvas.update_idletasks()
        canvas.config(scrollregion=canvas.bbox("all"))

    # Función para eliminar imágenes seleccionadas
    def delete_selected(self):
        self.detected_faces = self.delete_selected_images(self.scaled_image, self.detected_faces, self.checkbox_vars)
        # Actualizar la variable de control
        self.images_deleted = True
        self.display_detected_faces(self.frame, self.detected_faces, self.scaled_image)

    def display_detected_faces(self, frame, detected_faces, scaled_image):

        for widget in frame.winfo_children():
            widget.destroy()

        self.face_count_label1.configure(text="Rostros: {}".format(self.count_faces(self.detected_faces)))

        # Lista para mantener el estado de los checkboxes
        self.checkbox_vars = []

        # Contadores para controlar las columnas y filas de las imágenes
        col_count = 0
        row_count = 0

        self.person_images_tk = []
        style = ttk.Style()
        style.configure('TCheckbutton', font=('Calibri', 9))

        # Lista para mantener las imágenes personales

        for i, detection in enumerate(detected_faces):

            person_image = detection

            # Convertir la imagen de NumPy a imagen de PIL
            person_image_pil = Image.fromarray(cv2.cvtColor(person_image, cv2.COLOR_BGR2RGB))

            # Redimensionar la imagen
            person_image_pil = person_image_pil.resize((150, 150), Image.LANCZOS)

            # Redimensionar la imagen para mostrarla en tamaño más pequeño en la interfaz
            person_image_pil_small = person_image_pil.resize((80, 80), Image.LANCZOS)

            # Convertir la imagen de PIL a PhotoImage
            person_image_tk = ImageTk.PhotoImage(person_image_pil)  # Usar la imagen original aquí
            self.person_images_tk.append(person_image_tk)  # Agregar a la lista

            # Crear una variable para el estado del checkbox
            checkbox_var = tk.BooleanVar(value=False)
            self.checkbox_vars.append(checkbox_var)

            # Convertir la imagen de PIL a PhotoImage
            person_image_small_tk = ImageTk.PhotoImage(person_image_pil_small)

            # Mostrar la imagen en una etiqueta dentro del Frame
            label = customtkinter.CTkLabel(frame, image=person_image_small_tk, text="")

            # Agregar un checkbox para seleccionar la imagen
            checkbox = ttk.Checkbutton(frame, text="Seleccionar", variable=checkbox_var)

            # Colocar la etiqueta y el checkbox en la posición adecuada usando grid
            label.grid(row=row_count, column=col_count, padx=9, pady=5)
            checkbox.grid(row=row_count + 1, column=col_count, padx=9, pady=0)

            # Actualizar los contadores de columna y fila
            col_count += 1

            # Si col_count es 0, significa que estamos en una nueva fila y necesitamos actualizar los contadores
            if col_count == 0:
                row_count += 2
            elif col_count >= 6:
                col_count = 0
                row_count += 2

        return self.person_images_tk

    def on_click(self, index):
        print(index)

    def continue_pressed(self):

        # Crear una nueva instancia de la ventana del Clasificador de género
        if self.images_deleted:
            self.root.withdraw()
            faces=self.extract_faces(self.scaled_image, self.updated_detected_faces)
            app = GenderClassifierWindow(self.root, self.App, self.App_window, faces)

        else:
            self.root.withdraw()
            faces=self.extract_faces(self.scaled_image, self.detected_faces)
            app = GenderClassifierWindow(self.root, self.App, self.App_window, faces)

    def count_faces(self, detected_faces):
        return len(detected_faces)

    def go_back(self):

        # Hacer que la ventana anterior vuelva a ser visible
        self.Load.deiconify()
        # Cerrar la ventana actual
        self.root.destroy()

    def extract_faces(self, scaled_image, detected_faces):

        faces = []  # Lista para almacenar los rostros extraídos
        # Iterar sobre las detecciones de rostros
        for detection in detected_faces:
            #x1, y1, width1, height1 = detection['box']
            #x1, y1, width1, height1 = int(x1), int(y1), int(width1), int(height1)
            #face_roi = scaled_image[y1:y1+height1, x1:x1+width1]
            #faces.append(face_roi)
            faces.append(detection)
        return faces

    def delete_selected_images(self, scaled_image, detected_faces, checkbox_vars):

        # Eliminar las imágenes seleccionadas de detected_faces
        updated_detected_faces = []
        for detection, checkbox_var in zip(detected_faces, checkbox_vars):
            if not checkbox_var.get():
                updated_detected_faces.append(detection)

        self.updated_detected_faces = updated_detected_faces
        return updated_detected_faces

    def on_closing(self):

        # Restaura la ventana principal
        self.App_window.deiconify()

        # Cierra la ventana de PhotoLoadPage
        self.root.destroy()
        self.Load.destroy()

    def return_to_main_menu(self):
        # Restaura la ventana principal
        self.App_window.deiconify()
        # Cierra la ventana de PhotoLoadPage
        self.root.destroy()
        self.Load.destroy()


# -*- coding: utf-8 -*-
from pathlib import Path

class Manager:

    def create_readme():
        root_path = Path(__file__).parent
        info ="""## حل سوالات کوئرا
برای دیدن صفحه ی اصلی هر سوال در سایت کوئرا میتوانید روی نام هر سوال کلیک کنید و یا در قسمت توضیحات روی PDF کلیک کنید.
showmeyourcode.ir
        """
        table_header = [
            "شماره سؤال",
            "نام سؤال",
            "youtube",
            "لینک جواب",
            "توضیحات",
            ]
        with open('README.md',"w",encoding="utf8") as main_readme:
            main_readme.write(info+'\n')
            main_readme.write("|"+"|".join(table_header)+"|"+'\n')
            main_readme.write("|-"*len(table_header)+"|"+'\n')
            index=1
            for question_path in root_path.glob(r"*/"):
                if not question_path.is_file() and not str(question_path.relative_to(root_path)).startswith("."):
                    main_readme.write(f"|{index}")
                    # main_readme.write("|"+str(question_path.relative_to(root_path)))

                    with open(str(question_path.joinpath("readme.md")),"r",encoding="utf8") as local_readme:
                        main_readme.write("|"+local_readme.readline().strip())
                        main_readme.write("|"+local_readme.readline().strip())

                    main_readme.write("|")
                    for language in question_path.glob("*"):
                        if language.is_dir():
                            readme_path = str(language.relative_to(root_path)).replace(' ','%20').replace('\\','/')
                            main_readme.write(f"[{str(language.relative_to(question_path))}]({readme_path}), ")

                    main_readme.write("|")
                    for local_readmes in question_path.glob("*.md"):
                        if local_readmes.is_file():
                            readme_path = str(local_readmes.relative_to(root_path)).replace(' ','%20').replace('\\','/')
                            main_readme.write(f"[readme]({readme_path}), ")

                    for pdfs in question_path.glob("*.pdf"):
                        if pdfs.is_file():
                            pdf = str(pdfs.relative_to(root_path)).replace(' ','%20').replace('\\','/')
                            main_readme.write(f"[pdf]({pdf}), ")
                    main_readme.write("|\n")

                    index+=1

if __name__ == "__main__":
    Manager.create_readme()


# coding=gbk
import numpy as np
import pandas as pd
import re
from jieba import lcut

def clean_str(text):
    text = text.lower()
    # Clean the text
    text = re.sub(r"[^A-Za-z0-9^,!.\/'+-=]", " ", text)
    text = re.sub(r"what's", "what is ", text)
    text = re.sub(r"that's", "that is ", text)
    text = re.sub(r"there's", "there is ", text)
    text = re.sub(r"it's", "it is ", text)
    text = re.sub(r"\'s", " ", text)
    text = re.sub(r"\'ve", " have ", text)
    text = re.sub(r"can't", "can not ", text)
    text = re.sub(r"n't", " not ", text)
    text = re.sub(r"i'm", "i am ", text)
    text = re.sub(r"\'re", " are ", text)   #\'re 转义'
    text = re.sub(r"\'d", " would ", text)
    text = re.sub(r"\'ll", " will ", text)
    text = re.sub(r",", " ", text)
    text = re.sub(r"\.", " ", text)
    text = re.sub(r"!", " ! ", text)
    text = re.sub(r"\/", " ", text)
    text = re.sub(r"\^", " ^ ", text)
    text = re.sub(r"\+", " + ", text)
    text = re.sub(r"\-", " - ", text)
    text = re.sub(r"\=", " = ", text)
    text = re.sub(r"'", " ", text)
    text = re.sub(r"(\d+)(k)", r"\g<1>000", text)
    text = re.sub(r":", " : ", text)
    text = re.sub(r" e g ", " eg ", text)
    text = re.sub(r" b g ", " bg ", text)
    text = re.sub(r" u s ", " american ", text)
    text = re.sub(r"\0s", "0", text)
    text = re.sub(r" 9 11 ", "911", text)
    text = re.sub(r"e - mail", "email", text)
    text = re.sub(r"j k", "jk", text)
    text = re.sub(r"\s{2,}", " ", text)

    return text.strip()

#data_training的文本的下标是4，tfidf值的下标是7
#my_data的文本下标是4，点击量的下标是3
def load_data_and_labels(path):#读取文本的函数，可能要换成连接mysql的函数；注意是train.py读取文本的函数
    data_x, data_x_list, data_y = [], [], []#data_x为处理前的文本,格式为一个列表中包含着装着新闻内容的列表(用于输出),data_x_list是将文本变成一个大的列表形式(用于在接下来的分词处理)
    f = pd.ExcelFile(path)
    io = pd.io.excel.ExcelFile(path)
    for i in f.sheet_names:  # 读取里面每一个sheet
        dx = pd.read_excel(io, sheet_name=i, usecols=[4])  #这里是读取第五列，如果要修改读取的列数就修改这里的数字
        dy = pd.read_excel(io, sheet_name=i, usecols=[7])
        datax = dx.values.tolist()
        datay = dy.values.tolist()
        for j in datax:
            l = str(j[0]).strip().replace(u'\u3000', u' ').replace(u'\xa0', u' ')
            k = [str(j[0]).strip().replace(u'\u3000', u' ').replace(u'\xa0', u' ')]  # 这里还需要将标点符号换掉
            data_x.append(k)
            data_x_list.append(l)

        for m in datay:
            data_y.append(m[0])

    data = []
    max_sentence_length = 0
    max_paragraph_length = 0
    for id in range(len(data_x_list)):
        paragraphs = data_x_list[id]
        sentences_split = re.split('(。|！|\!|\.|？|\?)',paragraphs)
        sentences = []
        for i in range(int(len(sentences_split) / 2)):
            sent = sentences_split[2 * i] + sentences_split[2 * i + 1]
            sentences.append(sent)
        if max_paragraph_length < len(sentences):
            max_paragraph_length = len(sentences)
        for n, sentence in enumerate(sentences):
            tokens = lcut(sentence)
            if max_sentence_length < len(tokens):
                max_sentence_length = len(tokens)
            sentence = " ".join(tokens)
            sentences[n] = sentence

        data.append([id, sentences])

    print(path)
    # print("max sentence length = {}\n".format(max_sentence_length))
    # print("max_paragraph_length = {}\n".format(max_paragraph_length))

    df = pd.DataFrame(data=data, columns=["id", "sentences"])    #创建一个二维数据表，列名为id等
    x_text = df['sentences'].tolist()    #转为列表

    return x_text, data_y

def batch_iter(data, batch_size, num_epochs, shuffle=False):     #生成一个迭代器，输入x_batch时 共7200/10 * 100 = 72000次
    """
    Generates a batch iterator for a dataset.
    """
    data = np.array(data)
    data_size = len(data)
    num_batches_per_epoch = int((len(data) - 1) / batch_size) + 1   #获得每个epoch的batch数目，结果为720
    #for epoch in range(num_epochs):     #100次
    # Shuffle the data at each epoch
    if shuffle:
        shuffle_indices = np.random.permutation(np.arange(data_size))   #随机排列一个序列，或者数组。
        shuffled_data = data[shuffle_indices]
    else:
        shuffled_data = data
    for batch_num in range(num_batches_per_epoch):
        start_index = batch_num * batch_size
        end_index = min((batch_num + 1) * batch_size, data_size)
        yield shuffled_data[start_index:end_index]      #一次产出batch_size数量的句子-关系对

if __name__ == "__main__":
    trainFile = 'data.xlsx'
    testFile = 'SemEval2010_task8_all_data/SemEval2010_task8_testing_keys/TEST_FILE_FULL.TXT'

    a, b = load_data_and_labels(trainFile)
    print(len(a))
    print(len(b))

def eval_load_data_and_labels(path):#读取文本的函数，可能要换成连接mysql的函数；注意是eval.py读取文本的函数
    data_x, data_x_list, data_y = [], [], []
    f = pd.ExcelFile(path)
    io = pd.io.excel.ExcelFile(path)
    for i in f.sheet_names:  # 读取里面每一个sheet
        dx = pd.read_excel(io, sheet_name=i, usecols=[5])  # 这里是读取第五列，如果要修改读取的列数就修改这里的数字
        dy = pd.read_excel(io, sheet_name=i, usecols=[8])
        datax = dx.values.tolist()
        datay = dy.values.tolist()
        for j in datax:
            l = str(j[0]).strip().replace(u'\u3000', u' ').replace(u'\xa0', u' ')
            k = [str(j[0]).strip().replace(u'\u3000', u' ').replace(u'\xa0', u' ')]  # 这里还需要将标点符号换掉
            data_x.append(k)
            data_x_list.append(l)
        for m in datay:
            data_y.append(m[0])

    data = []
    # lines = [line.strip() for line in open(path)]
    max_sentence_length = 0
    max_paragraph_length = 0
    for id in range(len(data_x_list)):  # 主要目标是分词，y值已经处理好
        paragraphs = data_x_list[id]  # 读取文章
        sentences_split = re.split('(。|！|\!|\.|？|\?)', paragraphs)
        sentences = []
        for i in range(int(len(sentences_split) / 2)):
            sent = sentences_split[2 * i] + sentences_split[2 * i + 1]
            sentences.append(sent)
        # sentences = nltk.sent_tokenize(paragraphs)#用正则分割句子
        if max_paragraph_length < len(sentences):
            max_paragraph_length = len(sentences)
        for n, sentence in enumerate(sentences):
            # sentence = clean_str(sentence)
            tokens = lcut(sentence)
            # tokens = nltk.word_tokenize(sentence)   #用jieba分词
            if max_sentence_length < len(tokens):
                max_sentence_length = len(tokens)
            # if len(tokens) > FLAGS.max_sentence_length:
            #    print(tokens)
            sentence = " ".join(tokens)  # 有啥区别？？？
            sentences[n] = sentence

        data.append([id, sentences])

    print(path)
    print("max sentence length = {}\n".format(max_sentence_length))
    print("max_paragraph_length = {}\n".format(max_paragraph_length))

    df = pd.DataFrame(data=data, columns=["id", "sentences"])  # 创建一个二维数据表，列名为id等

    x_text = df['sentences'].tolist()  # 转为列表

    return x_text, data_x, data_y  # x_text为处理后的文本(用在模型中),data_x为处理前的文本(用于输出)


import pandas as pd
import os

path = "C:\\Users\\brunn\\Desktop\\SENAC\\topicos-avancados"
files = os.listdir(path)

extension = 'csv'
files_open = [path + '\\' + f for f in files if f[-len(extension):] == extension]

list_of_dataframes = []

for file in files_open:
  list_of_dataframes.append(pd.read_csv(file, delimiter=';'))

merged_data = pd.concat(list_of_dataframes)

merged_data


from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    cors = CORS(app)
    app.config["FLASK_DEBUG"] = True
    app.config['SECRET_KEY'] = 'secret-key-goes-here'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite'

    from .models import Result, Patient, DeliveryReports

    db.init_app(app)
    with app.app_context():
        db.create_all()

    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    return app


from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from starlette import status
from starlette.responses import RedirectResponse

from database import get_db
from domain.answer import answer_schema, answer_crud
from domain.question import question_crud, question_schema
from domain.user.user_router import get_current_user
from models import User

router = APIRouter(
    prefix="/api/answer",
)

@router.post("/create/{question_id}", response_model=question_schema.Question)
def answer_create(question_id: int,
                  _answer_create: answer_schema.AnswerCreate,
                  db: Session = Depends(get_db),
                  current_user: User = Depends(get_current_user)):
    # create answer
    question = question_crud.get_question(db, question_id=question_id)
    if not question:
        raise HTTPException(status_code=404, detail="Question not found")
    answer_crud.create_answer(db, question=question,
                              answer_create=_answer_create,
                              user=current_user)

    # redirect
    from domain.question.question_router import router as question_router
    url = question_router.url_path_for('question_detail',
                                       question_id=question_id)
    return RedirectResponse(url, status_code=303)

@router.put("/update", status_code=status.HTTP_204_NO_CONTENT)
def amswer_update(_answer_update: answer_schema.AnswerUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    db_answer = answer_crud.get_answer(db, answer_id=_answer_update.answer_id)
    if not db_answer:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="데이터를 찾을 수 없습니다.")
    if current_user.id != db_answer.user.id:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="수정 권한이 없습니다.")
    answer_crud.update_answer(db=db, db_answer=db_answer, answer_updaete=_answer_update)

@router.get("/detail/{answer_id}", response_model=answer_schema.Answer)
def answer_detail(answer_id: int, db: Session = Depends(get_db)):
    answer = answer_crud.get_answer(db, answer_id=answer_id)
    return answer

@router.post("/vote", status_code=status.HTTP_204_NO_CONTENT)
def answer_vote(_answer_vote: answer_schema.AnswerVote, db: Session = Depends(get_db), current_user: User =Depends(get_current_user)):
    db_answer = answer_crud.get_answer(db, answer_id=_answer_vote.answer_id)
    if not db_answer:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="데이터를 찾을 수 없다.")
    answer_crud.vote_answer(db, db_answer=db_answer, db_user=current_user)


"""
The goal of this program is to optimize the movement to achieve a rudi out pike (803<) for left twisters.
"""
import os
import numpy as np
import biorbd_casadi as biorbd
from casadi import MX, Function
from bioptim import (
            OptimalControlProgram,
            DynamicsList,
            DynamicsFcn,
            ObjectiveList,
            ObjectiveFcn,
            BoundsList,
            InitialGuessList,
            InterpolationType,
            OdeSolver,
            Node,
            Solver,
            BiMappingList,
            CostType,
            ConstraintList,
            ConstraintFcn,
            PenaltyController,
            MultiStart,
            Solution,
            MagnitudeType,
            BiorbdModel,
        )
import time
import pickle

class Model:
    """
    Attributes
    ----------
    model: str
        A reference to the name of the model
    with_hsl  :
        no hsl, don't use libhsl
    n_threads : int
        refers to the numbers of threads in the solver
    savesol :
        returns true if empty, else returns False
    show_online : bool
        returns true if empty, else returns False
    print_ocp : bool
        returns False if empty, else returns True """

    def __init__(self, model, n_threads=5, with_hsl=False, savesol=False, show_online=False, print_ocp=False):

        self.model = model
        self.with_hsl = with_hsl
        self.n_threads = n_threads
        self.savesol = savesol
        self.show_online = show_online
        self.print_ocp = print_ocp

        #
        # # if savesol :
        # #    return False
        #
        # if show_online:
        #     return False
        #
        # if print_ocp:
        #     return True

        #   parser = argparse.ArgumentParser()
        # parser.add_argument("model", type=str, help="the bioMod file")
        # parser.add_argument("--no-hsl", dest='with_hsl', action='store_false', help="do not use libhsl")
        # parser.add_argument("-j", default=1, dest='n_threads', type=int, help="number of threads in the solver")
        # parser.add_argument("--no-sol", action='store_false', dest='savesol', help="do not save the solution")
        # parser.add_argument("--no-show-online", action='store_false', dest='show_online', help="do not show graphs during optimization")
        # parser.add_argument("--print-ocp", action='store_true', dest='print_ocp', help="print the ocp")
        # args = parser.parse_args()
        #

try:
    import IPython

    IPYTHON = True
except ImportError:
    print("No IPython.")
    IPYTHON = False

def minimize_dofs(controller: PenaltyController, dofs: list, targets: list):
    diff = 0
    for i, dof in enumerate(dofs):
        diff += (controller.states['q'].cx_start[dof] - targets[i]) ** 2
    return diff

def prepare_ocp(
        biorbd_model_path: str, nb_twist: int, seed : int,
        ode_solver: OdeSolver = OdeSolver.RK4(),
) -> OptimalControlProgram:

    """
    Prepare the ocp

    Parameters
    ----------
    biorbd_model_path: str
        The path to the bioMod file
    ode_solver: OdeSolver
        The ode solver to use

    Returns
    -------
    The OptimalControlProgram ready to be solved
    """
    final_time = 1.87
    n_shooting = (40, 100, 100, 100, 40)

    biomodel = (BiorbdModel(biorbd_model_path))
    biorbd_model = (biomodel,biomodel, biomodel, biomodel,biomodel)

    nb_q = biorbd_model[0].nb_q
    nb_qdot = biorbd_model[0].nb_qdot
    nb_qddot_joints = nb_q - biorbd_model[0].nb_root

    # Pour la lisibilite
    X = 0
    Y = 1
    Z = 2
    Xrot = 3
    Yrot = 4
    Zrot = 5
    ZrotBD = 6
    YrotBD = 7
    ZrotABD = 8
    XrotABD = 9
    ZrotBG = 10
    YrotBG = 11
    ZrotABG = 12
    XrotABG = 13
    XrotC = 14
    YrotC = 15
    vX = 0
    vY = 1
    vZ = 2
    vXrot = 3
    vYrot = 4
    vZrot = 5
    vZrotBD = 6
    vYrotBD = 7
    vZrotABD = 8
    vYrotABD = 9
    vZrotBG = 10
    vYrotBG = 11
    vZrotABG = 12
    vYrotABG = 13
    vXrotC = 14
    vYrotC = 15

    # Add objective functions
    objective_functions = ObjectiveList()
    # objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_MARKERS, marker_index=1, weight=-1)
    objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="qddot_joints", node=Node.ALL_SHOOTING,
                            weight=1, phase=0)
    objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="qddot_joints", node=Node.ALL_SHOOTING,
                            weight=1, phase=1)
    objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="qddot_joints", node=Node.ALL_SHOOTING,
                            weight=1, phase=2)
    objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="qddot_joints", node=Node.ALL_SHOOTING,
                            weight=1, phase=3)
    objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="qddot_joints", node=Node.ALL_SHOOTING,
                            weight=1, phase=4)

    objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_TIME, min_bound=.0, max_bound=1.0, weight=100000,
                            phase=0)

    objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_TIME, min_bound=.0, max_bound=1.0, weight=100000,
                            phase=2)

    objective_functions.add(ObjectiveFcn.Mayer.SUPERIMPOSE_MARKERS, node=Node.END, first_marker='MidMainG',
                            second_marker='CibleMainG', weight=1000, phase=0)
    objective_functions.add(ObjectiveFcn.Mayer.SUPERIMPOSE_MARKERS, node=Node.END, first_marker='MidMainD',
                            second_marker='CibleMainD', weight=1000, phase=0)

    # arrete de gigoter les bras
    les_bras = [ZrotBD, YrotBD, ZrotABD, XrotABD, ZrotBG, YrotBG, ZrotABG, XrotABG]
    les_coudes = [ZrotABD, XrotABD, ZrotABG, XrotABG]
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_coudes, targets=np.zeros(len(les_coudes)), weight=1000, phase=0)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_bras, targets=np.zeros(len(les_bras)), weight=10, phase=0)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_bras, targets=np.zeros(len(les_bras)), weight=10, phase=1)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_bras, targets=np.zeros(len(les_bras)), weight=10, phase=2)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_bras, targets=np.zeros(len(les_bras)), weight=10, phase=3)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_bras, targets=np.zeros(len(les_bras)), weight=10, phase=4)
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Lagrange, node=Node.ALL_SHOOTING,
                            dofs=les_coudes, targets=np.zeros(len(les_coudes)), weight=1000, phase=4)

    # ouvre les hanches rapidement apres la vrille
    objective_functions.add(minimize_dofs, custom_type=ObjectiveFcn.Mayer, node=Node.END, dofs=[XrotC],
                            targets=[0], weight=10000, phase=3)

    # Dynamics
    dynamics = DynamicsList()
    dynamics.add(DynamicsFcn.JOINTS_ACCELERATION_DRIVEN)
    dynamics.add(DynamicsFcn.JOINTS_ACCELERATION_DRIVEN)
    dynamics.add(DynamicsFcn.JOINTS_ACCELERATION_DRIVEN)
    dynamics.add(DynamicsFcn.JOINTS_ACCELERATION_DRIVEN)
    dynamics.add(DynamicsFcn.JOINTS_ACCELERATION_DRIVEN)

    qddot_joints_min, qddot_joints_max, qddot_joints_init = -500, 500, 0
    u_bounds = BoundsList()
    for i in range(5):
        u_bounds.add("qddot_joints", min_bound=[qddot_joints_min] * nb_qddot_joints, max_bound=[qddot_joints_max] * nb_qddot_joints, phase=i)

    u_init = InitialGuessList()
    for i in range(5):
        u_init.add("qddot_joints", [qddot_joints_init] * nb_qddot_joints, phase=i)
        u_init[i]["qddot_joints"].add_noise(
            bounds=u_bounds[i]["qddot_joints"],
            magnitude=0.2,
            magnitude_type=MagnitudeType.RELATIVE,
            n_shooting=n_shooting[i],
            seed=seed,
        )

    # Path constraint
    x_bounds = BoundsList()
    for i in range(5):
        x_bounds.add("q", min_bound=biorbd_model[0].bounds_from_ranges("q").min, max_bound=biorbd_model[0].bounds_from_ranges("q").max, phase=i)
        x_bounds.add("qdot", min_bound=biorbd_model[0].bounds_from_ranges("qdot").min, max_bound=biorbd_model[0].bounds_from_ranges("qdot").max, phase=i)

    # Pour la lisibilite
    DEBUT, MILIEU, FIN = 0, 1, 2

    #
    # Contraintes de position: PHASE 0 la montee en carpe
    #

    zmax = 8
    # 12 / 8 * final_time**2 + 1  # une petite marge

    # deplacement
    x_bounds[0]["q"].min[X, :] = -.1
    x_bounds[0]["q"].max[X, :] = .1
    x_bounds[0]["q"].min[Y, :] = -1.
    x_bounds[0]["q"].max[Y, :] = 1.
    x_bounds[0]["q"].min[:Z + 1, DEBUT] = 0
    x_bounds[0]["q"].max[:Z + 1, DEBUT] = 0
    x_bounds[0]["q"].min[Z, MILIEU:] = 0
    x_bounds[0]["q"].max[Z, MILIEU:] = zmax  # beaucoup plus que necessaire, juste pour que la parabole fonctionne

    # le salto autour de x
    x_bounds[0]["q"].min[Xrot, :] = 0
    # 2 * 3.14 + 3 / 2 * 3.14 - .2
    x_bounds[0]["q"].max[Xrot, :] = -.50 + 3.14
    x_bounds[0]["q"].min[Xrot, DEBUT] = .50  # penche vers l'avant un peu carpe
    x_bounds[0]["q"].max[Xrot, DEBUT] = .50
    x_bounds[0]["q"].min[Xrot, MILIEU:] = 0
    x_bounds[0]["q"].max[Xrot, MILIEU:] = 4 * 3.14 + .1  # salto
    # limitation du tilt autour de y
    x_bounds[0]["q"].min[Yrot, DEBUT] = 0
    x_bounds[0]["q"].max[Yrot, DEBUT] = 0
    x_bounds[0]["q"].min[Yrot, MILIEU:] = - 3.14 / 16  # vraiment pas suppose tilte
    x_bounds[0]["q"].max[Yrot, MILIEU:] = 3.14 / 16
    # la vrille autour de z
    x_bounds[0]["q"].min[Zrot, DEBUT] = 0
    x_bounds[0]["q"].max[Zrot, DEBUT] = 0
    x_bounds[0]["q"].min[Zrot, MILIEU:] = -.1  # pas de vrille dans cette phase
    x_bounds[0]["q"].max[Zrot, MILIEU:] = .1

    # bras droit
    x_bounds[0]["q"].min[YrotBD, DEBUT] = 2.9  # debut bras aux oreilles
    x_bounds[0]["q"].max[YrotBD, DEBUT] = 2.9
    x_bounds[0]["q"].min[ZrotBD, DEBUT] = 0
    x_bounds[0]["q"].max[ZrotBD, DEBUT] = 0
    # bras gauche
    x_bounds[0]["q"].min[YrotBG, DEBUT] = -2.9  # debut bras aux oreilles
    x_bounds[0]["q"].max[YrotBG, DEBUT] = -2.9
    x_bounds[0]["q"].min[ZrotBG, DEBUT] = 0
    x_bounds[0]["q"].max[ZrotBG, DEBUT] = 0

    # coude droit
    x_bounds[0]["q"].min[ZrotABD:XrotABD + 1, DEBUT] = 0
    x_bounds[0]["q"].max[ZrotABD:XrotABD + 1, DEBUT] = 0
    # coude gauche
    x_bounds[0]["q"].min[ZrotABG:XrotABG + 1, DEBUT] = 0
    x_bounds[0]["q"].max[ZrotABG:XrotABG + 1, DEBUT] = 0

    # le carpe
    x_bounds[0]["q"].min[XrotC, DEBUT] = -.50  # depart un peu ferme aux hanches
    x_bounds[0]["q"].max[XrotC, DEBUT] = -.50
    x_bounds[0]["q"].max[XrotC, FIN] = -2.5
    # x_bounds[0].min[XrotC, FIN] = 2.7  # min du modele
    # le dehanchement
    x_bounds[0]["q"].min[YrotC, DEBUT] = 0
    x_bounds[0]["q"].max[YrotC, DEBUT] = 0
    x_bounds[0]["q"].min[YrotC, MILIEU:] = -.1
    x_bounds[0]["q"].max[YrotC, MILIEU:] = .1

    # Contraintes de vitesse: PHASE 0 la montee en carpe

    vzinit = 9.81 / (2 * final_time ) # vitesse initiale en z du CoM pour revenir a terre au temps final

    # decalage entre le bassin et le CoM
    CoM_Q_sym = MX.sym('CoM', nb_q)
    CoM_Q_init = x_bounds[0]["q"].min[:nb_q,
                 DEBUT]  # min ou max ne change rien a priori, au DEBUT ils sont egaux normalement
    CoM_Q_func = Function('CoM_Q_func', [CoM_Q_sym], [biorbd_model[0].center_of_mass(CoM_Q_sym)])
    bassin_Q_func = Function('bassin_Q_func', [CoM_Q_sym],
                             [biorbd_model[0].homogeneous_matrices_in_global(CoM_Q_sym, 0).to_mx()])  # retourne la RT du bassin

    r = np.array(CoM_Q_func(CoM_Q_init)).reshape(1, 3) - np.array(bassin_Q_func(CoM_Q_init))[-1,
                                                         :3]  # selectionne seulement la translation de la RT

    # en xy bassin
    x_bounds[0]["qdot"].min[vX:vY + 1, :] = -10
    x_bounds[0]["qdot"].max[vX:vY + 1, :] = 10
    x_bounds[0]["qdot"].min[vX:vY + 1, DEBUT] = -.5
    x_bounds[0]["qdot"].max[vX:vY + 1, DEBUT] = .5
    # z bassin
    x_bounds[0]["qdot"].min[vZ, :] = -50
    x_bounds[0]["qdot"].max[vZ, :] = 50
    x_bounds[0]["qdot"].min[vZ, DEBUT] = vzinit - .5
    x_bounds[0]["qdot"].max[vZ, DEBUT] = vzinit + .5

    # autour de x
    x_bounds[0]["qdot"].min[vXrot, :] = .5  # d'apres une observation video
    x_bounds[0]["qdot"].max[vXrot, :] = 20  # aussi vite que nécessaire, mais ne devrait pas atteindre cette vitesse
    # autour de y
    x_bounds[0]["qdot"].min[vYrot, :] = -50
    x_bounds[0]["qdot"].max[vYrot, :] = 50
    x_bounds[0]["qdot"].min[vYrot, DEBUT] = 0
    x_bounds[0]["qdot"].max[vYrot, DEBUT] = 0
    # autour de z
    x_bounds[0]["qdot"].min[vZrot, :] = -50
    x_bounds[0]["qdot"].max[vZrot, :] = 50
    x_bounds[0]["qdot"].min[vZrot, DEBUT] = 0
    x_bounds[0]["qdot"].max[vZrot, DEBUT] = 0

    # tenir compte du decalage entre bassin et CoM avec la rotation
    # Qtransdot = Qtransdot + v cross Qrotdot
    borne_inf = (x_bounds[0]["qdot"].min[vX:vZ + 1, DEBUT] + np.cross(r, x_bounds[0]["qdot"].min[vXrot:vZrot + 1, DEBUT]))[0]
    borne_sup = (x_bounds[0]["qdot"].max[vX:vZ + 1, DEBUT] + np.cross(r, x_bounds[0]["qdot"].max[vXrot:vZrot + 1, DEBUT]))[0]
    x_bounds[0]["qdot"].min[vX:vZ + 1, DEBUT] = min(borne_sup[0], borne_inf[0]), min(borne_sup[1], borne_inf[1]), min(
        borne_sup[2], borne_inf[2])
    x_bounds[0]["qdot"].max[vX:vZ + 1, DEBUT] = max(borne_sup[0], borne_inf[0]), max(borne_sup[1], borne_inf[1]), max(
        borne_sup[2], borne_inf[2])

    # bras droit
    x_bounds[0]["qdot"].min[vZrotBD:vYrotBD + 1, :] = -50
    x_bounds[0]["qdot"].max[vZrotBD:vYrotBD + 1, :] = 50
    x_bounds[0]["qdot"].min[vZrotBD:vYrotBD + 1, DEBUT] = 0
    x_bounds[0]["qdot"].max[vZrotBD:vYrotBD + 1, DEBUT] = 0
    # bras droit
    x_bounds[0]["qdot"].min[vZrotBG:vYrotBG + 1, :] = -50
    x_bounds[0]["qdot"].max[vZrotBG:vYrotBG + 1, :] = 50
    x_bounds[0]["qdot"].min[vZrotBG:vYrotBG + 1, DEBUT] = 0
    x_bounds[0]["qdot"].max[vZrotBG:vYrotBG + 1, DEBUT] = 0

    # coude droit
    x_bounds[0]["qdot"].min[vZrotABD:vYrotABD + 1, :] = -50
    x_bounds[0]["qdot"].max[vZrotABD:vYrotABD + 1, :] = 50
    x_bounds[0]["qdot"].min[vZrotABD:vYrotABD + 1, DEBUT] = 0
    x_bounds[0]["qdot"].max[vZrotABD:vYrotABD + 1, DEBUT] = 0
    # coude gauche
    x_bounds[0]["qdot"].min[vZrotABD:vYrotABG + 1, :] = -50
    x_bounds[0]["qdot"].max[vZrotABD:vYrotABG + 1, :] = 50
    x_bounds[0]["qdot"].min[vZrotABG:vYrotABG + 1, DEBUT] = 0
    x_bounds[0]["qdot"].max[vZrotABG:vYrotABG + 1, DEBUT] = 0

    # du carpe
    x_bounds[0]["qdot"].min[vXrotC, :] = -50
    x_bounds[0]["qdot"].max[vXrotC, :] = 50
    x_bounds[0]["qdot"].min[vXrotC, DEBUT] = 0
    x_bounds[0]["qdot"].max[vXrotC, DEBUT] = 0
    # du dehanchement
    x_bounds[0]["qdot"].min[vYrotC, :] = -50
    x_bounds[0]["qdot"].max[vYrotC, :] = 50
    x_bounds[0]["qdot"].min[vYrotC, DEBUT] = 0
    x_bounds[0]["qdot"].max[vYrotC, DEBUT] = 0

    #
    # Contraintes de position: PHASE 1 le salto carpe
    #

    # deplacement
    x_bounds[1]["q"].min[X, :] = -.1
    x_bounds[1]["q"].max[X, :] = .1
    x_bounds[1]["q"].min[Y, :] = -1.
    x_bounds[1]["q"].max[Y, :] = 1.
    x_bounds[1]["q"].min[Z, :] = 0
    x_bounds[1]["q"].max[Z, :] = zmax  # beaucoup plus que necessaire, juste pour que la parabole fonctionne

    # le salto autour de x
    x_bounds[1]["q"].min[Xrot, :] = 0
    x_bounds[1]["q"].max[Xrot, :] = -.50 + 4 * 3.14
    x_bounds[1]["q"].min[Xrot, FIN] = 2 * 3.14 - .1
    # limitation du tilt autour de y
    x_bounds[1]["q"].min[Yrot, :] = - 3.14 / 16
    x_bounds[1]["q"].max[Yrot, :] = 3.14 / 16
    # la vrille autour de z
    x_bounds[1]["q"].min[Zrot, :] = -.1
    x_bounds[1]["q"].max[Zrot, :] = .1

    # le carpe
    x_bounds[1]["q"].max[XrotC, :] = -2.5
    # le dehanchement
    x_bounds[1]["q"].min[YrotC, DEBUT] = -.1
    x_bounds[1]["q"].max[YrotC, DEBUT] = .1

    # Contraintes de vitesse: PHASE 1 le salto carpe

    # en xy bassin
    x_bounds[1]["qdot"].min[vX:vY + 1, :] = -10
    x_bounds[1]["qdot"].max[vX:vY + 1, :] = 10

    # z bassin
    x_bounds[1]["qdot"].min[vZ, :] = -50
    x_bounds[1]["qdot"].max[vZ, :] = 50

    # autour de x
    x_bounds[1]["qdot"].min[vXrot, :] = -50
    x_bounds[1]["qdot"].max[vXrot, :] = 50
    # autour de y
    x_bounds[1]["qdot"].min[vYrot, :] = -50
    x_bounds[1]["qdot"].max[vYrot, :] = 50

    # autour de z
    x_bounds[1]["qdot"].min[vZrot, :] = -50
    x_bounds[1]["qdot"].max[vZrot, :] = 50

    # bras droit
    x_bounds[1]["qdot"].min[vZrotBD:vYrotBD + 1, :] = -50
    x_bounds[1]["qdot"].max[vZrotBD:vYrotBD + 1, :] = 50
    # bras droit
    x_bounds[1]["qdot"].min[vZrotBG:vYrotBG + 1, :] = -50
    x_bounds[1]["qdot"].max[vZrotBG:vYrotBG + 1, :] = 50

    # coude droit
    x_bounds[1]["qdot"].min[vZrotABD:vYrotABD + 1, :] = -50
    x_bounds[1]["qdot"].max[vZrotABD:vYrotABD + 1, :] = 50
    # coude gauche
    x_bounds[1]["qdot"].min[vZrotABD:vYrotABG + 1, :] = -50
    x_bounds[1]["qdot"].max[vZrotABD:vYrotABG + 1, :] = 50

    # du carpe
    x_bounds[1]["qdot"].min[vXrotC, :] = -50
    x_bounds[1]["qdot"].max[vXrotC, :] = 50
    # du dehanchement
    x_bounds[1]["qdot"].min[vYrotC, :] = -50
    x_bounds[1]["qdot"].max[vYrotC, :] = 50

    #
    # Contraintes de position: PHASE 2 l'ouverture
    #

    # deplacement
    x_bounds[2]["q"].min[X, :] = -.2
    x_bounds[2]["q"].max[X, :] = .2
    x_bounds[2]["q"].min[Y, :] = -1.
    x_bounds[2]["q"].max[Y, :] = 1.
    x_bounds[2]["q"].min[Z, :] = 0
    x_bounds[2]["q"].max[Z, :] = zmax  # beaucoup plus que necessaire, juste pour que la parabole fonctionne

    # le salto autour de x
    x_bounds[2]["q"].min[Xrot, :] = 2 * 3.14 - .1
    x_bounds[2]["q"].max[Xrot, :] = -.50 + 4 * 3.14
    # limitation du tilt autour de y
    x_bounds[2]["q"].min[Yrot, :] = - 3.14 / 4
    x_bounds[2]["q"].max[Yrot, :] = 3.14 / 4
    # la vrille autour de z
    x_bounds[2]["q"].min[Zrot, :] = 0
    x_bounds[2]["q"].max[Zrot, :] = 3.14  # 5 * 3.14

    x_bounds[2]["q"].min[XrotC, FIN] = -.4

    # Contraintes de vitesse: PHASE 2 l'ouverture

    # en xy bassin
    x_bounds[2]["qdot"].min[vX:vY + 1, :] = -10
    x_bounds[2]["qdot"].max[vX:vY + 1, :] = 10

    # z bassin
    x_bounds[2]["qdot"].min[vZ, :] = -50
    x_bounds[2]["qdot"].max[vZ, :] = 50

    # autour de x
    x_bounds[2]["qdot"].min[vXrot, :] = -50
    x_bounds[2]["qdot"].max[vXrot, :] = 50
    # autour de y
    x_bounds[2]["qdot"].min[vYrot, :] = -50
    x_bounds[2]["qdot"].max[vYrot, :] = 50

    # autour de z
    x_bounds[2]["qdot"].min[vZrot, :] = -50
    x_bounds[2]["qdot"].max[vZrot, :] = 50

    # bras droit
    x_bounds[2]["qdot"].min[vZrotBD:vYrotBD + 1, :] = -50
    x_bounds[2]["qdot"].max[vZrotBD:vYrotBD + 1, :] = 50
    # bras droit
    x_bounds[2]["qdot"].min[vZrotBG:vYrotBG + 1, :] = -50
    x_bounds[2]["qdot"].max[vZrotBG:vYrotBG + 1, :] = 50

    # coude droit
    x_bounds[2]["qdot"].min[vZrotABD:vYrotABD + 1, :] = -50
    x_bounds[2]["qdot"].max[vZrotABD:vYrotABD + 1, :] = 50
    # coude gauche
    x_bounds[2]["qdot"].min[vZrotABD:vYrotABG + 1, :] = -50
    x_bounds[2]["qdot"].max[vZrotABD:vYrotABG + 1, :] = 50

    # du carpe
    x_bounds[2]["qdot"].min[vXrotC, :] = -50
    x_bounds[2]["qdot"].max[vXrotC, :] = 50
    # du dehanchement
    x_bounds[2]["qdot"].min[vYrotC, :] = -50
    x_bounds[2]["qdot"].max[vYrotC, :] = 50

    #
    # Contraintes de position: PHASE 3 la vrille et demie
    #

    # deplacement
    x_bounds[3]["q"].min[X, :] = -.2
    x_bounds[3]["q"].max[X, :] = .2
    x_bounds[3]["q"].min[Y, :] = -1.
    x_bounds[3]["q"].max[Y, :] = 1.
    x_bounds[3]["q"].min[Z, :] = 0
    x_bounds[3]["q"].max[Z, :] = zmax  # beaucoup plus que necessaire, juste pour que la parabole fonctionne

    # le salto autour de x
    x_bounds[3]["q"].min[Xrot, :] = 0
    x_bounds[3]["q"].min[Xrot, :] = 2 * 3.14 - .1

    x_bounds[3]["q"].max[Xrot, :] = 2 * 3.14 + 3 / 2 * 3.14 + .1  # 1 salto 3/4
    x_bounds[3]["q"].min[Xrot, FIN] = 2 * 3.14 + 3 / 2 * 3.14 - .1
    x_bounds[3]["q"].max[Xrot, FIN] = 2 * 3.14 + 3 / 2 * 3.14 + .1  # 1 salto 3/4

    # limitation du tilt autour de y
    x_bounds[3]["q"].min[Yrot, :] = - 3.14 / 4
    x_bounds[3]["q"].max[Yrot, :] = 3.14 / 4
    x_bounds[3]["q"].min[Yrot, FIN] = - 3.14 / 8
    x_bounds[3]["q"].max[Yrot, FIN] = 3.14 / 8
    # la vrille autour de z
    x_bounds[3]["q"].min[Zrot, :] = 0
    x_bounds[3]["q"].max[Zrot, :] = 5 * 3.14
    x_bounds[3]["q"].min[Zrot, FIN] = nb_twist * 3.14 - .1  # complete la vrille
    x_bounds[3]["q"].max[Zrot, FIN] = nb_twist * 3.14 + .1

    # le carpe  f4a les jambes
    x_bounds[3]["q"].min[XrotC, :] = -.4
    # le dehanchement

    # Contraintes de vitesse: PHASE 3 la vrille et demie

    # en xy bassin
    x_bounds[3]["qdot"].min[vX:vY + 1, :] = -10
    x_bounds[3]["qdot"].max[vX:vY + 1, :] = 10

    # z bassin
    x_bounds[3]["qdot"].min[vZ, :] = -50
    x_bounds[3]["qdot"].max[vZ, :] = 50

    # autour de x
    x_bounds[3]["qdot"].min[vXrot, :] = -50
    x_bounds[3]["qdot"].max[vXrot, :] = 50
    # autour de y
    x_bounds[3]["qdot"].min[vYrot, :] = -50
    x_bounds[3]["qdot"].max[vYrot, :] = 50

    # autour de z
    x_bounds[3]["qdot"].min[vZrot, :] = -50
    x_bounds[3]["qdot"].max[vZrot, :] = 50

    # bras droit
    x_bounds[3]["qdot"].min[vZrotBD:vYrotBD + 1, :] = -50
    x_bounds[3]["qdot"].max[vZrotBD:vYrotBD + 1, :] = 50
    # bras droit
    x_bounds[3]["qdot"].min[vZrotBG:vYrotBG + 1, :] = -50
    x_bounds[3]["qdot"].max[vZrotBG:vYrotBG + 1, :] = 50

    # coude droit
    x_bounds[3]["qdot"].min[vZrotABD:vYrotABD + 1, :] = -50
    x_bounds[3]["qdot"].max[vZrotABD:vYrotABD + 1, :] = 50
    # coude gauche
    x_bounds[3]["qdot"].min[vZrotABD:vYrotABG + 1, :] = -50
    x_bounds[3]["qdot"].max[vZrotABD:vYrotABG + 1, :] = 50

    # du carpe
    x_bounds[3]["qdot"].min[vXrotC, :] = -50
    x_bounds[3]["qdot"].max[vXrotC, :] = 50
    # du dehanchement
    x_bounds[3]["qdot"].min[vYrotC, :] = -50
    x_bounds[3]["qdot"].max[vYrotC, :] = 50

    #
    # Contraintes de position: PHASE 4 la reception
    #

    # deplacement
    x_bounds[4]["q"].min[X, :] = -.1
    x_bounds[4]["q"].max[X, :] = .1
    x_bounds[4]["q"].min[Y, FIN] = -.1
    x_bounds[4]["q"].max[Y, FIN] = .1
    x_bounds[4]["q"].min[Z, :] = 0
    x_bounds[4]["q"].max[Z, :] = zmax  # beaucoup plus que necessaire, juste pour que la parabole fonctionne
    x_bounds[4]["q"].min[Z, FIN] = 0
    x_bounds[4]["q"].max[Z, FIN] = .1

    # le salto autour de x
    x_bounds[4]["q"].min[Xrot, :] = 2 * 3.14 + 3 / 2 * 3.14 - .2  # penche vers avant -> moins de salto
    x_bounds[4]["q"].max[Xrot, :] = -.50 + 4 * 3.14  # un peu carpe a la fin
    x_bounds[4]["q"].min[Xrot, FIN] = -.50 + 4 * 3.14 - .1  #  salto fin un peu carpe
    x_bounds[4]["q"].max[Xrot, FIN] = -.50 + 4 * 3.14 + .1  #  salto fin un peu carpe
    # limitation du tilt autour de y
    x_bounds[4]["q"].min[Yrot, :] = - 3.14 / 16
    x_bounds[4]["q"].max[Yrot, :] = 3.14 / 16
    # la vrille autour de z
    x_bounds[4]["q"].min[Zrot, :] = nb_twist * 3.14 - .1  # complete la vrille
    x_bounds[4]["q"].max[Zrot, :] = nb_twist * 3.14 + .1

    # bras droit
    x_bounds[4]["q"].min[YrotBD, FIN] = 2.9 - .1  # debut bras aux oreilles
    x_bounds[4]["q"].max[YrotBD, FIN] = 2.9 + .1
    x_bounds[4]["q"].min[ZrotBD, FIN] = -.1
    x_bounds[4]["q"].max[ZrotBD, FIN] = .1
    # bras gauche
    x_bounds[4]["q"].min[YrotBG, FIN] = -2.9 - .1  # debut bras aux oreilles
    x_bounds[4]["q"].max[YrotBG, FIN] = -2.9 + .1
    x_bounds[4]["q"].min[ZrotBG, FIN] = -.1
    x_bounds[4]["q"].max[ZrotBG, FIN] = .1

    # coude droit
    x_bounds[4]["q"].min[ZrotABD:XrotABD + 1, FIN] = -.1
    x_bounds[4]["q"].max[ZrotABD:XrotABD + 1, FIN] = .1
    # coude gauche
    x_bounds[4]["q"].min[ZrotABG:XrotABG + 1, FIN] = -.1
    x_bounds[4]["q"].max[ZrotABG:XrotABG + 1, FIN] = .1

    # le carpe
    x_bounds[4]["q"].min[XrotC, :] = -.4
    x_bounds[4]["q"].min[XrotC, FIN] = -.60
    x_bounds[4]["q"].max[XrotC, FIN] = -.40  # fin un peu carpe
    # le dehanchement
    x_bounds[4]["q"].min[YrotC, FIN] = -.1
    x_bounds[4]["q"].max[YrotC, FIN] = .1

    # Contraintes de vitesse: PHASE 4 la reception

    # en xy bassin
    x_bounds[4]["qdot"].min[vX:vY + 1, :] = -10
    x_bounds[4]["qdot"].max[vX:vY + 1, :] = 10

    # z bassin
    x_bounds[4]["qdot"].min[vZ, :] = -50
    x_bounds[4]["qdot"].max[vZ, :] = 50

    # autour de x
    x_bounds[4]["qdot"].min[vXrot, :] = -50
    x_bounds[4]["qdot"].max[vXrot, :] = 50
    # autour de y
    x_bounds[4]["qdot"].min[vYrot, :] = -50
    x_bounds[4]["qdot"].max[vYrot, :] = 50

    # autour de z
    x_bounds[4]["qdot"].min[vZrot, :] = -50
    x_bounds[4]["qdot"].max[vZrot, :] = 50

    # bras droit
    x_bounds[4]["qdot"].min[vZrotBD:vYrotBD + 1, :] = -50
    x_bounds[4]["qdot"].max[vZrotBD:vYrotBD + 1, :] = 50
    # bras droit
    x_bounds[4]["qdot"].min[vZrotBG:vYrotBG + 1, :] = -50
    x_bounds[4]["qdot"].max[vZrotBG:vYrotBG + 1, :] = 50

    # coude droit
    x_bounds[4]["qdot"].min[vZrotABD:vYrotABD + 1, :] = -50
    x_bounds[4]["qdot"].max[vZrotABD:vYrotABD + 1, :] = 50
    # coude gauche
    x_bounds[4]["qdot"].min[vZrotABD:vYrotABG + 1, :] = -50
    x_bounds[4]["qdot"].max[vZrotABD:vYrotABG + 1, :] = 50

    # du carpe
    x_bounds[4]["qdot"].min[vXrotC, :] = -50
    x_bounds[4]["qdot"].max[vXrotC, :] = 50
    # du dehanchement
    x_bounds[4]["qdot"].min[vYrotC, :] = -50
    x_bounds[4]["qdot"].max[vYrotC, :] = 50

    #
    # Initial guesses
    #
    x0 = np.vstack((np.zeros((nb_q, 2)), np.zeros((nb_qdot, 2))))
    x1 = np.vstack((np.zeros((nb_q, 2)), np.zeros((nb_qdot, 2))))
    x2 = np.vstack((np.zeros((nb_q, 2)), np.zeros((nb_qdot, 2))))
    x3 = np.vstack((np.zeros((nb_q, 2)), np.zeros((nb_qdot, 2))))
    x4 = np.vstack((np.zeros((nb_q, 2)), np.zeros((nb_qdot, 2))))

    # bras droit  f4a la vrille
    # décollage prise del aposition carpée
    x0[Xrot, 0] = .50
    x0[ZrotBG] = -.75
    x0[ZrotBD] = .75
    x0[YrotBG, 0] = -2.9
    x0[YrotBD, 0] = 2.9
    x0[YrotBG, 1] = -1.35
    x0[YrotBD, 1] = 1.35
    x0[XrotC, 0] = -.5
    x0[XrotC, 1] = -2.6

    # rotater en salto (x) en carpé
    x1[ZrotBG] = -.75
    x1[ZrotBD] = .75
    x1[Xrot, 1] = 2 * 3.14
    x1[YrotBG] = -1.35
    x1[YrotBD] = 1.35
    x1[XrotC] = -2.6

    # ouverture des hanches
    x2[Xrot] = 2 * 3.14
    x2[Zrot, 1] = 0.2
    x2[ZrotBG, 0] = -.75
    x2[ZrotBD, 0] = .75
    x2[YrotBG, 0] = -1.35
    x2[YrotBD, 0] = 1.35
    x2[XrotC, 0] = -2.6

    # Vrille en position tendue
    x3[Xrot, 0] = 2 * 3.14
    x3[Xrot, 1] = 2 * 3.14 + 3 / 2 * 3.14
    x3[Zrot, 0] = 0  # METTRE 0 ?
    x3[Zrot, 1] = nb_twist * 3.14

    # Aterrissage (réduire le tilt)
    x4[Xrot, 0] = 2 * 3.14 + 3 / 2 * 3.14
    x4[Xrot, 1] = 4 * 3.14
    x4[Zrot] = nb_twist * 3.14
    x4[XrotC, 1] = -.5

    x_init = InitialGuessList()
    x_init.add("q", initial_guess=x0[:nb_q, :], interpolation=InterpolationType.LINEAR, phase=0)
    x_init.add("qdot", initial_guess=x0[nb_q:, :], interpolation=InterpolationType.LINEAR, phase=0)
    x_init.add("q", initial_guess=x1[:nb_q, :], interpolation=InterpolationType.LINEAR, phase=1)
    x_init.add("qdot", initial_guess=x1[nb_q:, :], interpolation=InterpolationType.LINEAR, phase=1)
    x_init.add("q", initial_guess=x2[:nb_q, :], interpolation=InterpolationType.LINEAR, phase=2)
    x_init.add("qdot", initial_guess=x2[nb_q:, :], interpolation=InterpolationType.LINEAR, phase=2)
    x_init.add("q", initial_guess=x3[:nb_q, :], interpolation=InterpolationType.LINEAR, phase=3)
    x_init.add("qdot", initial_guess=x3[nb_q:, :], interpolation=InterpolationType.LINEAR, phase=3)
    x_init.add("q", initial_guess=x4[:nb_q, :], interpolation=InterpolationType.LINEAR, phase=4)
    x_init.add("qdot", initial_guess=x4[nb_q:, :], interpolation=InterpolationType.LINEAR, phase=4)

    for i in range(5):
        x_init[i]["q"].add_noise(
            bounds=x_bounds[i]["q"],
            n_shooting=np.array(n_shooting[i])+1,
            magnitude=0.2,
            magnitude_type=MagnitudeType.RELATIVE,
            seed=seed,
            )
        x_init[i]["qdot"].add_noise(
            bounds=x_bounds[i]["qdot"],
            n_shooting=np.array(n_shooting[i])+1,
            magnitude=0.2,
            magnitude_type=MagnitudeType.RELATIVE,
            seed=seed,
            )

    constraints = ConstraintList()
    constraints.add(ConstraintFcn.SUPERIMPOSE_MARKERS, node=Node.ALL_SHOOTING, min_bound=-.1, max_bound=.1,
                    first_marker='MidMainG', second_marker='CibleMainG', phase=1)
    constraints.add(ConstraintFcn.SUPERIMPOSE_MARKERS, node=Node.ALL_SHOOTING, min_bound=-.1, max_bound=.1,
                    first_marker='MidMainD', second_marker='CibleMainD', phase=1)
    constraints.add(ConstraintFcn.TIME_CONSTRAINT, node=Node.END, min_bound=1e-4, max_bound=1.5, phase=1)
    constraints.add(ConstraintFcn.TIME_CONSTRAINT, node=Node.END, min_bound=1e-4, max_bound=0.7, phase=3)
    constraints.add(ConstraintFcn.TIME_CONSTRAINT, node=Node.END, min_bound=1e-4, max_bound=0.5, phase=4)

    return OptimalControlProgram(
        biorbd_model,
        dynamics,
        n_shooting,
        [final_time / len(biorbd_model)] * len(biorbd_model),
        x_init=x_init,
        u_init=u_init,
        x_bounds=x_bounds,
        u_bounds=u_bounds,
        objective_functions=objective_functions,
        constraints=constraints,
        n_threads=5,
    )

def construct_filepath(biorbd_model_path, nb_twist, seed):
    stunts = dict({3: "vrille_et_demi", 5: "double_vrille_et_demi", 7: "triple_vrille_et_demi"})
    stunt = stunts[nb_twist]
    athlete = biorbd_model_path.split('/')[-1].removesuffix('.bioMod')
    title_before_solve = f"{athlete}_{stunt}_{seed}"
    return title_before_solve

def save_results(sol: Solution,
                 *combinatorial_parameters,
                 **extra_parameter):
    """
    Solving the ocp
    Parameters
    ----------
    sol: Solution
        The solution to the ocp at the current pool
    """

    title_before_solve = construct_filepath(biorbd_model_path, nb_twist, seed)

    convergence = sol.status
    dict_state = {}
    q = []
    qdot = []
    tau = []

    for i in range(len(sol.states)) :
        q.append(sol.states[i]['q'])
        qdot.append(sol.states[i]['qdot'])
        tau.append(sol.controls[i]['qddot_joints'])

    dict_state['q'] = q
    dict_state['qdot'] = qdot
    dict_state['tau'] = tau
    del sol.ocp
    dict_state['sol'] = sol

    if convergence == 0 :
        convergence = 'CVG'
        print(f'{athlete}  doing' + f' {stunt}' + ' converge')
    else:
        convergence = 'DVG'
        print(f'{athlete} doing ' + f'{stunt}' + ' doesn t converge')

    if save_folder:
        with open(f'{save_folder}/{title_before_solve}_{convergence}.pkl', "wb") as file:
            pickle.dump(dict_state, file)
    else:
        raise RuntimeError(f"This folder {save_folder} does not exist")

def should_solve(*combinatorial_parameters, **extra_parameters):
    """
    Check if the filename already appears in the folder where files are saved, if not ocp must be solved
    """
    biorbd_model_path, nb_twist, seed = combinatorial_parameters
    save_folder = extra_parameters["save_folder"]
    file_path = construct_filepath(biorbd_model_path, nb_twist, seed)
    already_done_filenames = os.listdir(f"{save_folder}")

    if file_path not in already_done_filenames:
        return True
    else:
        return False

def prepare_multi_start(
    combinatorial_parameters: dict[tuple,...],
    save_folder: str = None,
    n_pools: int = 6
) -> MultiStart:

    """
    The initialization of the multi-start
    """
    return MultiStart(
        combinatorial_parameters=combinatorial_parameters,
        prepare_ocp_callback=prepare_ocp,
        post_optimization_callback=(save_results, {'save_folder': save_folder}),
        should_solve_callback=(should_solve, {'save_folder': save_folder}),
        solver=Solver.IPOPT(show_online_optim=False),  # You cannot use show_online_optim with multi-start
        n_pools=n_pools,
    )

def main():
    """
    Prepares and solves an ocp for a 803<. Animates the results
    """

    seed = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    nb_twist = [3, 5]
    athletes = [
        # "AdCh",
        # "AlAd",
        # "AuJo",
        # "Benjamin",
        # "ElMe",
        # "EvZl",
        # "FeBl",
        # "JeCh",
        # "KaFu",
        # "KaMi",
        # "LaDe",
        # "MaCu",
        # "MaJa",
        # "OlGa",
        "Sarah",
        # "SoMe",
        # "WeEm",
        # "ZoTs"
        ]

    all_paths = []
    for athlete in athletes :
        path = f'{athlete}'+'.bioMod'
        biorbd_model_path = "Models/Models_Lisa/" + f'{path}'
        all_paths.append(biorbd_model_path)

    combinatorial_parameters = {'bio_model_path': all_paths,
                                'nb_twist': nb_twist,
                                'seed': seed}
    save_folder = "Multistart_double_vrille"

    multi_start = prepare_multi_start(combinatorial_parameters=combinatorial_parameters, save_folder=save_folder, n_pools=6)

    multi_start.solver = Solver.IPOPT(show_online_optim=False, show_options=dict(show_bounds=False))
        #if Mod.with_hsl:
    multi_start.solver.set_linear_solver('ma57')
    #else:
    #    print("Not using ma57")
    multi_start.solver.set_maximum_iterations(3000)
    multi_start.solver.set_convergence_tolerance(1e-4)
    #multi_start.solver.set_print_level(0)

    multi_start.solve()

    #sol.graphs(show_bounds=True, show_now=False, save_path=f'{folder}/{athlete}')

if __name__ == "__main__":
    main()


class PointV2:
    """Representation of a two-dimensional point coordinate."""

    def __init__(self, x: float, y: float) -> None:
        """Initializes a PointV2 with the given coordinates."""
        self.x = x
        self.y = y

    def distance_to(self, other: "PointV2") -> float:
        """Computes the distance to another `PointV2`."""
        dx = self.x - other.x
        dy = self.y - other.y
        return (dx**2 + dy**2) ** 0.5

p1 = PointV2(x="5", y="7")
p2 = PointV2(x=5, y=7)