#pragma once

#include <stdint.h>


/// @brief Commands are directed to a specific SubSystem of the AVReceiver.
typedef enum {
	sysCommand,			// even with power off
	subMain,			// after power is on
	subRadio,
	subAudio,
	subZone1,
	subZone2,
	subZone3,
	subsystemCount
} AVRSubsystem_T;

/// @brief Functions with a specific subsystem are the target of a command
typedef enum {
	fncPower,
	fncVolume,
	fncMute,
	fncVolumeMemory,
	fncVolumeRecall,
	fncSpeakerAOnOff,
	fncSpeakerBOnOff,
	fncSpeakerBZone,
	fncZone2SpeakerOnOff,
	fncNightModeOnOff,
	fncEffect,
	fncDSPSoundScape,
	fncInput,
	fncRadioBand,
	fncRadioTune,
	fncRadioPresetPage,
	fncRadioPresetNumber,
	fncRadioPresetMemory,
	fncRadioPresetRecall,
	fncSleep,
	fncOSD,
	fnc6ChInput,
	fncEx_EsOnOff,
	fncInputMode,
	fncDualMono,
	fncDC1TrigControl,
	fncDC2TrigControl,
	fncDC1OnOff,
	fncDC2OnOff,
	fncReady,
	fncReportEnable,
	fncReportDelay,
	fncRequest,
	fncSetValue,
	fncFunctionCount
} AVRFunction_T;

/// @brief and the command applied to that function are captured here
typedef enum {
	eOn = 0,		///<! Anything that takes an On, Off, or Standby command
	eOff,
	eStandby,
	eUp,			///<! Volume and Radio tuning accept up and down
	eDown,
	eMuteOn,		///<! Volume accepts a mute on and mute off
	eMuteOff,
	eFM,			///<! The radio band can be selected
	eAM,
	eA,				///<! Preset Groups
	eB,
	eC,
	eD,
	eE,
	eF,
	e1,				///<! Preset Numbers within a group
	e2,
	e3,
	e4,
	e5,
	e6,
	e7,
	e8,
	eSleepOff,		///<! System Sleep can be initiated
	eSleep120,
	eSleep90,
	eSleep60,
	eSleep30,
	ePhono,			///<! The input can be selected
	eCD,
	eTuner,
	eCDR,
	eMD_Tape,
	eDVD,
	eDTV,
	eCable,
	eSat,
	eVCR1,
	eVCR2_DVR,
	eVCR3,
	eV_Aux,
	eOSDOff,		///<! The On Screen Display can be controlled
	eOSDShort,
	eOSDFull,
	eOnMatrix,		///<! I don't know what this does
	eESESOff,
	eAuto,
	eDiscrete,		///<! Or this
	eEffectOn,
	eStereo,
	eMain,			///<! Some systems commands target a zone
	eZone1,
	eZone2,
	eZone3,
	eZoneOR,
	Hall_A,			///<! Overall sound presence can be selected
	Hall_B,
	Hall_C,
	Hall_USA,
	Hall_E,
	Live_Concert,
	Tokyo,
	Freiburg,
	Royaumont,
	Village_Gate,
	Village_Vanguard,
	The_Bottom_Line,
	The_Roxy_Theater,
	Warehouse_Loft,
	Arena,
	Disco,
	Party,
	Game,
	Stereo_6_8Ch,
	Pop_Rock,
	DJ,
	Opera,
	Pavillion,
	Mono_Movie,
	Variety_Sports,
	Spectacre,
	Sci_Fi,
	Adventure,
	General,
	Normal,
	Enhanced,
	PLII_Movie,
	PLII_Music,
	Neo_6_Movie,
	Neo_6_Music,
	Direct_2Ch,
	Stereo_2Ch,
	THX_Ultra_PL,
	THX_Music,
	THX_Ultra_PL2,
	THX_Ultra_NEO6,
	eInpAuto,				///<! Not sure about this thing
	eDD_RF,
	eDTS,
	eDigital,
	eAnalog,
	eAAC,
	eDualMain,				///<! and again not sure
	eDualSub,
	eDualAll,

	e0ms,					///<! Response delays to the commands can be set
	e50ms,
	e100ms,
	e150ms,
	e200ms,
	e250ms,
	e300ms,
	e350ms,
	e400ms,

	eTuningFreq,			///<! There are a number of additional commands
	eMainVolDB,
	eZone2VolDB,
	eInputName,
	eZone2InputName,
	eZoneXVolDB,
	eZoneXInputName,
	eMasterVol,
	eZone2Vol,
	eMainLRBal,
	eMainLevel,
	eZone3Vol,

	eMainLevelR,
	eMainLevelL,
	eCenterLevel,
	eRearR,
	eRearL,
	eFrontR,
	eFrontL,
	eSurBackR,
	eSurBackL,
	eSwfr1,
	eSwfr2,

	eARGCount
} AVRArg_T;


/// @brief AVRMessageType_T
///
///	Indicates the type of message being sent to the status change callback
/// 
typedef enum {
	mtState,		///<! State machine state
	mtInfo,			///<! General purpose information (chunks of information that might be useful in a small scroll region)
	mtModelInfo,	///<! Model information
	mtStreamStart,	///<! Stream start of the status, each chunk is a generally a same-length string, totaling 100s of bytes.
	mtStream,		///<! Status stream that word-wraps...
	mtRcvdMsg,		///<! The byte stream of a received message (one packet at a time) suitable for a hex display
	mtXmtdMsg,		///<! The byte stream of a message being sent suitable for a hex display
	mtStatusMsg,	///<! The status message, suitable for showing a status update
	mtOperationResp,///<! Operation Response to some command
	mtParamUpdated,	///<! A parameter was updated
	mtErrNotFound,	///<! Command Not Found (and3 bytes packed into 'param'
	mtExtendedResp,	///<! Extended protocol response to an extended protocol command
	mtErrRxLength,	///<! The received message (from the AVR) had an unexpected length
	mtErrMalloc,	///<! Memory allocation error
	mtFailed,		///<! Failed to establish contact
} AVRMessageType_T;

/// @brief The Message table maps the valid combinations of subsystem, function, and command (argument)
///
typedef const struct {
	AVRSubsystem_T subsystem;	///<! the subystem target for the command
	AVRFunction_T function;		///<! the function within the subsystem
	AVRArg_T arg;				///<! the actual command to apply
	const char *Message;		///<! the message to send to the AVR for this command
	uint16_t MessageLen;		///<! the length of the message to send
} MessageTable_T;

/// NotifyCallBack
/// 
/// @brief Notify the host of an event
///
/// This is the function prototype for the status change callback from the AVR interface.
/// This can be used to interpret, or more commonly to display, various types of status information.
/// 
/// @param[in] type is the type of message being sent
/// @param[in] message is usually the [ASCII] byte stream, or NULL
/// @param[in] attrib is the strlen(message), or for other types, it may be a value
/// @returns true of the message was accepted
/// 
typedef bool (*NotifyCallBack)(AVRMessageType_T type, const void *message, uint32_t attrib);

/// SendCallBack
/// 
/// @brief Send a message to RS-232 Interface
/// 
/// This is used to send a message to the AVR via the RS-232 Channel that
/// is provided by the host
/// 
/// @param[in] message is the message to send which is null terminated
/// @param[in] len is the length of the message (seems redundant but it avoids buffer overruns)
/// @returns true if the message was accepted for sending
/// 
typedef bool (*SendCallBack)(const uint8_t *message, uint32_t len);


class AVRDriver
{
public:
	AVRDriver(
		NotifyCallBack notifyCB = NULL,	///<! used to notify the host of internal events
		SendCallBack sendCB = NULL			///<! used to send to the RS-232 interface
	);

	~AVRDriver();

	/// @brief internal operating states of the AVR interface
	typedef enum {
		stPoweringUp,				///<! powering up
		stAwaitingReadyResponse,	///<! waiting for the special ready response
		stInitializing,				///<! initializing
		stRetryInitializing,		///<! retrying initialization
		stReady,					///<! ready for commands
		stAwaitingResponse,			///<! waiting for a response to a command
		stFailed,					///<! failed to establish contact
		stMaxStates					///<! maximum states
	} AVRState_T;

	/// @brief Call this periodically so timed activities can be handled.
	/// 
	/// Every 1 or even 50 to 100 msec is ok.
	/// 
	/// @param[in] milliseconds since the program started
	/// @returns the current state of the AVR interface
	/// 
	AVRState_T Tick(uint32_t millisec);

	uint32_t GetTick();

	/// @brief When the system receives something from the device, give it to this function to handle it
	/// @param buffer 
	/// @param len 
	/// @return 0 if none, or the index into the main status array that was updated
	/// 
	uint8_t HandleMessage(const uint8_t *buffer, uint16_t len);

	/// @brief The primary command path to control the AVR
	/// 
	/// This lets you send a command to the AVR using this single interface, by choosing
	/// the AVR Subsystem of interest, the Function of interest, and passing argument(s)
	/// 
	/// @param[in] subsystem: Main | Zone 1 | Zone 2 | Zone 3
	/// @param[in] function : Power, Speaker, Volume, etc.
	/// @param[in] arg: on/off, etc.
	/// @param[in] variableData is for those functions where it needs to integrate variable value, such as setting the volume directly
	/// @return true if accepted
	///
	bool AVRCommand(AVRSubsystem_T subsystem,
		AVRFunction_T function,
		AVRArg_T arg,
		uint8_t variableData = 0);


	/// SendMessageToSerial
	/// 
	/// @brief This the public interface where received serial data is sent to get a command to the AVR
	/// @param[in] p the message to send
	/// @param[in] len of the message
	/// @return true
	/// 
	bool SendMessageToSerial(const void *p = NULL, uint16_t len = NULL);


	/// AVRSendOSDMessage
	/// 
	/// @brief Send a text message to the AVR for display on the connected TV
	/// @param[in] msg is a text string, which must not exceed 16 characters in length, is null terminated
	///				and is restricted to the following characters:
	///				" !#%&()*+,-.0123456789:<=>?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz"
	/// @return true if the message was accepted for sending, false if it is too long or has invalid characters.
	/// 
	bool AVRSendOSDMessage(const char *msg);

	/// Hex2Dec
	/// 
	/// @brief a handy little conversion utility
	/// 
	/// @param p is a pointer to an ASCII-HEX string
	/// @param dig is the number of digits to convert
	/// @return converted value
	/// 
	uint16_t Hex2Dec(const uint8_t *p, int dig);

	/// @brief For iterating over the message table
	/// @return the number of records in the table
	/// 
	uint16_t GetMessageTableSize();

    /// @brief a method to get a handle to a specific record
    /// @param i 
    /// @return a pointer to the record
	/// 
    MessageTable_T *GetMessageTableRecord(uint16_t i);


	/// Each DT is the hex-character from the stream
	///
	/// This could be simplified to uint8_t DT[138] except for the loss of the comments
	///
	typedef struct {
		uint8_t DT0;	// * Baud Rate		 '@'
		uint8_t DT1;	// * Receive Buffer  'E'
		uint8_t DT2;	// * Receive Buffer  '0'
		uint8_t DT3;	// *                 '1'
		uint8_t DT4;	// * Command Timeout '9'
		uint8_t DT5;	// *                 '0'
		uint8_t DT6;	// *                 '0'
		uint8_t DT7;	// * System          '0':Ok, '1':Busy
		uint8_t DT8;	// * Power            0:Off, 1:On
		uint8_t DT9;	// Input      0: Phono, 1:CD, 2:Tuner, 3:CD-R, 4:MD-Tape, 5:DVD, 6:D-TV, 7:Cbl, 9:VCR1, A:VCR2
		uint8_t DT10;	// 6ch input 0:Off, 1:On
		uint8_t DT11;	// Input Mode 0:AUTO, 2:DTS, 4:Analog, 5:Analog Only
		uint8_t DT12;	// Audio Mute 0:Off, 1:On
		uint8_t DT13;	// Zone2 Input 0: PHONO / 1: CD / 2: TUNER / 3: CD-R / 4: MD-TAPE / 5: DVD / 6: D-TV-LD / 7: CBL-SAT / 9: VCR1 / A: VCR2-DVR / C: V-AUX
		uint8_t DT14;	// Zone2 Mute 0: OFF / 1: ON
		uint8_t DT15;	// Master Volume Upper 4 bit
		uint8_t DT16;	// Master Volume Lower 4 bit
		uint8_t DT17;	// Zone2 Volume Upper 4 bit
		uint8_t DT18;	// Zone2 Volume Lower 4 bit
		uint8_t DT19;	// Program Upper 4 bit
		uint8_t DT20;	// Program Lower 4 bit
		uint8_t DT21;	// Effect 0: OFF / 1: ON
		uint8_t DT22;	// 6.1/ES key status 0: OFF / 1: MATRIX ON / 2: DISCRETE ON / 3: AUTO
		uint8_t DT23;	// OSD* 0: FULL / 1: SHORT / 2: OFF
		uint8_t DT24;	// Sleep 0: 120 / 2: 90 / 3: 60 / 4: 30 / 5: OFF
		uint8_t DT25;	// Tuner Page 0: Page A / 1: Page B / 2: Page C / 3: Page D / 4: PageE
		uint8_t DT26;	// Tuner No. 0: No.1 / 1: No.2 / 2: No.3 / 3: No.4 / 4: No.5 / 5: No.6 / 6: No.7 / 7: No.8
		uint8_t DT27;	// Night mode 0: OFF / 1: ON
		uint8_t DT28;	// Care
		uint8_t DT29;	// Speaker relay A 0: OFF / 1: ON
		uint8_t DT30;	// Speaker relay B 0: OFF / 1: ON
		uint8_t DT31;	// Playback 0: 6ch input / 1: Analog / 2: PCM / 3: DD*(except 2.0) / 4: DD(2.0) / 5: DD.Karaoke / 6: DD.EX / 7: DTS / 8: DTS-ES / 9: Other DIGITAL / A: DTS Analog Mute / B: DTS ES Discrete
		uint8_t DT32;	// Fs 0: Analog / 1: 32kHz / 2: 44.1kHz / 3: 48kiHz / 4: 64kHz / 5: 88.2kHz / 6: 96kHz / 7: Unknown B: DTS 96/24
		uint8_t DT33;	// EX/ES playback 0: OFF / 1: MATRIX ON / 2: DISCRETE ON
		uint8_t DT34;	// Thr / Bypass 0: Normal / 1: Bypass
		uint8_t DT35;	// RED dts 0: Release / 1: Wait
		uint8_t DT36;	// Head Phone 0: OFF / 1: ON
		uint8_t DT37;	// TUNER BAND 0: FM / 1: AM
		uint8_t DT38;	// TUNER TUNED 0: NOT TUNED / 1: TUNED
		uint8_t DT39;	// DC1 Control Out 0: LOW / 1: HIGH

		uint8_t DT40;	 // Don’t care
		uint8_t DT41;	 // Don't Care
		uint8_t DT42;	 // 0-2 DC1 TRG Ctrl. 0: Zone1 / 1: Zone2 / 2: Zone1&2
		uint8_t DT43;	 // 0/1 dts 96/24 0: OFF / 1: ON
		uint8_t DT44;	 // 0-2 DC2 TRG Ctrl. 0: Zone1 / 1: Zone2 / 2: Zone1&2
		uint8_t DT45;	 // 0/1 DC2 Trigger 0: LOW / 1: HIGH
		uint8_t DT46;	 // SP B set 0: Zone1 / 1: Zone2
		uint8_t DT47;	 // Zone 2 SP out 0: OFF / 1: ON
		uint8_t DT48;	 // MAIN R Upper 4bit
		uint8_t DT49;	 // Lower 4bit
		uint8_t DT50;	 // MAIN L Upper 4bit
		uint8_t DT51;	 // Lower 4bit
		uint8_t DT52;	 // CENTER Upper 4bit
		uint8_t DT53;	 // Lower 4bit
		uint8_t DT54;	 // REAR R Upper 4bit
		uint8_t DT55;	 // Lower 4bit
		uint8_t DT56;	 // REAR L Upper 4bit
		uint8_t DT57;	 // Lower 4bit
		uint8_t DT58;	 // SUR BACK Upper 4bit
		uint8_t DT59;	 // R Lower 4bit
		uint8_t DT60;	 // SUR BACK Upper 4bit
		uint8_t DT61;	 // L Lower 4bit
		uint8_t DT62;	 // FRONT R Upper 4bit
		uint8_t DT63;	 // Lower 4bit
		uint8_t DT64;	 // FRONT L Upper 4bit
		uint8_t DT65;	 // Lower 4bit
		uint8_t DT66;	 // SWFR 1 Upper 4bit
		uint8_t DT67;	 // Lower 4bit
		uint8_t DT68;	 // Don't Care
		uint8_t DT69;	 // Don't Care
		uint8_t DT70;	 // Don't Care
		uint8_t DT71;	 // Don't Care
		uint8_t DT72;	 // Don't Care
		uint8_t DT73;	 // Don't Care
		uint8_t DT74;	 // LFE Lvl. SP Upper 4bit
		uint8_t DT75;	 // Lower 4bit
		uint8_t DT76;	 // LFE Lvl. HP Upper 4bit
		uint8_t DT77;	 // Lower 4bit
		uint8_t DT78;	 // Audio Delay Upper 4bit
		uint8_t DT79;	 // Lower 4bit

		uint8_t DT80;	// Don't Care
		uint8_t DT81;	// Don't Care
		uint8_t DT82;	// Don't Care
		uint8_t DT83;	// Don't Care
		uint8_t DT84;	// Input mode set 0: AUTO / 1: LAST
		uint8_t DT85;	// Dimmer 0: -4 / 1: -3 / 2: -2 / 3: -1 / 4: 0
		uint8_t DT86;	// OSD Message
		uint8_t DT87;	// OSD shift Upper 4bit
		uint8_t DT88;	// Lower 4bit
		uint8_t DT89;	// Glay back 0: OFF / 1: AUTO
		uint8_t DT90;	// Video conversion 0: OFF / 1: ON
		uint8_t DT91;	// D. Range SP 0: MAX / 1: STD / 2: MIN
		uint8_t DT92;	// HP 0: MAX / 1: STD / 2: MIN
		uint8_t DT93;	// Zone 2 vol. Out
		uint8_t DT94;	// Don't Care
		uint8_t DT95;	// Memory guard 0: OFF / 1: ON
		uint8_t DT96;	// SP set Center 0: Large / 1: Small / 2: None
		uint8_t DT97;	// Main 0: Large / 1: Small
		uint8_t DT98;	// Rear L/R 0: Large / 1: Small / 2: None
		uint8_t DT99;	// Rear CT 0: Large / 1: Small / 2: None
		uint8_t DT100;	// Front 0: Yes / 1: None
		uint8_t DT101;	// LFE/BASS 0: SWFR / 1: Main / 2: Both
		uint8_t DT102;	// 6CH Center 0: Center / 1: Main
		uint8_t DT103;	// SWFR 0: SWFR / 1: Main
		uint8_t DT104;	// Main level 0: Normal / 1: -10dB
		uint8_t DT105;	// Test mode 0: OFF / 1: Dolby / 2: DTS
		uint8_t DT106;	// Don't Care
		uint8_t DT107;	// LVL 6CH MAIN L Upper 4bit
		uint8_t DT108;	// Lower 4bit
		uint8_t DT109;	// MAIN R Upper 4bit
		uint8_t DT110;	// Lower 4bit
		uint8_t DT111;	// CENTER Upper 4bit
		uint8_t DT112;	// Lower 4bit
		uint8_t DT113;	// SL Upper 4bit
		uint8_t DT114;	// Lower 4bit
		uint8_t DT115;	// SR Upper 4bit
		uint8_t DT116;	// Lower 4bit
		uint8_t DT117;	// SBL Upper 4bit
		uint8_t DT118;	// Lower 4bit
		uint8_t DT119;	// SBR Upper 4bit
		uint8_t DT120;	// Lower 4bit
		uint8_t DT121;	// FRONT L Upper 4bit
		uint8_t DT122;	// Lower 4bit
		uint8_t DT123;	// FRONT R Upper 4bit
		uint8_t DT124;	// Lower 4bit
		uint8_t DT125;	// SWFR Upper 4bit
		uint8_t DT126;	// Lower 4bit
		uint8_t DT127;	// 0 - C Z3 Input
		uint8_t DT128;	// 0/1 Z3 Mute
		uint8_t DT129;	// 0 - F Z3 Volume Upper 4bit

		uint8_t DT130;	// 0 - F Lower 4bit
		uint8_t DT131;	// Don't Care
		uint8_t DT132;	// MULTI_CH SELECT 00:6CH / 01:8CH TUNER / 02: 8CH CD / 04: 8CH CD-R / 05: 8CH DVD / 06: DTV / 07: 8CH CBL/SAT / 09: 8CH VCR1 / 0A: VCR2/DVR / 0C: VAUX
		uint8_t DT133;	// MULTI_CH SURROUND to 00: Surround / 01: Main
		uint8_t DT134;	// SP SET SW1 00: L-R / 01: F-R / 02: NONE
		uint8_t DT135;	// SP SET CROSSOVER 00: 40Hz / 01: 60Hz / 02: 80Hz / 03: 90Hz / 04: 100Hz / 05: 110Hz / 06: 120Hz / 07: 160Hz / 08: 200Hz
		uint8_t DT136;	// COMPONENT OSD 00: OFF / 01: ON
		uint8_t DT137;	// PB/SB SELECT 00: PR / 01: SB

		uint8_t DT138[100];	// From here on is just buffer in case it sends more data
	} AVR_Configuration_T;

	/// @brief prefix for the large received messages
	typedef struct {
		uint8_t type[5];	///<! Model ID of the AVR
		uint8_t version;	///<! Version of the A-Z
		uint8_t length[2];	///<! the length (1 - 255) following
	} AVR_StatusHeader_T;

	/// @brief the privately maintained status of the AVR
	typedef struct {
		bool headerValid;			///<! a short received message only makes the header valid
		bool configValid;			///<! a longer received message updates everything in the config
		AVR_StatusHeader_T header;	///<! the header block
		AVR_Configuration_T config;	///<! the rest of the data
	} AVR_Status_T;

	/// @brief a method to get the status of the AVR
	/// @return the status
	/// 
	const AVR_Status_T *GetAVRStatusData() {
		return &avrStatus;
	};

protected:

	NotifyCallBack NotifyHost;
	SendCallBack   RS232Send;

private:
	AVRState_T state = stPoweringUp;
	AVRState_T oldState = stMaxStates;

	bool bFirstTickInitialized = false;
	uint32_t firstTick_ms = 0;	// basically the time when the program started
	uint32_t lastTick_ms = 0;	// @TODO instead of this, offer a way for the class to get the current time
	uint32_t sentAtTime_ms = 0;

	AVR_Status_T avrStatus = { 0 };
	bool commandResponseReceived = false; // a response to the last command was received
	bool readyResponsReceived = false;	// the special system ready response was received
	int readyTries = 0;
	#define RETRY_INTERVAL_ms 1000
	#define MAXTRIES 5

	#define SERIALQUEUESIZE 5
	typedef struct {
		uint8_t *messageToSend;
		uint16_t len;
	} SerialQueue_T;

	SerialQueue_T serialQueue[SERIALQUEUESIZE] = { 0 };
	int serialQueueCount = 0;

	/// @brief Process a message which is likely to update the main status array
	/// @param buffer 
	/// @param len 
	/// @return the index into the main status array that was changed, or 0 if none
	/// 
	uint8_t ProcessResponse(const uint8_t *buffer, uint16_t len);

	void Inject2ASCIIHexCharsAt(char *p, uint8_t val);

	bool CheckTheChecksum(const uint8_t *szBuffer, uint32_t num);
	void FreeSendQueue();

};

