import math
from ableton.v2.base import liveobj_valid, listens
from ableton.v2.control_surface import Component, Layer
from ableton.v2.control_surface.mode import ModeButtonBehaviour
from ableton.v2.control_surface.control import RadioButtonControl, control_list, ButtonControl
from ableton.v2.control_surface.components import ClipSlotComponent, SceneComponent, SessionComponent, SessionRingComponent, SessionNavigationComponent, SimpleTrackAssigner

from ..elements import Elements
from .. import colors
from ..util import ExquisMode, ExquisModesComponent, all_tracks
from .mixer import ExquisMixerComponent
from ..color import Color, FX

class ExquisSessionRingComponent(SessionRingComponent):
	"A SessionRingComponent that snaps to the number of scenes when moving vertically"

	def _snapped_offsets(self, track_offset, scene_offset):
		snapped_track_offset = track_offset - track_offset % self.num_tracks
		snapped_scene_offset = scene_offset - scene_offset % self.num_scenes
		return (snapped_track_offset, snapped_scene_offset)

class ExquisClipSlotComponent(ClipSlotComponent):
	"A ClipSlotComponent that takes the color of the hosted clip where long-pressing the launch button allows to directly go back to the keyboard"

	@ClipSlotComponent.launch_button.pressed_delayed
	def _on_launch_button_pressed_delayed(self, button):
		self.canonical_parent.show_message("Release to directly go back to the keyboard")

	@ClipSlotComponent.launch_button.released_delayed
	def _on_launch_button_released_delayed(self, button):
		if not liveobj_valid(self._clip_slot):
			return
		if self._clip_slot.is_triggered and self._clip_slot.will_record_on_start:
			if hasattr(self.canonical_parent, "switch_to_notes_mode"):
				getattr(self.canonical_parent, "switch_to_notes_mode")()

	def _update_launch_button_color(self):
		if self.is_enabled():
			value_to_send = Color(0)
			if liveobj_valid(self._clip_slot):
				track = self._clip_slot.canonical_parent
				slot_or_clip = self._clip_slot.clip if self.has_clip() else self._clip_slot
				value_to_send = self._feedback_value(track, slot_or_clip)
			self.launch_button.color = value_to_send

	def _feedback_value(self, track, slot_or_clip):
		"Return the color for the given slot or clip"

		if slot_or_clip.color is not None:
			color = self._color_value(slot_or_clip)
			if slot_or_clip.is_triggered:
				if slot_or_clip.will_record_on_start:
					return self._triggered_to_record_color
				return Color(color, FX.pulsate_white)
			if slot_or_clip.is_playing:
				if slot_or_clip.is_recording:
					return Color(color, FX.pulsate)
				return Color(color)
			return Color(color, FX.darker(75))
		else:
			if slot_or_clip.is_triggered:
				if slot_or_clip.will_record_on_start:
					return self._triggered_to_record_color
				return self._triggered_to_play_color
			if slot_or_clip.is_playing:
				if slot_or_clip.is_recording:
					return self._recording_color
				return self._started_value
		
		if getattr(slot_or_clip, 'controls_other_clips', True):
			return self._stopped_value
		if self._track_is_armed(track):
			if self._clip_slot.has_stop_button:
				if self._record_button_color is not None:
					return self._record_button_color
		return self._empty_slot_color

class ExquisSceneComponent(SceneComponent):
	"A SceneComponent that reflects the state of the scene like in Live's Session View"

	clip_slot_component_type = ExquisClipSlotComponent

	def __init__(self, *a, **k):
		super(ExquisSceneComponent, self).__init__(*a, **k)
		self._selected_color = "Session.SceneSelected"
	
	def _update_launch_button(self):
		if self.is_enabled():
			value_to_send = self._no_scene_color
			if liveobj_valid(self._scene):
				value_to_send = self._scene_color
				if self._scene.is_triggered:
					value_to_send = self._triggered_color
				elif self._scene == self.song.view.selected_scene:
					value_to_send = self._selected_color
				else:
					possible_color = self._color_value(self._scene.color)
					if possible_color:
						value_to_send = possible_color
			self.launch_button.color = value_to_send

class ExquisSessionComponent(SessionComponent):
	"A SessionComponent adapted to the Exquis"

	scene_component_type = ExquisSceneComponent

	def _update_stop_all_clips_button(self):
		button = self._stop_all_button
		if button:
			color = "Session.StopClipTriggered" if button.is_pressed() else "Session.StopClip"
			button.set_light(color)
	
	def _update_all_scene_buttons(self):
		for scene in self._scenes:
			scene._update_launch_button()

class LongPressResetBehaviour(ModeButtonBehaviour):
	"A ModeButtonBehaviour that resets the parameter value when long-pressing the button"

	def __init__(self, mixer, index, *a, **k):
		super(LongPressResetBehaviour, self).__init__(*a, **k)
		self.index = index
		self.mixer = mixer
		
	def release_immediate(self, component, mode):
		component.push_mode(mode)

	def press_delayed(self, component, mode):
		"Reset the parameter value"

		selected_mode = component.selected_mode
		channel_strip = self.mixer.channel_strip(self.index)

		param = None
		if selected_mode == "volume":
			if channel_strip._volume_control is not None:
				param = channel_strip._volume_control.mapped_parameter()
		elif selected_mode == "pan":
			if channel_strip._pan_control is not None:
				param = channel_strip._pan_control.mapped_parameter()
		elif selected_mode == "send_a":
			if channel_strip._send_controls and len(channel_strip._send_controls) > 0:
				param = channel_strip._send_controls[0].mapped_parameter()
		elif selected_mode == "send_b":
			if channel_strip._send_controls and len(channel_strip._send_controls) > 1:
				param = channel_strip._send_controls[1].mapped_parameter()
		if param is not None and param.is_enabled:
			param.value = param.default_value

class SessionModeComponent(Component):
	"A component that handles the Session mode of the Exquis"

	new_scene_button = ButtonControl(color="Session.NewScene")
	new_track_button = ButtonControl(color="Session.NewTrack")
	slider = control_list(RadioButtonControl)

	def __init__(self, elements: Elements, *a, **k):
		super(SessionModeComponent, self).__init__(*a, **k)
		self.encoders = elements.encoders_dev_mode_elements

		self.session_ring = ExquisSessionRingComponent(
			name="SessionRing",
			num_tracks=4,
			num_scenes=7,
			tracks_to_use=lambda: all_tracks(self.song),
			always_snap_track_offset=True,
		)

		self.session_navigation = SessionNavigationComponent(
			name="SessionNavigation",
			session_ring=self.session_ring,
		)

		self.session = ExquisSessionComponent(
			name="Session",
			session_ring=self.session_ring,
		)
		self.session.set_rgb_mode(colors.LIVE_COLORS_TO_MIDI_VALUES, colors.RGB_COLOR_TABLE)

		self.mixer = ExquisMixerComponent(
			name="Mixer",
			tracks_provider=self.session_ring,
			track_assigner=SimpleTrackAssigner()
		)

		self.encoder_modes = ExquisModesComponent(name="SessionEncoderModes")
		self.add_children(self.encoder_modes)
		
		self.encoder_modes.add_mode("volume", ExquisMode(
			component=self.mixer,
			layer=Layer(volume_controls=elements.encoders_dev_mode_elements)
		), behaviour=LongPressResetBehaviour(self.mixer, 0))
		self.encoder_modes.add_mode("pan", ExquisMode(
			component=self.mixer,
			layer=Layer(pan_controls=elements.encoders_dev_mode_elements)
		), behaviour=LongPressResetBehaviour(self.mixer, 1))
		self.encoder_modes.add_mode("send_a", ExquisMode(
			component=self.mixer,
			layer=Layer(send_a_controls=elements.encoders_dev_mode_elements)
		), behaviour=LongPressResetBehaviour(self.mixer, 2))
		self.encoder_modes.add_mode("send_b", ExquisMode(
			component=self.mixer,
			layer=Layer(send_b_controls=elements.encoders_dev_mode_elements)
		), behaviour=LongPressResetBehaviour(self.mixer, 3))
		self.encoder_modes.layer = Layer(
			volume_button=elements.encoders_buttons_dev_mode["encoder_push_0_CC_dev_mode"],
			pan_button=elements.encoders_buttons_dev_mode["encoder_push_1_CC_dev_mode"],
			send_a_button=elements.encoders_buttons_dev_mode["encoder_push_2_CC_dev_mode"],
			send_b_button=elements.encoders_buttons_dev_mode["encoder_push_3_CC_dev_mode"]
		)
		self.encoder_modes.selected_mode = "volume"

		self.on_encoder_mode_changed.subject = self.encoder_modes
		self.on_tracks_changed.subject = self.session_ring
		self.on_selected_track_changed.subject = self.song.view
		self.on_selected_scene_changed.subject = self.song.view
	
	@listens("selected_mode")
	def on_encoder_mode_changed(self, mode):
		for e in filter(None, self.encoders):
			e.is_bidirectional = mode == "pan"
		if mode == "volume":
			for e in filter(None, self.encoders):
				e.palette_index = 21
		elif mode == "pan":
			for e in filter(None, self.encoders):
				e.palette_index = 128
		elif mode == "send_a":
			for e in filter(None, self.encoders):
				e.palette_index = colors.color_value(self.song.return_tracks[0].color) if len(self.song.return_tracks) > 0 else 0
		elif mode == "send_b":
			for e in filter(None, self.encoders):
				e.palette_index = colors.color_value(self.song.return_tracks[1].color) if len(self.song.return_tracks) > 1 else 0

	def update(self):
		if self.is_enabled():
			self.on_encoder_mode_changed(self.encoder_modes.selected_mode)
		super(SessionModeComponent, self).update()
		if self.is_enabled():
			self.on_selected_track_changed()
			self.on_tracks_changed()

	def set_up_button(self, _):
		self.session_navigation.set_page_up_button(_)
	def set_down_button(self, _):
		self.session_navigation.set_page_down_button(_)
	def set_left_button(self, _):
		self.session_navigation.set_page_left_button(_)
	def set_right_button(self, _):
		self.session_navigation.set_page_right_button(_)
	def set_clip_launch_buttons(self, _):
		self.session.set_clip_launch_buttons(_)
	def set_scene_launch_buttons(self, _):
		self.session.set_scene_launch_buttons(_)
	def set_stop_buttons(self, _):
		self.session.set_stop_track_clip_buttons(_)
	def set_stop_all_clips_button(self, _):
		self.session.set_stop_all_clips_button(_)
	def set_duplicate_button(self, _):
		self.session.set_managed_duplicate_button(_)
		self.mixer.set_managed_duplicate_button(_)
	def set_select_button(self, _):
		self.session.set_managed_select_button(_)
	def set_delete_button(self, _):
		self.session.set_managed_delete_button(_)
		self.mixer.set_managed_delete_button(_)
	def set_solo_buttons(self, _):
		self.mixer.set_solo_buttons(_)
	def set_mute_buttons(self, _):
		self.mixer.set_mute_buttons(_)
	def set_track_select_buttons(self, _):
		self.mixer.set_track_select_buttons(_)

	@listens("tracks")
	def on_tracks_changed(self):
		self.slider.control_count = math.ceil(len(self.session_ring.tracks_to_use()) / 4)
		slider_i = self.session_ring.track_offset // 4
		self.slider.checked_index = slider_i

	@listens("selected_scene")
	def on_selected_scene_changed(self):
		self.session._update_all_scene_buttons()
	
	@listens("selected_track")
	def on_selected_track_changed(self):
		selected_track = self.song.view.selected_track
		tracks = self.session_ring.tracks_to_use()
		if selected_track in tracks:
			index = tracks.index(selected_track)
			if not (self.session_ring.track_offset <= index < self.session_ring.track_offset + 4):
				index = (index // 4) * 4
				self.session_ring.set_offsets(index, self.session_ring.scene_offset)

	@slider.checked
	def on_slider(self, button):
		v = button.index
		if v > 5:
			v = 5
		self.session_ring.set_offsets(v * 4, self.session_ring.scene_offset)

	@new_scene_button.released_immediately
	def on_new_scene_button_pressed(self, _):
		self.canonical_parent.show_message("Long press to create a new scene")

	@new_track_button.released_immediately
	def on_new_track_button_pressed(self, _):
		self.canonical_parent.show_message("Long press to create a new track")

	@new_scene_button.pressed_delayed
	def on_new_scene_button_pressed(self, _):
		scene = self.song.create_scene(-1)
		if scene:
			self.song.view.selected_scene = scene
	
	@new_track_button.pressed_delayed
	def on_new_track_button_pressed(self, _):
		self.song.begin_undo_step()
		
		old_track = self.song.view.selected_track
		track = self.song.create_midi_track(-1)
		if not track:
			self.song.end_undo_step()
			return

		if old_track.can_be_armed and old_track.arm and self.song.exclusive_arm:
			old_track.arm = False
		self.song.view.selected_track = track
		track.arm = True

		self.song.end_undo_step()
