from enum import Enum

from ableton.v2.control_surface import ControlSurface
from ableton.v2.control_surface.layer import Layer
from ableton.v2.control_surface.mode import ModeButtonBehaviour
from ableton.v2.control_surface.components import BackgroundComponent as BackgroundComponentBase, UndoRedoComponent
from ableton.v2.control_surface.mode import ModesComponent, AddLayerMode
from ableton.v2.base import listens

from .devmode import DevMode, devmode_command
from .elements import Elements
from .component.session import SessionModeComponent
from .component.presets import PresetsModeComponent
from .component.tracks import TracksModeComponent
from .component.notes import NotesModeComponent
from .component.transport import TransportComponent
from .component.tempo import TempoModeComponent
from .component.settings import SettingsComponent, SCALES
from .component.device import DeviceModeComponent
from .skin import make_skin
from .util import ExquisMode, ExquisModesComponent, track_id
from .color import SelectedTrackColor, FX

def tracks_to_use(song):
	"Returns the tracks to use in the TracksModeComponent"

	return tuple(song.visible_tracks) + tuple(song.return_tracks) + (song.master_track,)

class BackgroundComponent(BackgroundComponentBase):
	"A BackgroundComponent with Exquis fixes"

	def __init__(self, *a, **k):
		super(BackgroundComponent, self).__init__(*a, **k)

	def _reset_control(self, control):
		control.reset()
		if control.name == "Encoders":
			for encoder in control:
				encoder.palette_index = 0

class ExquisScreen(Enum):
	KEYBOARD = 0
	SETTINGS = 1
	SOUND = 2

class Exquis(ControlSurface):
	"The main class of the Exquis control surface"

	def __init__(self, *a, **k):
		super(Exquis, self).__init__(*a, **k)
		self._last_mode = "session"
		self.standalone_screen = ExquisScreen.KEYBOARD
		self._track_id_for_upcoming_snapshot = None
		self.last_track_id = None
		self.skin = make_skin()
		with self.component_guard():
			self._init_devmode()
			self._init_elements()
			self._init_components()
			self._init_modes()
			self.on_selected_track_changed.subject = self.song.view
			self.on_mode_changed(self.modes.selected_mode)
		print("Exquis: Initialized")
	
	def _init_devmode(self):
		self.devmode = DevMode()
		self.devmode.add_value_listener(self.on_devmode)
	
	def _init_elements(self):
		self.elements = Elements(make_skin())
		self.elements.refresh.add_value_listener(self.on_refresh)
		self.elements.track_snapshot.add_value_listener(self.on_track_snapshot_received)
		self.elements.encoders_ccs_snapshot.add_value_listener(self.on_encoders_ccs_snapshot_received)
	
	def _init_components(self):
		# The background component's job is to reset the LEDs to their default state when not in use
		self.background = BackgroundComponent(name="Background")
		self.background.layer = Layer(
			up_button=self.elements.button["up"],
			down_button=self.elements.button["down"],
			left_button=self.elements.button["left"],
			right_button=self.elements.button["right"],
			matrix=self.elements.matrix_all,
			encoders=self.elements.encoders_dev_mode_elements,
			encoder0_push=self.elements.encoders_buttons_dev_mode["encoder_push_0_CC_dev_mode"],
			encoder1_push=self.elements.encoders_buttons_dev_mode["encoder_push_1_CC_dev_mode"],
			encoder2_push=self.elements.encoders_buttons_dev_mode["encoder_push_1_CC_dev_mode"],
			encoder3_push=self.elements.encoders_buttons_dev_mode["encoder_push_1_CC_dev_mode"],
			encoders_notes=self.elements.encoders_elements,
			encoder0_push_notes=self.elements.encoders_buttons["encoder_push_0_CC"],
			encoder1_push_notes=self.elements.encoders_buttons["encoder_push_1_CC"],
			encoder2_push_notes=self.elements.encoders_buttons["encoder_push_2_CC"],
			encoder3_push_notes=self.elements.encoders_buttons["encoder_push_3_CC"],
			notes_button=self.elements.button["loop"],
			session_button=self.elements.button["session"],
			play_button=self.elements.button["start_stop"],
			record_button=self.elements.button["record"],
			loop_button=self.elements.button["loop"],
			slider=self.elements.slider,
		)

		self.settings = SettingsComponent(name="Settings")
		self.settings.layer = Layer(
			tempo=self.elements.tempo,
			tempo_push=self.elements.tempo_push,
			root_note=self.elements.root_note,
			scale_index=self.elements.scale_index,
			custom_scale=self.elements.custom_scale,
		)

		self.transport = TransportComponent(song=self.song)
		self.transport.layer = Layer(
			start_stop=self.elements.button["start_stop"],
			record=self.elements.button["record"],
		)
		self.session_mode = SessionModeComponent(self.elements, song=self.song)
		self.tracks_mode = TracksModeComponent(song=self.song)
		self.notes_mode = NotesModeComponent(song=self.song)

		self.undo_redo = UndoRedoComponent()
		self.undo_redo.layer = Layer(
			undo_button=self.elements.button["left"],
			redo_button=self.elements.button["right"],
		)

		self.tempo = TempoModeComponent(song=self.song)

		self.device = DeviceModeComponent()

		self.presets = PresetsModeComponent()

	def _init_modes(self):
		class LongPressBehaviour(ModeButtonBehaviour):
			"A mode button behaviour that switches to a different mode when long-pressed"

			def __init__(self, long_press_mode, selected_color, unselected_color, *a, **k):
				super(LongPressBehaviour, self).__init__(*a, **k)
				self.long_press_mode = long_press_mode
				self.mode_selected_color = selected_color
				self.mode_unselected_color = unselected_color
				self.inhibit_release = False

			def press_immediate(self, component, mode):
				if self.long_press_mode is None:
					component.selected_mode = mode
					self.inhibit_release = True
					return
				if callable(self.long_press_mode):
					self.long_press_mode(True)
					component.selected_mode = mode
					return
				
				component.selected_mode = self.long_press_mode

			def release_immediate(self, component, mode):
				self.release_delayed(component, mode)
			
			def press_delayed(self, component, mode):
				pass
			
			def release_delayed(self, component, mode):
				if self.inhibit_release:
					self.inhibit_release = False
					return
				if callable(self.long_press_mode):
					self.long_press_mode(False)
				if component.selected_mode != mode:
					component.selected_mode = mode

		self.modes = ExquisModesComponent(name="Modes")
		self.modes.add_mode("notes", ExquisMode(
			component=self.notes_mode,
			layer=Layer(
				
			)
		), behaviour=LongPressBehaviour("tracks", selected_color=SelectedTrackColor(self.song.view), unselected_color=SelectedTrackColor(self.song.view, FX.darker(100))))
		self.modes.add_mode("session", ExquisMode(
			component=self.session_mode,
			layer=Layer(
				up_button=self.elements.button["up"],
				down_button=self.elements.button["down"],
				clip_launch_buttons=self.elements.matrix_clips,
				scene_launch_buttons=self.elements.matrix_scene_launch,
				solo_buttons=self.elements.matrix_solo,
				mute_buttons=self.elements.matrix_mute,
				track_select_buttons=self.elements.matrix_select,
				stop_buttons=self.elements.matrix_stop,
				stop_all_clips_button=self.elements.matrix_stop_all_clips,
				duplicate_button=self.elements.matrix_solo_right,
				select_button=self.elements.matrix_select_left,
				new_scene_button=self.elements.matrix_mute_right,
				new_track_button=self.elements.matrix_mute_left,
				delete_button=self.elements.matrix_select_right,
				slider=self.elements.slider,
			)
		), behaviour=LongPressBehaviour(self._on_session_pressed, selected_color=SelectedTrackColor(self.song.view), unselected_color=SelectedTrackColor(self.song.view, FX.darker(100))))
		self.modes.add_mode("tracks", ExquisMode(
			component=self.tracks_mode,
			layer=Layer(
				track_select_buttons=self.elements.matrix_all_but_last_row,
				encoders=self.elements.encoders_dev_mode_elements,
				encoder0_push=self.elements.encoders_buttons_dev_mode["encoder_push_0_CC_dev_mode"],
				encoder1_push=self.elements.encoders_buttons_dev_mode["encoder_push_1_CC_dev_mode"],
				encoder2_push=self.elements.encoders_buttons_dev_mode["encoder_push_2_CC_dev_mode"],
				encoder3_push=self.elements.encoders_buttons_dev_mode["encoder_push_3_CC_dev_mode"],
				left_button=self.elements.button["left"],
				right_button=self.elements.button["right"],
			)
		))
		self.modes.selected_mode = "notes"
		self.modes.layer = Layer(
			notes_button=self.elements.button["loop"],
			session_button=self.elements.button["session"],
		)
		self.on_mode_changed.subject = self.modes

		self.tempo_mode = ModesComponent(name="TempoModes")
		self.tempo.set_enabled(False)
		self.tempo_mode.add_mode("off", None)
		self.tempo_mode.add_mode("on", ExquisMode(
			component=self.tempo,
			layer=Layer(
				up_button=self.elements.button["up"],
				down_button=self.elements.button["down"],
			)
		))
		self.tempo_mode.selected_mode = "off"

		self.device_mode = ModesComponent(name="DeviceModes")
		self.device_mode.add_mode("off", AddLayerMode(
			component=self.device,
			layer=Layer(
				encoders=self.elements.encoders_elements,
				encoder0_push=self.elements.encoders_buttons["encoder_push_0_CC"],
				encoder1_push=self.elements.encoders_buttons["encoder_push_1_CC"],
				encoder2_push=self.elements.encoders_buttons["encoder_push_2_CC"],
				encoder3_push=self.elements.encoders_buttons["encoder_push_3_CC"],
			)
		))
		self.device_mode.add_mode("on", AddLayerMode(
			component=self.device,
			layer=Layer(
				bank_buttons=self.elements.slider,
				device_buttons=self.elements.matrix_last_row,
			)
		))
		self.device_mode.selected_mode = "off"

		self.preset_mode = ModesComponent(name="PresetModes")
		self.preset_mode.add_mode("off", None)
		self.preset_mode.add_mode("on", AddLayerMode(
			component=self.presets,
			layer=Layer(
				load_buttons=self.elements.matrix_all,
				page_buttons=self.elements.slider,
				up_button=self.elements.button["up"],
				down_button=self.elements.button["down"],
				left_button=self.elements.button["left"],
				right_button=self.elements.button["right"],
			)
		))
		self.preset_mode.selected_mode = "off"

	def _on_session_pressed(self, _):
		self._last_session_pressed = _
		self._tasks.add(self._on_session_pressed_task)
	
	def _on_session_pressed_task(self, _):
		self.tempo_mode.selected_mode = "on" if self._last_session_pressed else "off"

	@listens("selected_mode")
	def on_mode_changed(self, mode):
		self.update_devmode()
		# switching to session mode 
		if mode == "session":
			# Remove implicit arm from all MIDI tracks
			for t in self.song.tracks:
				if hasattr(t, "implicit_arm") and getattr(t, "has_midi_input", False) and getattr(t, "can_be_armed", False):
					t.implicit_arm = False
			# Audio tracks stays as they are in exlusive and non exclusive arm 
		
		# switching to note and track modes 	
		elif mode in ("tracks", "notes") and self._last_mode == "session":
			# midi track is selected : 
			#	 exclusif mode : 
			# 	 	if not armed : implicit armed 
			# 		if armed : stays hard armed do nothing
			#	 not exclusif mode : 
			#		if not armed : implicit armed 
			#		if armed : stays hard armed do nothing 
			# midi track is not selected : 
			#	 exclusif mode :
			# 		if not armed : stays not armed 
			# 		if armed : disarmed 
			#	 not exclusif mode : 
			# 		if not armed : stays not armed 
			# 		if armed : stays armed  
			# audio tracks is selected :
			# 	exclusive mode : 
			# 		if not armed : stays the same 
			#		if armed : stays the same 
			# 	non exclusive mode : 
			#		if not armed : stays the same 
			#		if armed : stays the same 
			# audio tracks is not selected :
			# 	exclusive mode : 
			# 		if not armed : stays the same 
			#		if armed : disarmed 
			# 	non exclusive mode : 
			#		if not armed : stays the same 
			#		if armed : stays the same
			for t in self.song.tracks:
				if getattr(t, "can_be_armed", False):
					if getattr(t, "has_midi_input", False) and t == self.song.view.selected_track:
						t.implicit_arm = True
					if getattr(t,"arm",False) and getattr(t, "has_midi_input", False) and t != self.song.view.selected_track and self.song.exclusive_arm:
						t.arm = False 
					if getattr(t,"arm",False) and getattr(t, "has_audio_input", False) and t != self.song.view.selected_track and self.song.exclusive_arm:
						t.arm = False

		if mode == "tracks":
			self.tempo_mode.selected_mode = "on"
			self.device_mode.selected_mode = "on"
		else:
			self.tempo_mode.selected_mode = "off"
			self.device_mode.selected_mode = "off"
		
		self._last_mode = mode
		self.update()

	def switch_to_notes_mode(self):
		self.modes.selected_mode = "notes"

	@listens("selected_track")
	def on_selected_track_changed(self):
		track = self.song.view.selected_track
		tid = track_id(track)
		# print("Exquis: Selected track changed to", track.name, "with ID", tid)
		# print("Exquis: Last track ID was", self.last_track_id)

		if tid != self.last_track_id:
			self.request_track_snapshot(self.last_track_id)

		self.last_track_id = tid
		self.send_track_snapshot(track)
		self.update()

	def disconnect(self):
		#FIXME: We can't self.request_track_snapshot(self.last_track_id) because the MIDI ports are already closed. What can we do?
		self.devmode.remove_value_listener(self.on_devmode)
		self.elements.refresh.remove_value_listener(self.on_refresh)
		super(Exquis, self).disconnect()
	
	def port_settings_changed(self):
		"Called when the MIDI ports of the control surface are changed."
		self.standalone_screen = ExquisScreen.KEYBOARD
		self.update_devmode()
		self.send_command(DevMode.CMD_USE_CUSTOM_SCALES_IN_SETTINGS, len(SCALES))
		self.send_palette()
		self.update()

		track = self.song.view.selected_track
		self.last_track_id = track_id(track)
		self.send_track_snapshot(track)

		self.request_encoders_ccs_snapshot()
	
	def on_devmode(self, _):
		# print("Exquis: Devmode change confirmed")
		pass
	
	def on_refresh(self, _):
		self.presets.switch_to_notes_mode_after_load = False

		if _[0] == 0: # Entered the Settings menu
			self.standalone_screen = ExquisScreen.SETTINGS
		elif _[0] == 1: # Entered the Sound menu
			self.standalone_screen = ExquisScreen.SOUND
			self.presets.rescan_presets()
			self.preset_mode.selected_mode = "on"
		elif _[0] == 0x7F:
			self.standalone_screen = ExquisScreen.KEYBOARD
			self.preset_mode.selected_mode = "off"
			self.request_track_snapshot(self.last_track_id)
		
		self.update_devmode()
		self.update()
	
	def update_devmode(self):
		if self.standalone_screen != ExquisScreen.KEYBOARD:
			self.devmode.send_value(DevMode.MASK_IN_SETTINGS if self.standalone_screen == ExquisScreen.SETTINGS else DevMode.MASK_IN_SOUND)
		else:
			mode = self.modes.selected_mode
			if mode == "notes":
				self.devmode.send_value(DevMode.MASK_OTHER_BUTTONS)
			else:
				self.devmode.send_value(DevMode.MASK_ALL_BUT_SETTINGS_SOUND)
		
		self.send_command(DevMode.CMD_REFRESH)
	
	def send_palette(self):
		def chunked_palette_commands(chunk_size=20):
			from .colors import RGB_COLOR_TABLE
			header = list(devmode_command(DevMode.CMD_PALETTE)[0:-1])
			footer = [0xF7]

			for i in range(0, len(RGB_COLOR_TABLE), chunk_size):
				chunk = RGB_COLOR_TABLE[i:i+chunk_size]
				data = header.copy()
				data.append(chunk[0][0])

				for _, rgb in chunk:
					b = (rgb & 0xFF) >> 1
					g = ((rgb >> 8) & 0xFF) >> 1
					r = ((rgb >> 16) & 0xFF) >> 1
					data += [r, g, b]
				data += footer
				yield data
		for chunk in chunked_palette_commands():
			self._send_midi(tuple(chunk), False)
	
	def send_command(self, cmd, *a):
		self._send_midi(devmode_command(cmd, *a), False)
	
	def send_track_snapshot(self, track):
		if not track:
			return
		data = track.get_data("exquis_snapshot", None)
		if data and len(data) > 0:
			# print("Exquis: Sending snapshot for track", track.name)
			# print(data)
			# print(len(data))
			self.send_command(DevMode.CMD_SNAPSHOT, *data)

	def request_track_snapshot(self, tid):
		print("Exquis: Requesting snapshot for track", tid)
		self._track_id_for_upcoming_snapshot = tid
		self.send_command(DevMode.CMD_SNAPSHOT)

	def request_encoders_ccs_snapshot(self):
		#print("Exquis: Requesting snapshot for rotary encoders ccs")
		self.send_command(DevMode.CMD_ENCODERS_CCS_SNAPSHOT)

	def update_save_status(self):
		old_name = self.song.view.selected_track.name
		self.song.view.selected_track.name = old_name + "bis"
		self.song.view.selected_track.name = old_name
		return

	def on_track_snapshot_received(self, snapshot):
		t_id = 0
		if self._track_id_for_upcoming_snapshot == None:
			t_id = self.last_track_id
		else:
			t_id = self._track_id_for_upcoming_snapshot
		
		for t in self.song.tracks:
			if track_id(t) == t_id:
				print("Exquis: Received snapshot for track", t.name)
				print("Exquis: track id", t_id)
				self._track_id_for_upcoming_snapshot = None
				self.update_save_status ()
				t.set_data("exquis_snapshot", snapshot)
				return

	def on_encoders_ccs_snapshot_received(self, snapshot):
		self.elements.update_encoders_cc(snapshot,self.skin) 
		#self.background.layer.encoders_notes = self.elements.encoders_elements
		 # -- Destroy the old ModesComponent --
		if hasattr(self, "device_mode"):
			try:
			# Remove from control surface, disconnect listeners, etc.
				self.disconnect_component(self.device_mode)
			except Exception:
				pass
			del self.device_mode

		# -- Recreate ModesComponent and add mode again --
		self.device_mode = ModesComponent(name="DeviceModes")
		self.device_mode_off_layer = Layer(
			encoders=self.elements.encoders_elements,
			encoder0_push=self.elements.encoders_buttons["encoder_push_0_CC"],
			encoder1_push=self.elements.encoders_buttons["encoder_push_1_CC"],
			encoder2_push=self.elements.encoders_buttons["encoder_push_2_CC"],
			encoder3_push=self.elements.encoders_buttons["encoder_push_3_CC"],
		)
		self.device_mode.add_mode("off", AddLayerMode(
			component=self.device,
			layer=self.device_mode_off_layer
		))
		self.device_mode.add_mode("on", AddLayerMode(
			component=self.device,
			layer=Layer(
				bank_buttons=self.elements.slider,
				device_buttons=self.elements.matrix_last_row,
			)
		))
		self.device_mode.selected_mode = "off"
	
	


