#include <ArduinoLowPower.h>

#define CHARBUFF 256
#define APN "simbase"
#define DOMAIN_NAME "114.142.160.204"
#define CLIENT_ID "mqtt_nano"
#define USERNAME ""
#define PASSWORD ""
#define TOPIC "pluto"

char response[CHARBUFF];
char request[CHARBUFF];

uint32_t uploads = 0;

uint16_t vbat;

//these character arrays should be null terminated.
uint8_t sendATcmd(char* cmd, const char* expAns, uint32_t timeout = 500, uint8_t rtries = 5){
	uint8_t gotAns = 0;
	uint8_t retries = rtries;
	bool match = false;
	
	while(gotAns < retries && (not match)){
		
		
		while( Serial1.available() > 0) {
			Serial.write(Serial1.read());    // Clean the input buffer
		}
		
		Serial.print(F("->"));
		Serial.print(cmd);
		
		Serial1.println(cmd);
		if(expAns == NULL){
			return true;
		}else{
			if(parseATcmd(&expAns, 1, timeout) >= 0){
				match = true;
			}
		}
		
		gotAns++;
	}
	
	
	return match;
}

int8_t waitATcmd(const char** expAns, uint8_t cases, uint32_t timeout){
	uint32_t tsend = millis();	
	uint16_t i = 0;
	bool match = false;
	uint32_t match_fl = 0;
	uint32_t respt;
	
	memset(response, '\0', CHARBUFF);
	
	do{
		
		if(Serial1.available()){    
			response[i] = Serial1.read();
			i++;
			//now we handle if the buffer fills up
			//this solution will only work when the string we want to check is less than half of CHARBUFF
			if (i >= CHARBUFF){
				memcpy(&response[0], &response[CHARBUFF/2], CHARBUFF/2);
				memset(&response[CHARBUFF/2], '\0', CHARBUFF/2);
				i = CHARBUFF/2;
			}
			// check if the desired answer is in the response of the module
			for(int8_t j = 0; j < cases; j++){
				if (strstr(response, expAns[j]) != NULL)    
				{
					match_fl |=  (1ul << j);
				}
			}
			if (match_fl == ((1ul << (cases +1)) - 1)){
				match = true;
			}
		}    
		// Waits for the asnwer with time out
		respt = millis() - tsend;
	}while((not match) && (respt < timeout));
	
	if (match){
		Serial.print("\nMatchs: ");
		Serial.println(match_fl);
		Serial.print("Response Time: ");
		Serial.println(respt);
	}
	
	return match ? match_fl : -1;
}

int8_t parseATcmd(const char** expAns, uint8_t cases, uint32_t timeout){
	uint32_t tsend = millis();	
	uint16_t i = 0;
	bool match = false;
	uint8_t match_id = 0;
	uint32_t respt;
	
	memset(response, '\0', CHARBUFF);
	
	do{
		
		if(Serial1.available()){    
			response[i] = Serial1.read();
			#if DGB
			Serial.print(response[i]);
			#endif
			i++;
			//now we handle if the buffer fills up
			//this solution will only work when the string we want to check is less than half of CHARBUFF
			if (i >= CHARBUFF){
				noInterrupts();
				memcpy(&response[0], &response[CHARBUFF/2], CHARBUFF/2);
				memset(&response[CHARBUFF/2], '\0', CHARBUFF/2);
				i = CHARBUFF/2;
				interrupts();
			}
			// check if the desired answer is in the response of the module
			for(int8_t j = 0; j < cases; j++){
				if (strstr(response, expAns[j]) != NULL)    
				{
					match = true;
					match_id = j;
				}
			}
		}    
		// Waits for the asnwer with time out
		respt = millis() - tsend;
	}while((not match) && (respt < timeout));
	
	if (match){
		Serial.print("\nMatch: ");
		Serial.println(expAns[match_id]);
		Serial.print("Response Time: ");
		Serial.println(respt);
	}
	
	return match ? match_id : -1;
}

uint8_t urcWait(char* cmd, uint32_t timeout){
	bool match = false;
	uint8_t matchln = 0;
	uint32_t tstart=millis();
	Serial.print("Wait:");
	Serial.println(cmd);
	while((not match) && ((millis() - tstart) < timeout)){
		if(Serial1.available()){
			uint8_t bt = Serial1.read();
			if(bt == cmd[matchln]){
				matchln++;
			}else{
				matchln = 0;
			}
		}
		if (cmd[matchln] == '\0'){match = true;}
	}
	Serial.println();
	return match;
}

uint8_t uploadATsequence(uint8_t ercd){
	uint8_t ercode = 0;
	
	ercode = sendATcmd("AT", "OK");
	if (ercode == 0){return 1;}
	
	ercode = sendATcmd("AT+CFUN=0", "OK",2000);
	if (ercode == 0){return 2;}
	
	ercode = sendATcmd("AT%XSYSTEMMODE=1,0,0,1", "OK");
	if (ercode == 0){return 3;}
	
	ercode = sendATcmd("AT+CGDCONT=0,\"IP\",\"" APN "\"", "OK");
	if (ercode == 0){return 4;}

	ercode = sendATcmd("AT+CFUN=1", "OK");
	if (ercode == 0){return 5;}

	ercode = sendATcmd("AT+COPS=1,2,\"50501\"", "OK", 10000);
	if (ercode == 0){return 4;}

	const int32_t attempts  = 200;
	for(int32_t i = 0; i < attempts; i++){
		sendATcmd("AT+CEREG?", NULL);
		static const char* reg_states[] = {"+CEREG: 0,1","+CEREG: 0,5"};
		ercode = parseATcmd(reg_states, 2, 500);
		delay(100);
		switch(ercode){
		case 0:
			i = attempts+1;
			break;
		case 1:
			i = attempts+1;
			break;
		}
		if(i == attempts - 1){return 6;}
	}

	ercode = sendATcmd("AT+CPIN?", "+CPIN: READY");
	if (ercode == 0){return 7;}
	urcWait("OK", 500);
	
	
	ercode = sendATcmd("AT#XMQTTCON=1,\"" CLIENT_ID "\",\"" USERNAME "\",\"" PASSWORD "\",\"" DOMAIN_NAME "\",1883", "#XMQTTEVT: 0,0", 2000);
	if (ercode == 0){return 8;}
	
	sprintf(request,"AT#XMQTTPUB=\"" TOPIC "\",\"uploads=%d,vbat=%d,err_code=%d\",0,0", uploads, vbat,ercd);

	ercode = sendATcmd(request, "OK",6000,1);
	if (ercode == 0){return 11;}
	
	ercode = sendATcmd("AT#XMQTTCON=0", "#XMQTTEVT: 1,0", 3000);
	if (ercode == 0){return 12;}
	urcWait("OK", 500);
	
	ercode = sendATcmd("AT+CFUN=0", "OK", 3000);
	if (ercode == 0){return 13;}
	
	return 0;
	
}



uint8_t nRFwake(){
	pinMode(nRF_SW_EN, OUTPUT);
	digitalWrite(nRF_SW_EN, LOW);
	delay(10);
	digitalWrite(nRF_SW_EN, HIGH);
	
	uint8_t ercode = urcWait("Ready", 5000);
	return ercode;
}

uint8_t nRFsleep(){
	uint8_t ercode;
	ercode = sendATcmd("AT#XSLEEP=1", "\0", 10,1);
	//ercode = sendATcmd("AT", "OK", 20, 1);
	//if we get no response then the module should be off
	ercode = !ercode;
	return ercode;
}

uint8_t nRFrst(){
	pinMode(nRF_RST, OUTPUT);
	digitalWrite(nRF_RST, LOW);
	delay(10);
	digitalWrite(nRF_RST,HIGH);
	
	
	uint8_t ercode = urcWait("Ready", 5000);
	return ercode;
}

void setup(){
	// delay(6000);

	Serial.begin(115200);
	Serial1.begin(115200);
	Serial.println("Begin");
	Serial.flush();
	pinMode(nRF_EN, OUTPUT);
	digitalWrite(nRF_EN, HIGH);

	nRFrst();
	//nRFsleep();
}

void loop(){
    uint8_t errode = 0;
    
	uploads++;

	pinMode(ADC_BATTERY, INPUT);
	analogReadResolution(12);
	vbat = (uint16_t)(analogRead(ADC_BATTERY)*1.03f);
		
	

	nRFwake();
	// nRFrst();
    errode = uploadATsequence(errode);
	if(errode){
		Serial.println();
		Serial.print("upload fail code: ");
		Serial.println(errode);
		Serial.println("resetting slm"); 
		nRFrst();
	}
    
	
    Serial.println(uploads);
	Serial.println("Sleeping");
	nRFsleep();
	Serial.flush();
	// LowPower.deepSleep(60000);
	delay(60000);
	Serial.println("Waking");

}
