#include "lgi/common/Lgi.h"
#include "lgi/common/Button.h"
#include "lgi/common/Variant.h"
#include "lgi/common/Path.h"
#include "lgi/common/Midi.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/Combo.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/ThreadEvent.h"
#include "lgi/common/EventTargetThread.h"

#include "mc4.h"
#include "resdefs.h"

#define IDC_LEDRING_BASE		200
#define IDC_BUTTON_BASE			300

#define LED_RING_PX				140
#define LED_RING_BTN_RADIUS		(LED_RING_PX/2*0.3)
#define LED_RING_LED_PX			7

enum ButtonEvents
{
	BTN_DOWN = 2000,
	BTN_UP,
	BTN_HELD
};

class LedRing : public LView, public ResObject
{
	int Index;
	int64 Pos;
	Hardware::ParamColourMode Mode;
	LMouse Click;
	int64 ClickPos;
	LEventSinkI *Sink;

public:
	int Min, Max;

	LedRing(int Id = -1, int Idx = 0) :
		Sink(NULL),
		ResObject(Res_Custom)
	{
		Min = 0;
		Max = 127;
		Pos = 0;
		Mode = Hardware::ParamColourRamp;
		Index = Idx;
		SetId(Id);
		
		SetColour(LColour(0x60, 0xe0, 0xe0), true);
	}

	void SetSink(LEventSinkI *sink)
	{
		Sink = sink;
	}

	void SetRange(int minRange, int maxRange)
	{
		Min = minRange;
		Max = maxRange;
		Invalidate();
	}
	
	void SetMode(Hardware::ParamColourMode m)
	{
		Mode = m;
		Invalidate();
	}
	
	int64 Value()
	{
		return Pos;
	}
	
	void Value(int64 p)
	{
		if (p < Min)
			p = Min;
		else if (p > Max)
			p = Max;
		if (p != Pos)
		{
			Pos = p;
			Invalidate();
			SendNotify();
		}
	}

	bool OnLayout(LViewLayoutInfo &Inf)
	{
		if (Inf.Width.Min == 0)
		{
			Inf.Width.Min =	 Inf.Width.Max = LED_RING_PX;
		}
		else
		{
			Inf.Height.Min = Inf.Height.Max = LED_RING_PX;
		}
		
		return true;
	}
	
	void OnPaint(LSurface *pDC)
	{
		LDoubleBuffer Db(pDC);
		LColour Back = LColour(L_MED);

		int Cx = (X()>>1)-1;
		int Cy = (Y()>>1)-1;
		pDC->Colour(Back);
		pDC->Rectangle();
		pDC->Colour(LColour(L_LOW).Mix(LColour::Black,0.4f));
		#if 0
		pDC->Box();
		#endif
		pDC->FilledCircle(Cx, Cy, LED_RING_BTN_RADIUS);
		
		int Leds = 16;
		double LedPx = LED_RING_LED_PX;
		LCss::ColorDef ColDef = GetCss(true)->Color();
		LColour Cols[2];
		Cols[0] = Back.Mix(LColour::Black, 0.03f);
		Cols[1].Set(ColDef.Rgb32, 32);
		
		int CurLed = 0;
		for (int s=0; s<Leds; s++)
		{
			double r = Cx - LedPx;
			LPointF pt(0, r);
			double a = (double)s * 2 * LGI_PI / Leds;
			pt.Rotate(a);
			
			int Range = Max - Min + 1;
			int Block = Range / Leds;
			int Index = ((int)Pos - Min) * Leds / Range;
			LColour c;

			switch (Mode)
			{
				default:
				case Hardware::ParamColourSingle:
				{
					c = Cols[Index == CurLed];
					break;
				}
				case Hardware::ParamColourRamp:
				{
					if (Index == CurLed)
						c = Cols[1];
					else if (Index > CurLed)
						c = Cols[1].Mix(Back);
					else
						c = Cols[0];
					break;
				}
			}
			
			LSolidBrush br(c);
			LPath p;
			p.Circle(Cx + pt.x, Cy + pt.y, (double)LedPx-0.1);
			p.Fill(pDC, br);
			
			CurLed++;
		}		
	}
	
	void OnMouseClick(LMouse &m)
	{
		int Cx = (X()>>1)-1;
		int Cy = (Y()>>1)-1;
		int x = abs(m.x - Cx);
		int y = abs(m.y - Cy);
		double Dist = sqrt( (double) ((x*x) + (y*y)) );

		#if 0
		LString msg;
		msg.Printf("OnMouseClick Dist=%f\n", Dist);
		m.Trace(msg);
		#endif

		/*
		if (Dist < ENCODER_BTN_RADIUS)
		{
			if (m.Down() && Sink)
			{
				Sink->PostEvent(M_ON_BUTTON,
								(LMessage::Param)(BtnEncoder1 + Index),
								(LMessage::Param)BTN_DOWN);
			}
		}
		else
		*/
		{
			Capture(m.Down());
			if (m.Down())
			{
				Click = m;
				ClickPos = Pos;
			}
		}
	}
	
	void OnMouseMove(LMouse &m)
	{
		#if 0
		LString msg;
		msg.Printf("OnMouseMove Cap=%i\n", IsCapturing());
		m.Trace(msg);
		#endif
		
		if (IsCapturing())
		{
			int Dy = Click.y - m.y;
			int64 NewPos = ClickPos + Dy;
			if (NewPos < Min)
				NewPos = Min;
			else if (NewPos > Max)
				NewPos = Max;				
			if (NewPos != Pos)
			{
				Value(NewPos);
			}
		}
	}
};

DeclFactory(LedRing)

class LLed : public LView, public ResObject
{
	LColour Col;

public:
	LLed() : ResObject(Res_Custom)
	{
		SetDefault();
	}

	void SetDefault()
	{
		Col.Rgb(128, 128, 128);
		Invalidate();
	}
	
	bool SetColour(LColour &c, bool Fore)
	{
		Col = c;
		Invalidate();
		return true;
	}
	
	void OnPaint(LSurface *pDC)
	{
		LDoubleBuffer Db(pDC);

		pDC->Colour(L_MED);
		pDC->Rectangle();

		int h = X() >> 1;
		LSolidBrush br(Col);
		LPath p;
		p.Circle(h, h, (double)h-0.1);
		p.Fill(pDC, br);
	}

	bool OnLayout(LViewLayoutInfo &Inf)
	{
		if (Inf.Width.Min == 0)
		{
			Inf.Width.Min =	
				Inf.Width.Max = 16;
		}
		else
		{
			Inf.Height.Min =	
				Inf.Height.Max = 16;
		}
		
		return true;
	}
};

DeclFactory(LLed)

class LBtn : public LView, public ResObject
{
	bool Down;

public:
	LBtn(int id = -1, const char *name = NULL) : ResObject(Res_Custom)
	{
		Down = false;
		if (id > 0)
			SetId(id);
		if (name)
			Name(name);
		LCss::ColorDef nopaint = GetCss(true)->NoPaintColor();
		GetCss(true)->NoPaintColor(LCss::ColorDef(LCss::ColorTransparent));
	}
	
	bool OnLayout(LViewLayoutInfo &Inf)
	{
		Inf.Width.Min = Inf.Width.Max = 30;
		Inf.Height.Min = Inf.Height.Max = 30;
		return true;
	}
	
	void OnMouseClick(LMouse &m)
	{
		Down = m.Down();
		SetPulse(Down ? BUTTONS_HELD_TIME : -1);
		Capture(Down);
		SendNotify(m);
		Invalidate();
	}
	
	void OnPulse()
	{
		SendNotify(LNotifyActivate); //BTN_HELD
		SetPulse();
	}
	
	void OnPaint(LSurface *pDC)
	{
		LRect c = GetClient();
		LThinBorder(pDC, c, Down ? EdgeXpSunken : EdgeXpRaised);
		pDC->Colour(LColour(222, 222, 222));
		pDC->Rectangle(&c);
		
		LDisplayString ds(GetFont(), Name());
		GetFont()->Transparent(true);
		GetFont()->Fore(LColour::Black);
		ds.Draw(pDC,
				c.x1 + ((c.X() - ds.X()) >> 1) + Down,
				c.y1 + ((c.Y() - ds.Y()) >> 1) + Down,
				&c);
	}
};

DeclFactory(LBtn)

struct HardwareUi : public LLayout, public LResourceLoad
{
	HardwarePriv *Hw;
	bool IgnoreNotify;

	// Buttons
	int Btns;
	LArray<Hardware::ButtonModes> BtnModes;
	
	// LEDs
	int Leds;
	LArray<LLed*> Led;
	
	// Continuous parameters
	int Encoders;
	int ExpressionInputs;
	LArray<LedRing*> Rings;
	
	// Midi setup UI
	LCombo *MidiInSelect, *MidiOutSelect;
	LButton *MidiConnect;
	
	HardwareUi(HardwarePriv *priv)
	{
		SetPourLargest(true);
		IgnoreNotify = false;
		Hw = priv;
		Btns = 14;
		Leds = 14;
		Encoders = 6;
		ExpressionInputs = 4;
		
		MidiInSelect = NULL;
		MidiOutSelect = NULL;
		MidiConnect = NULL;
	}
	
	~HardwareUi()
	{
	}

	const char *GetClass() override { return "HardwareUi"; }
	void OnCreate() override;
	int OnNotify(LViewI *c, LNotification n) override;
	LMessage::Result OnEvent(LMessage *Msg) override;

	LTableLayout *GetTable()
	{
		if (!Children.Length())
			return NULL;
		auto v = Children[0];
		return dynamic_cast<LTableLayout*>(v);
	}

	void OnPosChange() override
	{
		auto Tbl = GetTable();
		if (Tbl)
		{
			auto c = GetClient();
			Tbl->SetPos(c);
			// LgiTrace("%s:%i - %s -> %s\n", _FL, Tbl->GetClass(), c.GetStr());
		}
		else
		{
			LgiTrace("%s:%i - No table?\n", _FL);
		}
	}
};

struct HardwarePriv : private GMidi, public LEventTargetThread
{
	Mc4Shared *Shared;
	LView *Screen;
	HardwareUi *Ui;
	uint64 TimerTs;

	HardwarePriv(HardwareEvents *callback, Mc4Shared *shared) :
		Callback(callback),
		Shared(shared),
		LEventTargetThread("HardwarePrivThread", false)
	{
		Screen = NULL;
		Ui = new HardwareUi(this);
		TimerTs = 0;
	}
	
	bool MidiOpen()
	{
		// FIXME: Is this thread safe?
		return IsMidiOpen();
	}

	void PopulateCombos()
	{
		int Idx = -1;
		for (unsigned int i=0; i<In.Length(); i++)
		{
			const char *s = In[i];
			if (stristr(s, "Axe-fx"))
				Idx = i;
			Ui->MidiInSelect->Insert(s);
		}
		if (In.Length() == 0)
			Ui->MidiInSelect->Insert("(none)");
		if (Idx >= 0)
			Ui->MidiInSelect->Value(Idx);
			
		Idx = -1;
		for (unsigned int i=0; i<Out.Length(); i++)
		{
			const char *s = Out[i];
			if (stristr(s, "Axe-fx"))
				Idx = i;
			Ui->MidiOutSelect->Insert(s);
		}
		if (Out.Length() == 0)
			Ui->MidiOutSelect->Insert("(none)");
		if (Idx >= 0)
			Ui->MidiOutSelect->Value(Idx);
	}

private:
	HardwareEvents *Callback;

	void OnMidiIn(uint8_t *midi, size_t len)
	{
		Callback->OnMidiIn(midi, len);
	}
	
	void OnPulse() override
	{
		Callback->OnTimer();
	}

	LMessage::Result OnEvent(LMessage *Msg) override
	{
		switch (Msg->Msg())
		{
			case M_OPEN_MIDI:
			{
				LAutoString *ErrorMsg = new LAutoString;
				bool r = Connect((int)Msg->A(), (int)Msg->B(), ErrorMsg);
				Ui->PostEvent(M_OPEN_MIDI, (LMessage::Param)r, (LMessage::Param)ErrorMsg);
				if (r)
					Callback->OnConnection(true);
				break;
			}
			case M_SEND_MIDI:
			{
				LAutoPtr< LArray<uint8_t> > a((LArray<uint8_t>*)Msg->A());
				SendMidi(&a->First(), a->Length(), true);
				break;
			}
			case M_CLOSE_MIDI:
			{
				CloseMidi();
				Ui->PostEvent(M_CLOSE_MIDI);
				Callback->OnConnection(false);
				break;
			}
			case M_SET_PARAM:
			{
				Callback->OnParam((int)Msg->A(), (int)Msg->B());
				break;
			}
			case M_ON_BUTTON:
			{
				auto Sh = Shared->Lock(_FL);
				auto Btn = Sh->Global.GetBtn((int) Msg->A());


				int f = (int) Msg->B();
				Callback->OnButton(	Btn.b, Btn.i,
									f == BTN_DOWN || f == BTN_HELD,
									f == BTN_HELD);
			}
			case M_LED_RING_VALUE:
			case M_LED_RING_COLOUR:
			case M_SET_BUTTON_LABEL:
			{
				// Ui->PostEvent(Msg->Msg(), Msg->A(), Msg->B());
				Ui->OnEvent(Msg);
				break;
			}
			default:
			{
				return Callback->OnEvent(Msg);
			}
		}
		
		return 0;
	}
};

Hardware::Hardware(HardwareEvents *Callback, Mc4Shared *shared)
{
	d = new HardwarePriv(Callback, shared);
}

Hardware::~Hardware()
{
	delete d;
}

void Hardware::Run()
{
	d->Run();
}

bool Hardware::SetTimer(int Ms)
{
	return d->SetPulse(Ms);
}

int Hardware::GetButtons()
{
	return d->Ui->Btns;
}

bool Hardware::SetButtonMode(int Index, ButtonModes Mode)
{
	if (Index >= d->Ui->Btns)
		return false;
	
	d->Ui->BtnModes[Index] = Mode;
	return true;
}

int Hardware::GetParams()
{
	return d->Ui->Encoders + d->Ui->ExpressionInputs;
}

bool Hardware::SetParamRange(int Index, int Min, int Max)
{
	if (Index < GetParams() || !d->Ui->Rings[Index])
		return false;
	
	d->Ui->Rings[Index]->SetRange(Min, Max);
	return true;
}

bool Hardware::SetParamValue(int Index, int Value)
{
	if (Index < GetParams() || !d->Ui->Rings[Index])
		return false;
	
	d->Ui->Rings[Index]->Value(Value);
	return true;
}

bool Hardware::SetParamColour(int Index, LColour Colour, ParamColourMode Mode)
{
	if (Index < GetParams() || !d->Ui->Rings[Index])
		return false;
	
	d->Ui->Rings[Index]->GetCss(true)->Color(LCss::ColorDef(LCss::ColorRgb, Colour.c32()));
	return true;
}

int Hardware::GetLeds()
{
	return d->Ui->Leds;
}

bool Hardware::SetLed(int Index, LColour Col)
{
	if (Index >= d->Ui->Leds || !d->Ui->Led[Index])
		return false;
	
	if (Col.IsValid())
		d->Ui->Led[Index]->SetColour(Col, true);
	else
		d->Ui->Led[Index]->SetDefault();
	return true;
}

bool Hardware::SendMidi(uint8_t *Buf, size_t Length)
{
	if (!Buf || Length == 0)
	{
		LAssert(!"Invalid params");
		return false;
	}
	
	LArray<uint8_t> *a = new LArray<uint8_t>;
	if (!a)
		return false;
	a->Add(Buf, Length);
	return PostEvent(M_SEND_MIDI, (LMessage::Param)a);
}

bool Hardware::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b)
{
	return d->PostEvent(Cmd, a, b);
}

void Hardware::SetInterface(LView *Screen)
{
	d->Screen = Screen;
}

LViewI *Hardware::GetInterface()
{
	return d->Ui;
}

int LedCtrlIds[] = {
	IDC_LED1, IDC_LED2, IDC_LED3, IDC_LED4, IDC_LED5, IDC_LED6, IDC_LED7,
	IDC_LED8, IDC_LED9, IDC_LED10, IDC_LED11, IDC_LED12, IDC_LED13, IDC_LED14
};

int BtnLabelIds[] = {
	IDC_LABEL1, IDC_LABEL2, IDC_LABEL3, IDC_LABEL4, IDC_LABEL5, IDC_LABEL6, IDC_LABEL7, 
	IDC_LABEL8, IDC_LABEL9, IDC_LABEL10, IDC_LABEL11, IDC_LABEL12, IDC_LABEL13, IDC_LABEL14, 
};

void HardwareUi::OnCreate()
{
	if (!LoadFromResource(IDD_HARDWARE_MIMIC, this))
	{
		LAssert(!"No resource.");
		return;
	}

	LedRing *r;
	if (GetViewById(IDC_RING1, r))
	{
		Rings[0] = r;
		r->SetSink(Hw);
	}
	if (GetViewById(IDC_RING2, r))
	{
		Rings[1] = r;
		r->SetSink(Hw);
	}

	LTableLayout *t;
	if (GetViewById(IDC_SCREEN_TABLE, t))
	{
		t->GetCell(0,0)->Add(Hw->Screen);
		t->InvalidateLayout();
	}

	for (int i=0; i<CountOf(LedCtrlIds); i++)
	{
		LLed *u;
		if (GetViewById(LedCtrlIds[i], u))
			Led[i] = u;
	}

	GetViewById(IDC_MIDI_IN, MidiInSelect);
	GetViewById(IDC_MIDI_OUT, MidiOutSelect);
	GetViewById(IDC_MIDI_CONNECT, MidiConnect);
	Hw->PopulateCombos();

	AttachChildren();
}

int HardwareUi::OnNotify(LViewI *c, LNotification n)
{
	if (IgnoreNotify)
		return 0;

	int Id = c->GetId();
	for (auto r: Rings)
	{
		if (r->GetId() == Id)
		{
			auto Index = Rings.IndexOf(r);
			LAssert(Index >= 0);
			auto Value = c->Value();
		
			Hw->PostEvent(M_SET_PARAM, (LMessage::Param)KNOB_BASE+Index, (LMessage::Param)Value);
			return 0;
		}
	}
	
	LMouse m;
	ButtonEvents ev;
	if (n.IsMouseEvent())
	{
		m = n.GetMouseEvent();
		if (m.Down())
			ev = BTN_DOWN;
		else
			ev = BTN_UP;
	}
	else if (n.Type == LNotifyActivate)
	{
		ev = BTN_HELD;
	}
	switch (c->GetId())
	{
		#define HANDLE_BTN(idx) \
			case IDC_BTN##idx: \
				Hw->PostEvent(M_ON_BUTTON, (LMessage::Param)(idx-1), (LMessage::Param)ev); \
				break;
		HANDLE_BTN(1)
		HANDLE_BTN(2)
		HANDLE_BTN(3)
		HANDLE_BTN(4)
		HANDLE_BTN(5)
		HANDLE_BTN(6)
		HANDLE_BTN(7)
		HANDLE_BTN(8)
		HANDLE_BTN(9)
		HANDLE_BTN(10)
		HANDLE_BTN(11)
		HANDLE_BTN(12)
		HANDLE_BTN(13)
		HANDLE_BTN(14)

		case IDC_MIDI_CONNECT:
		{
			if (Hw->MidiOpen())
			{
				Hw->PostEvent(M_CLOSE_MIDI);
			}
			else
			{
				Hw->PostEvent(	M_OPEN_MIDI,
								(LMessage::Param)MidiInSelect->Value(),
								(LMessage::Param)MidiOutSelect->Value());
			}			
			break;
		}
	}
	
	return 0;
}

LMessage::Result HardwareUi::OnEvent(LMessage *Msg)
{
	switch (Msg->Msg())
	{
		case M_OPEN_MIDI:
		{
			bool Result = (bool)(Msg->A()!=0);
			LAutoPtr<LAutoString> ErrorMsg((LAutoString*)Msg->B());
			if (Result)
			{
				MidiConnect->Name("Disconnect");
				MidiConnect->GetParent()->SendNotify(LNotifyTableLayoutRefresh);
			}
			else
			{
				LgiMsg(this, "Error: %s", "Midi Connection", MB_OK, ErrorMsg->Get());
			}
			break;
		}
		case M_CLOSE_MIDI:
		{
			MidiConnect->Name("Connect");
			MidiConnect->GetParent()->SendNotify(LNotifyTableLayoutRefresh);
			break;
		}
		case M_LED_RING_VALUE:
		{
			IgnoreNotify = true;
			
			int Index = (int)Msg->A();
			if (Index >= 0 && Index < (int)Rings.Length())
			{
				LedRing *r = Rings[Index];
				if (r)
				{
					r->Value((int)Msg->B());
					r->Invalidate();
				}
			}

			IgnoreNotify = false;
			break;
		}
		case M_LED_RING_COLOUR:
		{
			IgnoreNotify = true;
			
			int Index = (int)Msg->A();
			if (Index >= 0 && Index < (int)Rings.Length())
			{
				LedRing *r = Rings[Index];
				if (r)
				{
					r->SetColour(LColour((uint32_t) Msg->B(), 32), true);
					r->Invalidate();
				}
			}

			IgnoreNotify = false;
			break;
		}
		case M_SET_BUTTON_LABEL:
		{
			int Idx = (int)Msg->A();
			LAutoPtr<LString> Txt((LString*)Msg->B());
			if (!Txt)
				break;

			if (Idx < 0 || Idx >= CountOf(BtnLabelIds))
				break;

			int Id = BtnLabelIds[Idx];
			LView *v;
			if (!GetViewById(Id, v))
				break;

			v->Name(*Txt);
			break;
		}
	}
	
	return LView::OnEvent(Msg);
}
