#!/bin/python
import serial
import wx
import threading
import serial_ports 

class AudioMuxConnection(threading.Thread):
	def __init__(self, port=None):
		super().__init__()

		self.transport = None
		self.buffer = ""
		self.running = 0

		if port:
			self.open(port)

	def open(self, portname):
		self.transport = serial.Serial(portname, timeout=1)
		self.buffer = ""
		self.onConnected()

	def close(self):
		if self.transport:
			self.stop()
			self.transport.close()
			self.transport = None

	def write(self, msg):
		if self.transport:
			self.transport.write((str(msg) + "\n").encode("ASCII"))
		
		
	def run(self):
		self.running = 1
		self.write("?") # query current state

		while self.running:
			self.onData(self.transport.read_all())

		self.transport.close()
		self.running = 0

	def stop(self):
		if self.running:
			self.running = 0
			self.join()
		

	def onData(self, data):
		""" Decodes all messages that are received from the audiomux """
		self.buffer += data.decode("ASCII")

		if "\n" in self.buffer:
			lines = self.buffer.split("\n")

			self.buffer = lines[-1]
			for line in lines[:-1]:
				self.onMessage(line)

	def onMessage(self, msg):
		""" The callback for a new message """
		pass

	def onConnected(self):
		""" The Callback for a successfull connection """
		pass


class ConnectionPanel(wx.Panel):
	def __init__(self, parent, onAccepted=None):
		super().__init__(parent)
		self.choice = wx.Choice(self, choices=serial_ports.list_all())
		
		self.button = wx.Button(self, label="Connect")
		self.button.Bind(wx.EVT_BUTTON, self.onSelected)

		sizer = wx.BoxSizer(wx.HORIZONTAL)
		sizer.Add(self.choice, wx.EXPAND)
		sizer.Add(self.button)

		self.SetSizerAndFit(sizer)
		self.onAccepted = onAccepted

	def onSelected(self, evt):
		self.value = self.choice.GetSelection()

		if self.value and self.onAccepted:
			self.onAccepted(self.value)

	@property
	def value(self):
		if hasattr(self, "_value") and self._value != wx.NOT_FOUND:
			return self.choice.GetString(self._value)
		
		return None

	@value.setter
	def value(self, v):
		self._value = v

	def Success(self):
		self.choice.Enable(0)
		self.button.SetLabel("Disconnect")

	def Clear(self):
		self.choice.Clear()
		self.choice.AppendItems(serial_ports.list_all())
		self.choice.Enable(1)
		self.button.SetLabel("Connect")

class InputButtons(wx.Panel):
	def __init__(self, parent, onChange=None):
		super().__init__(parent)
		sizer = wx.BoxSizer(wx.HORIZONTAL)
		self.onChange = onChange
		self.buttons = {}

		for i in range(6):
			self.buttons[i] = wx.ToggleButton(self, size=(30,30), label="{}".format(i+1), id = i)
			self.buttons[i].Bind(wx.EVT_TOGGLEBUTTON, lambda evt: self.onClick(evt.GetId()))
			sizer.Add(self.buttons[i], wx.EXPAND | wx.SHAPED)
		
		self.SetSizer(sizer)

	def SetActive(self, num):
		for i in self.buttons:
			if num == i:
				self.buttons[i].SetValue(1)
			else:
				self.buttons[i].SetValue(0)
	def SetAll(self):
		for button in self.buttons.values():
			button.SetValue(1)

	def onClick(self, num):
		self.SetActive(num)
		if self.onChange: 
			self.onChange(num)

class MainPanel(wx.Panel):
	def __init__(self, parent):
		super().__init__(parent)
		self.parent = parent

		sizer = wx.BoxSizer(wx.VERTICAL)

		sizer.Add(self.Inputs(), 0, wx.EXPAND)
		sizer.Add(wx.StaticLine(self), 0, wx.EXPAND)
		sizer.Add(self.Sliders(), 1, wx.EXPAND)
		sizer.Add(wx.StaticLine(self), 0, wx.EXPAND)
		sizer.Add(self.Buttons())

		self.SetSizer(sizer)

	def Inputs(self):
		self.inputs = InputButtons(self, lambda num: self.reqInputChange(num))
		return self.inputs

	def Sliders(self):
		self.gain = wx.Slider(self, style=wx.SL_VERTICAL|wx.SL_INVERSE, minValue=0, maxValue=20, size=(30,100))
		self.latt = wx.Slider(self, style=wx.SL_VERTICAL              , minValue=0, maxValue=90, size=(30,100))
		self.ratt = wx.Slider(self, style=wx.SL_VERTICAL              , minValue=0, maxValue=90, size=(30,100))
		self.bboost = wx.Slider(self, style=wx.SL_VERTICAL|wx.SL_INVERSE, minValue=0, maxValue=6, size=(30,100))
		self.tboost = wx.Slider(self, style=wx.SL_VERTICAL|wx.SL_INVERSE, minValue=0, maxValue=6, size=(30,100))
		
		self.SliderEvent(self.gain, "G")
		self.SliderEvent(self.latt, "L")
		self.SliderEvent(self.ratt, "R")
		self.SliderEvent(self.bboost, "B")
		self.SliderEvent(self.tboost, "T")
		
		sizer = wx.BoxSizer(wx.HORIZONTAL)
		
		self.SliderLabel(sizer, self.gain, "Gain")
		self.SliderLabel(sizer, self.latt, "L")
		self.SliderLabel(sizer, self.ratt, "R")
		self.SliderLabel(sizer, self.bboost, "Bass Boost")
		self.SliderLabel(sizer, self.tboost, "Treble Boost")

		sizer.AddStretchSpacer()
		
		return sizer

	def SliderEvent(self, slider, command):
		slider.Bind(wx.EVT_SLIDER, lambda evt: self.parent.write("{}{}".format(command, evt.GetInt())))


	def SliderLabel(self, sizer, slider, label):
		sizer.AddStretchSpacer()
		temp = wx.BoxSizer(wx.VERTICAL)
		temp.Add(slider, 1, wx.EXPAND|wx.ALL)
		temp.Add(wx.StaticText(self, label=label, style=wx.ALIGN_CENTRE), 0, wx.ALL)
		sizer.Add(temp, 0, wx.EXPAND | wx.ALL)

	def Buttons(self):
		self.muteBtn = wx.ToggleButton(self, label="Mute")
		self.muteBtn.Bind(wx.EVT_TOGGLEBUTTON, lambda evt: self.parent.write("M{}".format(int(evt.IsChecked()))))
		self.shortBtn = wx.ToggleButton(self, label="Short")
		self.shortBtn.Bind(wx.EVT_TOGGLEBUTTON, lambda evt: self.parent.write("S{}".format(int(evt.IsChecked()))))

		sizer = wx.BoxSizer(wx.HORIZONTAL)
		sizer.Add(self.muteBtn)
		sizer.Add(self.shortBtn)
		sizer.AddStretchSpacer()

		return sizer

	def reqInputChange(self, num):
		self.parent.write("C{}".format(num))

	def setInput(self, num):
		self.inputs.SetActive(num)

	def setGain(self, num):
		self.gain.setValue(num)

	def setTreble(self, num):
		self.tboost.SetValue(num)

	def setBass(self, num):
		self.bboost.SetValue(num)
	
	def setLeftAtt(self, num):
		self.latt.SetValue(num)
	
	def setRightAtt(self, num):
		self.ratt.SetValue(num)

	def mute(self, m):
		self.muteBtn.SetValue(m)
		self.shortBtn.Enable(not m)
		self.inputs.Enable(not m)
	
	def short(self, m):
		self.shortBtn.SetValue(m)
		self.muteBtn.Enable(not m)
		self.inputs.Enable(not m)

		if m:
			self.inputs.SetAll()
		
class Frame(wx.Frame, AudioMuxConnection):
	def __init__(self):
		# init parents
		wx.Frame.__init__(self,None, title="AudioMux Control")
		AudioMuxConnection.__init__(self)

		sizer = wx.BoxSizer(wx.VERTICAL)
		sizer.Add(self.newConnectionPanel(), 0, wx.EXPAND)
		sizer.Add(wx.StaticLine(self), 0, wx.EXPAND)
		sizer.Add(self.newMainPanel(), 1, wx.EXPAND)

		self.SetSizerAndFit(sizer)

	def newMainPanel(self):
		self.mainPanel = MainPanel(self)
		self.mainPanel.Enable(0)

		return self.mainPanel

	def newConnectionPanel(self):
		self.connectionPanel  = ConnectionPanel(self, lambda choice: self.onConnectionRequest(choice))
		return self.connectionPanel

	def onConnectionRequest(self, choice):
		if self.running:
			# stop the connection and update connection dialog
			self.close()
			self.connectionPanel.Clear()
			self.mainPanel.Enable(0)
		else:
			# try to start the serial connection
			AudioMuxConnection.__init__(self, choice)
			
	def onConnected(self):
		# serial connection established
		self.connectionPanel.Success()
		self.start()
		self.mainPanel.Enable(1)

	def onMessage(self, msg):
		msg = msg.strip("\r\n ")

		if msg[0] == 'C':
			num = int(msg.strip("C"))
			self.mainPanel.setInput(num)
		elif msg[0] == 'G':
			num = int(msg.strip("G"))
			self.mainPanel.setGain(num)
		elif msg[0] == 'T':
			num = int(msg.strip("T"))
			self.mainPanel.setTreble(num)
		elif msg[0] == 'B':
			num = int(msg.strip("B"))
			self.mainPanel.setBass(num)
		elif msg[0] == 'M':
			num = int(msg.strip("M"))
			self.mainPanel.mute(num)
		elif msg[0] == 'S':
			num = int(msg.strip("S"))
			self.mainPanel.short(num)
		elif msg[0] == 'L':
			num = int(msg.strip("L"))
			self.mainPanel.setLeftAtt(num)
		elif msg[0] == 'R':
			num = int(msg.strip("R"))
			self.mainPanel.setRightAtt(num)


if __name__ == "__main__":
	app = wx.App()
	frame = Frame()

	try:
		frame.Show()
		app.MainLoop()
	finally:
		frame.close()


	
