# ----------------------------------------------------------------------------
#
#  Copyright (C) 2018-2022 Fons Adriaensen <fons@linuxaudio.org>
#    
#  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 3 of the License, or
#  (at your option) 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.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http:#www.gnu.org/licenses/>.
#
# ----------------------------------------------------------------------------


import sys
import signal
import globdef as G
sys.path.append ('..')
from PyQt6 import QtGui, QtCore, QtWidgets
from copy import deepcopy 
from os.path import *
from utils.buttonbase import *
from gainmatwin import *
from delaymatwin import *
from inputwin import *
from outputwin import *
from matrixstate import *
from zita_jacktools.jackmatrix import db2lin


class Mainwindow (QtWidgets.QWidget):


    loadreqEvent = QtCore.pyqtSignal(object)

    
    def __init__(self, inps, outs, matrix, label = 'Matrix'): 
        super (Mainwindow, self).__init__()
        
        # Init main window.
        self.setWindowTitle (label)
        self.ninp = len (inps)
        self.nout = len (outs)
        self.move (100, 100)
        xs = self.ninp * (G.ELM_XS + 1) + G.OP_XS + 10
        ys = self.nout * (G.ELM_YS + 1) + G.IP_YS + 60
        if xs < 200: xs = 200
        self.setFixedSize (xs, ys)

        # Button styles.
        pal = self.palette () 
        bgc = pal.color (pal.ColorRole.Window)
        bsty1 = ButtonStyle (49, 19, bgc, [G.FG_N,G.COL4,G.COL5], [G.MK_N,G.COL0,G.COL0], G.BUT_FONT, 10)
        bsty2 = ButtonStyle (28, 21, bgc, [G.FG_N,G.COL5,G.COL2], [G.MK_N,G.COL0,G.COL9], G.BUT_FONT, 10)

        # Create the top menu buttons.
        B = ButtonBase (self, bsty1, True, 0)
        B.set_text ("Load")
        B.move (0, 0)
        B.brelseEvent.connect (self.load_action)
        B = ButtonBase (self, bsty1, True, 1)
        B.set_text ("Save")
        B.move (50, 0)
        B.brelseEvent.connect (self.save_action)
        B = ButtonBase (self, bsty1, True, 1)
        B.set_text ("SaveAs")
        B.move (100, 0)
        B.brelseEvent.connect (self.saveas_action)
        B = ButtonBase (self, bsty1, True, 0)
        B.set_text ("Reset")
        B.move (150, 0)
        B.brelseEvent.connect (self.clear_action)

        # Create the matrix windows.
        x = 10
        y = 20
        self.inputwin = Inputwin (self, self.ninp, self.input_callb, inps) 
        self.inputwin.move (x, y)
        y += G.IP_YS
        self.gainmatwin = Gainmatwin (self, self.nout, self.ninp, self.matrix_callb)
        self.gainmatwin.move (x, y)
        self.delaymatwin = Delaymatwin (self, self.nout, self.ninp, self.matrix_callb)
        self.delaymatwin.hide ()
        self.delaymatwin.move (x, y)
        x += self.gainmatwin.xs
        self.outputwin = Outputwin (self, self.nout, self.output_callb, outs) 
        self.outputwin.move (x, y)
        y -= G.IP_YS
        self.bgain = ButtonBase (self, bsty1, True, 0)
        self.bgain.set_text ("Gain")
        self.bgain.move (x + 20, y + 20)
        self.bgain.set_state (1)
        self.bgain.bpressEvent.connect (self.select_gain)
        self.bdelay = ButtonBase (self, bsty1, True, 0)
        self.bdelay.set_text ("Delay")
        self.bdelay.move (x + 20, y + 40)
        self.bdelay.bpressEvent.connect (self.select_delay)

        # Create preset buttons.
        n = 2 * min (10, (xs - 20) // 60)
        self.bpreset = [ ButtonBase (self, bsty2, True, i) for i in range (n) ]
        self.presets = [ Matrixstate (self.ninp, self.nout) for i in range (n) ]
        self.npreset = n;
        self.curpres = None
        for i in range (n):
            self.bpreset [i].move (10 + i * 30, ys - 30)
            self.bpreset [i].set_text (str (i+1))
            self.bpreset [i].brelseEvent.connect (self.preset_action)
        
        # Rename the JackMatrix ports.
        self.jackmatrix = matrix
        for i in range (self.ninp):
            self.jackmatrix.rename_input (i, "in-" + inps [i])
        for i in range (self.nout):
            self.jackmatrix.rename_output (i, "out-" + outs [i])

        # Apply current state.    
        self.currstate = Matrixstate (self.ninp, self.nout)
        self.apply_iostate ()
        self.apply_matstate ()
        self.path = '.'
        self.name = 'presets'
        self.loadreqEvent.connect (self.loadEvent)
        self.loadind = None
        self.show ()

        
    def select_gain (self, args):
        # Select gain matrix.
        self.bgain.set_state (1)
        self.bdelay.set_state (0)
        self.gainmatwin.show ()
        self.delaymatwin.hide ()

        
    def select_delay (self, args):
        # Select delay matrix.
        self.bdelay.set_state (2)
        self.bgain.set_state (0)
        self.delaymatwin.show ()
        self.gainmatwin.hide ()

        
    def input_callb (self, index, action, value, button):
        # Callback from input controls.
        W = self.inputwin
        D = self.currstate.inpgain
        E = D.elem (index)
        if   action == G.ACT_GAIN: E.gain = value
        elif action == G.ACT_MUTE: E.mute = value
        elif action == G.ACT_SOLO:
            if value and button == QtCore.Qt.MouseButton.LeftButton:
                W.exc_solo (index)
        else: return
        s = W.any_solo ()
        if action == G.ACT_SOLO:
            for i in range (0, self.ninp):
                self.set_inpgain (i, s)
        else:
            self.set_inpgain (index, s)
            self.preset_modif ()

            
    def output_callb (self, index, action, value, button):
        # Callback from output controls.
        W = self.outputwin
        D = self.currstate.outgain
        E = D.elem (index)
        if   action == G.ACT_GAIN: E.gain = value
        elif action == G.ACT_MUTE: E.mute = value
        elif action == G.ACT_SOLO:
            if value and button == QtCore.Qt.MouseButton.LeftButton:
                W.exc_solo (index)
        else: return
        s = W.any_solo ()
        if action == G.ACT_SOLO:
            for i in range (0, self.nout):
                self.set_outgain (i, s)
        else:
            self.set_outgain (index, s)
            self.preset_modif ()

        
    def matrix_callb (self, index, action, value, flags):
        # Callback from matrix controls.
        inp = index [1]
        out = index [0]
        if action == G.ACT_GAIN:
            E = self.currstate.matrix.elem (inp, out)
            E.gain = value
            E.inv = flags
            g = db2lin (value)
            if E.inv: g *= -1
            self.jackmatrix.set_matrix_gain (inp, out, g)
            self.delaymatwin.set_state (out, inp, None, E.gain > -200)
        elif action == G.ACT_DELAY:
            E = self.currstate.matrix.elem (inp, out)
            E.delay = value
            self.jackmatrix.set_matrix_delay (inp, out, 1e-3 * value)
        else: return
        self.preset_modif ()

        
    def load_action (self):    
        # Load presets from file.
        name = QtWidgets.QFileDialog.getOpenFileName (self, 'Load preset', self.path, '')
        if len (name [0]) == 0: return
        self.name = name [0]
        self.path = dirname (self.name)
        with open (name [0], 'r') as fobj:
            self.preset_modif ()
            while True:
                s = fobj.readline ()
                if len (s) == 0: break
                if s [0] == '#': continue
                s = s.split ()
                if len (s) != 2: continue
                if s [0] == 'PRE':
                    k = int (s [1]) 
                    if k > self.npreset: break
                    self.presets [k-1].read (fobj)
        if self.curpres is not None:
            self.curpres.set_state (0)
            self.curpres = None

            
    def save_action (self):                
        # Save all presets to file.
        if self.name is not None:
            self.save_presets ()

            
    def saveas_action (self):                
        # Save all presets to file.
        name = QtWidgets.QFileDialog.getSaveFileName (self, 'Save preset', self.path, '')
        if len (name [0]) == 0: return
        self.name = name [0]
        self.path = dirname (self.name)
        self.save_presets ()

        
    def save_presets (self):                
        # Save all presets to file.
        with open (self.name, 'w') as fobj:
            for k in range (self.npreset):
                fobj.write ("# --------------\n")
                fobj.write ("PRE %d\n" % (k+1,))
                self.presets [k].write (fobj)            

                
    def clear_action (self, B):
        # Clear current state or only solo.
        if B.modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier:
            self.currstate.reset ()
            self.apply_matstate ()
            if self.curpres is not None:
                self.curpres.set_state (0)
                self.curpres = None
        self.inputwin.exc_solo (None)
        self.outputwin.exc_solo (None)
        self.apply_iostate ()
      
            
    def preset_action (self, B):
        # Load or store a preset.
        if B.modifiers == QtCore.Qt.KeyboardModifier.ShiftModifier:
            self.presets [B.index] = deepcopy (self.currstate)
        else:
            self.currstate = deepcopy (self.presets [B.index])
            self.apply_iostate ()
            self.apply_matstate ()
        if self.curpres is not None:
            self.curpres.set_state (0)
        self.curpres = B
        self.curpres.set_state (1)

        
    def preset_modif (self):
        # Mark preset as modified
        if self.curpres is not None:
            self.curpres.set_state (2)

            
    def apply_iostate (self):
        # Copy current state to JackMatrix and GUI.
        # Inputs.
        D = self.currstate.inpgain
        s = self.inputwin.any_solo ()
        for inp in range (self.ninp):
            E = D.elem (inp)
            self.set_inpgain (inp, s)
            self.inputwin.set_state (inp, E.gain, E.mute)
        # Outputs.
        D = self.currstate.outgain
        s = self.outputwin.any_solo ()
        for out in range (self.nout):
            E = D.elem (out)
            self.set_outgain (out, s)
            self.outputwin.set_state (out, E.gain, E.mute)

            
    def apply_matstate (self):
        # Copy current state to JackMatrix and GUI.
        # Matrix.
        D = self.currstate.matrix
        for out in range (self.nout):
            for inp in range (self.ninp):
                E = D.elem (inp, out)
                g = db2lin (E.gain)
                if E.inv: g *= -1
                self.jackmatrix.set_matrix_gain (inp, out, g)
                self.jackmatrix.set_matrix_delay (inp, out, 1e-3 * E.delay)
                self.gainmatwin.set_state (out, inp, E.gain, E.inv)
                self.delaymatwin.set_state (out, inp, E.delay, E.gain > -200)

                
    def set_inpgain (self, inp, anysolo):
        # Set input gain including solo logic.
        W = self.inputwin
        E = self.currstate.inpgain.elem (inp)
        if E.mute or (anysolo and not W.get_solo (inp)): g = 0
        else: g = db2lin (E.gain)
        self.jackmatrix.set_input_gain (inp, g)

        
    def set_outgain (self, out, anysolo):
        # Set output gain including solo logic.
        W = self.outputwin
        E = self.currstate.outgain.elem (out)
        if E.mute or (anysolo and not W.get_solo (out)): g = 0
        else: g = db2lin (E.gain)
        self.jackmatrix.set_output_gain (out, g)

        
    def loadEvent (self, E):
        ButtonBase.modifiers = None
        self.preset_action (self.bpreset [self.loadind])

        
    def loadreq (self, k):
        self.loadind = k
        self.loadreqEvent.emit (self)
