#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Carla plugin host (plugin UI)
# Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# For a full copy of the GNU General Public License see the GPL.txt file

# ------------------------------------------------------------------------------------------------------------
# Imports (Custom Stuff)

from carla_host import *
from externalui import ExternalUI

# ------------------------------------------------------------------------------------------------------------
# Host Plugin object

class PluginHost(CarlaHostPlugin):
    def __init__(self):
        CarlaHostPlugin.__init__(self)

        if False:
            # kdevelop likes this :)
            self.fExternalUI = ExternalUI()

        # ---------------------------------------------------------------

        self.fExternalUI = None
        self.fIsRunning  = True
        self.fSampleRate = float(sys.argv[1]) if len(sys.argv) > 1 else 44100.0

    # -------------------------------------------------------------------

    def setExternalUI(self, extUI):
        self.fExternalUI = extUI

    def sendMsg(self, lines):
        if self.fExternalUI is None:
            return False

        self.fExternalUI.send(lines)
        return True

    # -------------------------------------------------------------------

    def engine_init(self, driverName, clientName):
        self.fIsRunning = True
        return True

    def engine_close(self):
        self.fIsRunning = False
        return True

    def engine_idle(self):
        if self.fExternalUI.idleExternalUI():
            return

        self.fIsRunning = False
        self.fExternalUI.d_uiQuit()

    def is_engine_running(self):
        return self.fIsRunning

    def set_engine_about_to_close(self):
        return

# ------------------------------------------------------------------------------------------------------------
# Main Window

class CarlaMiniW(ExternalUI, HostWindow):
    def __init__(self, host, parent=None):
        ExternalUI.__init__(self)
        HostWindow.__init__(self, host, parent)
        self.host = host

        if False:
            # kdevelop likes this :)
            host = PluginHost()
            self.host = host

        host.setExternalUI(self)

        if sys.argv[0].lower().endswith("/carla-plugin-patchbay"):
            from carla_patchbay import CarlaPatchbayW
            self.fContainer = CarlaPatchbayW(self, host)
            self.setupContainer(True, self.fContainer.themeData)
        else:
            from carla_rack import CarlaRackW
            self.fContainer = CarlaRackW(self, host)
            self.setupContainer(False)

        self.fFirstInit = True

        self.setWindowTitle(self.fUiName)
        self.ready()

    # -------------------------------------------------------------------
    # ExternalUI Callbacks

    def d_uiShow(self):
        if self.parent() is None:
            self.show()

    def d_uiHide(self):
        if self.parent() is None:
            self.hide()

    def d_uiQuit(self):
        self.close()
        app.quit()

    def d_uiTitleChanged(self, uiTitle):
        self.setWindowTitle(uiTitle)

    # -------------------------------------------------------------------
    # Qt events

    def closeEvent(self, event):
        self.closeExternalUI()
        HostWindow.closeEvent(self, event)

    # -------------------------------------------------------------------
    # Custom idler

    def idleExternalUI(self):
        while True:
            if self.fPipeRecv is None:
                return True

            try:
                msg = self.fPipeRecv.readline().strip()
            except IOError:
                return False

            if not msg:
                return True

            elif msg.startswith("PEAKS_"):
                pluginId = int(msg.replace("PEAKS_", ""))
                in1, in2, out1, out2 = [float(i) for i in self.readlineblock().split(":")]
                self.host._set_peaks(pluginId, in1, in2, out1, out2)

            elif msg.startswith("PARAMVAL_"):
                pluginId, paramId = [int(i) for i in msg.replace("PARAMVAL_", "").split(":")]
                paramValue = float(self.readlineblock())
                self.host._set_parameterValue(pluginId, paramId, paramValue)

            elif msg.startswith("ENGINE_CALLBACK_"):
                action   = int(msg.replace("ENGINE_CALLBACK_", ""))
                pluginId = int(self.readlineblock())
                value1   = int(self.readlineblock())
                value2   = int(self.readlineblock())
                value3   = float(self.readlineblock())
                valueStr = self.readlineblock().replace("\r", "\n")

                if action == ENGINE_CALLBACK_PLUGIN_RENAMED:
                    self.host._set_pluginName(pluginId, valueStr)
                elif action == ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED:
                    if value1 < 0:
                        self.host._set_internalValue(pluginId, value1, value3)
                    else:
                        self.host._set_parameterValue(pluginId, value1, value3)
                elif action == ENGINE_CALLBACK_PARAMETER_DEFAULT_CHANGED:
                    self.host._set_parameterDefault(pluginId, value1, value3)
                elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CC_CHANGED:
                    self.host._set_parameterMidiCC(pluginId, value1, value2)
                elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED:
                    self.host._set_parameterMidiChannel(pluginId, value1, value2)
                elif action == ENGINE_CALLBACK_PROGRAM_CHANGED:
                    self.host._set_currentProgram(pluginId, value1)
                elif action == ENGINE_CALLBACK_MIDI_PROGRAM_CHANGED:
                    self.host._set_currentMidiProgram(pluginId, value1)

                engineCallback(self.host, action, pluginId, value1, value2, value3, valueStr)

            elif msg.startswith("ENGINE_OPTION_"):
                option = int(msg.replace("ENGINE_OPTION_", ""))
                forced = bool(self.readlineblock() == "true")
                value  = self.readlineblock()

                if self.fFirstInit and not forced:
                    continue

                if option == ENGINE_OPTION_PROCESS_MODE:
                    self.host.processMode = int(value)
                elif option == ENGINE_OPTION_TRANSPORT_MODE:
                    self.host.transportMode = int(value)
                elif option == ENGINE_OPTION_FORCE_STEREO:
                    self.host.forceStereo = bool(value == "true")
                elif option == ENGINE_OPTION_PREFER_PLUGIN_BRIDGES:
                    self.host.preferPluginBridges = bool(value == "true")
                elif option == ENGINE_OPTION_PREFER_UI_BRIDGES:
                    self.host.preferUIBridges = bool(value == "true")
                elif option == ENGINE_OPTION_UIS_ALWAYS_ON_TOP:
                    self.host.uisAlwaysOnTop = bool(value == "true")
                elif option == ENGINE_OPTION_MAX_PARAMETERS:
                    self.host.maxParameters = int(value)
                elif option == ENGINE_OPTION_UI_BRIDGES_TIMEOUT:
                    self.host.uiBridgesTimeout = int(value)
                elif option == ENGINE_OPTION_PATH_BINARIES:
                    self.host.pathBinaries = value
                elif option == ENGINE_OPTION_PATH_RESOURCES:
                    self.host.pathResources = value

            elif msg.startswith("PLUGIN_INFO_"):
                pluginId = int(msg.replace("PLUGIN_INFO_", ""))
                self.host._add(pluginId)

                type_, category, hints, uniqueId, optsAvail, optsEnabled = [int(i) for i in self.readlineblock().split(":")]
                filename  = self.readlineblock().replace("\r", "\n")
                name      = self.readlineblock().replace("\r", "\n")
                iconName  = self.readlineblock().replace("\r", "\n")
                realName  = self.readlineblock().replace("\r", "\n")
                label     = self.readlineblock().replace("\r", "\n")
                maker     = self.readlineblock().replace("\r", "\n")
                copyright = self.readlineblock().replace("\r", "\n")

                pinfo = {
                    'type': type_,
                    'category': category,
                    'hints': hints,
                    'optionsAvailable': optsAvail,
                    'optionsEnabled': optsEnabled,
                    'filename': filename,
                    'name':  name,
                    'label': label,
                    'maker': maker,
                    'copyright': copyright,
                    'iconName': iconName,
                    'patchbayClientId': 0,
                    'uniqueId': uniqueId
                }
                self.host._set_pluginInfo(pluginId, pinfo)
                self.host._set_pluginRealName(pluginId, realName)

            elif msg.startswith("AUDIO_COUNT_"):
                pluginId, ins, outs = [int(i) for i in msg.replace("AUDIO_COUNT_", "").split(":")]
                self.host._set_audioCountInfo(pluginId, {'ins': ins, 'outs': outs})

            elif msg.startswith("MIDI_COUNT_"):
                pluginId, ins, outs = [int(i) for i in msg.replace("MIDI_COUNT_", "").split(":")]
                self.host._set_midiCountInfo(pluginId, {'ins': ins, 'outs': outs})

            elif msg.startswith("PARAMETER_COUNT_"):
                pluginId, ins, outs, count = [int(i) for i in msg.replace("PARAMETER_COUNT_", "").split(":")]
                self.host._set_parameterCountInfo(pluginId, count, {'ins': ins, 'outs': outs})

            elif msg.startswith("PARAMETER_DATA_"):
                pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_DATA_", "").split(":")]
                paramType, paramHints, midiChannel, midiCC = [int(i) for i in self.readlineblock().split(":")]
                paramName = self.readlineblock().replace("\r", "\n")
                paramUnit = self.readlineblock().replace("\r", "\n")

                paramInfo = {
                    'name': paramName,
                    'symbol': "",
                    'unit': paramUnit,
                    'scalePointCount': 0,
                }
                self.host._set_parameterInfo(pluginId, paramId, paramInfo)

                paramData = {
                    'type': paramType,
                    'hints': paramHints,
                    'index': paramId,
                    'rindex': -1,
                    'midiCC': midiCC,
                    'midiChannel': midiChannel
                }
                self.host._set_parameterData(pluginId, paramId, paramData)

            elif msg.startswith("PARAMETER_RANGES_"):
                pluginId, paramId = [int(i) for i in msg.replace("PARAMETER_RANGES_", "").split(":")]
                def_, min_, max_, step, stepSmall, stepLarge = [float(i) for i in self.readlineblock().split(":")]

                paramRanges = {
                    'def': def_,
                    'min': min_,
                    'max': max_,
                    'step': step,
                    'stepSmall': stepSmall,
                    'stepLarge': stepLarge
                }
                self.host._set_parameterRanges(pluginId, paramId, paramRanges)

            elif msg.startswith("PROGRAM_COUNT_"):
                pluginId, count, current = [int(i) for i in msg.replace("PROGRAM_COUNT_", "").split(":")]
                self.host._set_programCount(pluginId, count)
                self.host._set_currentProgram(pluginId, current)

            elif msg.startswith("PROGRAM_NAME_"):
                pluginId, progId = [int(i) for i in msg.replace("PROGRAM_NAME_", "").split(":")]
                progName = self.readlineblock().replace("\r", "\n")
                self.host._set_programName(pluginId, progId, progName)

            elif msg.startswith("MIDI_PROGRAM_COUNT_"):
                pluginId, count, current = [int(i) for i in msg.replace("MIDI_PROGRAM_COUNT_", "").split(":")]
                self.host._set_midiProgramCount(pluginId, count)
                self.host._set_currentMidiProgram(pluginId, current)

            elif msg.startswith("MIDI_PROGRAM_DATA_"):
                pluginId, midiProgId = [int(i) for i in msg.replace("MIDI_PROGRAM_DATA_", "").split(":")]
                bank, program = [int(i) for i in self.readlineblock().split(":")]
                name = self.readlineblock().replace("\r", "\n")
                self.host._set_midiProgramData(pluginId, midiProgId, {'bank': bank, 'program': program, 'name': name})

            elif msg == "error":
                error = self.readlineblock().replace("\r", "\n")
                engineCallback(self.host, ENGINE_CALLBACK_ERROR, 0, 0, 0, 0.0, error)

            elif msg == "show":
                self.fFirstInit = False
                self.d_uiShow()

            elif msg == "hide":
                self.d_uiHide()

            elif msg == "quit":
                self.fQuitReceived = True
                self.d_uiQuit()

            elif msg == "uiTitle":
                uiTitle = self.readlineblock().replace("\r", "\n")
                self.d_uiTitleChanged(uiTitle)

            else:
                print("unknown message: \"" + msg + "\"")

        return True

# ------------------------------------------------------------------------------------------------------------
# Embed plugin UI

if LINUX and not config_UseQt5:
    from PyQt4.QtGui import QHBoxLayout, QX11EmbedWidget

    class CarlaEmbedW(QX11EmbedWidget):
        def __init__(self, host, winId):
            QX11EmbedWidget.__init__(self)
            self.host = host

            self.fWinId = winId

            self.fLayout = QVBoxLayout(self)
            self.fLayout.setContentsMargins(0, 0, 0, 0)
            self.fLayout.setSpacing(0)
            self.setLayout(self.fLayout)

            gui = CarlaMiniW(host, self)
            gui.hide()

            gui.ui.act_file_quit.setEnabled(False)
            gui.ui.menu_File.setEnabled(False)
            gui.ui.menu_File.setVisible(False)
            #menuBar = gui.menuBar()
            #menuBar.removeAction(gui.ui.menu_File.menuAction())

            self.addWidget(gui.menuBar())
            self.addLine()
            self.addWidget(gui.ui.toolBar)
            self.addLine()
            self.addWidget(gui.centralWidget())

            self.setFixedSize(740, 512)
            self.embedInto(winId)
            self.show()

        def addWidget(self, widget):
            widget.setParent(self)
            self.fLayout.addWidget(widget)

        def addLine(self):
            line = QFrame(self)
            line.setFrameShadow(QFrame.Sunken)
            line.setFrameShape(QFrame.HLine)
            line.setLineWidth(0)
            line.setMidLineWidth(1)
            self.fLayout.addWidget(line)

        def showEvent(self, event):
            QX11EmbedWidget.showEvent(self, event)

            # set our gui as parent for all plugins UIs
            winIdStr = "%x" % self.fWinId
            self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr)

        def hideEvent(self, event):
            # disable parent
            self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0")

            QX11EmbedWidget.hideEvent(self, event)

# ------------------------------------------------------------------------------------------------------------
# Main

if __name__ == '__main__':
    # -------------------------------------------------------------
    # App initialization

    app = CarlaApplication("Carla2-Plugin")

    # -------------------------------------------------------------
    # Set-up custom signal handling

    setUpSignals()

    # -------------------------------------------------------------
    # Init host backend

    host = initHost("Carla-Plugin", PluginHost, False, True, True)
    host.processMode       = ENGINE_PROCESS_MODE_PATCHBAY if sys.argv[0].lower().endswith("/carla-plugin-patchbay") else ENGINE_PROCESS_MODE_CONTINUOUS_RACK
    host.processModeForced = True

    # FIXME
    loadHostSettings(host)

    # -------------------------------------------------------------
    # Create GUI

    try:
        winId = int(os.getenv("CARLA_PLUGIN_EMBED_WINID"))
    except:
        winId = 0

    host.setenv("CARLA_PLUGIN_EMBED_WINID", "0")

    if LINUX and winId != 0 and not config_UseQt5:
        gui = CarlaEmbedW(host, winId)
    else:
        gui = CarlaMiniW(host)

    # -------------------------------------------------------------
    # simulate an engire started callback FIXME

    engineCallback(host, ENGINE_CALLBACK_ENGINE_STARTED, 0, host.processMode, ENGINE_TRANSPORT_MODE_PLUGIN, 0.0, "Plugin")

    # -------------------------------------------------------------
    # App-Loop

    app.exit_exec()
