/*

Disable screen saver:
https://www.raspberrypi.org/forums/viewtopic.php?t=211855

*/

#include <string.h>
#include "lgi/common/Lgi.h"
#include "lgi/common/CssTools.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/FontCache.h"
#include "lgi/common/Variant.h"
#include "lgi/common/Button.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/Midi.h"
#include "lgi/common/LgiRes.h"

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

#if !defined(WIN32) && !defined(MAC)
#define assert(v)
#else
#include <assert.h>
#endif

#undef ThreadCheck
#define ThreadCheck() if (UiThread != LGetCurrentThread()) { LAssert(!"Thread error."); return; }
#define ThreadCheckRet(ret) if (UiThread != LGetCurrentThread()) { LAssert(!"Thread error."); return ret; }

static const char *Mc4ConnectedStyle = "background: #238; color: white;";
static const char *Mc4DisconnectedStyle = "background: #a00; color: white;";
static const char *Mc4EditedStyle = "background: orange; color: white;";

DelayBlockTempo Mc4DelayTempos[6] =
{
	Tempo_4,
	Tempo_4dot,
	Tempo_4trip,
	Tempo_8,
	Tempo_8dot,
	Tempo_8trip,
};

extern const char *Mc4DelayTempoNames[6]
{
	"1/4", "Dot 1/4", "1/4 Trip",
	"1/8", "Dot 1/8", "1/8 Trip",
};

LColour LedOff; // Basically just transparent black
LColour LedBankUpDown(128, 0, 255);
LColour LedBank(0, 0, 64);
LColour LedTunerColour(128, 255, 0);
LColour LedPresetColour(255, 0, 0);
LColour ColourDisabled(128, 128, 128);

char sNone[] = "None";

enum MenuValType
{
	MenuBool,
	MenuInt,
	MenuBlockIdx,
	MenuGroupEnum,
};

int ParamSelectCtrls[] =
{
	IDC_EXTERN_1_2, IDC_EXTERN_3_4, IDC_EXTERN_5_6, IDC_EXTERN_7_8,
	IDC_AMP1_GAIN, IDC_AMP1_BM, IDC_AMP1_TREB, IDC_AMP1_LEVEL
};

const char *ToString(ParameterModes m)
{
	switch (m)
	{
		case ModeExtern1and2:		return "Extern 1+2";
		case ModeExtern3and4:		return "Extern 3+4";
		case ModeExtern5and6:		return "Extern 5+6";
		case ModeExtern7and8:		return "Extern 7+8";
		case ModeAmp1GainMaster:	return "Amp1 Gain+Master";
		case ModeAmp1BassMids:		return "Amp1 Bass+Mids";
		case ModeAmp1TreblePres:	return "Amp1 Treble+Pres";
		case ModeAmp1Level:			return "Amp1 Level";
	}

	return "#err";
}

///////////////////////////////////////////////////////////////////////////
Mc4Dialog::Mc4Dialog(Mc4Ui *ui) : Ui(ui)
{
	Resource = 0;
	Hw = Ui->GetHw();
	Shared = Ui->GetShared();
	Ui->AddDialog(this);
}

Mc4Dialog::~Mc4Dialog()
{
	Ui->DeleteDialog(this);
}

void Mc4Dialog::SameWidth(int Len, int *IDs)
{
	int MaxPx = 0;
	LArray<LView*> Ctrls;
	for (int i=0; i<Len; i++)
	{
		LView *v;
		if (Ui->GetViewById(IDs[i], v))
		{
			Ctrls.Add(v);
			MaxPx = MAX(MaxPx, v->X());
		}

	}
	for (auto v: Ctrls)
	{
		LRect r = v->GetPos();
		if (r.X() < MaxPx)
		{
			r.x2 = r.x1 + MaxPx - 1;
			v->SetPos(r);
		}
	}
}

class ParamSelectDlg : public Mc4Dialog
{
public:
	ParamSelectDlg(Mc4Ui *ui) : Mc4Dialog(ui)
	{
		Load();
	}

	bool Load()
	{
		if (!Ui->LoadResource(Resource = IDD_PARAM_SELECT))
			return false;
		return true;
	}

	int OnNotify(LViewI *Ctrl, LNotification n)
	{
		switch (Ctrl->GetId())
		{
			// IDD_PARAM_SELECT
			case IDC_EXTERN_1_2:
			case IDC_EXTERN_3_4:
			case IDC_EXTERN_5_6:
			case IDC_EXTERN_7_8:
			case IDC_AMP1_GAIN:
			case IDC_AMP1_BM:
			case IDC_AMP1_TREB:
			case IDC_AMP1_LEVEL:
				Ui->SetParamGroup(Ctrl->GetId());
				return true;
			#if 0
			case IDC_PARAM_TABLE:
			{
				if (Flags == LNotifyTableLayoutChanged)
				{
					int Ids[] = {	IDC_EXTERN_1_2, IDC_EXTERN_3_4, IDC_EXTERN_5_6, IDC_EXTERN_7_8,
									IDC_AMP1_GAIN, IDC_AMP1_BM, IDC_AMP1_TREB, IDC_AMP1_LEVEL };
					SameWidth(CountOf(Ids), Ids);
					return true;
				}
				break;
			}
			#endif
		}

		return Mc4Dialog::OnNotify(Ctrl, n);
	}
};

class BankSelectDlg : public Mc4Dialog
{
	int Buttons;
	int Pane;

public:
	BankSelectDlg(Mc4Ui *ui, int pane) : Mc4Dialog(ui)
	{
		Buttons = 0;
		Pane = pane;
		Load();
	}
	~BankSelectDlg();

	bool Load()
	{
		if (!Ui->LoadResource(Resource = IDD_BANK_SELECT))
			return false;
		
		LTableLayout *t;
		if (!Ui->GetViewById(IDC_BANK_TABLE, t))
			return false;
		
		auto Sh = Shared->Lock(_FL);
		int Bank = 0;
		int BankId = BANK_BTN_BASE;
		int DelayId = DELAY_BTN_BASE;
		for (int y=0; y<BUTTONS_Y; y++)
		{
			for (int x=0; x<BUTTONS_X; x++)
			{
				auto c = t->GetCell(x, y);
				auto b = Sh->Global.GetBtn(x, y);
				if (b.b == BtnTypeBank)
				{
					LString s;
					s.Printf("%i-%i",
							Bank + Mc4Ui::DisplayOffset,
							Bank + Sh->Global.BankSize - 1 + Mc4Ui::DisplayOffset);
					auto btn = new LButton(BankId++, 0, 0, -1, -1, s);
					btn->CssClasses()->New() = "BtnCfg";
					c->Add(btn);
					Bank += Sh->Global.BankSize;
					Buttons++;
				}
				else if (b.b == BtnTypeDelayDivision &&
						b.i < CountOf(Mc4DelayTempoNames))
				{
					auto s = Mc4DelayTempoNames[b.i];
					auto btn = new LButton(DelayId++, 0, 0, -1, -1, s);
					btn->CssClasses()->New() = "BtnCfg";
					c->Add(btn);
					Buttons++;
				}
			}
		}

		if (Sh->Global.CurrentBtnPane == 1)
		{
			LString s;
			s.Printf("Current bank: %i-%i",
					Sh->BankStart + Mc4Ui::DisplayOffset,
					Sh->BankStart + Sh->Global.BankSize - 1 + Mc4Ui::DisplayOffset);
			Ui->SetCtrlName(IDC_CUR_BANK, s);
		}
		else if (Sh->Global.CurrentBtnPane == 2)
		{
			Ui->SetCtrlName(IDC_TITLE, "Select delay division");
			Ui->SetCtrlName(IDC_CUR_BANK, NULL);
		}

		return true;
	}

	int OnNotify(LViewI *Ctrl, LNotification n)
	{
		int Id = Ctrl->GetId();
		if (Id >= BANK_BTN_BASE && Id < BANK_BTN_BASE + Buttons)
		{
			auto s = Ui->GetCtrlName(Id);
			if (s)
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh)
				{
					LAssert(Sh->Global.CurrentBtnPane == 1);
					auto i = Atoi(s);
					if (i >= 0)
						Sh->BankStart = (int)i - Mc4Ui::DisplayOffset;
				}
			}

			Ui->PostEvent(M_LEAVE_BANK_SELECT);
			return true;
		}
		else if (Id >= DELAY_BTN_BASE && Id < DELAY_BTN_BASE + Buttons)
		{
			int Idx = Id - DELAY_BTN_BASE;
			if (Idx >= 0 && Idx < CountOf(Mc4DelayTempos))
				Hw->PostEvent(M_SET_DELAY_DIV, Mc4DelayTempos[Idx]);
			else
				LAssert(!"Invalid delay division index");

			Ui->PostEvent(M_LEAVE_BANK_SELECT);
			return true;
		}

		return Mc4Dialog::OnNotify(Ctrl, n);
	}
}	*BankSel = NULL;

BankSelectDlg::~BankSelectDlg()
{
	if (BankSel)
	{
		BankSel = NULL;
		Ui->PostEvent(M_SWITCH_MODE);
	}
}

class PlayDlg : public Mc4Dialog
{
public:
	PlayDlg(Mc4Ui *ui) : Mc4Dialog(ui)
	{
		Load();
	}

	bool Load()
	{
		if (!Ui->LoadResource(Resource = IDD_PLAY))
			return false;

		// Save the preset if dirty...
		bool Save = false;
		{
			auto Sh = Shared->Lock(_FL);
			if (Sh)
				Save = Sh->Preset.Dirty && Sh->PresetIndex >= 0;
		}
		if (Save)
			Ui->SerializePreset(true);

		// Create the IA buttons and text
		LTableLayout *t;
		if (Ui->GetViewById(IDC_SUBTABLE, t))
		{
			for (int i=0; i<IA_BTNS; i++)
			{
				LString n;
				LButton *b;
				LTextLabel *txt;
				n.Printf("IA%i", i + 1);

				auto c = t->GetCell(i, 0);
				c->PaddingRight("0.15em");
				c->PaddingBottom("0.15em");
				c->TextAlign("center");
				c->Add(b = new LButton(IA_BTN_BASE + i, 0, 0, -1, -1, n));

				c = t->GetCell(i, 1);
				c->PaddingLeft("0.15em");
				c->PaddingRight("0.15em");
				c->TextAlign("center");
				c->Add(txt = new LTextLabel(IA_TXT_BASE + i, 0, 0, 20, 20, "-none-"));
				// txt->CssClasses()->New() = "IaLabel";
				txt->GetCss(true)->FontSize("20pt");

				b->OnStyleChange();
				txt->OnStyleChange();
			}
			
			t->SendNotify(LNotifyTableLayoutRefresh);
		}
			
		// Update the screen with the current patch and IA descriptions
		return Update();
	}

	#define PROFILE_UPDATE	0
	#if PROFILE_UPDATE
		#define PROFILE(str) Prof.Add(str)
	#else
		#define PROFILE(str)
	#endif

	bool Update()
	{
		#if PROFILE_UPDATE
		GProfile Prof("UpdatePlayScreen");
		#endif

		PROFILE("GetViews");
	
		LViewI *Num = NULL, *Name = NULL;
		Ui->GetViewById(IDC_PATCH_NUMBER, Num);
		Ui->GetViewById(IDC_PATCH_NAME, Name);

		PROFILE("LockShared");

		int PresetIndex = -1;
		bool IsConnected = false;
		LString PresetName;
		{
			auto Sh = Shared->Lock(_FL);
			if (!Sh)
				return false;

			PresetIndex = Sh->PresetIndex;
			IsConnected = Sh->IsConnected;
			PresetName = (char*)(Sh->PresetName);
		
			LString p = Ui->GetPresetPath(false);
			Sh->Preset.Serialize(p, false);
		}

		PROFILE("ParseCss");
	
   		const char *Defs = IsConnected ? Mc4ConnectedStyle : Mc4DisconnectedStyle;
   		Ui->GetCss(true)->Parse(Defs);

		PROFILE("SetNum");
		if (Num)
		{
			if (PresetIndex >= 0)
				Num->Value(PresetIndex + Ui->DisplayOffset);
			else
				Num->Name("##");
		}
		PROFILE("SetName");
		if (Name)
		{
			if (IsConnected)
				Name->Name(PresetName);
			else
				Name->Name("Disconnected...");
			Name->SendNotify(LNotifyTableLayoutRefresh);
		}
		PROFILE("SetCtrlCol");
		if (PresetIndex < 0 || !IsConnected)
		{
			Ui->SetCtrlColour(ColourDisabled, true, true);
		}

		for (unsigned i=0; i<IA_BTNS; i++)
		{
			PROFILE("IA");

			LViewI *v;
			if (Ui->GetViewById(IA_TXT_BASE + i, v))
			{
   				LString msg;
				auto Sh = Shared->Lock(_FL);
				if (Sh)
				{
					auto Press = Sh->Preset.GetTypePress();
					IA *ia = i < Press.Length() ? Press[i] : NULL;	 			
					if (ia)
					{
						for (unsigned n=0; n<ia->Cmds.Length(); n++)
						{
							MidiCmd &c = ia->Cmds[n];
							if (c.Id == MIDI_CMD_SCENE)
								msg.Printf("Scene%i", c.Param);
							else if (c.Id == MIDI_CMD_BLOCK && c.Param < AxefxBlockCount())
								msg = AxefxGetBlocks()[c.Param].Name;					
							else if (c.Id == MIDI_CMD_CC)
								msg = ToString((AxefxCc)c.Param);
						}

						if (ia->AutoSelectMode &&
							ia->AutoSelectMode != IDC_NONE)
						{
							auto s = LLoadString(ia->AutoSelectMode);
							if (s)
								msg += LString("\n") + LString(s);
						}
					}
					else msg = "-missing-";
				}
			
				auto css = v->GetCss(true);
				LColour Fore = !msg.Get() || msg(0) == '-' ? ColourDisabled : LColour::White;
				LString ForeCss;
				ForeCss.Printf("color: #%.2x%.2x%.2x", Fore.r(), Fore.g(), Fore.b());
				v->CssStyles(ForeCss);
				if (!msg)
					msg = "-none-";
				v->Name(msg);

				if (i == IA_BTNS - 1)
					v->SendNotify(LNotifyTableLayoutRefresh);
			}
			else LAssert(!"No ctrl?");
		}

		Ui->UpdatePlayParams();
		auto St = Ui->GetStatusTxt();
		if (St)
			Ui->SetCtrlName(IDC_STATUS, St);
	
		PROFILE("Inval");
		Ui->Invalidate();

		return false;
	}

	int OnNotify(LViewI *Ctrl, LNotification n)
	{
		switch (Ctrl->GetId())
		{
			// Play page
			case IDC_SETUP:
				new SetupDlg(Ui);
				return true;
			case IDC_PARAM_SEL:
				new ParamSelectDlg(Ui);
				return true;
			default:
			{
				int Id = Ctrl->GetId();
				if (Id >= IA_BTN_BASE && Id < IA_BTN_BASE + IA_BTNS)
				{
					new IaConfigDlg(Ui, Id - IA_BTN_BASE);
					return true;
				}
				break;
			}
		}

		return Mc4Dialog::OnNotify(Ctrl, n);
	}

	LMessage::Result OnEvent(LMessage *Msg)
	{
		switch (Msg->Msg())
		{
			case M_ON_PARAM:
			{
				int Idx = (int)Msg->A();
				int Val = (int)Msg->B();
				if (Idx >= 0 && Idx < MAX_PARAM)
				{
					ParameterModes ParamGroup = ModeMax;
					{
						auto Sh = Shared->Lock(_FL);
						if (Sh) ParamGroup = Sh->ParamGroup;
					}

					int Base = 0;
					switch (ParamGroup)
					{
						case ModeExtern1and2:
							Base = 1;
							break;
						case ModeExtern3and4:
							Base = 3;
							break;
						case ModeExtern5and6:
							Base = 5;
							break;
						case ModeExtern7and8:
							Base = 7;
							break;
					}
					if (Base)
					{
						LString Msg;
						Msg.Printf("Extern%i: %i", Base + Idx - KNOB_BASE, Val);
						Ui->SetCtrlName(IDC_STATUS, Msg);
						Ui->SetCtrlValue(IDC_ENCODER, Val);
					}
					else printf("%s:%i - ParamGroup=%i\n", _FL, ParamGroup);
				}
				break;
			}
		}

		return Mc4Dialog::OnEvent(Msg);
	}
};

class StartupDlg : public Mc4Dialog
{
	uint64 StartTs;

public:
	StartupDlg(Mc4Ui *ui) : Mc4Dialog(ui)
	{
		StartTs = LCurrentTime();
		Load();
	}

	bool Load()
	{
   		const char *Defs = Mc4DisconnectedStyle;
   		Ui->GetCss(true)->Parse(Defs);
		return Ui->LoadResource(Resource = IDD_STARTUP);
	}

	void OnPulse()
	{
		if (LCurrentTime() - StartTs >= 1000)
		{
			new PlayDlg(Ui);
			
			bool Show = false;
			bool IsConnected = false;
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh)
				{
					if (Sh->FirmwareVer[0])
						Show = true;
					IsConnected = Sh->IsConnected;
				}					
			}
			
			if (Show)
				Ui->ShowFirmwareMsg();
			
			if (!IsConnected && Hw)
				Hw->PostEvent(M_SET_PRESET_NUMBER);

			delete this;
		}
	}
};

class TunerDlg : public Mc4Dialog
{
public:
	TunerDlg(Mc4Ui *ui) : Mc4Dialog(ui)
	{
		Load();
	}

	bool Load()
	{
		return Ui->LoadResource(Resource = IDD_TUNER);
	}
};

///////////////////////////////////////////////////////////////////////////
int Mc4Ui::DisplayOffset = 1;

Mc4Ui::Mc4Ui(LEventSinkI *hw, Mc4Shared *shared) : Hw(hw)
{
	Shared = shared;
	UiThread = LGetCurrentThread();
	PrevModeCtrlId = -1;
	
	#ifdef WINDOWS
	// SetStyle(GetStyle() & ~WS_CLIPCHILDREN); // Remove clip children style
	#endif
	
	auto r = HW_SCREEN;
	SetPos(r);
	
	LFile::Path p(LSP_APP_INSTALL);
	#ifdef MAC
	p--;
	#endif
	p--;
	p += "resources";
	
	if (!p.Exists())
	{
		printf("%s:%i - Error: '%s' doesn't exist.\n", _FL, p.GetFull().Get());
		LAssert(!"Where is the resource folder?");
	}
	else
	{
		ResourcePath = p.GetFull();

		p--;
		p += "settings";
		if (!LDirExists(p) && !FileDev->CreateFolder(p))
			LgiTrace("%s:%i - failed to create settings folder: %s\n", _FL, p.GetFull().Get());
		else
			SettingsPath = p.GetFull();
		
		// Turn on global element styling
		LResources::SetLoadStyles(true);
			
		// Style the current object
		LResources::StyleElement(this);
			
		// Load the start up screen...
		new StartupDlg(this);
			
		#if 0
		// Turn this on to take a screenshot of the load screen...
		_Paint(Screen);
		ScreenShot();
		#endif
			
		// Timeout to load the play screen and switch of tempo LED
		SetPulse(40);
		// }
	}

	LFile::Path Glob = SettingsPath.Get();
	Glob += "Global.xml";
	GlobalSettingsPath = Glob;

	int ParamValue[EXTERN_PARAMS] = {-1};
	{
		auto Sh = Shared->Lock(_FL);
		if (Sh)
		{
			Sh->Preset.Parent = this;
			
			// Load the global settings...
			Sh->Global.Serialize(GlobalSettingsPath, false);
			memcpy(ParamValue, Sh->Global.ParamValue, sizeof(ParamValue));
		}
	}

	// Push the param values down to the logic / hw
	for (unsigned i=0; Hw && i<CountOf(ParamValue); i++)
	{
		if (ParamValue[i] < 0)
			continue;
			
		Hw->PostEvent(	M_SET_PARAM,
						(LMessage::Param) i,
						(LMessage::Param) ParamValue[i]);
	}

	SetParamGroup(IDC_EXTERN_3_4);
	UpdateButtonLabels();
}

Mc4Ui::~Mc4Ui()
{
	SerializeGlobal(true);
}

void Mc4Ui::InvalidateAll(LViewI *v)
{
	v->Invalidate();
	for (auto c: v->IterateViews())
		InvalidateAll(c);
}

bool Mc4Ui::SerializePreset(bool Write)
{
	LString p = GetPresetPath(false);
	auto Sh = Shared->Lock(_FL);
	if (!Sh)
		return false;
	
	return Sh->Preset.Serialize(p, Write);
}

bool Mc4Ui::SerializeGlobal(bool Write)
{
	LString p = GetGlobalPath();
	auto Sh = Shared->Lock(_FL);
	if (!Sh)
		return false;
		
	return Sh->Global.Serialize(p, Write);
}

void Mc4Ui::UpdateButtonLabels()
{
	auto Sh = Shared->Lock(_FL);
	if (!Sh)
		return;

	auto Cfg = Sh->Global.BtnConfig[Sh->Global.CurrentBtnPane];
	int Idx = 0;
	int Bank = 0;
	int PresetBtnIdx = 0;
	bool UpdateIaLeds = false;

	for (int y=0; y<BUTTONS_Y; y++)
	{
		for (int x=0; x<BUTTONS_X; x++, Idx++)
		{
			auto &b = Cfg[x][y];
			LString *s = new LString;
			if (b.b == BtnTypeIA ||
				b.b == BtnTypePreset)
			{
				s->Printf("%s%i", ToString(b.b), b.i+1);
			}
			else if (b.b == BtnTypeBank)
			{
				s->Printf("%s\n%i-%i", ToString(b.b), Bank, Bank+Sh->Global.BankSize-1);
				Bank += Sh->Global.BankSize;
			}
			else if (b.b == BtnTypeDelayDivision)
			{
				if (b.i >= 0 && b.i < CountOf(Mc4DelayTempoNames))
					*s = Mc4DelayTempoNames[b.i];
				else
					LAssert(!"Invalid index.");
			}
			else
			{
				s->Printf("%s", ToString(b.b));
			}
	
			Hw->PostEvent(M_SET_BUTTON_LABEL, (LMessage::Param)Idx, (LMessage::Param)s);

			switch (b.b)
			{
				case BtnTypeTuner:
					Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)LedTunerColour.c32());
					break;
				case BtnTypeBankDown:
				case BtnTypeBankUp:
				case BtnTypeModeSwitch:
					Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)LedBankUpDown.c32());
					break;
				case BtnTypeBank:
					Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)LedBank.c32());
					break;
				case BtnTypePreset:
					// Is it the current preset?
					if (PresetBtnIdx == Sh->PresetIndex - Sh->BankStart)
						Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)LedPresetColour.c32());
					else
						Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)0);
					PresetBtnIdx++;
					break;
				case BtnTypeIA:
					UpdateIaLeds = true;
					// Fall through
				default:
					Hw->PostEvent(M_SET_LED_COLOUR, (LMessage::Param)Idx, (LMessage::Param)0);
					break;
			}
		}
	}
	
	Hw->PostEvent(M_ON_BUTTON_CONFIG);
}

void Mc4Ui::OnCreate()
{
	ThreadCheck();
	AttachChildren();
}

void Mc4Ui::OnPulse()
{
	ThreadCheck();

	auto l = LastDialog();
	if (l)
		l->OnPulse();
}

void Mc4Ui::UpdatePlayParams() // Updates just the currently selected params
{
   	auto Sh = Shared->Lock(_FL);
   	if (Sh)
		SetCtrlName(IDC_CUR_PARAMS, ToString(Sh->ParamGroup));
}

LLayoutCell *Mc4Ui::EmptyCell(int x, int y)
{
	ThreadCheckRet(NULL);

	auto *t = GetTable();
	if (!t)
		return NULL;

	auto *c = t->GetCell(x, y, false);
	if (!c)
		return NULL;

	// Remove any existing children from the cell
	LVariant v;
	if (!c->GetValue("children", v) || v.Type != GV_LIST)
		return NULL;

	for (auto child: *v.Value.Lst)
	{
		if (child->Type == GV_GVIEW)
		{
			c->Remove(child->Value.View);
			delete child->Value.View;
		}
		else LAssert(0);
	}
	
	return c;
}

bool Mc4Ui::ShowAxefxBlocks(char Start, char End)
{
	ThreadCheckRet(false);

	auto *c = EmptyCell(0, 2);
	if (!c)
		return false;

	// Add in buttons for the axefx blocks
	for (auto b=AxefxGetBlocks(); b->Name; b++)
	{
		char first = ToLower(b->Name[0]);
		if (first >= ToLower(Start) && first <= ToLower(End))
		{
			LButton *btn = new LButton(AXEFXBLOCK_BTN_BASE + (int)(b - AxefxGetBlocks()), 0, 0, -1, -1, b->Name);
			if (btn)
			{
				c->Add(btn);
				btn->GetCss(true)->Padding("10px");
			}
		}
	}
	
	c->GetTable()->InvalidateLayout();
	Invalidate();
	return true;
}

LColour ParamColours[] =
{
	// ModeExtern1and2
	LColour("#004080"),
	// ModeExtern3and4
	LColour("#008080"),
	// ModeExtern5and6
	LColour("#008027"),
	// ModeExtern7and8
	LColour("#67a000"),
	// ModeAmp1GainMaster
	LColour("#ff1380"),
	// ModeAmp1BassMids
	LColour("#cf3f0a"),
	// ModeAmp1TreblePres
	LColour("#dd6000"),
	// ModeAmp1Level
	LColour("#ffff00"),
};

void Mc4Ui::SetParamGroup(int ctrlId)
{
	ParameterModes pm = ModeMax, curpm = ModeMax;

	{
		auto Sh = Shared->Lock(_FL);
		curpm = Sh->ParamGroup;
	}

	for (int i=0; i<CountOf(ParamSelectCtrls); i++)
	{
		int id = ParamSelectCtrls[i];

		if (curpm == i)
			PrevModeCtrlId = id;

		if (id == ctrlId)
			pm = (ParameterModes)i;
		else
			SetCtrlValue(id, false);
	}

	if (pm != ModeMax)
	{
		bool Updated = false;

		{
			auto Sh = Shared->Lock(_FL);
			if (Sh && Sh->ParamGroup != pm)
			{
				Sh->ParamGroup = pm;
				Updated = true;
			}
		}
		if (Updated)
		{
			Hw->PostEvent(M_PARAM_MODE_CHANGE);
			Hw->PostEvent(M_LED_RING_COLOUR, 0, ParamColours[pm].c32());
			Hw->PostEvent(M_LED_RING_COLOUR, 1, ParamColours[pm].c32());
		}
	}

	OnBack(1);
	UpdatePlayParams();
}

int Mc4Ui::OnNotify(LViewI *Ctrl, LNotification n)
{
	ThreadCheckRet(0);

	auto l = LastDialog();
	if (l)
	{
		auto r = l->OnNotify(Ctrl, n);
		if (r)
			return r;
	}
	
	switch (Ctrl->GetId())
	{
		// Global
		case IDC_BACK:
			OnBack();
			break;
	}

	return false;
}

bool Mc4Ui::ScreenShot()
{
	ThreadCheckRet(false);
	
	LFile::Path p(LSP_APP_INSTALL);
	p += "Screenshots";
	if (!p.IsFolder())
		FileDev->CreateFolder(p);
	if (!p.IsFolder())
		return false;

	LAutoPtr<LFilter> Png(GFilterFactory::New("*.png", O_WRITE, NULL));
	if (!Png)
		return false;

	LFile::Path img;

	for (int i=1; i<10000; i++)
	{
		LString leaf;
		img = p;
		leaf.Printf("screenshot%i.png", i);
		img += leaf;
		if (!img.IsFile())
			break;
	}
	
	LFile out;
	if (!out.Open(img, O_WRITE))
		return false;

	LAssert(!"fixme.");
	// return Png->WriteImage(&out, Screen) == LFilter::IoSuccess;
	return NULL;
}

bool Mc4Ui::OnViewKey(LView *v, LKey &k)
{
	ThreadCheckRet(false);

	if (k.IsChar)
	{
		switch (k.c16)
		{
			case 's':
			case 'S':
			{
				if (k.Down())
					ScreenShot();
				return true;
			}
		}
	}
	
	return false;
}

void Mc4Ui::OnMouseClick(LMouse &m)
{
	ThreadCheck();

	if (m.IsContextMenu())
	{
	}
	else if (m.Down())
	{
	}
}

void Mc4Ui::AddDialog(Mc4Dialog *dlg)
{
	LAssert(!Dialogs.HasItem(dlg));
	Dialogs.Add(dlg);
}

bool Mc4Ui::DeleteDialog(Mc4Dialog *dlg)
{
	LAssert(Dialogs.HasItem(dlg));
	return Dialogs.Delete(dlg);
}

bool Mc4Ui::LoadResource(int Res)
{
	ThreadCheckRet(false);

	while (Children.Length())
		delete Children[0];

	LRect pos;
	if (!LoadFromResource(Res, this, &pos))
	{
		printf("%s:%i - Error: LoadFromResource failed.\n", _FL);
		return false;
	}

	if (Children.Length())
	{
		auto *t = dynamic_cast<LTableLayout*>(Children[0]);
		if (t)
		{
			auto c = GetClient();
			LAssert(c.Valid());
			t->SetPos(c);
		}
	}

	switch (Res)
	{
		case IDD_PARAM_SELECT:
		{
			ParameterModes ParamGroup = ModeMax;
			{
				auto Sh = Shared->Lock(_FL);
				ParamGroup = Sh->ParamGroup;
			}

			for (int i=0; i<CountOf(ParamSelectCtrls); i++)
			{
				LButton *b;
				int Id = ParamSelectCtrls[i];
				if (GetViewById(Id, b))
				{
					b->SetIsToggle(true);
					b->Value(i == ParamGroup);
				}
			}
		}
	}

	#if LGI_VIEW_HANDLE
	if (Handle())
	#endif
		AttachChildren();
	Invalidate();
	return true;
}

LString Mc4Ui::GetGlobalPath()
{
	return GlobalSettingsPath;
}

LString Mc4Ui::GetPresetPath(bool Db, int Idx)
{
	ThreadCheckRet(LString());
	
	if (Idx < 0)
	{
		auto Sh = Shared->Lock(_FL);
		Idx = Sh->PresetIndex;		
	}

	LFile::Path p;
	if (Idx >= 0)
	{
		p = SettingsPath;
		
		LString f;
		f.Printf("Preset %i.%s", Idx, Db ? "IA.db" : "xml");
		p += f;
	}
	
	return p.GetFull();
}

LMessage::Result Mc4Ui::OnEvent(LMessage *Msg)
{
	ThreadCheckRet(0);
	
	switch (Msg->Msg())
	{
		case M_PARAM_MODE_CHANGE:
		{
			// Set the param group...
			int ctrlId = (int) Msg->A();
			if (ctrlId > 0)
				SetParamGroup(ctrlId);
			else if (ctrlId == -1 && PrevModeCtrlId > 0)
				SetParamGroup(PrevModeCtrlId);
			break;
		}
		case M_AMP_PARAM:
		{
			if (!Hw)
				break;
			int Value = (int)Msg->B();
			LMessage::Param p = (LMessage::Param) (Value >> 9);
			switch ((int)Msg->A())
			{
				case AmpBlock_Gain:
					SetCtrlValue(IDC_GAIN, Value >> 9);
					break;
				case AmpBlock_Master:
					SetCtrlValue(IDC_MASTER, Value >> 9);
					break;
				case AmpBlock_Level:
					SetCtrlValue(IDC_LEVEL, Value >> 9);
					break;
				case AmpBlock_Bass:
					SetCtrlValue(IDC_BASS, Value >> 9);
					break;
				case AmpBlock_Mid:
					SetCtrlValue(IDC_MIDS, Value >> 9);
					break;
				case AmpBlock_Treble:
					SetCtrlValue(IDC_TREBLE, Value >> 9);
					break;
			}
			break;
		}
		case M_SET_DIRTY:
		{
			const char *Defs = Mc4EditedStyle;
			GetCss(true)->Parse(Defs);
			InvalidateAll(this);
			break;
		}
		case M_SET_PRESET_NUMBER:
		{
			SetCtrlValue(IDC_PATCH_NUMBER, (int)Msg->A() + DisplayOffset);

			// Load settings for this preset
			LString Path = GetPresetPath(false);
			if (!SerializePreset(false) && LFileExists(Path))
				SetCtrlName(IDC_STATUS, StatusTxt = "Error: loading preset file.");
			
			auto l = LastDialog();
			if (l)
				l->Update();
			break;
		}
		case M_SET_PRESET_NAME:
		{
			SetCtrlColour(LColour::White, false, true);
			
			LViewI *c;
			if (GetViewById(IDC_PATCH_NAME, c))
			{
				{
					auto Sh = Shared->Lock(_FL);
					if (Sh)
						c->Name(Sh->PresetName);
				}
				c->SendNotify(LNotifyTableLayoutRefresh);
			}
			break;
		}
		case M_UPDATE_PLAY_SCREEN:
		{
			auto l = LastDialog();
			if (l)
				l->Update();
			break;
		}
		case M_DRAW_BANK_RANGE:
		{
			DrawBankRange();
			break;
		}
		case M_STATUS_MSG:
		{
			LAutoPtr<LString> Str((LString*)Msg->A());
			SetCtrlName(IDC_STATUS, StatusTxt = *Str);
			break;
		}
		case M_FIRMWARE_VER:
		{
			ShowFirmwareMsg();
			break;
		}
		case M_TUNER_STATE:
		{
			bool TunerOn = false;
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh)
					TunerOn = Sh->TunerOn;
			}
			
			// printf("Got M_TUNER_STATE on=%i\n", TunerOn);
			if (TunerOn)
			{
				SetCtrlName(IDC_STATUS, StatusTxt = "Tuner");
				
				if (CurrentDialog() != IDD_TUNER)
					new TunerDlg(this);
			}
			else
			{
				if (CurrentDialog() == IDD_TUNER)
					OnBack();
				else
					printf("%s:%i - Not tuner dlg.\n", _FL);
			}
			break;
		}
		case M_TUNER_STATUS:
		{
			LAutoPtr<LString> Str((LString*)Msg->B());
			SetCtrlName(IDC_NOTE_INFO, *Str);
			SetCtrlValue(IDC_TUNING, (int)Msg->A());
			break;
		}
		case M_SET_CTRL_COLOUR:
		{
			LColour c((uint32_t)Msg->A(), 32);
			int f = (int)Msg->B();
			SetCtrlColour(c, TestFlag(f, DO_NUM), TestFlag(f, DO_NAME));
			break;
		}
		case M_ON_PARAM:
		{
			int Idx = (int)Msg->A();
			int Val = (int)Msg->B();
			if (Idx >= 0 && Idx < MAX_PARAM)
			{
				auto Sh = Shared->Lock(_FL);
				if (!Sh)
					break;

				// Params are split into "expression" and "knob" based.
				// The first MAX_EXPRESSION_PARAM indexes are expression inputs
				// Following by MAX_KNOB_PARAM knob inputs which are switch by the
				// ParamGroup setting.
				if (Idx >= KNOB_BASE)
				{
					// A knob based value
					int i = Sh->GroupIndex();
					if (i > 0)
						Sh->Global.ParamValue[i + (Idx - MAX_EXPRESSION_PARAM)] = Val;
				}
				else
				{
					// An expression based value
					Sh->Global.ParamValue[Idx] = Val;
				}
			}

			auto Dlg = LastDialog();
			if (Dlg)
				Dlg->OnEvent(Msg);
			break;
		}
		case M_SWITCH_MODE:
		{
			bool Pane3 = Msg->A() != 0;
			int CurrentBtnPane = 0;

			{
				auto Sh = Shared->Lock(_FL);
				if (!Sh)
					break;

				if (Pane3)
					Sh->Global.CurrentBtnPane = 2;
				else
					Sh->Global.CurrentBtnPane = !Sh->Global.CurrentBtnPane;
				CurrentBtnPane = Sh->Global.CurrentBtnPane;
			}

			UpdateButtonLabels();

			if (BankSel)
			{
				BankSel = NULL;
				OnBack();
			}
			if (CurrentBtnPane)
			{
				BankSel = new BankSelectDlg(this, CurrentBtnPane);
			}

			DrawBankRange();
			break;
		}
		case M_LEAVE_BANK_SELECT:
		{
			{
				auto Sh = Shared->Lock(_FL);
				if (!Sh)
					break;
				Sh->Global.CurrentBtnPane = 0;
			}

			if (BankSel)
			{
				BankSel = NULL;
				OnBack();
				UpdateButtonLabels();
			}
			break;
		}
		case M_CLOSE:
		{
			LCloseApp();
			break;
		}
	}
	
	return LView::OnEvent(Msg);
}

bool Mc4Ui::OnLayout(LViewLayoutInfo &Inf)
{
	if (!Inf.Width.Min)
	{
		Inf.Width.Min = 800;
		Inf.Width.Max = 800;
	}
	else
	{
		int y = HW_SCREEN.Y();
		Inf.Height.Min = y;
		Inf.Height.Max = y;
	}

	return true;
}

void Mc4Ui::OnPaint(LSurface *pDC)
{
	ThreadCheck();

	if (!GetCss())
	{		
		char s[256];
		sprintf_s(s, sizeof(s), "Err: NoStyle");
		LDisplayString ds(LSysFont, s);
		ds.Draw(pDC, 1, 1);
		return;
	}

	LCssTools t(GetCss(), GetFont());
	LRect c = GetClient();
	c = t.PaintBorderAndPadding(pDC, c);

	LColour Back = t.GetBack();
	if (Back.IsValid())
	{
		pDC->Colour(Back);
		pDC->Rectangle(&c);
	}
}

void Mc4Ui::DrawBankRange()
{
	ThreadCheck();

	{
		auto Sh = Shared->Lock(_FL);
		StatusTxt.Printf("Bank: %i to %i",
						Sh->BankStart + DisplayOffset,
						Sh->BankStart + Sh->Global.BankSize + DisplayOffset - 1);
	}
	
	SetCtrlName(IDC_STATUS, StatusTxt);
}

void Mc4Ui::SetCtrlColour(LColour c, bool DoNum, bool DoName)
{
	ThreadCheck();
	LCss::ColorDef col(LCss::ColorRgb, c.c32());

	LTextLabel *v;
	if (DoNum && GetViewById(IDC_PATCH_NUMBER, v))
	{
		v->GetCss(true)->Color(col);
		v->OnStyleChange();
	}
	if (DoName && GetViewById(IDC_PATCH_NAME, v))
	{
		v->GetCss(true)->Color(col);
		v->OnStyleChange();
	}
}

void Mc4Ui::ShowFirmwareMsg()
{
	LViewI *c;
	if (GetViewById(IDC_STATUS, c))
	{
		LString Name = "Axefx";

		{
			auto Sh = Shared->Lock(_FL);
			if (Sh)
			{
				int Ver = Sh->FirmwareVer[0];
				if (Ver >= 20)
				{
					Ver -= 20;
					Name = "Quantum";
				}
				StatusTxt.Printf("Firmware: %s v%i.%i", Name.Get(), Ver, Sh->FirmwareVer[1]);
			}
		}
		
		c->Name(StatusTxt);
		c->SendNotify(LNotifyTableLayoutRefresh);
	}
}

void Mc4Ui::OnBack(unsigned Count)
{
	for (unsigned i=0; i<Count; i++)
	{
		if (Dialogs.Length())
		{
			auto l = Dialogs.Last();
			if (l->Resource == IDD_TUNER)
			{
				auto Sh = Shared->Lock(_FL);
				if (Sh)
					Sh->TunerOn = false;
				if (Hw)
					Hw->PostEvent(M_SET_TUNER, (LMessage::Param)false);
			}

			delete Dialogs.Last();
		}
	}

	if (Dialogs.Length())
		Dialogs.Last()->Load();
	else
		new PlayDlg(this);
}

