/*
 * EBYTE LoRa E220 Series
 *
 * AUTHOR:  Renzo Mischianti
 * VERSION: 1.0.6
 *
 * https://www.mischianti.org
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2022 Renzo Mischianti www.mischianti.org All right reserved.
 *
 * You may copy, alter and reuse this code in any way you like, but please leave
 * reference to www.mischianti.org in your comments if you redistribute this code.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef LoRa_E220_h
#define LoRa_E220_h

#if !defined(ARDUINO_ARCH_STM32) && !defined(ESP32) && !defined(ARDUINO_ARCH_SAMD) && !defined(ARDUINO_ARCH_MBED) && !defined(__STM32F1__) && !defined(__STM32F4__)
	#define ACTIVATE_SOFTWARE_SERIAL
#endif
#if defined(ESP32)
	#define HARDWARE_SERIAL_SELECTABLE_PIN
#endif

#ifdef ACTIVATE_SOFTWARE_SERIAL
	#include <SoftwareSerial.h>
#endif

#include <includes/statesNaming.h>

#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#define MAX_SIZE_TX_PACKET 200

// Uncomment to enable printing out nice debug messages.
//#define LoRa_E220_DEBUG

// Define where debug output will be printed.
#define DEBUG_PRINTER Serial

// Setup debug printing macros.
#ifdef LoRa_E220_DEBUG
	#define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
	#define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
#else
	#define DEBUG_PRINT(...) {}
	#define DEBUG_PRINTLN(...) {}
#endif

enum MODE_TYPE {
	MODE_0_NORMAL 			= 0,
	MODE_0_TRANSMISSION 	= 0,
	MODE_1_WOR_TRANSMITTER 	= 1,
	MODE_1_WOR				= 1,
	MODE_2_WOR_RECEIVER 	= 2,
	MODE_2_POWER_SAVING 	= 2,
	MODE_3_CONFIGURATION 	= 3,
	MODE_3_PROGRAM 			= 3,
	MODE_3_SLEEP 			= 3,
	MODE_INIT 				= 0xFF
};

enum PROGRAM_COMMAND {
	WRITE_CFG_PWR_DWN_SAVE 	= 0xC0,
	READ_CONFIGURATION 		= 0xC1,
	WRITE_CFG_PWR_DWN_LOSE 	= 0xC2,
	WRONG_FORMAT 			= 0xFF,
	RETURNED_COMMAND 		= 0xC1,
	SPECIAL_WIFI_CONF_COMMAND = 0xCF
};

enum REGISTER_ADDRESS {
	REG_ADDRESS_CFG			= 0x00,
	REG_ADDRESS_SPED 		= 0x02,
	REG_ADDRESS_TRANS_MODE 	= 0x03,
	REG_ADDRESS_CHANNEL 	= 0x04,
	REG_ADDRESS_OPTION	 	= 0x05,
	REG_ADDRESS_CRYPT	 	= 0x06,
	REG_ADDRESS_PID		 	= 0x08
};

enum PACKET_LENGHT {
	PL_CONFIGURATION 	= 0x08,

	PL_SPED				= 0x01,
	PL_OPTION			= 0x01,
	PL_TRANSMISSION_MODE= 0x01,
	PL_CHANNEL			= 0x01,
	PL_CRYPT			= 0x02,
	PL_PID				= 0x03
};

#pragma pack(push, 1)
struct Speed {
	uint8_t airDataRate :3; //bit 0-2
	String getAirDataRateDescription() {
		return getAirDataRateDescriptionByParams(this->airDataRate);
	}

	uint8_t uartParity :2; //bit 3-4
	String getUARTParityDescription() {
		return getUARTParityDescriptionByParams(this->uartParity);
	}

	uint8_t uartBaudRate :3; //bit 5-7
	String getUARTBaudRateDescription() {
		return getUARTBaudRateDescriptionByParams(this->uartBaudRate);
	}

};

struct TransmissionMode {
	byte WORPeriod :3; //bit 2,1,0
	String getWORPeriodByParamsDescription() {
		return getWORPeriodByParams(this->WORPeriod);
	}
	byte reserved2 :1; //bit 3
	byte enableLBT :1; //bit 4
	String getLBTEnableByteDescription() {
		return getLBTEnableByteByParams(this->enableLBT);
	}
	byte reserved :1; //bit 5

	byte fixedTransmission :1; //bit 6
	String getFixedTransmissionDescription() {
		return getFixedTransmissionDescriptionByParams(this->fixedTransmission);
	}

	byte enableRSSI :1; //bit 7
	String getRSSIEnableByteDescription() {
		return getRSSIEnableByteByParams(this->enableRSSI);
	}
};

struct Option {
	uint8_t transmissionPower :2; //bit 0-1
	String getTransmissionPowerDescription() {
		return getTransmissionPowerDescriptionByParams(this->transmissionPower);
	}
	uint8_t reserved :3; //bit 2-4

	uint8_t RSSIAmbientNoise :1; //bit 5
	String getRSSIAmbientNoiseEnable() {
		return getRSSIAmbientNoiseEnableByParams(this->RSSIAmbientNoise);
	}

	uint8_t subPacketSetting :2; //bit 6-7
	String getSubPacketSetting() {
		return getSubPacketSettingByParams(this->subPacketSetting);
	}

};

struct Crypt {
	byte CRYPT_H = 0;
	byte CRYPT_L = 0;
};

struct Configuration {
	byte COMMAND = 0;
	byte STARTING_ADDRESS = 0;
	byte LENGHT = 0;

	byte ADDH = 0;
	byte ADDL = 0;

	struct Speed SPED;
	struct Option OPTION;

	byte CHAN = 0;
	String getChannelDescription() {
		return String(this->CHAN + OPERATING_FREQUENCY) + F("MHz");
	}

	struct TransmissionMode TRANSMISSION_MODE;

	struct Crypt CRYPT;
};

struct ModuleInformation {
	byte COMMAND = 0;
	byte STARTING_ADDRESS = 0;
	byte LENGHT = 0;

	byte model = 0;
	byte version = 0;
	byte features = 0;
};

struct ResponseStatus {
	Status code;
	String getResponseDescription() {
		return getResponseDescriptionByParams(this->code);
	}
};

struct ResponseStructContainer {
	void *data;
	byte rssi;
	ResponseStatus status;
	void close() {
		free(this->data);
	}
};
struct ResponseContainer {
	String data;
	byte rssi;
	ResponseStatus status;
};

struct ConfigurationMessage
{
	byte specialCommand1 = 0xCF;
	byte specialCommand2 = 0xCF;

	unsigned char message[];
};

//struct FixedStransmission {
//		byte ADDL = 0;
//		byte ADDH = 0;
//		byte CHAN = 0;
//		void *message;
//};
#pragma pack(pop)

class LoRa_E220 {
	public:
#ifdef ACTIVATE_SOFTWARE_SERIAL
		LoRa_E220(byte txE220pin, byte rxE220pin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(byte txE220pin, byte rxE220pin, byte auxPin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(byte txE220pin, byte rxE220pin, byte auxPin, byte m0Pin, byte m1Pin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
#endif

		LoRa_E220(HardwareSerial* serial, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(HardwareSerial* serial, byte auxPin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(HardwareSerial* serial, byte auxPin, byte m0Pin, byte m1Pin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);

#ifdef HARDWARE_SERIAL_SELECTABLE_PIN
		LoRa_E220(byte txE220pin, byte rxE220pin, HardwareSerial* serial, UART_BPS_RATE bpsRate, uint32_t serialConfig = SERIAL_8N1);
		LoRa_E220(byte txE220pin, byte rxE220pin, HardwareSerial* serial, byte auxPin, UART_BPS_RATE bpsRate, uint32_t serialConfig = SERIAL_8N1);
		LoRa_E220(byte txE220pin, byte rxE220pin, HardwareSerial* serial, byte auxPin, byte m0Pin, byte m1Pin, UART_BPS_RATE bpsRate, uint32_t serialConfig = SERIAL_8N1);
#endif

#ifdef ACTIVATE_SOFTWARE_SERIAL
		LoRa_E220(SoftwareSerial* serial, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(SoftwareSerial* serial, byte auxPin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
		LoRa_E220(SoftwareSerial* serial, byte auxPin, byte m0Pin, byte m1Pin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600);
#endif

//		LoRa_E220(byte txE220pin, byte rxE220pin, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600, MODE_TYPE mode = MODE_0_NORMAL);
//		LoRa_E220(HardwareSerial* serial = &Serial, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600, MODE_TYPE mode = MODE_0_NORMAL);
//		LoRa_E220(SoftwareSerial* serial, UART_BPS_RATE bpsRate = UART_BPS_RATE_9600, MODE_TYPE mode = MODE_0_NORMAL);

		bool begin();
        Status setMode(MODE_TYPE mode);
        MODE_TYPE getMode();

		ResponseStructContainer getConfiguration();
		ResponseStatus setConfiguration(Configuration configuration, PROGRAM_COMMAND saveType = WRITE_CFG_PWR_DWN_LOSE);

		ResponseStructContainer getModuleInformation();
		ResponseStatus resetModule();

		ResponseStatus sendMessage(const void *message, const uint8_t size);

	    ResponseContainer receiveMessageUntil(char delimiter = '\0');
		ResponseStructContainer receiveMessage(const uint8_t size);
		ResponseStructContainer receiveMessageRSSI(const uint8_t size);
	        
        ResponseStructContainer receiveMessageComplete(const uint8_t size, bool enableRSSI);
		ResponseContainer receiveMessageComplete(bool enableRSSI);
	
		ResponseStatus sendMessage(const String message);
		ResponseContainer receiveMessage();
		ResponseContainer receiveMessageRSSI();

		ResponseStatus sendFixedMessage(byte ADDH, byte ADDL, byte CHAN, const String message);

        ResponseStatus sendFixedMessage(byte ADDH,byte ADDL, byte CHAN, const void *message, const uint8_t size);
        ResponseStatus sendBroadcastFixedMessage(byte CHAN, const void *message, const uint8_t size);
        ResponseStatus sendBroadcastFixedMessage(byte CHAN, const String message);

		ResponseContainer receiveInitialMessage(const uint8_t size);

		ResponseStatus sendConfigurationMessage( byte ADDH,byte ADDL, byte CHAN, Configuration *configuration, PROGRAM_COMMAND programCommand = WRITE_CFG_PWR_DWN_SAVE);

        int available();

		void (*setMPins)(int, int);

	private:

		HardwareSerial* hs;

#ifdef ACTIVATE_SOFTWARE_SERIAL
		SoftwareSerial* ss;
#endif

		bool isSoftwareSerial = true;

		int8_t txE220pin = -1;
		int8_t rxE220pin = -1;
		int8_t auxPin = -1;

#ifdef HARDWARE_SERIAL_SELECTABLE_PIN
		uint32_t serialConfig = SERIAL_8N1;
#endif

		int8_t m0Pin = -1;
		int8_t m1Pin = -1;

		unsigned long halfKeyloqKey = 0x06660708;
		unsigned long encrypt(unsigned long data);
		unsigned long decrypt(unsigned long data);

		UART_BPS_RATE bpsRate = UART_BPS_RATE_9600;

		struct NeedsStream {
			template<typename T>
			void begin(T &t, uint32_t baud) {
				DEBUG_PRINTLN("Begin ");
				t.setTimeout(500);
				t.begin(baud);
				stream = &t;
			}

#ifdef HARDWARE_SERIAL_SELECTABLE_PIN
//		  template< typename T >
//		  void begin( T &t, uint32_t baud, SerialConfig config ){
//			  DEBUG_PRINTLN("Begin ");
//			  t.setTimeout(500);
//			  t.begin(baud, config);
//			  stream = &t;
//		  }
//
			template< typename T >
			void begin( T &t, uint32_t baud, uint32_t config ) {
				DEBUG_PRINTLN("Begin ");
				t.setTimeout(500);
				t.begin(baud, config);
				stream = &t;
			}

			template< typename T >
			void begin( T &t, uint32_t baud, uint32_t config, int8_t txE220pin, int8_t rxE220pin ) {
				DEBUG_PRINTLN("Begin ");
				t.setTimeout(500);
				t.begin(baud, config, txE220pin, rxE220pin);
				stream = &t;
			}
#endif

			void listen() {}

			Stream *stream;
		};
		NeedsStream serialDef;

		MODE_TYPE mode = MODE_0_NORMAL;

		void managedDelay(unsigned long timeout);
		Status waitCompleteResponse(unsigned long timeout = 1000, unsigned int waitNoAux = 100);
		void flush();
		void cleanUARTBuffer();

		Status sendStruct(void *structureManaged, uint16_t size_);
		Status receiveStruct(void *structureManaged, uint16_t size_);
		void writeProgramCommand(PROGRAM_COMMAND cmd, REGISTER_ADDRESS addr, PACKET_LENGHT pl);

		RESPONSE_STATUS checkUARTConfiguration(MODE_TYPE mode);

#ifdef LoRa_E220_DEBUG
		void printParameters(struct Configuration *configuration);
#endif
};

#endif