
import logging
from time import monotonic, sleep
from zyncoder.zyncore import lib_zyncore
from zyngine.zynthian_signal_manager import zynsigman
from zyngine.ctrldev.zynthian_ctrldev_base import zynthian_ctrldev_base
from zynlibs.zynseq import zynseq
from zyngine.zynthian_engine_sooperlooper import zynthian_engine_sooperlooper



# ──────────────────────────────────────────────────────────────────────────────
#  CONSTANTES — MAPPING MIDI LPD8 GEN1
# ──────────────────────────────────────────────────────────────────────────────

# Canal MIDI des pads (0-indexed : canal MIDI 1 = 0)
LPD8_PAD_CHANNEL  = 0

# CC des 8 knobs (gauche→droite)
KNOB_CC = [24,25,26,27]
#CC des Mutes 4 pads bas
MUTE_CC = [106,107,108,109]
MUTE2_CC = [36,37,38,39]
LOOP_SELECTED_SYMBOL = "selected_loop_num"
WET_SYMBOL = "wet"

# ──────────────────────────────────────────────────────────────────────────────
#  DRIVER PRINCIPAL
# ──────────────────────────────────────────────────────────────────────────────

class zynthian_ctrldev_akai_mpk_mini(zynthian_ctrldev_base):
   

    # ── Identification ALSA ───────────────────────────────────────────────────
    # Vérifier avec 'aconnect -l' et adapter si nécessaire.
    dev_ids = [
        "MPK Mini Mk II IN 1",
    ]

    # Les notes de pads sont consommées ici.
    # Les CC des knobs passent librement (return False dans midi_event).
    unroute_from_chains = False

  
    def __init__(self, state_manager, idev_in, idev_out):
        super().__init__(state_manager, idev_in, idev_out)
        
        # S'abonner au signal de chargement ZS3
        zynsigman.register_queued(zynsigman.S_STATE_MAN, zynsigman.SS_LOAD_ZS3, self.on_zs3_loaded)
        zynsigman.register_queued(zynsigman.S_STATE_MAN, zynsigman.SS_LOAD_SNAPSHOT, self.on_snapshot_loaded)
        self.idev_out = idev_out

        self.chain = None
        self.processor = None
        self.loop = None
        self.mute_state=[False,False,False,False]
        
        logging.debug("MPK MINI (Vangelis): driver prêt — ")
        return

    def end(self):
        zynsigman.unregister(zynsigman.S_STATE_MAN, zynsigman.SS_LOAD_ZS3, self.on_zs3_loaded)
        zynsigman.unregister(zynsigman.S_STATE_MAN, zynsigman.SS_LOAD_SNAPSHOT, self.on_snapshot_loaded)
        super().end()
        return
        
    def on_snapshot_loaded(self):
        self.refresh(force=True)
        return

    def on_zs3_loaded(self, zs3_id):
        self.refresh(force=True)
        return

    # ── Refresh — resynchronisation des LEDs ──────────────────────────────────

    def refresh(self, force=False):
        #get sooperlooper processor
        cm = self.state_manager.chain_manager
        for chain_id, chain in cm.chains.items():
            if chain_id == 0:
                continue
                
            logging.debug(f"Chaine : {chain.get_name()}")
            processors = chain.get_processors()
            for processor in processors:
                logging.debug(f"Processor : {processor.get_name()}")
                if processor.get_name()=="SooperLooper":
                    self.chain = chain
                    self.processor = processor
                    for symbol, zctrl in processor.controllers_dict.items():
                        
                        logging.debug(f"Parametre : {symbol}  Valeur : {processor.controllers_dict[symbol].get_value()}")
        
        if self.processor is not None:
            count=0
            for note in MUTE2_CC:
                key = f"mute:{count}"
                
                if key in self.processor.controllers_dict:
                    logging.debug(f"CLE : {key} VALEUR : {int(self.processor.controllers_dict[key].get_value())}")
                    self.note_on(0, 9+count,0)
                    self.mute_state[count] = False
                    #(0, 9+count,int(self.processor.controllers_dict[key].get_value())*127)
                
                count=count+1
        
        
        return
     

    def note_off(self,  channel : int, note: int):
        lib_zyncore.dev_send_note_off(self.idev_out, channel, note, 0)
        return

    def note_on(self,channel : int,  note: int, velocity : int):
        lib_zyncore.dev_send_note_on(self.idev_out, channel, note, velocity)
        return
    
    # ── Traitement des événements MIDI ────────────────────────────────────────

    def midi_event(self, ev):
        """
        Dispatcher les événements MIDI reçus du LPD8.

        Retourne True  → événement consommé ici.
        Retourne False → événement transmis normalement dans Zynthian.
        """
        evtype = (ev[0] >> 4) & 0x0F
        channel = ev[0] & 0x0F
        byte1   = ev[1] & 0x7F
        byte2   = ev[2] & 0x7F
        
        logging.debug(f"Evtype : {evtype}")
        logging.debug(f"Channel : {channel}")
        logging.debug(f"Byte1 : {byte1}")
        logging.debug(f"Byte2 : {byte2}")

        # ── Canal des PADS : canal MIDI 1 (ch=0) ─────────────────────────────
        #if channel == LPD8_PAD_CHANNEL:
        if evtype == 0xB:                
            if byte1 in KNOB_CC:
                loop = int(self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].get_value())
                
                #select loop 1 to 4 depending of Knob 1 to 4
                if byte1==24:
                    if loop!=1:
                        self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].set_value(1)
                elif byte1==25:
                    if loop!=2:
                        self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].set_value(2)
                elif byte1==26:
                    if loop!=3:
                        self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].set_value(3)
                elif byte1==27:
                    if loop!=4:
                        self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].set_value(4)
                #set paramter wet of the loop selected   
                self.processor.controllers_dict[WET_SYMBOL].set_value(byte2/127)
                
                return True   
                
                
            if byte1 in MUTE_CC:
                #If pad 1 to 3, mute loop 1 to 3
                if byte1==MUTE_CC[0]:
                    if "mute:0" in self.processor.controllers_dict:
                        self.mute_state[0] = not self.mute_state[0]
                        self.note_on(0, 9,int(self.mute_state[0])*127)
                        self.processor.controllers_dict["mute:0"].set_value(int(self.mute_state[0]))
                if byte1==MUTE_CC[1]:
                    if "mute:1" in self.processor.controllers_dict:
                        self.mute_state[1] = not self.mute_state[1]
                        self.note_on(0, 10,int(self.mute_state[1])*127)
                        self.processor.controllers_dict["mute:1"].set_value(int(self.mute_state[1]))
                if byte1==MUTE_CC[2]:
                    if "mute:2" in self.processor.controllers_dict:
                        self.mute_state[2] = not self.mute_state[2]
                        self.note_on(0, 11,int(self.mute_state[2])*127)
                        self.processor.controllers_dict["mute:2"].set_value(int(self.mute_state[2]))  
                # if Pad 4, trigger the selected loop
                if byte1==MUTE_CC[3]:
                    loop = int(self.processor.controllers_dict[LOOP_SELECTED_SYMBOL].get_value()) - 1                    
                    trigger = f"trigger:{loop}"
                    logging.debug(f"Trigger = {trigger}")
                    self.processor.controllers_dict[trigger].set_value(1)
                return True   
            
                
                    
            
              
        return False # libres pour MIDI Learn dans Zynthian

    # ── Logique métier : chaînes et bypass ────────────────────────────────────

