/*

	To run GUI apps over ssh you need to do:
	
	$ export DISPLAY=':0'
	$ xhost +


*/
#pragma once

#if defined(__arm__)
#define RPI					1
#endif

#include "lgi/common/TableLayout.h"

#define APP_VER					"4.0"

#define HW_SCREEN				LRect(0, 56 /* Linux menu and decor */, 800-1, 480-1)
#define DEBUG_MAX_LOCK_MS		10

/// A controller is a external switch plugged into the foot controller. It will trigger
/// various things when changed. The settings are stored in the preset structure. Set this
/// to zero to disable support. Otherwise set to the number of positions available on the
/// switch.
///
/// The hardware specific code in 'main.c' will have to read the controller switch's position
/// and then call Mc1_OnButton(BtnController, ControllerPosition). However there should be a
/// short de-bounce time before the event is fired.
#define MAX_CONTROLLER			0

#define MAX_PATCH				(128 * 3)
#define MAX_CMDS				2
#define MAX_EXPRESSION_PARAM	2
#define MAX_KNOB_PARAM			2
#define KNOB_BASE				MAX_EXPRESSION_PARAM
#define MAX_PARAM				(MAX_EXPRESSION_PARAM + MAX_KNOB_PARAM)
#define EXTERN_PARAMS			8
#define MAX_MENU_DEPTH			5
#define MAX_PRESET_GROUP		5
#define MAX_PARAM_VALUE			0xff
#define MAX_BUTTON_PANES		3

#define BUTTONS_MASK			0x0000f3ff
#define BUTTONS_DEBOUNCE		40 // ms
#define BUTTONS_HELD_TIME		1600 // ms
#define BUTTONS_HAS_AMP2		1
#define BUTTONS_X				7
#define BUTTONS_Y				2

#define MIDI_CMD_MAX_SYSEX		0xfb
#define MIDI_CMD_SCENE			0xfc
#define MIDI_CMD_CC				0xfd
#define MIDI_CMD_BLOCK			0xfe
#define MIDI_CMD_NONE			0xff // This is the default value for EEPROM memory

#define DRAW_PARAM_X			6

#define TEMPO_LIGHT_MS			60	// How long to show tempo light in milliseconds

#define IA_BTNS					6

#define IA_TXT_BASE				1000
#define IA_BTN_BASE				1100
#define AXEFXBLOCK_BTN_BASE		1200
#define PALETTE_COLOUR_BASE		1300
#define GROUP_BASE				1400
#define BTN_DEF_BASE			1500
#define BANK_BTN_BASE			1600
#define DELAY_BTN_BASE			1700

typedef LArray<uint8_t>			MidiBuf;

/// The various parameter modes that the controller can run in.
enum ParameterModes
{
	/// The default parameter mode sends midi CC message when the parameter changes
	ModeExtern1and2,
	ModeExtern3and4,
	ModeExtern5and6,
	ModeExtern7and8,
	/// In Amp1 mode the parameter sends an Axefx Sysex to modify the Amp1 block
	/// e.g. Gain/Bass/Mid/Treble/Presence/Master
	ModeAmp1GainMaster,
	ModeAmp1BassMids,
	ModeAmp1TreblePres,
	ModeAmp1Level,
	#if 0
	/// In Amp2 mode the parameter sends an Axefx Sysex to modify the Amp2 block
	ModeAmp2GainMaster,
	ModeAmp2BassMids,
	ModeAmp2TreblePres,
	ModeAmp2Level,
	#endif
	ModeMax
};

enum Mc4Messages
{
	M_SET_DIRTY = M_USER + 300,
	M_SET_PRESET_NUMBER,		// Usually Logic->UI, but does support opp. dir.
	M_SET_PRESET_NAME,
	M_UPDATE_PLAY_SCREEN,
	M_DRAW_BANK_RANGE,
	M_STATUS_MSG,				// A=(LString*)StatusMsg
	M_FIRMWARE_VER,
	M_SET_TUNER,				// A=(bool)TunerOn
	M_TUNER_STATE,
	M_TUNER_STATUS,				// A=(int)TunerValue, B=(LString*)Msg
	M_OPEN_MIDI,				// A=(int)InputMidiIndex, B=(int)OutputMidiIndex
	M_SEND_MIDI,				// A=(MidiBuf*)Data
	M_CLOSE_MIDI,
	M_ON_PARAM,					// A=(int)ParamIndex, (int)ParamValue
	M_SET_PARAM,                // A=(int)GlobalParamIndex (0-7 typically), (int)ParamValue
	M_ON_BUTTON,				// A=(hiword)BtnType(loword)Index, (ButtonEvents)Event
	M_SET_CTRL_COLOUR,			// A=(uint32)Colour32, (SetCtrlColourFlags)Flags
	M_SET_BTN_MODE,				// A=(int)Index, B=(ButtonModes)Mode
	M_SET_LED_COLOUR,			// A=(int)Index, B=(uint32)Colour32
	M_AMP_PARAM,				// A=(int)ParamId, B=(int)Value
	M_LED_RING_VALUE,			// A=(int)Index, B=(int)Value
	M_LED_RING_COLOUR,			// A=(int)Index, B=(uint32)Value
	M_LED_RING_UPDATE,
	M_PARAM_MODE_CHANGE,
	M_SET_BUTTON_LABEL,			// A=(int)BtnIndex, B=(LString*)Label
	M_SWITCH_MODE,
	M_LEAVE_BANK_SELECT,
	M_ON_BUTTON_CONFIG,
	M_SET_DELAY_DIV,			// A=(DelayBlockTempo)DelayDivision
};

enum SetCtrlColourFlags
{
	DO_NUM = 0x1,
	DO_NAME = 0x2,
};

#include "../../axefx/include/axefx.h"

#define SETUP_MENU_LFET		BtnPreset4
#define SETUP_MENU_RIGHT	BtnPreset5

// If you have more than 16 bits defined for buttons then you'll need to change
// this type to a 32bit value (like unsigned long)
typedef uint32_t			ButtonBits;
// This needs to be the highest bit index used into the ButtonBits. It's safe to
// just make it the same as the bit size of ButtonBits.
#define MAX_BUTTONS			16

/// These are the command supported between the software editor and the MC1/2
enum MemecodeMidiCmd
{
	MC_MIDI_GET_INIT = 1,
	MC_MIDI_GET_GLOBAL,
	MC_MIDI_GET_PRESET,
	MC_MIDI_SET_GLOBAL,
	MC_MIDI_SET_PRESET,
};

extern DelayBlockTempo Mc4DelayTempos[6];
extern const char *Mc4DelayTempoNames[6];

/// This is the Memecode Controller midi sysex ID
extern uint8_t McId[];

extern LColour LedOff; // Basically just transparent black
extern LColour LedBankChange;
extern LColour LedTunerColour;
extern LColour LedPresetColour;
extern LColour ColourDisabled;
extern char sNone[];
extern int AmpModeIds[6];

struct ParamResponse
{
	uint8_t Block;
	uint8_t Param;
	uint8_t Value;
	char Text[32];
};

/*	The following structures represent the format of settings stored to
	persistent storage. Which on the hardware controller is EEPROM
	and on the desktop software is simply the hard disk. The global 
	settings are stored at start (offset 0) and the presets are located
	after that. To find the actual location of the preset data you
	have to get the "Size" member of the global settings, skip that
	number of bytes and then treat the data at that location as an 
	array of PresetCfg structures. */

/// This structure is used to store a midi command. That command
/// can take the form of either a midi CC message, an Axefx Block,
/// or an Axefx sysex parameter.
struct MidiCmd
{
	/// MIDI_CMD_NONE:
	///     There is no command configured here.
	/// MIDI_CMD_SCENE:
	///		Change the Axefx scene
	/// MIDI_CMD_CC:
	///		This command sends the CC specified in 'Param'
	/// MIDI_CMD_BLOCK:
	///		This command sends the CC attached to Blocks[Param]
	///		Effectively it's a shortcut for switching an axefx block
	///		on/off via a CC.
	/// Else:
	///		'Id' is a BlockId for the sysex set/get param, and the
	///		'Param' is the ParameterId.
	///		\sa Mc1_SetAxefxParam
	uint8_t Id;

	/// MIDI_CMD_NONE:
	///		Ignored
	/// MIDI_CMD_CC:
	///		This is the midi CC command to send.
	/// MIDI_CMD_SCENE:
	///		The scene number.
	/// MIDI_CMD_BLOCK:
	///		This is an index into the Blocks array.
	/// Else: axefx sysex cmd:
	///		This is the ParamId for the sysex set param.
	///		\sa Mc1_SetAxefxParam
	uint8_t Param;

	/// This is the min and max values allowed
	int Min, Max;
	
	void Init()
	{
		Id = MIDI_CMD_NONE;
		Param = 0;
		Min = 0;
		Max = 127;
	}
};

enum IaBlockAction
{
	BlockAction_Null,
	BlockAction_OnOff,
	BlockAction_SelectXY
};

struct IA
{
	/// The colour for the LED
	LColour LedColour;

	/// Auto select parameter mode. This is automatically select 
	/// a group of knob parameters when this IA is engaged.
	int AutoSelectMode;

	/// When a block is selected the user can either engage / disengage the
	/// block or switch it's A:B state.
	IaBlockAction BlockAction;

	/// Group index. All IAs with the same group
	/// are mutually exclusive. Turn on one, turns
	/// off all the others in the same group.
	int Group;

	/// This is all the commands for the IAs
	LArray<MidiCmd> Cmds;

	LColour &GetColour()
	{
		if (LedColour.IsTransparent())
			return (LColour&)LColour::Red;
		return LedColour;
	}
	
	bool Serialize(LXmlTag &t, bool Write);
};

struct IaRef
{
	int Index;
	int Press;

	IaRef(int idx, int press = 0)
	{
		Index = idx;
		Press = press;
	}

	IaRef(const IaRef &r)
	{
		Index = r.Index;
		Press = r.Press;
	}
};

/// This structure defines the information stored per-preset
/// in the persistent memory of the controller
class PresetCfg
{
	/// Settings for the instant access buttons
	LArray<IA> IAs;

public:
	LEventSinkI *Parent;

	/// True if there are unsaved edits in the preset...
	bool Dirty;

	PresetCfg()
	{
		Parent = NULL;
		Dirty = false;
	}
	
	IA &GetIa(IaRef ref);
	LArray<IA*> GetTypePress();
	void SetDirty();
	bool Serialize(const char *Path, bool Write);
};

/// This defines the types of HW buttons you can have
enum HwButton
{
	BtnTypeNone,
	BtnTypeTuner,
	BtnTypeTempo,
	BtnTypeIA,
	BtnTypePreset,
	BtnTypeBankUp,
	BtnTypeBankDown,
	BtnTypeModeSwitch,
	BtnTypeBank,
	BtnTypeDelayDivision,
	BtnTypeMax,
};
extern const char *ToString(HwButton b);

struct HwButtonCfg
{
	HwButton b;
	unsigned i;

	HwButtonCfg()
	{
		b = BtnTypeNone;
		i = 0;
	}

	HwButtonCfg &operator =(HwButton btn)
	{
		b = btn;
		i = 0;
		return *this;
	}
};

/// This structure is the overall layout of global settings, and
/// exists at the very start of the persistent storage
struct GlobalCfg
{
	/// The size is bytes allocated to this structure, which
	/// is always at the start of persistent storage. To skip
	/// to the preset data, just start at 'Size' bytes.
	uint16 Size;

	/// This is all the params for the expression controllers.
	/// There are 3 modes, one for "normal" operation, which 
	/// sends CC commands by default, one for
	/// controlling "Amp1", which by default sends sysex cmds,
	/// and one for "Amp2", which again sends sysex cmds.
	/// To find the right command index into the array like this:
	///
	///			Params[CurrentParamMode][ParamIndex][CmdIdx]
	///
	MidiCmd Params[ModeMax][MAX_PARAM][MAX_CMDS];

	/// The tuner button midi CC
	uint8_t TunerCc;
	
	/// The tempo button midi CC
	uint8_t TempoCc;
	
	/// Which midi channel to use -or- 0xff to read the midi channel
	/// from inputs s1, s2, s3 and s4, one bit each.
	uint8_t MidiChannel;
	
	/// This setting is how close to 0 or 1024 defines invalid values.
	/// Invalid = (0 to ParamValid) or (1024 - ParamValid to 1024)
	uint8_t ParamValid;

	/// Number of ms between reading the analog inputs, effectively
	/// damping down the number of MIDI messages sent.
	uint8_t ParamDamping;
	
	/// Saving the previous param values...
	int ParamValue[EXTERN_PARAMS];
	
	/// Button configuration
	int CurrentBtnPane;
	int BankSize;
	HwButtonCfg BtnConfig[MAX_BUTTON_PANES][BUTTONS_X][BUTTONS_Y];

	GlobalCfg();

	void ReindexBtns()
	{
		BankSize = 0;

		for (int p=0; p<MAX_BUTTON_PANES; p++)
		{
			int c[BtnTypeMax];
			ZeroObj(c);
			for (int y=0; y<BUTTONS_Y; y++)
			{
				for (int x=0; x<BUTTONS_X; x++)
					BtnConfig[p][x][y].i = c[BtnConfig[p][x][y].b]++;
			}

			if (p == 0)
				BankSize += c[BtnTypePreset];
		}
	}

	int GetButtons()
	{
		return BUTTONS_X * BUTTONS_Y;
	}

	HwButtonCfg GetBtn(int Idx)
	{
		int x = Idx % BUTTONS_X;
		int y = Idx / BUTTONS_X;
		return GetBtn(x, y);
	}

	HwButtonCfg GetBtn(int x, int y)
	{
		if (CurrentBtnPane < 0 || CurrentBtnPane >= MAX_BUTTON_PANES)
			return HwButtonCfg();
		
		auto Cfg = BtnConfig[CurrentBtnPane];
		if (x >= 0 && x < BUTTONS_X && y >= 0 && y < BUTTONS_Y)
			return Cfg[x][y];

		return HwButtonCfg();
	}

	bool Serialize(const char *Path, bool Write);
};

extern int ParamGroupIndex(ParameterModes g);

struct Mc4SharedData
{
	/// Starting index of the bank
	int BankStart;
	/// The current preset index (or -1 if invalid)
	int PresetIndex;
	/// Name of the current preset
	LString PresetName;

	/// Axefx firmware version
	uint8_t FirmwareVer[2];
	/// True if the hardware is connected to the Axefx
	bool IsConnected;

	/// The current preset settings
	PresetCfg Preset;
	/// The current global settings
	GlobalCfg Global;

	/// TRUE if the tuner is activated
	bool TunerOn;

	/// Current parameter group. Because the MC4 has only 2 hardware knobs,
	/// you can switch their function between different externs. This keeps
	/// track of what they are currently assigned to.
	ParameterModes ParamGroup;

	Mc4SharedData()
	{
		FirmwareVer[0] = FirmwareVer[1] = 0;
		IsConnected = false;
		PresetIndex = 0;
		BankStart = 0;
		TunerOn = false;
		ParamGroup = ModeMax;
	}

	int GroupIndex()
	{
		return ParamGroupIndex(ParamGroup);
	}
};

typedef LThreadSafeInterface<Mc4SharedData> Mc4Shared;

#include "Hardware.h"

enum AxefxBlockXY
{
	BlockXy_Get,
	BlockXy_SetX,
	BlockXy_SetY,
	BlockXy_Toggle,
};

// This part of the MC4 code implements all the control logic.
// Responding to hardware events, sending / receiving MIDI etc.
class Mc4Logic : public HardwareEvents
{
	// Interfaces
	LEventSinkI *Ui;
	Hardware *Hw;
	Mc4Shared *Shared;

	// Axefx State
	int PostPreset;
	int TunerIdx;
	int TempoIdx;
	LArray<unsigned> IaIdxs;
	uint64 TempoOnTs;
	int PresetEffects;
	uint8_t PresetEffect[4 * 12];
	bool DelayTempoCycle;
	bool DelayToggle;
	LArray<ParamType> ToggleBlockXY;

    // Current values for EXTERN and AMP parameters
    int LedRingValue[ModeMax][MAX_KNOB_PARAM];

	// This is whether each IA effect is on or off.
	LArray<bool> EffectOn;

public:
	Mc4Logic(Mc4Shared *shared);
	~Mc4Logic();

	void SetInterfaces(Hardware *hw, LEventSinkI *ui);
	void SetCtrlColour(const LColour &c, bool DoNum, bool DoName);
	void SetPreset(int i, uint8_t SendMidi);
	void SetPresetIndex(int Idx);
	void ToggleEffect(uint8_t Idx, uint8_t GrpCheck);
	bool GetEffectOn(int idx);

	// Receive Midi handlers
	bool IsTempo(uint8_t *p, size_t len);
	void IsAxefxParam(uint8_t *p, size_t len);
	void IsTuner(uint8_t *p, size_t len);
	void IsPresetNumber(uint8_t *p, size_t len);
	void IsPresetName(uint8_t *Ptr, size_t Len);
	void IsFirmwareVersion(uint8_t *Ptr, size_t Len);
	void IsStateChanged(uint8_t *Ptr, size_t Len);
	void IsEffectAndBypass(uint8_t *Ptr, size_t Len);
	void IsAccessBlockXy(uint8_t *Ptr, size_t Len);
	void OnPostPreset();
	
	// Send Midi helpers
	void RequestPatchNumber();
	void RequestPatchName(int i);
	void SetSendSysex(uint8_t SendSysex);
	void RequestEffectsAndBypass();
	void SendCc(uint8_t Cc, uint8_t Val);
	void ToggleTuner();
	void SetTuner(bool On);
	void GetAxefxParam(ParamType BlockId, ParamType ParamId);
	void SetAxefxParam(ParamType BlockId, ParamType ParamId, uint16_t Value);
	void SetAxefxBlockXY(ParamType BlockId, AxefxBlockXY Mode);
	void SetScene(int Scene);
	void SetDelayDivision(DelayBlockTempo d);

	// Hardware events...
	void SetThreadId(OsThreadId ThreadId);
	void OnConnection(bool Connected);
	void OnButton(HwButton BtnType, int BtnIndex, bool Down, bool Held);
	void OnParam(int Index, int NewValue);
	void OnMidiIn(uint8_t *Buf, size_t Length);
	void OnTimer();
	LMessage::Result OnEvent(LMessage *Msg);
};

class Mc4Dialog : public LEventTargetI
{
protected:
	class Mc4Ui *Ui;
	LEventSinkI *Hw;
	Mc4Shared *Shared;

	void SameWidth(int Len, int *IDs);

public:
	int Resource;

	Mc4Dialog(Mc4Ui *ui);
	~Mc4Dialog();

	virtual bool Load() { return false; }
	virtual bool Update() { return false; }
	virtual void OnPulse() {};
	virtual int OnNotify(LViewI *Ctrl, LNotification n) { return 0; }
	LMessage::Result OnEvent(LMessage *Msg) { return 0; }
};

class Mc4Ui : public LView, public LResourceLoad
{
	// Interfaces
	LEventSinkI *Hw;
	Mc4Shared *Shared;

	// Paths
	LString ResourcePath;
	LString SettingsPath;
	LString GlobalSettingsPath;

	// Dialogs
	LArray<Mc4Dialog*> Dialogs;
	int CurrentDialog() { return Dialogs.Length() ? Dialogs.Last()->Resource : 0; }
	Mc4Dialog *LastDialog() { return Dialogs.Length() ? Dialogs.Last() : NULL; }
	
	// Misc
	OsThread UiThread;
	int PrevModeCtrlId;
	LString StatusTxt;
	
	void StyleChildren(LViewI *v);
	void ShowIAConfig(int Idx);
	void InvalidateAll(LViewI *v);

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

public:
	static int DisplayOffset;

	Mc4Ui(LEventSinkI *hw, Mc4Shared *shared);
	~Mc4Ui();
		
	const char *GetClass() override { return "Mc4Ui"; }
	const char *GetResourcePath() { return ResourcePath; }
	void OnShutdown() { Hw = NULL; }
	bool LoadResource(int Res);
	LEventSinkI *GetHw() { return Hw; }
	Mc4Shared *GetShared() { return Shared; }
	void AddDialog(Mc4Dialog *dlg);
	bool DeleteDialog(Mc4Dialog *dlg);
	LLayoutCell *EmptyCell(int x, int y);
	bool ShowAxefxBlocks(char Start, char End);
	void UpdatePlayParams(); // Updates just the currently selected params
	void UpdateButtonLabels();
	bool SerializePreset(bool Write);
	bool SerializeGlobal(bool Write);
	const char *GetStatusTxt() { return StatusTxt; } 
	
	// Methods...
	bool ScreenShot();
	void DrawBankRange();
	void SetCtrlColour(LColour c, bool DoNum, bool DoName);
	void OnBack(unsigned Count = 1);
	LString GetPresetPath(bool Db, int Idx = -1);
	LString GetGlobalPath();
	void ShowFirmwareMsg();
	void SetParamGroup(
		/// e.g. IDC_EXTERN_1_2.. or IDC_AMP1_GAIN...
		int ctrlId
		);

	// View events...
	bool OnLayout(LViewLayoutInfo &Inf) override;
	void OnPaint(LSurface *pDC) override;
	void OnPulse() override;
	void OnCreate() override;
	int OnNotify(LViewI *Ctrl, LNotification n) override;
	bool OnViewKey(LView *v, LKey &k) override;
	void OnMouseClick(LMouse &m) override;
	LMessage::Result OnEvent(LMessage *Msg) override;
};

class SetupDlg : public Mc4Dialog
{
	bool PresetDirty, GlobalDirty;
	void SetIaGroup(int Ia, int Grp);

public:
	SetupDlg(Mc4Ui *ui);
	~SetupDlg();

	bool Load();
	int OnNotify(LViewI *Ctrl, LNotification n) override;
};

class IaConfigDlg : public Mc4Dialog
{
	IaRef EditRef;

public:
	IaConfigDlg(Mc4Ui *ui, IaRef ref);

	bool Load();
	bool Update();
	void OnAutoSelectParam(int Id);
	void OnBlockAction(IaBlockAction action);
	int OnNotify(LViewI *Ctrl, LNotification n) override;
};

