from configparser import ConfigParser
from functools import partial
import json
import logging
from math import pi
import os

from numpy import arctan

from lib.config import conf_dir
from lib.plot import PlotWidget
from lib.psycrometry import PsyState, PsychroState, _Pbar, _height
from lib.unidades import (Temperature, Pressure, Length, Mass,
                          SpecificVolume, Enthalpy)
from lib.utilities import formatLine
from tools.qt import QtCore, QtGui, QtWidgets, tr
from tools.UI_Tables.prefMEOS import Isolinea
from UI.widgets import Entrada_con_unidades, LineConfig

class PsychroPlot(PlotWidget):
    """
    Plot widget for psychrometric chart
        Add custom margins
        Define a point for text state properties, to easy remove and redraw
    """
    def __init__(self, *args, **kwargs):
        PlotWidget.__init__(self, *args, **kwargs)
        self.state = None

    def config(self, config):
        self.ax.set_autoscale_on(False)
        chart = config.getboolean("Psychr", "chart")
        xlabel = "Tdb, " + Temperature.text()
        ylabel = "%s, %s/%s" % (
            tr("pychemqt", "Absolute humidity"),
            Mass.text(), Mass.text())

        tmin = Temperature(config.getfloat("Psychr", "isotdbStart")).config()
        tmax = Temperature(config.getfloat("Psychr", "isotdbEnd")).config()
        wmin = config.getfloat("Psychr", "isowStart")
        wmax = config.getfloat("Psychr", "isowEnd")

        if chart:
            self.ax.set_xlabel(xlabel, size="large")
            self.ax.set_ylabel(ylabel, size="large")
            self.ax.set_xlim(tmin, tmax)
            self.ax.set_ylim(wmin, wmax)
            self.ax.yaxis.set_ticks_position("right")
            self.ax.yaxis.set_label_position("right")
            self.ax.figure.subplots_adjust(left=0.05, top=0.95)
        else:
            self.ax.set_xlabel(ylabel, size="large")
            self.ax.set_ylabel(xlabel, size="large")
            self.ax.set_xlim(wmin, wmax)
            self.ax.set_ylim(tmin, tmax)
            self.ax.xaxis.set_ticks_position("top")
            self.ax.xaxis.set_label_position("top")
            self.ax.figure.subplots_adjust(right=0.95, bottom=0.05)

        kw = formatLine(config, "Psychr", "crux")
        self.lx = self.ax.axhline(**kw)  # the horiz line
        self.ly = self.ax.axvline(**kw)  # the vert line

    def createCrux(self, state, chart):
        """Update horizontal and vertical lines to show click point"""
        self.state = state
        if chart:
            self.lx.set_ydata(state.w)
            self.ly.set_xdata(state.tdb.config())
        else:
            self.lx.set_ydata(state.tdb.config())
            self.ly.set_xdata(state.w)
        self.showPointData(state, chart)

    def clearCrux(self):
        """Clear crux lines for click interaction in plot"""
        self.lx.set_ydata(0)
        self.ly.set_xdata(0)

    def showPointData(self, state, chart=True):
        """Update data of current cursor point in plot annotates"""
        self.clearPointData()

        txt = []
        for key in ("tdb", "tdp", "twb", "HR", "w", "h", "v", "rho"):
            txt.append(("%s: %s" % (key, state.__getattribute__(key).str),))

        if chart:
            loc = "upper left"
        else:
            loc = "lower right"
        self.ax.table(cellText=txt, loc=loc, cellLoc="left", colLoc="left")
        self.ax.tables[0].auto_set_column_width(0)
        self.draw()

    def clearPointData(self):
        while self.ax.tables:
            self.ax.tables.pop()
        self.draw()

class PsychroInput(QtWidgets.QWidget):
    """Widget with parameter for psychrometric state"""
    parameters = ["tdb", "twb", "tdp", "w", "HR", "v", "h"]
    stateChanged = QtCore.pyqtSignal(PsyState)
    pressureChanged = QtCore.pyqtSignal()

    def __init__(self, state=None, readOnly=False, parent=None):
        """
        constructor
        optional state parameter to assign initial psychrometric state
        """
        super().__init__(parent)

        self.state = PsychroState(P=101325)

        layout = QtWidgets.QGridLayout(self)
        self.checkPresion = QtWidgets.QRadioButton(
            tr("pychemqt", "Pressure"))
        layout.addWidget(self.checkPresion, 1, 1, 1, 1)
        self.P = Entrada_con_unidades(Pressure, value=101325)
        self.P.valueChanged.connect(self.changePressure)
        layout.addWidget(self.P, 1, 2, 1, 1)
        self.checkAltitud = QtWidgets.QRadioButton(
            tr("pychemqt", "Altitude"))
        layout.addWidget(self.checkAltitud, 2, 1, 1, 1)
        self.z = Entrada_con_unidades(Length, value=0)
        self.checkPresion.toggled.connect(self.P.setEnabled)
        self.checkAltitud.toggled.connect(self.z.setEnabled)
        self.z.valueChanged.connect(self.changeAltitude)
        self.checkPresion.setChecked(True)
        self.z.setEnabled(False)
        layout.addWidget(self.z, 2, 2, 1, 1)
        layout.addItem(QtWidgets.QSpacerItem(
            10, 10, QtWidgets.QSizePolicy.Policy.Fixed,
            QtWidgets.QSizePolicy.Policy.Fixed), 3, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel(tr(
            "pychemqt", "Select point")), 4, 1, 1, 2)
        self.variables = QtWidgets.QComboBox()
        for txt in PsyState.TEXT_MODE:
            self.variables.addItem(txt)
        self.variables.currentIndexChanged.connect(self.updateInputs)
        layout.addWidget(self.variables, 5, 1, 1, 2)

        layout.addWidget(QtWidgets.QLabel("Tdb:"), 6, 1, 1, 1)
        self.tdb = Entrada_con_unidades(Temperature)
        self.tdb.valueChanged.connect(partial(self.updateKwargs, "tdb"))
        layout.addWidget(self.tdb, 6, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel("Twb:"), 7, 1, 1, 1)
        self.twb = Entrada_con_unidades(Temperature)
        self.twb.valueChanged.connect(partial(self.updateKwargs, "twb"))
        layout.addWidget(self.twb, 7, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel("Tdp:"), 8, 1, 1, 1)
        self.tdp = Entrada_con_unidades(Temperature)
        self.tdp.valueChanged.connect(partial(self.updateKwargs, "tdp"))
        layout.addWidget(self.tdp, 8, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel(tr(
            "pychemqt", "Humidity Ratio:")), 9, 1, 1, 1)
        self.w = Entrada_con_unidades(float, textounidad="kgw/kgda")
        self.w.valueChanged.connect(partial(self.updateKwargs, "w"))
        layout.addWidget(self.w, 9, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel(tr(
            "pychemqt", "Relative humidity:")), 10, 1, 1, 1)
        self.HR = Entrada_con_unidades(float, textounidad="%")
        self.HR.valueChanged.connect(partial(self.updateKwargs, "HR"))
        layout.addWidget(self.HR, 10, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel(tr(
            "pychemqt", "Volume")), 11, 1, 1, 1)
        self.v = Entrada_con_unidades(SpecificVolume)
        self.v.valueChanged.connect(partial(self.updateKwargs, "v"))
        layout.addWidget(self.v, 11, 2, 1, 1)
        layout.addWidget(QtWidgets.QLabel(tr(
            "pychemqt", "Enthalpy")), 12, 1, 1, 1)
        self.h = Entrada_con_unidades(Enthalpy)
        self.h.valueChanged.connect(partial(self.updateKwargs, "h"))
        layout.addWidget(self.h, 12, 2, 1, 1)
        layout.addItem(QtWidgets.QSpacerItem(
            20, 20, QtWidgets.QSizePolicy.Policy.Fixed,
            QtWidgets.QSizePolicy.Policy.Fixed), 13, 1, 1, 2)

        self.setReadOnly(readOnly)
        self.updateInputs(0)
        if state:
            self.setState(state)

    def updateInputs(self, index):
        """Update inputs appearance to highlight active"""
        for par in self.parameters:
            self.__getattribute__(par).setReadOnly(True)
            self.__getattribute__(par).setResaltado(False)
        for par in PsyState.VAR_NAME[index]:
            self.__getattribute__(par).setReadOnly(False)
            self.__getattribute__(par).setResaltado(True)

        index = self.variables.currentIndex()
        kwargs = {"P": self.P.value}
        for par in PsyState.VAR_NAME[index]:
            if self.__getattribute__(par).value:
                kwargs[par] = self.state.__getattribute__(par)
        self.state = PsychroState(**kwargs)

    def setReadOnly(self, readOnly):
        self.checkPresion.setEnabled(not readOnly)
        self.checkAltitud.setEnabled(not readOnly)
        self.P.setReadOnly(readOnly)
        self.z.setReadOnly(readOnly)
        self.variables.setEnabled(not readOnly)
        for par in self.parameters:
            self.__getattribute__(par).setReadOnly(True)
            self.__getattribute__(par).setResaltado(False)

    def updateKwargs(self, key, value):
        """Update kwargs of state instance, if its correctly defined show it"""
        kwargs = {key: value}
        self.state(**kwargs)
        if self.state.status:
            self.setState(self.state)
            self.stateChanged.emit(self.state)

    def setState(self, state):
        """Fill data input with state properties"""
        self.state = state
        if state.w < state.ws:
            for p in self.parameters:
                self.__getattribute__(p).setValue(state.__getattribute__(p))

    def changePressure(self, value):
        """Change pressure to global plot and for states"""
        self.z.setValue(_height(value))
        self.state = PsychroState(P=value)
        self.pressureChanged.emit()

    def changeAltitude(self, value):
        """Change pressure through altitude and ICAO equation"""
        presion = _Pbar(value)
        self.P.setValue(presion)
        self.state = PsychroState(P=value)
        self.pressureChanged.emit()

class UI_Psychrometry(QtWidgets.QDialog):
    """Psychrometric charts tool"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle(tr(
            "pychemqt", "Psychrometric chart"))
        self.setWindowIcon(QtGui.QIcon(QtGui.QPixmap(
            os.environ["pychemqt"] + "/images/button/psychrometric.png")))
        self.showMaximized()

        layout = QtWidgets.QGridLayout(self)
        self.plt = PsychroPlot(parent=self, width=100, height=1, dpi=90)
        self.plt.fig.canvas.mpl_connect('button_press_event', self.click)
        layout.addWidget(self.plt, 1, 3, 2, 2)

        self.inputs = PsychroInput()
        self.inputs.stateChanged.connect(self.plt.createCrux)
        self.inputs.pressureChanged.connect(self.plot)
        layout.addWidget(self.inputs, 1, 1, 1, 1)
        layout.addItem(QtWidgets.QSpacerItem(
            10, 10, QtWidgets.QSizePolicy.Policy.Expanding,
            QtWidgets.QSizePolicy.Policy.Expanding), 2, 1)

        self.buttonShowToolbox = QtWidgets.QToolButton()
        self.buttonShowToolbox.setCheckable(True)
        self.buttonShowToolbox.toggled.connect(self.showToolBar)
        layout.addWidget(self.buttonShowToolbox, 1, 2, 2, 1)
        self.line = QtWidgets.QFrame()
        self.line.setFrameShape(QtWidgets.QFrame.Shape.VLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
        layout.addWidget(self.line, 1, 3, 3, 1)

        self.progressBar = QtWidgets.QProgressBar()
        self.progressBar.setVisible(False)
        layout.addWidget(self.progressBar, 3, 3)
        self.status = QtWidgets.QLabel()
        layout.addWidget(self.status, 3, 3)

        btBox = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Close)
        butonPNG = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(
            os.environ["pychemqt"], "images", "button", "image.png")),
            tr("pychemqt", "Save as PNG"))
        butonPNG.clicked.connect(self.plt.savePNG)
        butonConfig = QtWidgets.QPushButton(QtGui.QIcon(os.path.join(
            os.environ["pychemqt"], "images", "button", "configure.png")),
            tr("pychemqt", "Configure"))
        butonConfig.clicked.connect(self.configure)
        btBox.rejected.connect(self.reject)
        layout.addWidget(btBox, 3, 4)
        btBox.layout().insertWidget(0, butonPNG)
        btBox.layout().insertWidget(0, butonConfig)

        self.showToolBar(False)
        self.Preferences = ConfigParser()
        self.Preferences.read(conf_dir+"pychemqtrc")
        self.plot()
        logging.info(tr(
            "pychemqt", "Started psychrometric chart tool"))

    def configure(self):
        """Show configuration dialog"""
        dlg = ConfigDialog(self.Preferences)
        if dlg.exec():
            self.Preferences = dlg.value(self.Preferences)
            self.Preferences.write(open(conf_dir+"pychemqtrc", "w"))
            self.plot()

    def showToolBar(self, checked):
        """Show/Hide left toolbar with additional funcionality"""
        self.inputs.setVisible(checked)
        if checked:
            image = "arrow-left-double.png"
        else:
            image = "arrow-right-double.png"
        self.buttonShowToolbox.setIcon(QtGui.QIcon(
            os.environ["pychemqt"] + os.path.join("images", "button", image)))

    def drawlabel(self, name, t, W, label, unit):
        """
        Draw annotation for isolines
            name: name of isoline
            t: x array of line
            W: y array of line
            label: text value to draw
            unit: text units to draw
        """
        if self.Preferences.getboolean("Psychr", name+"label"):
            TMIN = self.Preferences.getfloat("Psychr", "isotdbStart")
            TMAX = self.Preferences.getfloat("Psychr", "isotdbEnd")
            tmin = Temperature(TMIN).config()
            tmax = Temperature(TMAX).config()
            wmin = self.Preferences.getfloat("Psychr", "isowStart")
            wmax = self.Preferences.getfloat("Psychr", "isowEnd")
            if self.Preferences.getboolean("Psychr", "chart"):
                x = tmax-tmin
                y = wmax-wmin
                i = 0
                for ti, wi in zip(t, W):
                    if tmin <= ti <= tmax and wmin <= wi <= wmax:
                        i += 1
            else:
                x = wmax-wmin
                y = tmax-tmin
                i = 0
                for ti, wi in zip(t, W):
                    if tmin <= wi <= tmax and wmin <= ti <= wmax:
                        i += 1

            if isinstance(label, float):
                label = "%4g" % label

            if self.Preferences.getboolean("Psychr", name+"units"):
                label += unit
            pos = self.Preferences.getfloat("Psychr", name+"position")
            p = int(i*pos/100-1)
            rot = arctan((W[p]-W[p-1])/y/(t[p]-t[p-1])*x)*360/2/pi
            self.plt.ax.annotate(label, (t[p], W[p]), rotation=rot,
                                 size="small", ha="center", va="center")

    def plot(self):
        """Plot chart"""
        self.plt.clearPointData()
        self.plt.ax.clear()
        chart = self.Preferences.getboolean("Psychr", "chart")
        self.plt.config(self.Preferences)
        filename = conf_dir+"%s_%i.json" % (
            PsychroState().__class__.__name__, self.inputs.P.value)
        if os.path.isfile(filename):
            with open(filename, "r") as archivo:
                data = json.load(archivo)
                self.status.setText(tr(
                    "pychemqt", "Loading cached data..."))
                QtWidgets.QApplication.processEvents()
        else:
            self.progressBar.setVisible(True)
            self.status.setText(tr(
                "pychemqt", "Calculating data..."))
            QtWidgets.QApplication.processEvents()
            data = PsychroState.calculatePlot(self)
            with open(filename, "w") as file:
                json.dump(data, file, indent=4)

            self.progressBar.setVisible(False)
        self.status.setText(
            tr("pychemqt", "Plotting..."))
        QtWidgets.QApplication.processEvents()

        # Saturation line
        t = [Temperature(ti).config() for ti in data["t"]]
        Hs = data["Hs"]
        fmt = formatLine(self.Preferences, "Psychr", "saturation")
        if chart:
            self.plt.plot(t, Hs, **fmt)
        else:
            self.plt.plot(Hs, t, **fmt)

        # Iso dew bulb temperaure lines, vertial lines in normal h-Tdb plot,
        # vertical in mollier diagram
        fmt = formatLine(self.Preferences, "Psychr", "isotdb")
        for i, T in enumerate(t):
            if chart:
                self.plt.plot([T, T], [0, Hs[i]], **fmt)
            else:
                self.plt.plot([0, Hs[i]], [T, T], **fmt)

        # Iso humidity lines, horizontal lines in normal h-Tdb plot, vertical
        # in mollier diagram
        H = data["H"]
        th = data["th"]
        tm = Temperature(self.Preferences.getfloat("Psychr", "isotdbEnd"))
        fmt = formatLine(self.Preferences, "Psychr", "isow")
        for i, H in enumerate(H):
            ts = Temperature(th[i]).config()
            if chart:
                self.plt.plot([ts, tm.config()], [H, H], **fmt)
            else:
                self.plt.plot([H, H], [ts, tm.config()], **fmt)

        # Iso relative humidity lines
        fmt = formatLine(self.Preferences, "Psychr", "isohr")
        for Hr, H0 in list(data["Hr"].items()):
            if chart:
                self.plt.plot(t, H0, **fmt)
                self.drawlabel("isohr", t, H0, Hr, "%")
            else:
                self.plt.plot(H0, t, **fmt)
                self.drawlabel("isohr", H0, t, Hr, "%")

        # Iso wet bulb temperature lines
        fmt = formatLine(self.Preferences, "Psychr", "isotwb")
        for T, (H, Tw) in list(data["Twb"].items()):
            value = Temperature(T).config()
            Tw_conf = [Temperature(Twi).config() for Twi in Tw]
            txt = Temperature.text()
            if chart:
                self.plt.plot(Tw_conf, H, **fmt)
                self.drawlabel("isotwb", Tw_conf, H, value, txt)
            else:
                self.plt.plot(H, Tw_conf, **fmt)
                self.drawlabel("isotwb", H, Tw_conf, value, txt)

        # Isochor lines
        fmt = formatLine(self.Preferences, "Psychr", "isochor")
        for v, (Td, H) in list(data["v"].items()):
            value = SpecificVolume(v).config()
            Td_conf = [Temperature(Tdi).config() for Tdi in Td]
            txt = SpecificVolume.text()
            if chart:
                self.plt.plot(Td_conf, H, **fmt)
                self.drawlabel("isochor", Td_conf, H, value, txt)
            else:
                self.plt.plot(H, Td_conf, **fmt)
                self.drawlabel("isochor", H, Td_conf, value, txt)

        if self.plt.state:
            self.plt.createCrux(self.plt.state, chart)
        self.plt.draw()
        self.status.setText("%s %s" % (
            tr("pychemqt", "Using"),
            PsychroState().__class__.__name__[3:]))

    def click(self, event):
        """Update input and graph annotate when mouse click over chart"""
        if event.xdata and event.ydata:
            chart = self.Preferences.getboolean("Psychr", "chart")
            if chart:
                state = self.createState(event.xdata, event.ydata)
            else:
                state = self.createState(event.ydata, event.xdata)
            if state.w <= state.ws:
                self.inputs.setState(state)
                self.plt.createCrux(state, chart)
            else:
                self.plt.clearCrux()
                self.plt.clearPointData()

    def createState(self, x, y):
        """Create psychrometric state from click or mouse position"""
        tdb = Temperature(x, "conf")
        punto = PsychroState(P=self.inputs.P.value, tdb=tdb, w=y)
        return punto

    def setProgressValue(self, value):
        """Update progress bar with new value"""
        self.progressBar.setValue(int(value))
        QtWidgets.QApplication.processEvents()

class Config(QtWidgets.QWidget):
    """Phychrometric chart configuration"""
    lineas = [
        ("IsoTdb", Temperature,
         tr(
             "pychemqt", "Iso dry bulb temperature")),
        ("IsoW", float,
         tr(
             "pychemqt", "Iso absolute humidity")),
        ("IsoHR", float,
         tr(
             "pychemqt", "Iso relative humidity")),
        ("IsoTwb", Temperature,
         tr(
             "pychemqt", "Iso wet bulb temperature")),
        ("Isochor", SpecificVolume,
         tr("pychemqt", "Isochor"))]

    def __init__(self, config, parent=None):
        """constructor, config optional parameter to input project config"""
        super().__init__(parent)
        lyt = QtWidgets.QGridLayout(self)
        lyt.setContentsMargins(0, 0, 0, 0)
        scroll = QtWidgets.QScrollArea()
        scroll.setFrameStyle(QtWidgets.QFrame.Shape.NoFrame)
        lyt.addWidget(scroll)

        dlg = QtWidgets.QWidget()
        layout = QtWidgets.QGridLayout(dlg)

        groupType = QtWidgets.QGroupBox(tr(
            "pychemqt", "Chart type"))
        groupLayout = QtWidgets.QVBoxLayout(groupType)
        self.checkASHRAE = QtWidgets.QRadioButton(
            tr(
                "pychemqt", "ASHRAE Chart, W vs Tdb"))
        groupLayout.addWidget(self.checkASHRAE)
        self.checkMollier = QtWidgets.QRadioButton(
            tr("pychemqt", "Mollier Chart ix"))
        groupLayout.addWidget(self.checkMollier)
        layout.addWidget(groupType, 0, 1, 1, 2)

        self.virial = QtWidgets.QCheckBox(tr(
            "pychemqt", "Use virial equation of state"))
        layout.addWidget(self.virial, 1, 1, 1, 2)
        self.coolProp = QtWidgets.QCheckBox(tr(
            "pychemqt", "Use external library coolProp (faster)"))
        self.coolProp.setEnabled(False)
        layout.addWidget(self.coolProp, 2, 2)
        self.refprop = QtWidgets.QCheckBox(tr(
            "pychemqt", "Use external library refprop (fastest)"))
        self.refprop.setEnabled(False)
        layout.addWidget(self.refprop, 3, 2)
        layout.addItem(QtWidgets.QSpacerItem(
            10, 10, QtWidgets.QSizePolicy.Policy.Fixed,
            QtWidgets.QSizePolicy.Policy.Fixed), 4, 1)

        self.satlineconfig = LineConfig(
            "saturation", tr(
                "pychemqt", "Saturation Line Style"))
        layout.addWidget(self.satlineconfig, 5, 1, 1, 2)
        self.cruxlineconfig = LineConfig(
            "crux", tr(
                "pychemqt", "Crux Line Style"))
        layout.addWidget(self.cruxlineconfig, 6, 1, 1, 2)
        group = QtWidgets.QGroupBox(
            tr("pychemqt", "Isolines"))
        layout.addWidget(group, 7, 1, 1, 2)
        layoutgroup = QtWidgets.QGridLayout(group)
        self.comboIsolineas = QtWidgets.QComboBox()
        layoutgroup.addWidget(self.comboIsolineas, 1, 1)
        self.Isolineas = QtWidgets.QStackedWidget()
        self.comboIsolineas.currentIndexChanged.connect(
            self.Isolineas.setCurrentIndex)
        layoutgroup.addWidget(self.Isolineas, 2, 1)
        for name, unit, text in self.lineas:
            self.comboIsolineas.addItem(text)
            self.Isolineas.addWidget(Isolinea(unit, name, config, "Psychr"))
        layout.addItem(QtWidgets.QSpacerItem(
            10, 10, QtWidgets.QSizePolicy.Policy.Expanding,
            QtWidgets.QSizePolicy.Policy.Expanding), 10, 2)

        scroll.setWidget(dlg)

        if os.environ["CoolProp"]:
            self.virial.toggled.connect(self.coolProp.setEnabled)
        if os.environ["refprop"]:
            self.virial.toggled.connect(self.refprop.setEnabled)

        if config.has_section("Psychr"):
            if config.getboolean("Psychr", 'chart'):
                self.checkASHRAE.setChecked(True)
            else:
                self.checkMollier.setChecked(True)
            self.virial.setChecked(config.getboolean("Psychr", 'virial'))
            self.coolProp.setChecked(config.getboolean("Psychr", 'coolprop'))
            self.refprop.setChecked(config.getboolean("Psychr", 'refprop'))
            self.satlineconfig.setConfig(config, "Psychr")
            self.cruxlineconfig.setConfig(config, "Psychr")

    def value(self, config):
        """Return value for main dialog"""
        if not config.has_section("Psychr"):
            config.add_section("Psychr")

        config.set("Psychr", "chart", str(self.checkASHRAE.isChecked()))
        config.set("Psychr", "virial", str(self.virial.isChecked()))
        config.set("Psychr", "coolprop", str(self.coolProp.isChecked()))
        config.set("Psychr", "refprop", str(self.refprop.isChecked()))
        config = self.satlineconfig.value(config, "Psychr")
        config = self.cruxlineconfig.value(config, "Psychr")

        for indice in range(self.Isolineas.count()):
            config = self.Isolineas.widget(indice).value(config)
        return config

class ConfigDialog(QtWidgets.QDialog):
    """Dialog to config thermal method calculations"""
    def __init__(self, config=None, parent=None):
        super().__init__(parent)
        self.setWindowTitle(tr(
            "pychemqt", "Psychrometric chart configuration"))
        layout = QtWidgets.QVBoxLayout(self)
        self.widget = Config(config)
        layout.addWidget(self.widget)
        self.buttonBox = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Cancel
            | QtWidgets.QDialogButtonBox.StandardButton.Ok)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        layout.addWidget(self.buttonBox)

    def value(self, config):
        """Function result for wizard"""
        config = self.widget.value(config)
        return config

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    aireHumedo = UI_Psychrometry()
    sys.exit(app.exec())


# coding: utf-8

from urlparse import urlparse
from uuid import uuid4
import hashlib
import re
import unicodedata
import urllib

from google.appengine.datastore.datastore_query import Cursor
from google.appengine.ext import ndb
from webargs import fields as wf
from webargs.flaskparser import parser
import flask

import config

###############################################################################
# Request Parameters
###############################################################################
def param(name, cast=None):
  def switch(case):
    return {
      int: wf.Int(missing=None),
      float: wf.Float(missing=None),
      bool: wf.Bool(missing=None),
      list: wf.DelimitedList(wf.Str(), delimiter=',', missing=[]),
    }.get(case)
  if cast is None or cast is ndb.Key:
    cast_ = wf.Str(missing=None)
  else:
    cast_ = switch(cast) or cast
  args = parser.parse({name: cast_})
  value = args[name]
  return ndb.Key(urlsafe=value) if cast is ndb.Key and value else value

def is_trusted_url(next_url):
  if not next_url:
    return ''
  next_url_host = urlparse(next_url).hostname
  if config.TRUSTED_HOSTS and next_url_host not in config.TRUSTED_HOSTS:
    return flask.url_for('welcome')
  if not next_url.startswith(flask.request.host_url):
    return flask.url_for('welcome')
  return next_url

def get_next_url(next_url=''):
  args = parser.parse({
    'next': wf.Str(missing=None), 'next_url': wf.Str(missing=None)
  })
  next_url = next_url or args['next'] or args['next_url']
  if next_url:
    do_not_redirect_urls = [flask.url_for(u) for u in [
      'signin', 'signup', 'user_forgot', 'user_reset',
    ]]
    if any(url in next_url for url in do_not_redirect_urls):
      return flask.url_for('welcome')
    return is_trusted_url(next_url)
  return is_trusted_url(flask.request.referrer)

###############################################################################
# Model manipulations
###############################################################################
def get_dbs(
        query, order=None, limit=None, cursor=None, prev_cursor=False,
        keys_only=None, **filters
):
  model_class = ndb.Model._kind_map[query.kind]
  query_prev = query
  if order:
    for o in order.split(','):
      if o.startswith('-'):
        query = query.order(-model_class._properties[o[1:]])
        if prev_cursor:
          query_prev = query_prev.order(model_class._properties[o[1:]])
      else:
        query = query.order(model_class._properties[o])
        if prev_cursor:
          query_prev = query_prev.order(-model_class._properties[o])

  for prop, value in filters.iteritems():
    if value is None:
      continue
    for val in value if isinstance(value, list) else [value]:
      query = query.filter(model_class._properties[prop] == val)
      if prev_cursor:
        query_prev = query_prev.filter(model_class._properties[prop] == val)

  limit = limit or config.DEFAULT_DB_LIMIT
  if limit == -1:
    return list(query.fetch(keys_only=keys_only)), {'next': None, 'prev': None}

  cursor = Cursor.from_websafe_string(cursor) if cursor else None
  model_dbs, next_cursor, more = query.fetch_page(
    limit, start_cursor=cursor, keys_only=keys_only,
  )
  next_cursor = next_cursor.to_websafe_string() if more else None
  if not prev_cursor:
    return list(model_dbs), {'next': next_cursor, 'prev': None}
  model_dbs_prev, prev_cursor, prev_more = query_prev.fetch_page(
    limit, start_cursor=cursor.reversed() if cursor else None, keys_only=True
  )
  prev_cursor = prev_cursor.reversed().to_websafe_string() \
      if prev_cursor and cursor else None
  return list(model_dbs), {'next': next_cursor, 'prev': prev_cursor}

def get_keys(*arg, **kwargs):
  return get_dbs(*arg, keys_only=True, **kwargs)

###############################################################################
# JSON Response Helpers
###############################################################################
def jsonpify(*args, **kwargs):
  params = parser.parse({'callback': wf.Str(missing=None)})
  if params['callback']:
    content = '%s(%s)' % (
      params['callback'], flask.jsonify(*args, **kwargs).data,
    )
    mimetype = 'application/javascript'
    return flask.current_app.response_class(content, mimetype=mimetype)
  return flask.jsonify(*args, **kwargs)

###############################################################################
# Helpers
###############################################################################
def is_iterable(value):
  return isinstance(value, (tuple, list))

def check_form_fields(*fields):
  fields_data = []
  for field in fields:
    if is_iterable(field):
      fields_data.extend([field.data for field in field])
    else:
      fields_data.append(field.data)
  return all(fields_data)

def generate_next_url(next_cursor, base_url=None, cursor_name='cursor'):
  if isinstance(next_cursor, dict):
    next_cursor = next_cursor.get('next')
  if not next_cursor:
    return None
  base_url = base_url or flask.request.base_url
  args = flask.request.args.to_dict()
  args[cursor_name] = next_cursor
  return '%s?%s' % (base_url, urllib.urlencode(args))

def uuid():
  return uuid4().hex

_slugify_strip_re = re.compile(r'[^\w\s-]')
_slugify_hyphenate_re = re.compile(r'[-\s]+')

def slugify(text):
  if not isinstance(text, unicode):
    text = unicode(text)
  text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore')
  text = unicode(_slugify_strip_re.sub('', text).strip().lower())
  return _slugify_hyphenate_re.sub('-', text)

_username_re = re.compile(r'^[a-z0-9]+(?:[\.][a-z0-9]+)*$')

def is_valid_username(username):
  return bool(_username_re.match(username))

def create_name_from_email(email):
  return re.sub(r'_+|-+|\.+|\++', ' ', email.split('@')[0]).title()

def password_hash(user_db, password):
  m = hashlib.sha256()
  m.update(user_db.key.urlsafe())
  m.update(user_db.created.isoformat())
  m.update(m.hexdigest())
  m.update(password.encode('utf-8'))
  m.update(config.CONFIG_DB.salt)
  return m.hexdigest()

def update_query_argument(name, value=None, ignore='cursor', is_list=False):
  ignore = ignore.split(',') if isinstance(ignore, str) else ignore or []
  arguments = {}
  for key, val in flask.request.args.items():
    if key not in ignore and (is_list and value is not None or key != name):
      arguments[key] = val
  if value is not None:
    if is_list:
      values = []
      if name in arguments:
        values = arguments[name].split(',')
        del arguments[name]
      if value in values:
        values.remove(value)
      else:
        values.append(value)
      if values:
        arguments[name] = ','.join(values)
    else:
      arguments[name] = value
  query = '&'.join('%s=%s' % item for item in sorted(arguments.items()))
  return '%s%s' % (flask.request.path, '?%s' % query if query else '')

def parse_tags(tags, separator=None):
  if not is_iterable(tags):
    tags = unicode(tags.strip()).split(separator or config.TAG_SEPARATOR)
  return filter(None, sorted(list(set(tags))))

###############################################################################
# Lambdas
###############################################################################
strip_filter = lambda x: x.strip() if x else ''
email_filter = lambda x: x.lower().strip() if x else ''
sort_filter = lambda x: sorted(x) if x else []


import logging

from django.db.models.signals import pre_save, post_save, post_delete, m2m_changed
from django.dispatch import receiver

from historie.models import Historie
from services.mailer import Mailer
from uzivatel.models import Organizace, Osoba, User
from rest_framework.authtoken.models import Token

logger = logging.getLogger(__name__)

@receiver(post_save, sender=Organizace)
def orgnaizace_save_metadata(sender, instance: Organizace, **kwargs):
    instance.save_metadata()

@receiver(post_save, sender=Osoba)
def osoba_save_metadata(sender, instance: Osoba, **kwargs):
    instance.save_metadata()

@receiver(pre_save, sender=User)
def create_ident_cely(sender, instance, **kwargs):
    """
    Přidelení identu celý pro usera.
    """
    if not kwargs['update_fields'] and instance.id:
        # Save it, so it can be used in post_save
        database_user_query = User.objects.filter(id=instance.id)
        if database_user_query.count() > 0:
            instance.old = database_user_query.first()
        else:
            instance.old = None
    if instance.pk is None:
        instance.model_is_updated = False
        logger.debug("uzivatel.signals.create_ident_cely.running_create_ident_cely_receiver")
        if not instance.ident_cely:
            users = User.objects.all().order_by("-ident_cely")
            if users.count() > 0:
                last_user = users.first()
                dash_index = last_user.ident_cely.rfind("-")
                number = int(last_user.ident_cely[dash_index + 1:]) + 1
                instance.ident_cely = "U-" + "{0}".format(str(number)).zfill(6)
            else:
                instance.ident_cely = "U-000001"
    if len(kwargs["update_fields"]) == 1 and "last_login" in kwargs["update_fields"]:
        instance.suppress_signal = True

@receiver(post_save, sender=User)
def user_post_save_method(sender, instance: User, created: bool, **kwargs):
    if not instance.suppress_signal:
        instance.save_metadata()
    send_deactivation_email(sender, instance, **kwargs)
    send_new_user_email_to_admin(sender, instance, created)
    send_account_confirmed_email(sender, instance, created)
    # Create or change token when user changed.
    try:
        old_token = Token.objects.get(user=instance)
    except Token.DoesNotExist:
        Token.objects.create(user=instance)
    else:
        old_token.delete()
        Token.objects.create(user=instance)

def send_deactivation_email(sender, instance: User, **kwargs):
    """
    Signál pro poslání deaktivačního emailu uživately.
    """
    if not kwargs.get('update_fields') and hasattr(instance, 'old') and instance.old is not None:
        kwargs['update_fields'] = []
        if instance.is_active != instance.old.is_active:
            kwargs['update_fields'].append('is_active')
    if kwargs['update_fields']:
        if 'is_active' in kwargs['update_fields'] and instance.is_active is False:
            Mailer.send_eu03(user=instance)

def send_new_user_email_to_admin(sender, instance: User, created):
    """
    Signál pro zaslání info o nově registrovaném uživately adminovy.
    """
    if created is True and instance.created_from_admin_panel is False:
        Mailer.send_eu04(user=instance)

def send_account_confirmed_email(sender, instance: User, created):
    """
    signál pro zaslání emailu uživately o jeho konfirmaci.
    """
    if created is True and instance.created_from_admin_panel is True:
        Mailer.send_eu02(user=instance)

@receiver(post_delete, sender=User)
def delete_profile(sender, instance, *args, **kwargs):
    """
    Signál pro zaslání emailu uživately o jeho smazání.
    """
    instance: User
    Mailer.send_eu03(user=instance)
    Historie.save_record_deletion_record(record=instance)
    instance.save_metadata(use_celery=False)
    instance.record_deletion()

@receiver(post_delete, sender=Osoba)
def osoba_delete_repository_container(sender, instance: Osoba, **kwargs):
    instance.record_deletion()

@receiver(post_delete, sender=Organizace)
def osoba_delete_repository_container(sender, instance: Organizace, **kwargs):
    instance.record_deletion()


"""SmartAPI registry access utility."""
from functools import cache

import httpx

class SmartAPI:
    """SmartAPI."""

    def __init__(self):
        """Initialize."""
        self.base_url = "http://smart-api.info/api"

    @cache
    def get_operations_endpoints(self):
        """Find all endpoints that support at least one workflow operation."""
        endpoints = self.get_trapi_endpoints()

        operations_endpoints = []
        for endpoint in endpoints:
            if endpoint["operations"] is not None:
                operations_endpoints.append(endpoint)

        return operations_endpoints

    @cache
    def get_trapi_endpoints(self):
        """Find all endpoints that match a query for TRAPI."""
        response_content = httpx.get(
            self.base_url + "/query?limit=1000&q=TRAPI",
            headers={"accept": "application/json"},
        )

        response_content.raise_for_status()
        response_dict = response_content.json()

        endpoints = []
        for hit in response_dict["hits"]:
            try:
                url = hit["servers"][0]["url"]
            except (KeyError, IndexError):
                url = None
            try:
                version = hit["info"]["x-trapi"]["version"]
            except KeyError:
                version = None
            try:
                operations = hit["info"]["x-trapi"]["operations"]
            except KeyError:
                operations = None
            try:
                title = hit["info"]["title"]
            except KeyError:
                title = None
            endpoints.append({
                "url": url,
                "operations": operations,
                "version": version,
                "title": title,
            })

        return endpoints

def main():
    """Run CLI."""
    import argparse
    import json

    argparser = argparse.ArgumentParser(
        description="CLI testing of the ResponseCache class"
    )
    argparser.add_argument(
        "--get_trapi_endpoints",
        action="count",
        help="Get a list of TRAPI endpoints",
    )
    argparser.add_argument(
        "--get_operations_endpoints",
        action="count",
        help="Get a list of TRAPI endpoints that support operations",
    )
    args = argparser.parse_args()

    if (
        args.get_trapi_endpoints is None
        and args.get_operations_endpoints is None
    ):
        argparser.print_help()
        return

    smartapi = SmartAPI()

    if args.get_trapi_endpoints:
        endpoints = smartapi.get_trapi_endpoints()
        print(json.dumps(endpoints, sort_keys=True, indent=2))

    if args.get_operations_endpoints:
        endpoints = smartapi.get_operations_endpoints()
        print(json.dumps(endpoints, sort_keys=True, indent=2))

if __name__ == "__main__":
    main()


from django.test import TestCase
import requests
import pandas as pd
from concurrent.futures import ThreadPoolExecutor

# Create your tests here.

AUTH = ('sesan', 'omoyemi124')
# main_url = "http://localhost:8000/api/snails-activity/"

main_url = "http://127.0.0.1:8000/api/snails-inventory/"

def __makeRequest(url):
    return requests.get(url, auth=AUTH)

def __createDataFrame(data):
    return pd.DataFrame(data).sort_values("dateTimeRecorded")

def get_data(url):
    response = __makeRequest(url).json()
    df = __createDataFrame(response["results"])

    while response.get("next"):
        print(response.get("next"))
        url = response.get("next")
        data = __makeRequest(url).json().get("results")
        df = df.append(__createDataFrame(data), ignore_index=True)
        response = __makeRequest(url).json()
    print(df)

with ThreadPoolExecutor() as e:
    e.submit(get_data, main_url)


import os
import torch
import numpy as np
import cv2
import seaborn as sns
from einops import repeat, rearrange
from pytorch_lightning import seed_everything
from PIL import Image
from omegaconf import OmegaConf
from ldm.util import instantiate_from_config
from ldm.models.diffusion.ddim import DDIMSampler as humansd_DDIMSampler
from mmpose.apis import inference_bottom_up_pose_model, init_pose_model
from modelscope.models.base import TorchModel
from modelscope.preprocessors.base import Preprocessor
from modelscope.pipelines.base import Model, Pipeline
from modelscope.utils.config import Config
from modelscope.pipelines.builder import PIPELINES
from modelscope.preprocessors.builder import PREPROCESSORS
from modelscope.models.builder import MODELS
DEVICE="cuda" if torch.cuda.is_available() else "cpu"
IMAGE_RESOLUTION=512

@MODELS.register_module('Pose-driven-image-generation', module_name='HumanSD')
class MyCustomModel(TorchModel):

    def __init__(self, model_dir, *args, **kwargs):
        super().__init__(model_dir, *args, **kwargs)
        self.gen_model,self.sampler,self.pose_model = self.init_model(model_dir,**kwargs)

    def forward(self, image_dir,prompt,neg_prompt,sample_steps,seed,guidance_scale,num_samples, **forward_params):
        input_image = Image.open(image_dir)
        image = np.array(input_image.convert("RGB"))
        image = self.resize_image(image,IMAGE_RESOLUTION)
        mmpose_results=inference_bottom_up_pose_model(self.pose_model, image, dataset='BottomUpCocoDataset', dataset_info=None, pose_nms_thr=1.0, return_heatmap=False, outputs=None)[0]
        mmpose_filtered_results=[]
        for mmpose_result in mmpose_results:
            if mmpose_result["score"]>0.01:
                mmpose_filtered_results.append(mmpose_result)

        humansd_pose_image=self.draw_humansd_skeleton(image,mmpose_filtered_results,0.01)

        # humansd
        self.sampler.make_schedule(sample_steps, ddim_eta=0.0, verbose=True)
        do_full_sample = 1.0 == 1.

        t_enc = min(int(1.0 * sample_steps), sample_steps-1)
        humansd_result = self.paint_humansd(
            humansd_sampler=self.sampler,
            image=image,
            pose_image=humansd_pose_image,
            prompt=prompt,
            t_enc=t_enc,
            seed=seed,
            scale=guidance_scale,
            num_samples=num_samples,
            callback=None,
            do_full_sample=do_full_sample,
            device=DEVICE,
            negative_prompt=neg_prompt
        )
        # save_image=np.concatenate(humansd_result,0)

        # save_image=save_image[...,[2,1,0]]
        return humansd_result

    def init_model(self, model_dir,**kwargs):
        """Provide default implementation based on TorchModel and user can reimplement it.
            include init model and load ckpt from the model_dir, maybe include preprocessor
            if nothing to do, then return lambda x: x
        """
        humansd_config=OmegaConf.load("configs/humansd/humansd-inference.yaml")
        humansd_model = instantiate_from_config(humansd_config.model)
        humansd_model.load_state_dict(torch.load(os.path.join(model_dir,"humansd-v1.ckpt"))["state_dict"], strict=False)
        humansd_model = humansd_model.to(DEVICE)
        humansd_sampler = humansd_DDIMSampler(humansd_model)

        mmpose_model=init_pose_model("humansd_data/models/mmpose/configs/body/2d_kpt_sview_rgb_img/associative_embedding/coco/higherhrnet_w48_coco_512x512_udp.py", os.path.join(model_dir,"higherhrnet_w48_humanart_512x512_udp.pth"), device=DEVICE)
        return humansd_model,humansd_sampler,mmpose_model

    def resize_image(self,input_image, resolution):
        H, W, C = input_image.shape
        H = float(H)
        W = float(W)
        k = float(resolution) / min(H, W)
        H *= k
        W *= k
        H = int(np.round(H / 64.0)) * 64
        W = int(np.round(W / 64.0)) * 64
        img = cv2.resize(input_image, (W, H), interpolation=cv2.INTER_LANCZOS4 if k > 1 else cv2.INTER_AREA)
        return img

    def draw_humansd_skeleton(self,image, present_pose,mmpose_detection_thresh):
        humansd_skeleton=[
                [0,0,1],
                [1,0,2],
                [2,1,3],
                [3,2,4],
                [4,3,5],
                [5,4,6],
                [6,5,7],
                [7,6,8],
                [8,7,9],
                [9,8,10],
                [10,5,11],
                [11,6,12],
                [12,11,13],
                [13,12,14],
                [14,13,15],
                [15,14,16],
            ]
        humansd_skeleton_width=10
        humansd_color=sns.color_palette("hls", len(humansd_skeleton))

        def plot_kpts(img_draw, kpts, color, edgs,width):
                for idx, kpta, kptb in edgs:
                    if kpts[kpta,2]>mmpose_detection_thresh and \
                        kpts[kptb,2]>mmpose_detection_thresh :
                        line_color = tuple([int(255*color_i) for color_i in color[idx]])

                        cv2.line(img_draw, (int(kpts[kpta,0]),int(kpts[kpta,1])), (int(kpts[kptb,0]),int(kpts[kptb,1])), line_color,width)
                        cv2.circle(img_draw, (int(kpts[kpta,0]),int(kpts[kpta,1])), width//2, line_color, -1)
                        cv2.circle(img_draw, (int(kpts[kptb,0]),int(kpts[kptb,1])), width//2, line_color, -1)

        pose_image = np.zeros_like(image)
        for person_i in range(len(present_pose)):
            if np.sum(present_pose[person_i]["keypoints"])>0:
                plot_kpts(pose_image, present_pose[person_i]["keypoints"],humansd_color,humansd_skeleton,humansd_skeleton_width)

        return pose_image

    def make_batch_sd(
        self,
        image,
        pose_image,
        txt,
        device,
        num_samples=1,):
        batch={
            "jpg":(torch.from_numpy(image).to(dtype=torch.float32) / 255 *2 - 1.0),
            "pose_img": (torch.from_numpy(pose_image).to(dtype=torch.float32) / 255 *2 - 1.0),
            "txt": num_samples * [txt],
        }

        batch["pose_img"] = rearrange(batch["pose_img"], 'h w c -> 1 c h w')
        batch["pose_img"] = repeat(batch["pose_img"].to(device=device),
                            "1 ... -> n ...", n=num_samples)

        batch["jpg"] = rearrange(batch["jpg"], 'h w c -> 1 c h w')
        batch["jpg"] = repeat(batch["jpg"].to(device=device),
                            "1 ... -> n ...", n=num_samples)
        return batch

    def paint_humansd(self,humansd_sampler, image, pose_image, prompt, t_enc, seed, scale, device, num_samples=1, callback=None,
          do_full_sample=False,negative_prompt="longbody, lowres, bad anatomy, bad hands, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality"):
        model = humansd_sampler.model
        seed_everything(seed)

        with torch.no_grad():
            batch = self.make_batch_sd(
                image,pose_image, txt=prompt, device=device, num_samples=num_samples)
            z = model.get_first_stage_encoding(model.encode_first_stage(
                batch[model.first_stage_key]))  # move to latent space
            c = model.cond_stage_model.encode(batch["txt"])
            c_cat = list()
            for ck in model.concat_keys:
                cc = batch[ck]
                if len(cc.shape) == 3:
                    cc = cc[..., None]
                cc = cc.to(memory_format=torch.contiguous_format).float()
                cc = model.get_first_stage_encoding(model.encode_first_stage(cc))
                c_cat.append(cc)

            c_cat = torch.cat(c_cat, dim=1)
            # cond
            cond = {"c_concat": [c_cat], "c_crossattn": [c]}

            # uncond cond
            uc_cross = model.get_unconditional_conditioning(num_samples, negative_prompt)
            uc_full = {"c_concat": [c_cat], "c_crossattn": [uc_cross]}
            if not do_full_sample:
                # encode (scaled latent)
                z_enc = humansd_sampler.stochastic_encode(
                    z, torch.tensor([t_enc] * num_samples).to(model.device))
            else:
                z_enc = torch.randn_like(z)
            # decode it
            samples = humansd_sampler.decode(z_enc, cond, t_enc, unconditional_guidance_scale=scale,
                                    unconditional_conditioning=uc_full, callback=callback)
            x_samples_ddim = model.decode_first_stage(samples)
            result = torch.clamp((x_samples_ddim + 1.0) / 2.0, min=0.0, max=1.0)
            result = result.cpu().numpy().transpose(0, 2, 3, 1) * 255
        return [Image.fromarray(img.astype(np.uint8)) for img in result]

@PREPROCESSORS.register_module('Pose-driven-image-generation', module_name='HumanSD-preprocessor')
class MyCustomPreprocessor(Preprocessor):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #self.trainsforms = self.init_preprocessor(**kwargs)

    def __call__(self, results):
        return results

    # def init_preprocessor(self, **kwarg):
    #     """ Provide default implementation based on preprocess_cfg and user can reimplement it.
    #         if nothing to do, then return lambda x: x
    #     """
    #     return lambda x: x

@PIPELINES.register_module('Pose-driven-image-generation', module_name='HumanSD-pipeline')
class MyCustomPipeline(Pipeline):
    """ Give simple introduction to this pipeline.

    Examples:

    >>> from modelscope.pipelines import pipeline
    >>> input = "Hello, ModelScope!"
    >>> my_pipeline = pipeline('my-task', 'my-model-id')
    >>> result = my_pipeline(input)

    """

    def __init__(self, model, preprocessor=MyCustomPreprocessor(), **kwargs):
        """
        use `model` and `preprocessor` to create a custom pipeline for prediction
        Args:
            model: model id on modelscope hub.
            preprocessor: the class of method be init_preprocessor
        """
        super().__init__(model=MyCustomModel(model), preprocessor=preprocessor,auto_collate=False)

    def _sanitize_parameters(self, **pipeline_parameters):
        """
        this method should sanitize the keyword args to preprocessor params,
        forward params and postprocess params on '__call__' or '_process_single' method
        considered to be a normal classmethod with default implementation / output

        Default Returns:
            Dict[str, str]:  preprocess_params = {}
            Dict[str, str]:  forward_params = {}
            Dict[str, str]:  postprocess_params = pipeline_parameters
        """
        return {}, pipeline_parameters, {}

    def _check_input(self, inputs):
        pass

    def _check_output(self, outputs):
        pass

    def forward(self, inputs, **forward_params):
        """ Provide default implementation using self.model and user can reimplement it
        """
        results = self.model(inputs["image_dir"],inputs["prompt"],inputs["neg_prompt"],inputs["sample_steps"],inputs["seed"],inputs["guidance_scale"],inputs["num_samples"])
        return results

    def postprocess(self, inputs):
        """ If current pipeline support model reuse, common postprocess
            code should be write here.

        Args:
            inputs:  input data

        Return:
            dict of results:  a dict containing outputs of model, each
                output should have the standard output name.
        """
        return inputs

# Tips: usr_config_path is the temporary save configuration location， after upload modelscope hub, it is the model_id
usr_config_path = './'
config = Config({
    "framework": 'pytorch',
    "task": 'Pose-driven-image-generation',
    "model": {'type': 'HumanSD'},
    "pipeline": {"type": "HumanSD-pipeline"},
    "allow_remote": False
})
config.dump('configuration.json')

if __name__ == "__main__":
    from modelscope.models import Model
    from modelscope.pipelines import pipeline
    #model = Model.from_pretrained(usr_config_path)
    image_dir = "fff1daa9ef182b86387d802dba686426e7feb396_19692096.jpg"
    prompt = "a woman"
    neg_prompt = "lmonochrome, lowres, bad anatomy, worst quality, low quality"
    inference = pipeline('Pose-driven-image-generation', model = "./")
    inputs = {"image_dir":image_dir,"prompt":prompt,"neg_prompt": neg_prompt,"sample_steps":30,"seed":None,"guidance_scale":10.0,"num_samples":2 }
    output = inference(input=inputs)
    output_dir = 'test'
    if not os.path.exists(output_dir):
        os.mkdir(output_dir)
    for i in range(len(output)):
        output[i].save(os.path.join(output_dir,str(i).zfill(4)+'.jpg'))