# coding=utf-8

from __future__ import division
from design.MainData import MainData
from canvas import *
from algorithm import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import (QIcon, QFont)
from PyQt5.QtWidgets import (qApp,
                             QAction,
                             QComboBox,
                             QDesktopWidget,
                             QFileDialog,
                             QGridLayout,
                             QInputDialog,
                             QRadioButton,
                             QLabel,
                             QLCDNumber,
                             QMainWindow,
                             QMessageBox,
                             QPushButton,
                             QSpinBox,
                             QTableWidget,
                             QTableWidgetItem,
                             QTabWidget,
                             QTextEdit,
                             QToolTip,
                             QWidget)

# noinspection PyNonAsciiChar
class App(QMainWindow, MainData):
    """
    @
    """

    # noinspection PyArgumentList,PyMissingConstructor
    def __init__(self):
        # noinspection PyCompatibility
        QMainWindow.__init__(self)

        # noinspection PyCallByClass,PyTypeChecker
        QToolTip.setFont(QFont('SansSerif', 10))
        self.setGeometry(100, 100, 900, 550)
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
        self.setWindowTitle('MathDemo')
        self.setWindowIcon(QIcon('python.png'))
        self.setWindowFlags(Qt.WindowStaysOnTopHint)

        self.actionLoad()

        self.menuBarLoad()

        #############################################
        # set current image that you are operating. #
        #############################################
        self.currentImage = ""
        self.rainImage()

        self.statusBar().showMessage('Ready')

        self.show()

    def actionLoad(self):
        """
        set MainData.action
        """

        self.action["showOpenDialog"] = QAction('Open File', self)
        self.action["showOpenDialog"].setIcon(QIcon('open.png'))
        self.action["showOpenDialog"].setShortcut('Ctrl+O')
        self.action["showOpenDialog"].setStatusTip('Open File')
        self.action["showOpenDialog"].triggered.connect(self.showOpenDialog)

        self.action["qApp.quit"] = QAction('Exit application', self)
        self.action["qApp.quit"].setIcon(QIcon('exit.jpg'))
        self.action["qApp.quit"].setShortcut('Ctrl+Q')
        self.action["qApp.quit"].setStatusTip('Exit application')
        self.action["qApp.quit"].triggered.connect(qApp.quit)

        self.action["orthogonalTableImage"] = QAction('Orthogonal Table', self)
        self.action["orthogonalTableImage"].setIcon(QIcon('numpy_logo.jpg'))
        self.action["orthogonalTableImage"].setShortcut('Ctrl+T')
        self.action["orthogonalTableImage"].setStatusTip('Orthogonal Table')
        self.action["orthogonalTableImage"].triggered.connect(self.orthogonalTableImage)

        self.action["convexHullImage"] = QAction('Convex Hull', self)
        self.action["convexHullImage"].setIcon(QIcon('numpy_logo.jpg'))
        self.action["convexHullImage"].setShortcut('Ctrl+C')
        self.action["convexHullImage"].setStatusTip('Convex Hull')
        self.action["convexHullImage"].triggered.connect(self.convexHullImage)

        self.action["gravitationalSystemImage"] = QAction('Gravitational System', self)
        self.action["gravitationalSystemImage"].setIcon(QIcon('scipy_logo.jpg'))
        self.action["gravitationalSystemImage"].setShortcut('Ctrl+G')
        self.action["gravitationalSystemImage"].setStatusTip('Gravitational System')
        self.action["gravitationalSystemImage"].triggered.connect(self.gravitationalSystemImage)

        self.action["analyticFunctionImage"] = QAction('Analytic Function', self)
        self.action["analyticFunctionImage"].setIcon(QIcon('numpy_logo.jpg'))
        self.action["analyticFunctionImage"].setShortcut('Ctrl+A')
        self.action["analyticFunctionImage"].setStatusTip('Analytic Function')
        self.action["analyticFunctionImage"].triggered.connect(self.analyticFunctionImage)

        self.action["sourceCodeImage"] = QAction('Source Code', self)
        self.action["sourceCodeImage"].setShortcut('F2')
        self.action["sourceCodeImage"].setStatusTip('Source Code')
        self.action["sourceCodeImage"].triggered.connect(self.sourceCodeImage)

    def menuBarLoad(self):
        """
        set MainWindow.menuBar
        """
        self.statusBar()
        menubar = self.menuBar()

        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(self.action["showOpenDialog"])
        fileMenu.addAction(self.action["qApp.quit"])

        statisticsMenu = menubar.addMenu('&Statistics')
        statisticsMenu.addAction(self.action["orthogonalTableImage"])

        statisticsMenu = menubar.addMenu('&Geometry')
        statisticsMenu.addAction(self.action["convexHullImage"])

        statisticsMenu = menubar.addMenu('&Ode')
        statisticsMenu.addAction(self.action["gravitationalSystemImage"])

        statisticsMenu = menubar.addMenu('&Complex')
        statisticsMenu.addAction(self.action["analyticFunctionImage"])

        statisticsMenu = menubar.addMenu('&Help')
        statisticsMenu.addAction(self.action["sourceCodeImage"])

    def controlLayout(self, layout=None, name=None, var=None, position=None, signal=None):
        """
        control layout
        :param layout: GridLayout = QGridLayout()
        :param name: name of control, name is a string
        :param var: var is a dict
        :param position: position is a list with 4 numeric
        :param signal: signal function
        """
        if name == "QLabel":
            # var = {"text": [string]}
            for j in range(0, len(position)):
                self.control[name].append(QLabel(var["text"][j]))
                self.control[name][-1].setAlignment(Qt.AlignCenter)
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])

        if name == "QTabWidget":
            # var = {"text": [[string]], "widget": [[PyQt5.QtWidgets.QWidget]]}
            for j in range(0, len(position)):
                self.control[name].append(QTabWidget())
                for k in range(0, len(var["text"][j])):
                    self.control[name][-1].addTab(var["widget"][j][k], self.tr(var["text"][j][k]))
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])

        if name == "QPushButton":
            # var = {"text": [string]}
            for j in range(0, len(position)):
                self.control[name].append(QPushButton(var["text"][j]))
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])
                if signal is not None:
                    self.control[name][-1].clicked.connect(signal)

        if name == "QTextEdit":
            # var = {"text": [[string]]}
            for j in range(0, len(position)):
                self.control[name].append(QTextEdit())
                if len(var["text"]) != 0:
                    if len(var["text"][j]) != 0:
                        for line in var["text"][j]:
                            self.control[name][-1].append(line)
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])

        if name == "QRadioButton":
            # var = {"text": [string], "isChecked": [bool]}
            for j in range(0, len(position)):
                self.control[name].append(QRadioButton(var["text"][j]))
                self.control[name][-1].setChecked(var["isChecked"][j])
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])
                if signal is not None:
                    self.control[name][-1].clicked.connect(signal)

        if name == "QComboBox":
            # var = {"itemText": [[string]], "currentIndex": [int]}
            for j in range(0, len(position)):
                self.control[name].append(QComboBox())
                self.control[name][-1].addItems(var["itemText"][j])
                if len(var["currentIndex"]) != 0:
                    self.control[name][-1].setCurrentIndex(var["currentIndex"][j])
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])
                if signal is not None:
                    self.control[name][-1].currentIndexChanged.connect(signal)

        if name == "QSpinBox":
            # var = {"range": [[int, int]], "singleStep": [int], "prefix": [string], "suffix": [string], "value": [int]}
            for j in range(0, len(position)):
                self.control[name].append(QSpinBox())
                self.control[name][-1].setRange(var["range"][j][0], var["range"][j][1])
                self.control[name][-1].setSingleStep(var["singleStep"][j])
                if len(var["prefix"]) != 0:
                    if len(var["prefix"][j]) != 0:
                        self.control[name][-1].setPrefix(var["prefix"][j])
                if len(var["suffix"]) != 0:
                    if len(var["suffix"][j]) != 0:
                        self.control[name][-1].setSuffix(var["suffix"][j])
                self.control[name][-1].setValue(var["value"][j])
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])
                if signal is not None:
                    self.control[name][-1].valueChanged.connect(signal)

        if name == "QTableWidget":
            # var = {"headerLabels": [[string]], "data": [numpy.array]}
            for i in range(0, len(position)):
                self.control[name].append(QTableWidget(1, 1))
                if len(var["headerLabels"]) != 0:
                    if len(var["headerLabels"][i]) != 0:
                        self.control[name][-1].setColumnCount(len(var["headerLabels"][i]))
                        self.control[name][-1].setHorizontalHeaderLabels(var["headerLabels"][i])
                if len(var["data"]) != 0:
                    if len(var["data"][i]) != 0:
                        row, column = var["data"][i].shape
                        self.control[name][-1].setRowCount(row)
                        self.control[name][-1].setColumnCount(column)
                        for j in range(0, row):
                            for k in range(0, column):
                                newItem = QTableWidgetItem(str(var["data"][i][j][k]))
                                self.control[name][-1].setItem(j, k, newItem)
                self.control[name][-1].resizeColumnsToContents()
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[i][0], position[i][1], position[i][2], position[i][3])

        if name == "QLCDNumber":
            # var = {"value": [int]}
            for j in range(0, len(position)):
                self.control[name].append(QLCDNumber(self))
                if len(var["value"]) != 0:
                    if len(var["value"][j]) != 0:
                        self.control[name][-1].display(var["value"][j])
                    else:
                        self.control[name][-1].display(0)
                # noinspection PyArgumentList
                layout.addWidget(self.control[name][-1], position[j][0], position[j][1], position[j][2], position[j][3])

    def imageRead(self, imageName=None):
        """
        load data into current page, or write data from current page.
        """
        if len(self.control["QRadioButton"]) != 0:
            length = len(self.control["QRadioButton"])
            for j in range(0, length):
                isChecked = self.controlData[imageName]["QRadioButton"]["isChecked"][j]
                self.control["QRadioButton"][j].setChecked(isChecked)

        if len(self.control["QComboBox"]) != 0:
            pass

        if len(self.control["QComboBox"]) != 0:
            length = len(self.control["QComboBox"])
            for j in range(0, length):
                currentIndex = self.controlData[imageName]["QComboBox"]["currentIndex"][j]
                self.control["QComboBox"][j].setCurrentIndex(currentIndex)

        if len(self.control["QSpinBox"]) != 0:
            length = len(self.control["QSpinBox"])
            for j in range(0, length):
                value = self.controlData[imageName]["QSpinBox"]["value"][j]
                self.control["QSpinBox"][j].setValue(value)

        if len(self.control["QTableWidget"]) != 0:
            length = len(self.control["QTableWidget"])
            for i in range(0, length):
                data = self.controlData[imageName]["QTableWidget"]["data"][i]
                row, column = data.shape
                self.control["QTableWidget"][i].setRowCount(row)
                self.control["QTableWidget"][i].setColumnCount(column)
                for j in range(0, row):
                    for k in range(0, column):
                        newItem = QTableWidgetItem(str(data[j][k]))
                        self.control["QTableWidget"][i].setItem(j, k, newItem)
                self.control["QTableWidget"][i].resizeColumnsToContents()

        if len(self.control["QLCDNumber"]) != 0:
            length = len(self.control["QLCDNumber"])
            for j in range(0, length):
                value = self.controlData[imageName]["QLCDNumber"]["value"][j]
                self.control["QLCDNumber"][j].display(value)

    def rainImage(self):
        """
        @
        """
        self.currentImage = "rainImage"
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        # noinspection PyArgumentList
        widget = QWidget()
        self.canvas["rainImage"] = RainCanvas(parent=widget)
        layout.addWidget(self.canvas["rainImage"])

        widget.setLayout(layout)
        self.setCentralWidget(widget)

    def orthogonalTableImage(self):
        """
        layout and initialization data
        """
        ##########################
        # layout of current page #
        ##########################
        self.currentImage = "orthogonalTableImage"
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        text = ['水平数', '重复次数', '实验次数', '因素数']
        position = [[0, 0, 1, 1], [0, 2, 1, 1], [0, 4, 1, 1], [0, 6, 1, 1]]
        self.controlLayout(layout=layout, name="QLabel", var={"text": text}, position=position, signal=None)

        itemText = [list(map(str, range(2, 10))), list(map(str, range(0, 10)))]
        position = [[0, 1, 1, 1], [0, 3, 1, 1]]
        self.controlLayout(layout=layout, name="QComboBox", var={"itemText": itemText, "currentIndex": []},
                           position=position, signal=self.orthogonalTableImageSignal)

        position = [[0, 5, 1, 1], [0, 7, 1, 1]]
        self.controlLayout(layout=layout, name="QLCDNumber", var={"value": []}, position=position, signal=None)

        # noinspection PyArgumentList
        widget = [[QWidget(), QWidget()]]
        for j in range(0, 2):
            widgetLayout = QGridLayout()
            position = [[0, 0, 1, 1]]
            self.controlLayout(layout=widgetLayout, name="QTableWidget", var={"headerLabels": [], "data": []},
                               position=position, signal=None)
            widget[0][j].setLayout(widgetLayout)
        text = [["Table", "Title"]]
        position = [[1, 0, 1, 8]]
        self.controlLayout(layout=layout, name="QTabWidget", var={"text": text, "widget": widget},
                           position=position, signal=None)

        # noinspection PyArgumentList
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        ###########################################################################
        # initialization self.controlData["orthogonalTableImage"] then refresh it #
        # refresh self.control["orthogonalTableImage"]                            #
        ###########################################################################
        if "orthogonalTableImage" not in self.controlData:
            self.addFrame("orthogonalTableImage")
            self.orthogonalTableImageSignal()
        else:
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.disconnect(self.orthogonalTableImageSignal)
            self.imageRead(imageName="orthogonalTableImage")
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.connect(self.orthogonalTableImageSignal)

        self.statusBar().showMessage('Ready')

    def orthogonalTableImageSignal(self):
        """
        respond of current page(orthogonalTableImage), then write data into MainData.controlData
        """
        self.statusBar().showMessage('Starting generate table and title...')

        ###########################################################
        # initialization self.controlData["orthogonalTableImage"] #
        ###########################################################
        imageName = "orthogonalTableImage"
        self.controlDataClear(imageName)

        ####################################################
        # refresh self.controlData["orthogonalTableImage"] #
        ####################################################
        for j in range(0, len(self.control["QComboBox"])):
            itemText = list()
            for k in range(0, self.control["QComboBox"][j].count()):
                itemText.append(self.control["QComboBox"][j].itemText(k))
            self.controlData[imageName]["QComboBox"]["itemText"].append(itemText)

            currentIndex = self.control["QComboBox"][j].currentIndex()
            self.controlData[imageName]["QComboBox"]["currentIndex"].append(currentIndex)

        level = int(self.control["QComboBox"][0].currentText())
        time = int(self.control["QComboBox"][1].currentText())
        obj = OrthogonalTableMap(level, time)
        row, column = obj.table.shape

        self.controlData[imageName]["QLCDNumber"]["value"].append(row)
        self.controlData[imageName]["QLCDNumber"]["value"].append(column)

        self.controlData[imageName]["QTableWidget"]["data"].append(obj.table)
        self.controlData[imageName]["QTableWidget"]["data"].append(obj.title)

        self.controlData[imageName]["save"] = [level, time, obj.table, obj.title]

        ################################################
        # refresh self.control["orthogonalTableImage"] #
        ################################################
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.disconnect(self.orthogonalTableImageSignal)
        self.imageRead(imageName=imageName)
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.connect(self.orthogonalTableImageSignal)

        if obj.error == 1:
            self.statusBar().showMessage('Unable to generate title.')
        else:
            self.statusBar().showMessage('Table and title generated.')

    def gravitationalSystemImage(self):
        """
        layout and initialization data
        """
        ##########################
        # layout of current page #
        ##########################
        self.currentImage = "gravitationalSystemImage"
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        text = ['长度单位', '时间单位', '质量单位', '电量单位',
                '质点类型', '质点个数', '动画时长', '总帧数', '播放速率',
                '坐标轴标签', '视角', '速度矢量', '加速度矢量']
        position = [[0, 0, 1, 1], [0, 1, 1, 1], [0, 2, 1, 1], [0, 3, 1, 1],
                    [0, 4, 1, 1], [0, 5, 1, 1], [0, 6, 1, 1], [2, 0, 1, 1], [2, 1, 1, 1],
                    [2, 2, 1, 1], [2, 3, 1, 1], [2, 4, 1, 1], [2, 5, 1, 1]]
        self.controlLayout(layout=layout, name="QLabel", var={"text": text}, position=position, signal=None)

        itemText = [['ly', 'km', 'm', 'mm', 'nm'], ['year', 'day', 's', 'ms', 'μs'],
                    ['kg', '太阳(1.99+e30kg)', '质子(1.7-e27kg)', '电子(9.1-e31kg)'], ['C', 'e(1.6-e19C)'],
                    ['天体-天体', '电荷-电荷', '自定义-自定义'], list(map(str, range(2, 10))),
                    list(map(str, range(5, 65, 5))), ['100', '1000', '10000', '100000'],
                    ['显示', '隐藏'], ['静止', '旋转'], ['隐藏速度矢量', '显示当前速度矢量', '显示所有速度矢量'],
                    ['隐藏加速度矢量', '显示当前加速度矢量', '显示所有加速度矢量']]
        currentIndex = [2, 2, 0, 0, 2, 1, 2, 2, 0, 1, 1, 1]
        position = [[1, 0, 1, 1], [1, 1, 1, 1], [1, 2, 1, 1], [1, 3, 1, 1],
                    [1, 4, 1, 1], [1, 5, 1, 1], [1, 6, 1, 1], [3, 0, 1, 1],
                    [3, 2, 1, 1], [3, 3, 1, 1], [3, 4, 1, 1], [3, 5, 1, 1]]
        self.controlLayout(layout=layout, name="QComboBox", var={"itemText": itemText, "currentIndex": currentIndex},
                           position=position, signal=self.gravitationalSystemImageSignal)

        var = {"range": [[1, 1000]], "singleStep": [1], "prefix": ['X  '], "suffix": ["  倍"], "value": [100]}
        position = [[3, 1, 1, 1]]
        self.controlLayout(layout=layout, name="QSpinBox", var=var,
                           position=position, signal=self.gravitationalSystemImageSignal)

        text = ['启用调试单位']
        isChecked = [True]
        position = [[2, 6, 1, 1]]
        self.controlLayout(layout=layout, name="QRadioButton", var={"text": text, "isChecked": isChecked},
                           position=position, signal=self.gravitationalSystemImageSignal)

        text = ['播放动画']
        position = [[3, 6, 1, 1]]
        self.controlLayout(layout=layout, name="QPushButton", var={"text": text},
                           position=position, signal=self.gravitationalSystemImageDraw)

        # noinspection PyArgumentList
        widget = [[QWidget(), QWidget()]]
        name = ["QTableWidget", "QTextEdit"]
        var = [{"headerLabels": [["mass", "electricity", "X-coordinate", "Y-coordinate", "Z-coordinate",
                                  "X-velocity", "Y-velocity", "Z-velocity"]],
                "data": [numpy.array([[1, 0, 1, 0, 0, 0, 1, 0],
                                      [1, 1, 0, 1, 0, 0, 0, 1],
                                      [1, -1, 0, 0, 1, 1, 0, 0]])]},
               {"text": [["yes", "no"]]}]
        for j in range(0, 2):
            widgetLayout = QGridLayout()
            position = [[0, 0, 1, 1]]
            self.controlLayout(layout=widgetLayout, name=name[j], var=var[j], position=position, signal=None)
            widget[0][j].setLayout(widgetLayout)
        text = [["Initial Condition", "Note"]]
        position = [[4, 0, 1, 7]]
        self.controlLayout(layout=layout, name="QTabWidget", var={"text": text, "widget": widget},
                           position=position, signal=None)

        # noinspection PyArgumentList
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        ###############################################################################
        # initialization self.controlData["gravitationalSystemImage"] then refresh it #
        # refresh self.control["gravitationalSystemImage"]                            #
        ###############################################################################
        if "gravitationalSystemImage" not in self.controlData:
            self.addFrame("gravitationalSystemImage")
            self.gravitationalSystemImageSignal()
        else:
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.disconnect(self.gravitationalSystemImageSignal)
            for j in range(0, len(self.control["QSpinBox"])):
                self.control["QSpinBox"][j].valueChanged.disconnect(self.gravitationalSystemImageSignal)
            for j in range(0, len(self.control["QRadioButton"])):
                self.control["QRadioButton"][j].clicked.disconnect(self.gravitationalSystemImageSignal)
            self.imageRead(imageName="gravitationalSystemImage")
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.connect(self.gravitationalSystemImageSignal)
            for j in range(0, len(self.control["QSpinBox"])):
                self.control["QSpinBox"][j].valueChanged.connect(self.gravitationalSystemImageSignal)
            for j in range(0, len(self.control["QRadioButton"])):
                self.control["QRadioButton"][j].clicked.connect(self.gravitationalSystemImageSignal)

        self.statusBar().showMessage('Ready')

    def gravitationalSystemImageSignal(self):
        """
        respond of current page(gravitationalSystemImage), then write data into MainData.dataClasses
        """
        self.statusBar().showMessage('Saving Page data...')

        ###############################################################
        # initialization self.controlData["gravitationalSystemImage"] #
        ###############################################################
        imageName = "gravitationalSystemImage"
        self.controlDataClear(imageName)

        ########################################################
        # refresh self.controlData["gravitationalSystemImage"] #
        ########################################################
        for j in range(0, len(self.control["QComboBox"])):
            itemText = list()
            for k in range(0, self.control["QComboBox"][j].count()):
                itemText.append(self.control["QComboBox"][j].itemText(k))
            self.controlData[imageName]["QComboBox"]["itemText"].append(itemText)

            currentIndex = self.control["QComboBox"][j].currentIndex()
            self.controlData[imageName]["QComboBox"]["currentIndex"].append(currentIndex)

        for i in range(0, len(self.control["QTableWidget"])):
            currentRow = self.control["QTableWidget"][i].rowCount()
            row = int(self.control["QComboBox"][5].currentText())
            column = self.control["QTableWidget"][i].columnCount()
            data = numpy.zeros((row, column), dtype=numpy.float64)
            for j in range(0, row):
                for k in range(0, column):
                    if j < currentRow:
                        # noinspection PyBroadException
                        try:
                            data[j][k] = float(self.control["QTableWidget"][i].item(j, k).text())
                        except:
                            data[j][k] = 0
            self.controlData[imageName]["QTableWidget"]["data"].append(data)

        for j in range(0, len(self.control["QSpinBox"])):
            value = self.control["QSpinBox"][j].value()
            self.controlData[imageName]["QSpinBox"]["value"].append(value)

        for j in range(0, len(self.control["QRadioButton"])):
            isChecked = self.control["QRadioButton"][j].isChecked()
            self.controlData[imageName]["QRadioButton"]["isChecked"].append(isChecked)

        ####################################################
        # refresh self.control["gravitationalSystemImage"] #
        ####################################################
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.disconnect(self.gravitationalSystemImageSignal)
        for j in range(0, len(self.control["QSpinBox"])):
            self.control["QSpinBox"][j].valueChanged.disconnect(self.gravitationalSystemImageSignal)
        for j in range(0, len(self.control["QRadioButton"])):
            self.control["QRadioButton"][j].clicked.disconnect(self.gravitationalSystemImageSignal)
        self.imageRead(imageName="gravitationalSystemImage")
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.connect(self.gravitationalSystemImageSignal)
        for j in range(0, len(self.control["QSpinBox"])):
            self.control["QSpinBox"][j].valueChanged.connect(self.gravitationalSystemImageSignal)
        for j in range(0, len(self.control["QRadioButton"])):
            self.control["QRadioButton"][j].clicked.connect(self.gravitationalSystemImageSignal)

        self.statusBar().showMessage('Page data Saved.')

    def gravitationalSystemImageDraw(self):
        """
        Draw animation of solution of ordinary differential equations
        """
        ########################################################
        # refresh self.controlData["gravitationalSystemImage"] #
        ########################################################
        self.gravitationalSystemImageSignal()
        self.statusBar().showMessage('Start to solving the ordinary differential equations...')

        #####################################################
        # get parameters of ordinary differential equations #
        #####################################################
        imageName = "gravitationalSystemImage"
        aniArg = self.controlData[imageName]["QComboBox"]["currentIndex"][8:]

        data = self.controlData[imageName]["QTableWidget"]["data"][0]
        mass = data[:, 0]
        for j in range(0, len(mass)):
            if mass[j] < 0 or mass[j] == 0:
                self.statusBar().showMessage('mass[%d] must be positive.' % j)
                return

        electric = numpy.abs(data[:, 1])

        electricType = numpy.sign(data[:, 1])

        cood = data[:, 2:5]
        coodCheck = numpy.dot(cood, cood.T)
        for j in range(0, len(mass)):
            for k in range(j, len(mass)):
                if (coodCheck[j, j] + coodCheck[k, k]) == 2 * coodCheck[j, k] and j != k:
                    self.statusBar().showMessage('point[%d] and point[%d] share the same coordinate.' % (j, k))
                    return

        vel = data[:, 5:]

        if self.controlData[imageName]["QRadioButton"]["isChecked"][0]:
            GUnit = 1
            KUnit = 1
        else:
            lenUnitMap = {0: 9.46 * 10 ** 15, 1: 1000, 2: 1, 3: 0.001, 4: 10 ** (-9)}
            lenUnit = lenUnitMap[self.controlData[imageName]["QComboBox"]["currentIndex"][0]]

            timeUnitMap = {0: 3.1536 * 10 ** 7, 1: 86400, 2: 1, 3: 0.001, 4: 10 ** (-6)}
            timeUnit = timeUnitMap[self.controlData[imageName]["QComboBox"]["currentIndex"][1]]

            massUnitMap = {0: 1, 1: 1.99 * 10 ** 30, 2: 1.7 * 10 ** (-27), 3: 9.1 * 10 ** (-31)}
            massUnit = massUnitMap[self.controlData[imageName]["QComboBox"]["currentIndex"][2]]

            electricUnitMap = {0: 1, 1: 1.6 * 10 ** (-19)}
            electricUnit = electricUnitMap[self.controlData[imageName]["QComboBox"]["currentIndex"][3]]

            GUnit = 6.67408 * 10 ** (-11) * massUnit * timeUnit ** 2 / lenUnit ** 3
            KUnit = 8.987 * 10 ** 9 * electricUnit ** 2 * timeUnit ** 2 / lenUnit ** 3 / massUnit

        timeLength = int(self.control["QComboBox"][6].currentText())
        nodeNumber = int(self.control["QComboBox"][7].currentText())
        t = numpy.arange(0, timeLength, timeLength / nodeNumber)

        aniSpeed = self.controlData[imageName]["QSpinBox"]["value"][0]

        ########################################################
        # draw the solution of ordinary differential equations #
        ########################################################
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        # noinspection PyArgumentList
        widget = QWidget()
        self.canvas["gravitationalSystemImage"] = GravitationCanvas(aniArg, mass, electric, electricType, cood, vel,
                                                                    GUnit, KUnit, t, aniSpeed, parent=widget)
        self.controlData[imageName]["save"] = [mass, electric, electricType, cood, vel, GUnit, KUnit, t,
                                               self.canvas["gravitationalSystemImage"].track,
                                               self.canvas["gravitationalSystemImage"].vector,
                                               self.canvas["gravitationalSystemImage"].acc]

        layout.addWidget(self.canvas["gravitationalSystemImage"])
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.statusBar().showMessage('Ready')

    def convexHullImage(self):
        """
        layout and initialization data
        """
        ##########################
        # layout of current page #
        ##########################
        self.currentImage = "convexHullImage"
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        text = ['空间维数', '散点个数', '查看迭代步数']
        position = [[0, 0, 1, 1], [0, 2, 1, 1], [0, 4, 1, 1]]
        self.controlLayout(layout=layout, name="QLabel", var={"text": text}, position=position, signal=None)

        itemText = [list(map(str, range(2, 15))), list(map(str, range(3, 50))),
                    ["-complete-"] + list(map(str, range(1, 9)))]
        currentIndex = [1, 6, 0]
        position = [[0, 1, 1, 1], [0, 3, 1, 1], [0, 5, 1, 1]]
        self.controlLayout(layout=layout, name="QComboBox", var={"itemText": itemText, "currentIndex": currentIndex},
                           position=position, signal=self.convexHullImageSignal)

        text = ['随机生成散点集']
        position = [[0, 6, 1, 1]]
        self.controlLayout(layout=layout, name="QPushButton", var={"text": text},
                           position=position, signal=self.convexHullImageRandom)

        # noinspection PyArgumentList
        widget = [[QWidget(), QWidget(), QWidget()]]
        matrixLayout = QGridLayout()
        matrix = [numpy.array([[6, 0, 9, 9, 6, 6, 2, 0, 6],
                               [5, 7, 2, 6, 7, 3, 7, 4, 4],
                               [8, 3, 2, 5, 0, 0, 7, 6, 5]])]
        self.controlLayout(layout=matrixLayout, name="QTableWidget", var={"headerLabels": [], "data": matrix},
                           position=[[0, 0, 1, 1]], signal=None)
        widget[0][0].setLayout(matrixLayout)

        patches_listLayout = QGridLayout()
        patches_list = [numpy.array([[4, 2, 6, 0, 1, 1, 0, 0, 2, 5, 5, 5],
                                     [6, 4, 0, 6, 6, 7, 2, 7, 5, 2, 1, 7],
                                     [3, 3, 3, 7, 4, 6, 3, 2, 4, 7, 4, 1]])]
        self.controlLayout(layout=patches_listLayout, name="QTableWidget",
                           var={"headerLabels": [], "data": patches_list}, position=[[0, 0, 1, 1]], signal=None)
        widget[0][1].setLayout(patches_listLayout)

        canvasLayout = QGridLayout()
        self.canvas["convexHullImage"] = ConvexHullCanvas(matrix[0], patches_list, parent=widget[0][2])
        canvasLayout.addWidget(self.canvas["convexHullImage"])
        widget[0][2].setLayout(canvasLayout)

        text = [["Points", "Patches", "Convex Hull 3D"]]
        position = [[1, 0, 1, 7]]
        self.controlLayout(layout=layout, name="QTabWidget", var={"text": text, "widget": widget},
                           position=position, signal=None)

        # noinspection PyArgumentList
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        ######################################################################
        # initialization self.controlData["convexHullImage"] then refresh it #
        # refresh self.control["convexHullImage"]                            #
        ######################################################################
        if "convexHullImage" not in self.controlData:
            self.addFrame("convexHullImage")
            self.convexHullImageSignal()
        else:
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.disconnect(self.convexHullImageSignal)
            self.imageRead(imageName="convexHullImage")
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.connect(self.convexHullImageSignal)

        self.statusBar().showMessage('Ready')

    def convexHullImageSignal(self):
        """
        respond of current page(convexHullImage), then write data into MainData.controlData
        """
        self.statusBar().showMessage('Setting patches...')

        ######################################################
        # initialization self.controlData["convexHullImage"] #
        ######################################################
        imageName = "convexHullImage"
        self.controlDataClear(imageName)

        ###############################################
        # refresh self.controlData["convexHullImage"] #
        ###############################################
        m = int(self.control["QComboBox"][0].itemText(self.control["QComboBox"][0].currentIndex()))
        n = int(self.control["QComboBox"][1].itemText(self.control["QComboBox"][1].currentIndex()))
        if m < n:
            pass
        else:
            self.statusBar().showMessage('The number of points should be more than dimension.')
            return

        list(map(str, range(4, 50)))
        for j in range(0, len(self.control["QComboBox"])):
            itemText = list()
            for k in range(0, self.control["QComboBox"][j].count()):
                itemText.append(self.control["QComboBox"][j].itemText(k))
            self.controlData[imageName]["QComboBox"]["itemText"].append(itemText)

            currentIndex = self.control["QComboBox"][j].currentIndex()
            self.controlData[imageName]["QComboBox"]["currentIndex"].append(currentIndex)

        row = int(self.control["QComboBox"][0].currentText())
        column = int(self.control["QComboBox"][1].currentText())
        currentRow = self.control["QTableWidget"][0].rowCount()
        currentColumn = self.control["QTableWidget"][0].columnCount()
        matrix = numpy.zeros((row, column), dtype=numpy.float64)
        for j in range(0, row):
            for k in range(0, column):
                if j < currentRow and k < currentColumn:
                    # noinspection PyBroadException
                    try:
                        matrix[j][k] = float(self.control["QTableWidget"][0].item(j, k).text())
                    except:
                        matrix[j][k] = 0
        self.controlData[imageName]["QTableWidget"]["data"].append(matrix)

        obj = ConvexHullMap(matrix=matrix)
        obj.complete()
        patches = numpy.array(obj.patches).T
        self.controlData[imageName]["QTableWidget"]["data"].append(patches)

        self.controlData[imageName]["save"] = [matrix, patches]

        ##############
        # draw image #
        ##############
        if int(self.control["QComboBox"][0].currentText()) == 3:
            self.canvas[imageName].canvasData["matrix"] = matrix
            if self.control["QComboBox"][2].currentIndex() == 0:
                self.canvas[imageName].canvasData["patches_list"] = [obj.patches]
            else:
                new_obj = ConvexHullMap(matrix=matrix)
                self.canvas[imageName].canvasData["patches_list"] = [copy.deepcopy(new_obj.patches)]
                for j in range(0, int(self.control["QComboBox"][2].currentText())):
                    new_obj.classify_points()
                    if new_obj.next_points.count(None) == len(new_obj.next_points):
                        break
                    new_obj.expand_patches()
                    self.canvas[imageName].canvasData["patches_list"].append(copy.deepcopy(new_obj.patches))
        self.canvas[imageName].fig.clf()
        self.canvas[imageName].axes = axes3d.Axes3D(self.canvas[imageName].fig)
        self.canvas[imageName].complete_draw()
        self.canvas[imageName].fig.canvas.draw()

        ###########################################
        # refresh self.control["convexHullImage"] #
        ###########################################
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.disconnect(self.convexHullImageSignal)
        self.imageRead(imageName=imageName)
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.connect(self.convexHullImageSignal)

        self.statusBar().showMessage('End of setting title.')

    def convexHullImageRandom(self):
        """
        Reset coordinates of scattered point set
        """
        ###############################################
        # refresh self.controlData["convexHullImage"] #
        ###############################################
        self.statusBar().showMessage('Start to resetting coordinates of scattered points...')

        #########################################
        # get parameters of scattered point set #
        #########################################
        imageName = "convexHullImage"

        n = int(self.control["QComboBox"][0].currentText())
        m = int(self.control["QComboBox"][1].currentText())
        matrix = numpy.random.random_integers(low=0, high=10, size=(n, m))
        self.controlData[imageName]["QTableWidget"]["data"][0] = matrix
        self.controlData[imageName]["save"][0] = matrix

        self.imageRead(imageName=imageName)

        self.convexHullImageSignal()

        self.statusBar().showMessage('End of resetting coordinates of scattered points.')

    def analyticFunctionImage(self):
        """
        layout and initialization data
        """
        ##########################
        # layout of current page #
        ##########################
        self.currentImage = "analyticFunctionImage"
        self.controlClear()

        layout = QGridLayout()
        layout.setSpacing(10)

        text = ['次方数', '拉伸系数', '辐角']
        position = [[0, 0, 1, 1], [0, 2, 1, 1], [0, 4, 1, 1]]
        self.controlLayout(layout=layout, name="QLabel", var={"text": text}, position=position, signal=None)

        itemText = [list(map(str, range(1, 20)))]
        position = [[0, 1, 1, 1]]
        self.controlLayout(layout=layout, name="QComboBox", var={"itemText": itemText, "currentIndex": []},
                           position=position, signal=self.analyticFunctionImageSignal)

        var = {"range": [[1, 100], [-180, 180]], "singleStep": [1, 1],
               "prefix": [], "suffix": ['*0.1', '*pi/180'], "value": [10, 0]}
        position = [[0, 3, 1, 1], [0, 5, 1, 1]]
        self.controlLayout(layout=layout, name="QSpinBox", var=var,
                           position=position, signal=self.analyticFunctionImageSignal)

        text = [['%f*e**(i*%f)*z**%d + z = 1' % (1, 1, 0)]]
        # noinspection PyArgumentList
        widget = [[QWidget()]]
        position = [[1, 0, 1, 6]]
        widgetLayout = QGridLayout()
        self.canvas["analyticFunctionImage"] = AnalyticFunctionCanvas(1, 1, 0, parent=widget[0][0])
        widgetLayout.addWidget(self.canvas["analyticFunctionImage"])
        widget[0][0].setLayout(widgetLayout)
        self.controlLayout(layout=layout, name="QTabWidget", var={"text": text, "widget": widget},
                           position=position, signal=None)

        # noinspection PyArgumentList
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        ############################################################################
        # initialization self.controlData["analyticFunctionImage"] then refresh it #
        # refresh self.control["analyticFunctionImage"]                            #
        ############################################################################
        if "analyticFunctionImage" not in self.controlData:
            self.addFrame("analyticFunctionImage")
            self.analyticFunctionImageSignal()
        else:
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.disconnect(self.analyticFunctionImageSignal)
            for j in range(0, len(self.control["QSpinBox"])):
                self.control["QSpinBox"][j].valueChanged.disconnect(self.analyticFunctionImageSignal)
            self.imageRead(imageName="analyticFunctionImage")
            for j in range(0, len(self.control["QComboBox"])):
                self.control["QComboBox"][j].currentIndexChanged.connect(self.analyticFunctionImageSignal)
            for j in range(0, len(self.control["QSpinBox"])):
                self.control["QSpinBox"][j].valueChanged.disconnect(self.analyticFunctionImageSignal)

        self.statusBar().showMessage('Ready')

    def analyticFunctionImageSignal(self):
        """
        respond of current page(analyticFunctionImage), then write data into MainData.controlData
        """
        self.statusBar().showMessage('Starting draw image...')

        ############################################################
        # initialization self.controlData["analyticFunctionImage"] #
        ############################################################
        imageName = "analyticFunctionImage"
        self.controlDataClear(imageName)

        #####################################################
        # refresh self.controlData["analyticFunctionImage"] #
        #####################################################
        for j in range(0, len(self.control["QComboBox"])):
            itemText = list()
            for k in range(0, self.control["QComboBox"][j].count()):
                itemText.append(self.control["QComboBox"][j].itemText(k))
            self.controlData[imageName]["QComboBox"]["itemText"].append(itemText)

            currentIndex = self.control["QComboBox"][j].currentIndex()
            self.controlData[imageName]["QComboBox"]["currentIndex"].append(currentIndex)

        for j in range(0, len(self.control["QSpinBox"])):
            value = self.control["QSpinBox"][j].value()
            self.controlData[imageName]["QSpinBox"]["value"].append(value)

        ##############
        # draw image #
        ##############
        self.canvas[imageName].n = self.controlData[imageName]["QComboBox"]["currentIndex"][0] + 1
        self.canvas[imageName].r = self.controlData[imageName]["QSpinBox"]["value"][0] * 0.1
        self.canvas[imageName].t = self.controlData[imageName]["QSpinBox"]["value"][1] * math.pi / 180
        text = '%f*e**(i*%f)*z**%d + z = 1' % (self.canvas[imageName].n, self.canvas[imageName].r,
                                               self.canvas[imageName].t)
        self.control["QTabWidget"][0].setTabText(0, text)
        self.canvas[imageName].fig.clf()
        self.canvas[imageName].axes = self.canvas[imageName].fig.add_subplot(111)
        self.canvas[imageName].axes.grid(True)
        self.canvas[imageName].complete_draw()
        self.canvas[imageName].fig.canvas.draw()

        ################################################
        # refresh self.control["analyticFunctionImage"] #
        ################################################
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.disconnect(self.analyticFunctionImageSignal)
        self.imageRead(imageName=imageName)
        for j in range(0, len(self.control["QComboBox"])):
            self.control["QComboBox"][j].currentIndexChanged.connect(self.analyticFunctionImageSignal)

        self.statusBar().showMessage('Image is drawn.')

    @staticmethod
    def sourceCodeImage():
        """
        @
        """
        pass

    def keyPressEvent(self, event):
        """
        :param event:
        :return:
        """
        if event.key() == Qt.Key_Escape:
            try:
                if self.currentImage == "rainImage":
                    self.orthogonalTableImage()
                elif self.currentImage == "orthogonalTableImage":
                    pass
                elif self.currentImage == "convexHullImage":
                    pass
                elif self.currentImage == "gravitationalSystemImage":
                    self.gravitationalSystemImage()
                elif self.currentImage == "analyticFunctionImage":
                    pass
            except KeyError:
                self.startImage()
            self.statusBar().showMessage('Esc is pressed!')

    def showOpenDialog(self):
        """
        @
        """
        # noinspection PyCallByClass,SpellCheckingInspection
        fname = QFileDialog.getOpenFileName(self, 'Open file', '/home')

        # if fname[0]:
        #     # noinspection PyArgumentEqualDefault
        #     f = open(fname[0], 'r')
        #     with f:
        #         data = f.read()
        #         self.textEdit.setText(data)

    def buttonClicked(self):
        """
        @
        """
        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')
        # noinspection PyCallByClass,PyTypeChecker
        QInputDialog.getText(self, 'Input Dialog', 'Enter your name:')

    def closeEvent(self, event):
        """
        @
        """
        # noinspection PyCallByClass,PyTypeChecker
        reply = QMessageBox.question(self, 'Message', "Are you sure to quit?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()


"""
Does the work to translate colour/effect names to ANSI codes
"""

# std libs
import numbers
import functools as ftl

# third-party libs
import numpy as np
from matplotlib.colors import to_rgb

# local libs
from recipes.dicts import ManyToOneMap

# relative libs
from .ansi import parse

# source: https://en.wikipedia.org/wiki/ANSI_escape_code
# http://ascii-table.com/ansi-escape-sequences.php

# Escape sequence
ESC = '\033'  # All sequences start with this character # equivalent to \x1b
CSI = ESC + '['  # Control Sequence Initiator
END = CSI + '0m'

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ANSI Codes for Text effects and colours  FG_CODES BG_CODE
FG_CODES = {
    'bold': 1,
    'dim': 2,  # faint
    'italic': 3,
    'underline': 4,
    'blink': 5,  # blink slow
    # 'blink' : 6,           # blink fast
    'invert': 7,
    'hidden': 8,  # conceal
    'strike': 9,
    # ------------------
    # 10	Primary(default) font
    # 11–19	{\displaystyle n} n-th alternate font	Select the {\displaystyle n}
    # n-th alternate font (14 being the fourth alternate font, up to 19 being
    # the 9th alternate font).
    # 20	Fraktur	hardly ever supported
    # 21	Bold: off or Underline: Double	Bold off not widely supported;
    # double underline hardly ever supported.
    # 22	Normal color or intensity	Neither bold nor faint
    # 23	Not italic, not Fraktur
    # 24	Underline: None	Not singly or doubly underlined
    # 25	Blink: off
    # 26	Reserved
    # 27	Image: Positive
    # 28	Reveal  conceal off
    # 29	Not crossed out
    # ------------------
    'black': 30,
    'red': 31,
    'green': 32,
    'yellow': 33,
    'blue': 34,
    'magenta': 35,
    'cyan': 36,
    'light gray': 37,
    # 38	Reserved for extended set foreground color typical supported next
    # arguments are 5;n where {\displaystyle n}￼ is color index (0..255) or
    # 2;r;g;b where {\displaystyle r,g,b}￼ are red, green and blue color
    # channels (out of 255)
    'default': 39,  # Default text color (foreground)
    # ------------------
    'frame': 51,
    'circle': 52,
    'overline': 53,
    # 54	Not framed or encircled
    # 55	Not overlined
    # ------------------
    'dark gray': 90,
    'gray': 90,
    'light red': 91,
    'light green': 92,
    'light yellow': 93,
    'light blue': 94,
    'light magenta': 95,
    'light cyan': 96,
    'white': 97,
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Background Colours
BG_CODES = {
    'black': 40,
    'red': 41,
    'green': 42,
    'yellow': 43,
    'blue': 44,
    'magenta': 45,
    'cyan': 46,
    'light gray': 47,
    # ------------------
    # 48	Reserved for extended set background color	typical supported next
    # arguments are 5;n where {\displaystyle n}￼ is color index (0..255) or
    # 2;r;g;b where {\displaystyle r,g,b}￼ are red, green and blue color
    # channels (out of 255)
    'default': 49,
    # 49	Default background color	implementation defined (according to
    # standard)
    # 50	Reserved
    # ------------------
    # 56–59	Reserved
    # 60	ideogram underline or right side line	hardly ever supported
    # 61	ideogram double underline or double line on the right side	hardly
    # ever supported
    # 62	ideogram overline or left side line	hardly ever supported
    # 63	ideogram double overline or double line on the left side
    # hardly ever supported
    # 64	ideogram stress marking	hardly ever supported
    # 65	ideogram attributes off	hardly ever supported, reset the effects of
    # all of 60–64
    # ------------------
    'dark gray': 100,
    'light red': 101,
    'light green': 102,
    'light yellow': 103,
    'light blue': 104,
    'light magenta': 105,
    'light cyan': 106,
    'white': 107,
}
# TODO: alternate colour names here: https://en.wikipedia.org/wiki/ANSI_escape_code

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convenient short colour descriptions a la matplotlib
mplShortsMap = {
    'b': 'blue',
    'g': 'green',
    'r': 'red',
    'c': 'cyan',
    'm': 'magenta',
    'y': 'yellow',
    'k': 'black',
    'w': 'white',
}

#
effectShortsMap = {'B': 'bold',
                   'I': 'italic',
                   'U': 'underline',
                   'S': 'strike',
                   'unbold': 'dim',
                   'strikethrough': 'strike',
                   'blink': 'blink_slow',
                   'hide': 'hidden',
                   'faint': 'dim'
                   }
# volcab is translated before keyword mappings in Many2One, so the uppercase
# here works

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# alias map for allowed keywords for functions
kwAliasMap = {
    # text
    'text': 'fg',
    'txt': 'fg',
    'colour': 'fg',
    'color': 'fg',
    'c': 'fg',
    'fg': 'fg',
    'foreground': 'fg',
    'rgb': 'fg',
    # background
    'highlight': 'bg',
    'background': 'bg',
    'bg': 'bg',
    'bc': 'bg',
    'bgc': 'bg'
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Movement = {} # TODO

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# def _aliasFactory(codes, aliases):
#     """Create the code translation dict"""
#     Codes = ManyToOneMap(codes)
#     Codes.add_vocab(aliases)
#     Codes.add_map(str.lower)
#     return Codes

class KeyMap(ManyToOneMap):
    """
    Resolve all the various ways in which colours or effects can be specified.
    """

    def __init__(self, dic=None, **kws):
        super().__init__(dic, **kws)
        self.add_vocab(kwAliasMap)  # translation

    def __missing__(self, key):
        try:
            return super().__missing__(key)
        except KeyError:
            pass
        raise KeyError(
            f'{key} is not a valid description for text or background effect.')

class CodeMap(ManyToOneMap):  # CodeMap
    """
    Resolve all the various names for colours or effects into codes
    """

    # def __init__(self, dic=None, **kws):
    #     super().__init__(dic, **kws)
    # add mappings for matplotlib color names eg: 'r' --> 'red' etc..
    # self.add_vocab(mplShortsMap)
    # add a layer that maps to lower case: 'REd' --> 'red'
    # self.add_mapping(str.lower)

    def __getitem__(self, key):
        # make sure we always return a str
        return str(super().__getitem__(key))

    def __missing__(self, key):
        try:
            return super().__missing__(key)
        except KeyError:
            raise KeyError(f'Unknown colour or effect {key!r}') from None

# additional shorthands for bold / italic text
fg_codes = CodeMap(FG_CODES)
fg_codes.add_vocab(effectShortsMap)

codes = KeyMap(fg=fg_codes,
               bg=CodeMap(BG_CODES))

FORMAT_8BIT = KeyMap(fg='38;5;{:d}',
                     bg='48;5;{:d}')
FORMAT_24BIT = KeyMap(fg='38;2;{:d};{:d};{:d}',
                      bg='48;2;{:d};{:d};{:d}')

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dispatch functions for translating user input to ANSI codes

@ftl.singledispatch
def resolve(obj, fg_or_bg='fg'):
    """default dispatch func for resolving ANSI codes from user input"""
    raise TypeError('Could not interpret %r (type %r) as a colour / effect '
                    'for %r' % (obj, type(obj), fg_or_bg))

# TODO: might want to give the functions below names for more readable traceback

@resolve.register(type(None))
def _(obj, fg_or_bg='fg'):
    return
    yield

@resolve.register(str)
def _(obj, fg_or_bg='fg'):
    if obj == '':
        return

    try:
        yield from resolve(to_24bit(obj), fg_or_bg)
    except ValueError:
        yield codes[fg_or_bg][obj]

@resolve.register(numbers.Integral)
def _(obj, fg_or_bg='fg'):
    # integers are interpreted as 8-bit colour codes
    if 0 <= obj < 256:
        yield FORMAT_8BIT[fg_or_bg].format(obj)
    else:
        raise ValueError('Could not interpret key %r as a 8 bit colour' % obj)

@resolve.register(np.ndarray)
@resolve.register(list)
@resolve.register(tuple)
def _(obj, fg_or_bg='fg'):
    # tuple, lists are interpreted as 24-bit rgb true colour codes
    if is_24bit(obj):
        if all(0 <= _ < 256 for _ in obj):
            yield FORMAT_24BIT[fg_or_bg].format(*obj)
        else:
            raise ValueError(
                f'Could not interpret key {obj!r} as a 24 bit colour'
            )
    else:
        for p in obj:
            yield from resolve(p, fg_or_bg)

@resolve.register(dict)
def _(obj, _=''):
    for key, val in obj.items():
        # `val` may have tuple of effects: eg: ((55, 55, 55), 'bold', 'italic')
        # but may also be a rgb tuple eg: (55, 55, 55)
        yield from resolve(val, key)

def is_24bit(obj):
    if len(obj) != 3:
        return False

    return all(isinstance(o, numbers.Integral) for o in obj)

def to_24bit(name):
    return tuple((np.multiply(to_rgb(name), 255).astype(int)))

def _gen_codes(*properties, **kws):
    """

    Parameters
    ----------
    properties
    kws

    Yields
    -------
    code: int
    """

    yield from resolve(properties)
    yield from resolve(kws)

def _get_params(*properties, **kws):
    # get the nrs '34;48;5;22' part of the code
    return ';'.join(_gen_codes(*properties, **kws))

def get(*properties, **kws):
    """
    Get the ANSI code for `properties` and `kws`

    Parameters
    ----------
    properties:
    kws:

    Returns
    -------

    """
    return ''.join((CSI, _get_params(*properties, **kws), 'm'))

def from_list(fg=None, bg=None):
    if fg is not None:
        return list(map(get, fg))

    if bg is not None:
        return [get(bg=_) for _ in bg]

def apply(s, *properties, **kws):
    """
    Apply the ANSI codes mapped to by `properties` and `kws` to the string `s`

    Parameters
    ----------
    s
    properties
    kws

    Returns
    -------

    """
    # first convert to str
    # string = str(s)

    # get code bits eg: '34;48;5;22'
    new_codes = _get_params(*properties, **kws)
    if not len(new_codes):
        return s

    # In order to get the correct representation of the string,
    # we strip and ANSI codes that are in place and stack the new codes
    # This means previous colours are replaced, but effects like 'bold' or
    # 'italic' will stack for recursive invocations.  This also means we get
    # the shortest representation of the string given the parameters which is
    # nice and efficient. If we were to apply blindly our string would be
    # longer than needed by a few (non-display) characters. This might seem
    # innocuous but becomes deadly once you start doing more complicated
    # effects on longer strings
    # note: final byte 'm' only valid for SGR (Select Graphic Rendition) and
    #  not other codes, but this is all we support for now

    return ''.join(''.join((CSI, params, ';', new_codes, 'm', w, END))
                   for _, params, _, w, _ in parse(s)
                   )  # .replace('\n', f'{END}\n{CSI}{new_codes}m'
    #
    # Finally, terminate and restart all codes at a newline boundary so that
    # we can more easily stack text blocks horizontally

def apply_naive(s, *properties, **kws):
    """
    Initial naive implementation of `apply` that blindly wraps the string with
    the ANSI codes.  Use `apply` instead of this function.
    """

    # get code string eg: '34;48;5;22'
    code = get(*properties, **kws)
    if not len(code):
        return s

    return ''.join((code, s, END))


# Author:      Lane Moseley
# Description: This file demonstrates the usage of the custom logistic regression
#              module implemented in the ML library.
# Resources Used:
#    Fish Dataset:
#         Included with the project, but also available from Kaggle:
#         https://www.kaggle.com/aungpyaeap/fish-market
#    Iris Dataset:
#         https://archive.ics.uci.edu/ml/datasets/iris
#         https://gist.github.com/curran/a08a1080b88344b0c8a7
#    Logistic Regression Using scikit-learn:
#         https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html

import matplotlib.pyplot as plt
from ML import LogisticRegression, plot_decision_regions
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression as skLogisticRegression
from sklearn.metrics import classification_report

def main():
    # IRIS DATASET #############################################################
    df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data', header=None)

    # Extract the first 100 labels
    y = df.iloc[0:100, 4].values

    # Convert the labels to either 1 or 0
    y = np.where(y == 'Iris-setosa', 0, 1)

    # Extract features from dataset [sepal_length, petal_length]
    X = df.iloc[0:100, [0, 2]].values

    # plot variables
    title = 'Iris Dataset'
    xlabel = 'Sepal Length [cm]'
    ylabel = 'Petal Length [cm]'

    # Plot what we have so far
    # Plot labels
    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    # Plot the setosa data
    plt.scatter(X[:50, 0], X[:50, 1], color='red', marker='o', label='setosa')
    # Plot the versicolor data
    plt.scatter(X[50:100, 0], X[50:100, 1], color='blue', marker='x', label='versicolor')
    # Setup the plot legend
    plt.legend(loc='upper left')

    # Display the plot
    plt.show()

    # scikit-learn logistic regression
    skLR = skLogisticRegression()
    skLR.fit(X, y)

    # plot the decision regions and display metrics to the console
    plot_decision_regions(X, y, skLR, resolution=0.1, x_label=xlabel, y_label=ylabel,
                          title=title + "\nscikit-learn Logistic Regression Model")
    print(title + "\nscikit-learn Logistic Regression Model")
    print(classification_report(y, skLR.predict(X)))

    # ML.py logistic regression
    mlLR = LogisticRegression(learning_rate=0.05, iterations=25)
    mlLR.fit(X, y)

    # plot the decision regions and display metrics to the console
    plot_decision_regions(X, y, mlLR, resolution=0.1, x_label=xlabel, y_label=ylabel,
                          title=title + "\nML.py Logistic Regression Model")
    print(title + "\nML.py Logistic Regression Model")
    print(classification_report(y, mlLR.predict(X)))

    # FISH DATASET #############################################################
    df = pd.read_csv('./Fish.csv')
    df = df.drop(df.index[0:61])        # Parkki rows
    df = df.drop(df.index[11:84])       # Smelt rows

    # Extract the data for Parkki and Smelt fish
    y = df.iloc[:, 0].values

    # Convert the labels to either 0 or 1
    y = np.where(y == 'Parkki', 0, 1)

    # Extract features from dataset [weight, length]
    X = df.iloc[:, [2, 1]].values

    # plot variables
    title = 'Fish Dataset'
    xlabel = 'Length [cm]'
    ylabel = 'Weight [g]'

    # Plot what we have so far
    # Plot labels
    plt.title(title)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    # Plot the Parkki data
    plt.scatter(X[:11, 0], X[:11, 1], color='red', marker='o', label='Parkki')
    # Plot the Smelt data
    plt.scatter(X[11:, 0], X[11:, 1], color='blue', marker='x', label='Smelt')
    # Setup the plot legend
    plt.legend(loc='upper left')

    # Display the plot
    plt.show()

    # scikit-learn logistic regression
    skLR = skLogisticRegression()
    skLR.fit(X, y)

    # plot the decision regions and display metrics to the console
    plot_decision_regions(X, y, skLR, resolution=0.1, x_label=xlabel, y_label=ylabel,
                          title=title + "\nscikit-learn Logistic Regression Model")
    print(title + "\nscikit-learn Logistic Regression Model")
    print(classification_report(y, skLR.predict(X)))

    # ML.py logistic regression
    mlLR = LogisticRegression(learning_rate=0.01, iterations=10)
    mlLR.fit(X, y)

    # plot the decision regions and display metrics to the console
    plot_decision_regions(X, y, mlLR, resolution=0.1, x_label=xlabel, y_label=ylabel,
                          title=title + "\nML.py Logistic Regression Model")
    print(title + "\nML.py Logistic Regression Model")
    print(classification_report(y, mlLR.predict(X)))

if __name__ == "__main__":
    main()


from . import views
from django.conf.urls import url, include

urlpatterns = [
    url(r'^new$', views.add_new_user),
    url(r'^new/process$', views.add_new_user_p),
    url(r'^edit$', views.edit_user),
    url(r'^edit/process', views.edit_user_p),
    url(r'^edit/(?P<u_id>\d+)$', views.edit_user_by_id),
    url(r'^edit/(?P<u_id>\d+)/process$', views.edit_user_by_id_p),
    url(r'^show/(?P<u_id>\d+)$', views.show_user),
    url(r'^show/(?P<u_id>\d+)/post/', include('apps.msg_app.urls')),
]


from dataclasses import dataclass
import pytest
from pydantic import ValidationError, Json, BaseModel
from typing import Optional
from qwery import Model, Query, JSONB

class ExampleModel(Model):
    class Meta:
        table_name = "test"

    a: int
    b: Optional[str]
    c: bool

def test_compile_select_query():
    base = Query(ExampleModel).select().where("a = {.a}")
    assert callable(base.fetch_one())

    assert base.fetch_one().sql == "SELECT a, b, c FROM test WHERE a = $1"
    assert (
        base.limit(1).fetch_one().sql == "SELECT a, b, c FROM test WHERE a = $1 LIMIT 1"
    )

    test_model_query_complex = (
        Query(ExampleModel).select(raw="COUNT(*)").where("a = {.a}").fetch_one()
    )
    assert test_model_query_complex.sql == "SELECT COUNT(*) FROM test WHERE a = $1"
    assert callable(test_model_query_complex)

def test_compile_insert_query():
    test_model_query = Query(ExampleModel).insert().execute()
    assert test_model_query.sql == "INSERT INTO test (a, b, c) VALUES ($1, $2, $3)"
    assert callable(test_model_query)

    test_model_query_complex = (
        Query(ExampleModel).insert().on_conflict("a").returning().fetch_one()
    )
    assert (
        test_model_query_complex.sql
        == "INSERT INTO test (a, b, c) VALUES ($1, $2, $3) ON CONFLICT (a) DO NOTHING RETURNING *"
    )
    assert callable(test_model_query_complex)

def test_compile_delete_query():
    test_model_query = Query(ExampleModel).delete().where("a = {.a}").execute()
    assert test_model_query.sql == "DELETE FROM test WHERE a = $1"
    assert callable(test_model_query)

def test_compile_update_query():
    test_model_query = (
        Query(ExampleModel).update("b", "a", "c").where("a = {.a}").execute()
    )
    assert test_model_query.sql == "UPDATE test SET b = $1, a = $2, c = $3 WHERE a = $2"
    assert callable(test_model_query)

def test_compile_dynamic_update_query():
    test_model_query = Query(ExampleModel).dynamic_update().where("a = {.a}").execute()
    assert test_model_query.sql == "UPDATE test SET {dynamic} WHERE a = $1"
    assert callable(test_model_query)

@pytest.mark.asyncio
async def test_validate_select_query():
    test_model_query = Query(ExampleModel).select().where("a = {.a}").fetch_one()
    with pytest.raises(ValidationError):
        await test_model_query(None, a="fuck")

@pytest.mark.asyncio
async def test_validate_insert_query():
    test_model_query = Query(ExampleModel).insert(ignore={"b"}).execute()
    with pytest.raises(ValidationError) as excinfo:
        await test_model_query(None, a=1, c="yeet")

    assert excinfo.value.errors() == [
        {
            "loc": ("c",),
            "msg": "value could not be parsed to a boolean",
            "type": "type_error.bool",
        }
    ]

    test_model_query = Query(ExampleModel).insert(body=True).execute()
    with pytest.raises(ValidationError) as excinfo:
        await test_model_query(None, a=1, c=1)

    assert excinfo.value.errors() == [
        {
            "loc": ("example_model",),
            "msg": "field required",
            "type": "value_error.missing",
        }
    ]

class ExampleEmbeddedData(BaseModel):
    a: int
    b: str
    c: bool

class ExampleJSONModel(Model):
    class Meta:
        table_name = "test"

    data: Optional[JSONB[ExampleEmbeddedData]]

@pytest.mark.asyncio
async def test_embedded_json():
    example = ExampleEmbeddedData(a=1, b="test", c=True)

    insert = Query(ExampleJSONModel).insert().execute()
    _, args = insert._query.build(data=example)
    assert args[0] == example.json()

    update = Query(ExampleJSONModel).dynamic_update().execute()
    _, args = update._query.build(data=example)
    assert args[0] == example.json()

    assert ExampleJSONModel(data=example).dict() == {
        "data": {"a": 1, "b": "test", "c": True}
    }


#!/usr/bin/env python
# coding: utf-8

# In[1]:

# In[2]:

import cv2
import numpy as np
import easyocr
import matplotlib.pyplot as plt

# In[3]:

im_1_path = '/Users/abrahamkom/Groupe 3IL ðŸ“–/Test/folder/permis4.jpg'
im_2_path = '/Users/abrahamkom/Groupe 3IL ðŸ“–/Test/folder/PERMIS.jpg'
print(im_1_path)

# <h1 class="alert alert-success">Fonction de reconnaissance </h>

# In[4]:

def recognize_text(img_path):
    '''loads an image and recognizes text.'''

    reader = easyocr.Reader(['fr'], gpu = True)
    return reader.readtext(img_path,detail=0)

# In[ ]:

# In[5]:

result = recognize_text(im_1_path)

# In[6]:

print(result)

# In[46]:

import json

# In[53]:

# In[54]:

#print(data)

# In[60]:

personne = {"Contry" : result[1],
            "firstName" : result[3],
            "laastName" : result[4],
            "birthDate" : result[5],
            "birthPlace" : result[5],
            "issueDate" : result[6],
            "expiryDate" : result[8],
            "deliveredAutority" : "",
            "licenceNumlber" : result[9],
            "licenceCategory" : result[10],
            "mrzBand" : ""
           }

#personne

# In[ ]:


import os
import combat.combat_core as com
import factory.factory_core as fty
import config.config_core as cfg
import expedition.expedition_core as exp
import fleet_switcher.fleet_switcher_core as fsw
import fleet.fleet_core as flt
import nav.nav as nav
import pvp.pvp_core as pvp
import quest.quest_core as qst
import repair.repair_core as rep
import resupply.resupply_core as res
import scheduler.scheduler_core as sch
import ship_switcher.ship_switcher_core as ssw
import stats.stats_core as sts
import util.kca as kca_u
from kca_enums.expeditions import ExpeditionEnum
from util.logger import Log
from kca_enums.maps import MapEnum

from constants import COMBAT_CONFIG

class Kcauto(object):
    """Primary kcauto class.
    """
    end_loop_at_port = False
    is_first_print_fleet = True

    def __init__(self):
        kca_u.kca.hook_chrome()

    def start_kancolle(self):
        kca_u.kca.start_kancolle()

    def find_kancolle(self):
        kca_u.kca.find_kancolle()

    def find_browser(self):
        kca_u.kca.find_browser()

    def hook_health_check(self):
        kca_u.kca.hook_health_check()

    def check_config(self):
        if cfg.config.config_changed:
            Log.log_msg("Config change detected. Loading updated config.")
            if cfg.config.initialize_config():
                com.combat.update_from_config()
                exp.expedition.update_from_config()
                pvp.pvp.update_from_config()
                qst.quest.update_from_config()
                sch.scheduler.update_from_config()

    def initialization_check(self):
        if sts.stats.rsc.ammo is None:
            Log.log_msg("kcauto is initializing.")
            if not exp.expedition.receive_expedition():
                nav.navigate.to('refresh_home')
                sts.stats.set_print_loop_end_stats()

    def check_for_expedition(self):
        if not exp.expedition.receive_expedition():
            if exp.expedition.expect_returned_fleets():
                nav.navigate.to('refresh_home')
                sts.stats.set_print_loop_end_stats()

    def run_print_fleet_logic(self):

        if not com.combat.enabled and self.is_first_print_fleet:
            self.is_first_print_fleet = False
            nav.navigate.to('refresh_home')
            flt.fleets.fleets[1].get_fleet_id_and_name()
        else:
            return False

    def run_expedition_logic(self):
        if not exp.expedition.enabled:
            return False

        if not exp.expedition.timer.is_time_up():
            return False

        if exp.expedition.expect_returned_fleets() or \
          (set([ExpeditionEnum.E5_33, ExpeditionEnum.E5_34,
                ExpeditionEnum.EE_S1, ExpeditionEnum.EE_S2]) & set(
                    cfg.config.expedition.all_expeditions) and com.combat.time_to_sortie == True):
            self.find_kancolle()
            nav.navigate.to('refresh_home')

        if exp.expedition.fleets_are_ready:

            if exp.expedition.exp_for_fleet == []:

                exp.expedition.get_expedition_ranking()

                if cfg.config.expedition.fleet_preset == "auto":
                    if not fsw.fleet_switcher.assign_exp_ship():
                        exp.expedition.enabled = False
                        Log.log_error(f"Failed to assign ships for self balance expedition, disable expedition module.")
                        return False

            if exp.expedition.is_fleetswitch_needed():
                if self._run_fleetswitch_logic('expedition') == -2:
                    exp.expedition.timer.set(15*60)
                    Log.log_warn(f"Failed to switch ships for self balance expedition, disable expedition module for 15 mins.")
                    return False

            exp.expedition.goto()
            exp.expedition.send_expeditions()
            self.run_quest_logic('expedition')
            sts.stats.set_print_loop_end_stats()

    def run_factory_logic(self):

        if not fty.factory.enabled or not fty.factory.disable_time_up():
            return False

        self.run_quest_logic('factory', fast_check=False)
        nav.navigate.to('home')

        anything_is_done = False

        if "F5" in qst.quest.next_check_intervals.keys():
            anything_is_done = True

            self._run_fleetswitch_logic('factory_develop')

            fty.factory.goto()
            if fty.factory.develop_logic(1) == True:
                self.run_quest_logic('factory', fast_check=True, back_to_home=True, force=True)
                nav.navigate.to('home')

        if "F6" in qst.quest.next_check_intervals.keys():
            anything_is_done = True

            self._run_fleetswitch_logic('factory_build')

            fty.factory.goto()
            if fty.factory.build_logic(1) == True:
                self.run_quest_logic('factory', fast_check=True, back_to_home=True, force=True)
                nav.navigate.to('home')
            else:
                # disable module for 15 mins
                fty.factory.set_timer()

        if "F7" in qst.quest.next_check_intervals.keys():
            anything_is_done = True

            self._run_fleetswitch_logic('factory_develop')

            fty.factory.goto()
            if fty.factory.develop_logic(3) == True:
                self.run_quest_logic('factory', fast_check=True, back_to_home=True, force=True)
                nav.navigate.to('home')

        if "F8" in qst.quest.next_check_intervals.keys():
            anything_is_done = True

            self._run_fleetswitch_logic('factory_build')

            fty.factory.goto()
            """If F8 is already 80% done, one more build could finish the quest"""
            """Therefore, no if == True here"""
            fty.factory.build_logic(3)
            self.run_quest_logic('factory', fast_check=True, back_to_home=True, force=True)
            nav.navigate.to('home')
            #always disable module for 15 mins
            fty.factory.set_timer()

        if anything_is_done == False:
            """Daily factory process done, disable from now"""
            fty.factory.enabled = False

    def run_pvp_logic(self):
        if not pvp.pvp.enabled:
            return False

        if pvp.pvp.time_to_pvp():
            self.find_kancolle()
            self.run_quest_logic('pvp')
            nav.navigate.to('home')
            self._run_fleetswitch_logic('pvp')
            self.run_resupply_logic(back_to_home=True)
            sts.stats.set_print_loop_end_stats()
        else:
            return False

        pvp.pvp.goto()
        while pvp.pvp.pvp_available():
            pvp.pvp.conduct_pvp()
            self.run_resupply_logic(back_to_home=True)
            self.run_quest_logic('pvp', fast_check=True, back_to_home=True)
            if pvp.pvp.pvp_available():
                pvp.pvp.goto()
        sts.stats.set_print_loop_end_stats()
        return True

    def run_combat_logic(self):
        quest_selected = False
        if not com.combat.enabled or com.combat.time_to_sortie == False:
            return False
        else :
            #update port api, for _run_fleetswitch_logic
            nav.navigate.to('refresh_home')

        was_sortie_queue_empty = False
        #set sortie_queue if it is empty
        if len(com.combat.get_sortie_queue()) == 0:
            was_sortie_queue_empty = True
            Log.log_debug(f"cfg.config.combat.sortie_map_read_only:{cfg.config.combat.sortie_map_read_only}")
            if cfg.config.combat.sortie_map_read_only == MapEnum.auto_map_selete:
                self.run_quest_logic('auto_sortie', fast_check=False, back_to_home=False, force= True) #quest module will call set_sortie_queue
            else:
                Log.log_debug(f"Manual sortie mode:{cfg.config.combat.sortie_map_read_only.value}")

                sortie_queue = [cfg.config.combat.sortie_map_read_only.value]
                com.combat.set_sortie_queue(sortie_queue)
        else:
            Log.log_msg(f"Sortie queue:{com.combat.get_sortie_queue()}")

        if len(com.combat.get_sortie_queue()) == 0: #If no combat map available, turn off combat module
            Log.log_debug(f"Stop combat module cause no combat quest available")
            com.combat.enabled = False
            return False
        else:
            #update current sortie_map
            cfg.config.combat.sortie_map = com.combat.get_sortie_queue()[0]

            """Check if multi stage map requested"""
            MULTI_STAGE_MAPS = {"7-2":["G", "M"], "7-3":["E", "M"], "7-5":["K", "M", "Q", "T"]}
            if cfg.config.combat.sortie_map.value in MULTI_STAGE_MAPS:
                nav.navigate.to('combat')
                Log.log_error(f"com.combat.sortie_map_stage: {com.combat.sortie_map_stage}")
                stage = MULTI_STAGE_MAPS[cfg.config.combat.sortie_map.value][com.combat.sortie_map_stage - 1]
                Log.log_error(f"stage: {stage}")

                cfg.config.combat.sortie_map = cfg.config.combat.sortie_map.value + "-" + stage

        #update map_data for combat module
        com.combat.load_map_data(cfg.config.combat.sortie_map)

        if cfg.config.combat.override == False:
            #load user config
            config_json = cfg.config.load_json(cfg.config.cfg_path)
            cfg.config.combat.config_override(config_json)

            #load default config
            default_json = cfg.config.load_json(COMBAT_CONFIG + "default.json")
            cfg.config.combat.config_override(default_json)

            #load default config
            sortie_queue = com.combat.get_sortie_queue()

            if os.path.isfile(COMBAT_CONFIG + sortie_queue[0] + ".json"):
                default_json = cfg.config.load_json(COMBAT_CONFIG + sortie_queue[0] + ".json")
                cfg.config.combat.config_override(default_json)
            else:
                Log.log_warn(f"{sortie_queue[0]} combat config not found, use default combat config instead.")

        #apply for combat queue, assume map_data is up-to-date
        self.run_quest_logic('combat', fast_check = not was_sortie_queue_empty, force= was_sortie_queue_empty)

        if self._run_fleetswitch_logic('combat') == 0:
            #update port api, for should_and_able_to_sortie
            nav.navigate.to('refresh_home')

        if com.combat.should_and_able_to_sortie(ignore_supply=True):

            self.run_resupply_logic()

            com.combat.goto()

            if com.combat.conduct_sortie():

                #sortie success, pop the head of sortie_queue
                com.combat.pop_sortie_queue()

                sts.stats.set_print_loop_end_stats()
                exp.expedition.receive_expedition()
            else:
                Log.log_error(f"Sortie failed.")

    def run_resupply_logic(self, back_to_home=False):
        if res.resupply.need_to_resupply:
            self.find_kancolle()
            res.resupply.goto()
            res.resupply.resupply_fleets()
            self.handle_back_to_home(back_to_home)
            if not back_to_home:
                self.end_loop_at_port = True
            sts.stats.set_print_loop_end_stats()

    def run_repair_logic(self, back_to_home=False):
        if rep.repair.can_conduct_repairs:
            self.find_kancolle()
            rep.repair.goto()
            rep.repair.repair_ships()
            self.handle_back_to_home(back_to_home)
            if not back_to_home:
                self.end_loop_at_port = True
            sts.stats.set_print_loop_end_stats()

    def _run_fleetswitch_logic(self, context):

        switch_needed = False

        while fsw.fleet_switcher.require_fleetswitch(context):
            switch_needed = True
            fsw.fleet_switcher.goto()
            if not fsw.fleet_switcher.switch_fleet(context):
                self.handle_back_to_home(True)
                return -2
            self.handle_back_to_home(True)

        if switch_needed:
            return 0
        else:
            return -1

    def run_shipswitch_logic(self, back_to_home=False):

        switch_list = ssw.ship_switcher.get_ship_switch_list()

        if switch_list:
            nav.navigate.to('home')
            ssw.ship_switcher.goto()
            ssw.ship_switcher.switch_ships(switch_list)
            self.handle_back_to_home(back_to_home)
            if not back_to_home:
                self.end_loop_at_port = True

    def run_quest_logic(
            self, context=None, fast_check=False, back_to_home=False, force=False):
        if not qst.quest.enabled:
            return False

        if qst.quest.need_to_check(context) or force == True:
            self.find_kancolle()
            qst.quest.goto()
            qst.quest.manage_quests(context, fast_check)
            sts.stats.quest.times_checked += 1
            self.handle_back_to_home(back_to_home)
            sts.stats.set_print_loop_end_stats()

    def handle_back_to_home(self, back_to_home):
        if back_to_home:
            nav.navigate.to('home')

    def run_scheduler(self):
        sch.scheduler.check_and_process_rules()

    def check_end_loop_at_port(self):
        if self.end_loop_at_port:
            self.end_loop_at_port = False
            self.handle_back_to_home(True)

    @property
    def scheduler_kca_active(self):
        return sch.scheduler.kca_active

    def print_stats(self):
        if sts.stats.print_loop_end_stats:
            sts.stats.loop_count += 1
            sts.stats.print_stats()

kcauto = Kcauto()


#!/usr/bin/env python3

"""
zbdump.py
"""

import logging
import subprocess
import sys
import time
import asyncio

from typing import Any, Optional, Union
from scapy.all import Dot15d4FCS # type: ignore
import scapy.all as sp
import datetime as dt

_LOGGER = logging.getLogger(__name__)

logging.basicConfig(level=logging.DEBUG)

def install(package):
    subprocess.check_call([sys.executable, "-m", "pip", "install", package])

try:
    from killerbee import KillerBee
except ImportError:
    try:
        from ..killerbee import KillerBee
    except ImportError:
        install("git+https://github.com/antonio-boiano/killerbee.git#egg=killerbee")
        from killerbee import KillerBee

try:
    from killerbee.scapy_extensions import *
except ImportError:
    try:
        from ..killerbee.scapy_extensions import *
    except ImportError:
        install("git+https://github.com/antonio-boiano/killerbee.git#egg=killerbee")
        from killerbee.scapy_extensions import *

try:
    from .zbsocket import *
    from .zbconst import DEFAULT_ZIGBEE_CHANNEL,DEFAULT_QUEUE_BUFFER_SIZE
    from .zb_dissector import ZbDissector,ZbFiltering,header_154
except ImportError:
    from zbsocket import *
    from zbconst import DEFAULT_ZIGBEE_CHANNEL,DEFAULT_QUEUE_BUFFER_SIZE
    from zb_dissector import ZbDissector,ZbFiltering,header_154

class AsyncDump:
    KillerBee = KillerBee

    def __init__(self,channel=11,subghz_page=0,dev_path:Optional[str]=None,hardware:Optional[str]=None,kb:Optional[KillerBee]=None) -> None:
        self.kb: Optional[KillerBee] = kb
        self.channel = 11 if channel is None else channel
        self.subghz_page=subghz_page
        self._queue_list = []
        self._filter_dict = {}
        self.dev_path=dev_path
        self.hardware=hardware
        self.zbdissect = ZbDissector()
        self.zbfilter = ZbFiltering()

        if(self.kb is None):
            try:
                self.kb = KillerBee(device=self.dev_path,hardware=self.hardware)
            except Exception as e:
                _LOGGER.debug("KillerBee cannot find device"+str(e))
                raise

    def subscribe (self,queue:asyncio.Queue=None,filter:str=None)->asyncio.Queue: # type: ignore
        """
        Subscribe to the queue to receive packets from the sniffer.
        Args:
            queue (asyncio.Queue, optional): Queue to receive packets. Defaults to None, in this case a new queue is created.
        Returns:
            asyncio.Queue:  Queue to receive packets.
        """
        if not queue:
            queue = asyncio.Queue(DEFAULT_QUEUE_BUFFER_SIZE)
        self._queue_list.append(queue)
        self._filter_dict[queue]=filter
        return queue
    #To Do add filtering capabilities from class zb_filter. In case of filtering return in the queue also the disssected pcket
    #def set_filter(self,queue:asyncio.Queue,filter:str):

    def unsubscribe (self,queue:asyncio.Queue):
        """
        Unsubscribe from the queue to stop receiving packets from the sniffer.

        Args:
            queue (asyncio.Queue): Queue to unsubscribe from.
        """

        try:
            self._queue_list.remove(queue)
        except ValueError:
            pass

        self._filter_dict.pop(queue,None)

    async def start_dump(self,channel:Optional[int]=None,subghz_page:Optional[int]=None,queue=None,async_handler=None):

        if subghz_page is not None:
            self.subghz_page=subghz_page

        if channel is not None:
            self.channel=channel
        if queue is not None:
            self.subscribe(queue)

        if self.kb is not None:
            if not self.kb.is_valid_channel(self.channel, self.subghz_page):
                _LOGGER.debug("Channel and sub_ghz not valid. Using Default channel %d and sub_ghz 0 instead" % DEFAULT_ZIGBEE_CHANNEL)
                self.channel=DEFAULT_ZIGBEE_CHANNEL
                self.subghz_page=0

            self.kb.sniffer_on(channel=self.channel,page=self.subghz_page)

            if async_handler is None: async_handler = asyncio.create_task
            self.dump_task=async_handler(self.dump_packets())

    async def dump_packets(self):

        async def handle_queue (packet,queue):
            if self._filter_dict[queue] is not None:
                if not self.zbfilter.verify(self._filter_dict[queue],header=packet["header"]):
                    return None
                else:
                    try:
                        queue.put_nowait(packet)
                    except asyncio.QueueFull:
                        self.unsubscribe(queue)
            else:
                try:
                    queue.put_nowait(packet)
                except asyncio.QueueFull:
                    self.unsubscribe(queue)

        async def handle_dissecting(packet:dict[Union[int, str], Any]):
            if packet['bytes'] is None: return None
            packet_header = self.zbdissect.packet_dissecting(packet['bytes'])
            packet['header']=packet_header
            await asyncio.gather(*[handle_queue(packet,queue) for queue in self._queue_list])

        if self.kb :
            while True:
                await asyncio.sleep(0)
                packet: Optional[dict[Union[int, str], Any]] = self.kb.pnext()
                if packet is None or not self._queue_list:
                    continue
                else:
                    await asyncio.create_task(handle_dissecting(packet))

    async def read_pcap(self,file_path,queue:asyncio.Queue=None): # type: ignore
        if not os.path.exists(file_path): return None, None
        if queue is None: queue =  asyncio.Queue(DEFAULT_QUEUE_BUFFER_SIZE)

        async def actual_pcap_read(self,file_path,queue:asyncio.Queue):
            pcap = sp.PcapReader(file_path)
            for p in pcap:
                packet = {"bytes":None,"datetime":None,"dbm":None}
                packet["bytes"]= sp.raw(p) # type: ignore
                packet["datetime"]=dt.datetime(1970, 1, 1) + dt.timedelta(seconds=float(p.time)) # type: ignore
                await queue.put(packet)

        return queue , asyncio.create_task(actual_pcap_read(self,file_path,queue))

    async def get_dev_info(self):
        if self.kb:
            return self.kb.get_dev_info()

    def get_frequency(self):
        if self.kb is not None:
            freq = self.kb.frequency(self.channel, self.subghz_page) / 1000.0
            return freq
        else:
            return None

    def shutdown(self):
        for k in  self._queue_list: self.unsubscribe(k)
        if self.kb is not None:
            self.kb.sniffer_off()
            self.kb.close()
        if self.dump_task:
            if not self.dump_task.cancelled(): self.dump_task.cancel()

class SockZbDump:
    def __init__(
        self,
        channel,
        pcapfile = None,
        dev_path = None,
        dev_name = None,
        ppi=0,
        subghz_page=0,
        pan_id_hex=None,
        count=-1,
        timeout=-1
    ) -> None:

        self.packetcount: int = 0
        self.kb: Optional[KillerBee] = None
        self.pcap_dumper: Optional[PcapDumper] = None
        self.usok: Optional[Usocket] = None
        self.unbuffered: Optional[Any] = None

        self.channel = channel
        self.pcapfile = pcapfile
        self.devstring = dev_path
        self.device = dev_name
        self.ppi = ppi
        self.subghz_page = subghz_page
        self.pan_id_hex = pan_id_hex
        self.count = count
        self.timeout=timeout

    def close(self) -> None:
        if self.kb is not None:
            self.kb.sniffer_off()
            self.kb.close()

        if self.pcap_dumper is not None:
            self.pcap_dumper.close()

    def dump_packets(self):
        pan = None
        if self.pan_id_hex:
            panid: Optional[int] = int(self.pan_id_hex, 16)
        else:
            panid = None
        if self.kb is not None:
            rf_freq_mhz = self.kb.frequency(self.channel, self.subghz_page) / 1000.0
        else:
            rf_freq_mhz = 0.0

        _LOGGER.debug(
            "zbdump: listening on '{}', channel {}, page {} ({} MHz), link-type DLT_IEEE802_15_4, capture size 127 bytes".format(
                self.devstring, self.channel, self.subghz_page, rf_freq_mhz
            )
        )

        timeout_start = time.time()

        while (time.time() < timeout_start + self.timeout) or (self.count != self.packetcount):
            if self.kb is not None:
                packet: Optional[dict[Union[int, str], Any]] = self.kb.pnext()
            else:
                packet = None

            if packet is None:
                continue

            if panid is not None:
                pan, layer = kbgetpanid(Dot15d4FCS(packet["bytes"]))

            if panid is None or panid == pan:
                self.packetcount += 1
                if self.pcap_dumper is not None:
                    self.pcap_dumper.pcap_dump(
                        packet["bytes"], ant_dbm=packet["dbm"], freq_mhz=rf_freq_mhz
                    )
            if self.usok is not None:
                self.usok.send_data_dstream(pickle.dumps(packet))

    def capture(self):

        if self.pcapfile is not None and self.pcapfile is not None:
            self.pcap_dumper = PcapDumper(DLT_IEEE802_15_4, self.pcapfile, ppi=self.ppi) # type: ignore

        if self.devstring is None:
            _LOGGER.debug(
                "Autodetection features will be deprecated - please include interface string (e.g. -i /dev/ttyUSB0)"
            )
        if self.device is None:
            _LOGGER.debug(
                "Autodetection features will be deprecated - please include device string (e.g. -d apimote)"
            )

        self.kb = KillerBee(device=self.devstring, hardware=self.device)

        if not self.kb.is_valid_channel(self.channel, self.subghz_page):
            _LOGGER.error(
                "ERROR: Must specify a valid IEEE 802.15.4 channel for the selected device."
            )
            self.kb.close()

        self.kb.set_channel(self.channel, self.subghz_page)
        self.kb.sniffer_on()

        self.usok = Usocket()
        try:
            self.usok.ustream_start()
        except:
            self.usok.close()
            raise

        self.dump_packets()

        self.kb.sniffer_off()
        self.kb.close()
        self.usok.close()
        if self.pcap_dumper is not None:
            self.pcap_dumper.close()

        _LOGGER.debug(f"{self.packetcount} packets captured")