#include <ArduinoLowPower.h>
#include <RTCZero.h>
#include <SD.h>
#include <OneWire.h>
#include <SPI.h>
#include <Adafruit_BME280.h>
#include <avr/dtostrf.h>
#include <time.h>

Adafruit_BME280 bme;
OneWire ds(A1);

float bme_T=0,bme_H=0;
float ambient, windspeed;
byte addr[8];
byte data[9];
int16_t raw;
bool timecheck = 0;
bool log_trigger = 0;
bool ftp_trigger =0;
bool epoch_set = 0;
bool loop_set = 0;
bool once = 0;
uint8_t logid = 1;
uint32_t target1,target2,cur_time,cur_loop,loop_dist;

#define CHARBUFF 2048
#define SITEID "Weather_15"
#define DBG 0 //this causes instability we probably need to decrease the nrf baud rate
#define LOG_INTERVAL 15 // for amount of time sleep mode & sleep interval
#define SERVER_LOG1 23 // 9.00  at GMT +10
#define SERVER_LOG2 8 // 18.00 at GMT +10 

char response[CHARBUFF];
char request[CHARBUFF];

#define MINUTE 60000
#define SECOND 1000
const int chipSelect = SDCARD_SS_PIN;

RTCZero rtc;
int tz;
struct tm t;

//sd logging file
File sd_f; // log
File sd_u; // upload
int last=0;
int fileNo;
char curFileName[8], lastFileName[8];
char buf[2000];

uint8_t uploads = 0;
// uint32_t bkp_epoch;

bool has_sd = 0;

//logged data reponse stings
char datetime[64];
char TEMP[8];
char HUM[8];
char AMB_T[8];
char WDSPD[8];
char CBC[7];

//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();
			#if DBG
			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){
				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 DBG
			Serial.write(bt);
			#endif
		}
		if (cmd[matchln] == '\0'){match = true;}
	}
	Serial.println();
	return match;
}

void sensor_scan()
{
  uint32_t timeout_start;
  Serial.println("Start scanning....");
  digitalWrite(LED_BUILTIN, HIGH);
  
  bme_T = bme.readTemperature();
  bme_H = bme.readHumidity();

  int sensorValue = analogRead(0);       // read analog input pin 0p
  float rawVal = sensorValue * 3.3/4096; // constant = max voltage / 4096
  windspeed = (rawVal - 0.4028) / 1.6065 * 32.4; //(output voltage - zero val)/1.6065*32.4
  if(sensorValue <500){windspeed  = 0;}

  ds.search(addr);
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  
  ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad
  
  for ( int i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();

    raw = (data[1] << 8) | data[0];
  }
  ambient = (float)raw / 16.0;
  dtostrf(bme_T,3,2,TEMP);
  dtostrf(bme_H,3,2,HUM);
  dtostrf(ambient,3,2,AMB_T);
  dtostrf(windspeed,3,2,WDSPD);
  
  digitalWrite(LED_BUILTIN, LOW);

}

uint8_t mobileStart() {
    uint8_t ercode = 1;
  
  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\",\"simbase\"", "OK");
	if (ercode == 0){return 4;}

  ercode = sendATcmd("AT+CFUN=1", "OK");
  if (ercode == 0){return 5;}
  
  const int32_t attempts  = 20;
	for(int32_t i = 0; i < attempts; i++){
		ercode = sendATcmd("AT+CEREG?", NULL);
		static const char* reg_states[] = {"+CEREG: 0,1","+CEREG: 0,5"};
		ercode = parseATcmd(reg_states, 2, 20000);
		delay(100);
		switch(ercode){
		case 0:
			i = attempts+1;
			break;
		case 1:
			i = attempts+1;
			break;
		}
	}
  
  ercode = sendATcmd("AT+CPIN?", "+CPIN: READY");
  if (ercode == 0){return 7;}
  urcWait("OK", 500);
}

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 CBCread(){
  pinMode(ADC_BATTERY, INPUT);
  analogReadResolution(12);
  delay(2);
  uint16_t vbat = (uint16_t)(analogRead(ADC_BATTERY)*1.03f);
  sprintf(CBC, "%d", vbat);
}

void SDsetup(){
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present. Continuing without SD logging");
  }else{
  Serial.println("card initialized.");
  has_sd = 1;
  }
}

void sleepy(int time_ms){
  Serial.println("Sleeping");
  nRFsleep();
  Serial.flush();
  LowPower.deepSleep(time_ms);
  // delay(1000);
}

void pull_net_time(){
  char dump[256];
  int yr,mt,dy,hr,mn,sc;
  uint8_t ercode=1;
  
  while ( ercode==1)
  {
    ercode = sendATcmd("AT+CCLK?","ERROR", 2000,1);
  }
//  if(sendATcmd("AT+CCLK?", "ERROR", 2000, 1)) {return;}
  char *str;
  str = strstr(response,"+CCLK");
  Serial.println(response);

  sscanf(str, "%s \"%d/%d/%d,%d:%d:%d%i\"", dump, &yr,&mt,&dy,&hr,&mn,&sc,&tz);

  t.tm_year = yr+100; // time struct year 0  is at 1900
  t.tm_mon = mt - 1; // month value of Jan - Dec is from 0 - 11 not 1 - 12
  t.tm_mday = dy;
  t.tm_hour = hr;
  t.tm_min = mn;
  t.tm_sec = sc; 

  int checkval = mktime(&t) - rtc.getEpoch();
  Serial.println(checkval);
  if (timecheck == 0)
  {
    rtc.setDate((uint8_t)dy, (uint8_t)mt, (uint8_t)yr);
    rtc.setTime((uint8_t)hr, (uint8_t)mn, (uint8_t)sc);
    timecheck = 1;
  }
  if( -300 <= checkval && checkval <= 300)
  {
    rtc.setDate((uint8_t)dy, (uint8_t)mt, (uint8_t)yr);
    rtc.setTime((uint8_t)hr, (uint8_t)mn, (uint8_t)sc);
  }

}

void filename() {
  //set curFileName(for SD log) & lastFileName (for upload)
  //fileNo is there to avoid filename duplication in case of the possibility of 3 files created in a same day

  if(rtc.getDay()!=last) {
    fileNo = 1;
  }
  else {
    fileNo++;
  }
  memcpy(lastFileName,curFileName,sizeof(curFileName));
  sprintf(curFileName, "%u%02u%02u%02u",
    rtc.getYear(), rtc.getMonth(), rtc.getDay(), fileNo);
  last = rtc.getDay();
}

void setup(){
	// delay(6000);
  pinMode(A6,OUTPUT);
  digitalWrite(A6,HIGH);
  digitalWrite(A6,LOW);
	Serial.begin(115200);
	Serial1.begin(115200);
	Serial.println("Begin");
	Serial.flush();
    sleepy(1000);
  bme.begin(0x76);
  analogReadResolution(12);
  pinMode(A2, INPUT); 
	pinMode(nRF_EN, OUTPUT);
	digitalWrite(nRF_EN, HIGH);

	nRFrst();
	//nRFsleep();

    SDsetup();
    rtc.begin();
    rtc.setDate(0,0,0);
    rtc.setTime(0,0,0);
    mobileStart();
    pull_net_time();
    timecheck = 1;
    sendATcmd("AT+CFUN=0","OK",3000);
    filename();
    loop_set=0;
}

void loop(){
  digitalWrite(A6,HIGH);
  digitalWrite(A6,LOW);
//    if (loop_set==0)
//  {
//    target1 = SERVER_LOG1 * 3600;
//    target2 = SERVER_LOG2 * 3600;
//
//    cur_time = rtc.getHours() *3600 + rtc.getMinutes() *60 +rtc.getSeconds();
//    Serial.print("Timing:  ");Serial.println(cur_time);
//    if (cur_time < target2)
//    { // GMT0 9.00
//      loop_dist = (target2 - cur_time) / LOG_INTERVAL;
//      Serial.println(1);
//      Serial.println(target2 - cur_time);
//    } 
//	else if(cur_time < target1)
//    { // GMT0 23.00
//     loop_dist = (target1 - cur_time) / LOG_INTERVAL;
//     Serial.println(2);
//    }
//    else
//    { // above GMT0 23.00
//      loop_dist = (24*3600 - cur_time + target2)  / LOG_INTERVAL;
//      Serial.println(3);
//    }
//    Serial.println(cur_loop);
//    Serial.println(loop_dist);
//    loop_set = 1;
//  }
  if (loop_set ==0)
  {
    cur_time = rtc.getMinutes() *60 +rtc.getSeconds();
    loop_dist = (3600 - cur_time) / LOG_INTERVAL;
    Serial.println(loop_dist);
    //loop_dist = 240; // 3600/15 = 240
    loop_set = 1;
  }
  sensor_scan();
  CBCread();
  if(has_sd){sd_log();} 
  cur_loop++;

    if(cur_loop==loop_dist)
  {
    //target time reached, start making new file and log to server
    Serial.println("Start Log.....");
    
    filename();
    nRFwake();
    read_sd();
    loop_set=0;
    cur_loop=0;
  }
  digitalWrite(A6,HIGH);
  digitalWrite(A6,LOW);
  sleepy((LOG_INTERVAL-1) * SECOND); // 1 second for time to wake up
//   delay(LOG_INTERVAL * SECOND);
}

void sd_log(){
  char writebuf[256];
  sprintf(writebuf, "%.8s.csv", curFileName);
  Serial.print("Logging data to SD: ");
  Serial.println(writebuf);
  sd_f = SD.open(writebuf, FILE_WRITE);
  uploads++;

  sprintf(datetime, "%02u/%02u/%02u %.02u:%02u:%02u", 
      rtc.getDay(),
      rtc.getMonth(),
      rtc.getYear(),
      rtc.getHours(),
      rtc.getMinutes(),
      rtc.getSeconds()
      );
      
  Serial.print("RTC Time: ");
  Serial.println(datetime);

  sprintf(writebuf,"%s,%s,%s,%s,%s,%s\n",
    datetime,CBC,TEMP,HUM,AMB_T,WDSPD); 
//bme_T,bme_H,ambient,windspeed


//  sprintf(writebuf,"%s,%s,%.2f,%.2f,%.2f,%.2f\n",
//    datetime,CBC,bme_T,bme_H,ambient,windspeed); 
    
    Serial.println(writebuf);
    
//clear memory
  memset(&CBC[0],0,sizeof(CBC));
  memset(&TEMP[0],0,sizeof(TEMP));
  memset(&HUM[0],0,sizeof(HUM));
  memset(&AMB_T[0],0,sizeof(AMB_T));
  memset(&WDSPD[0],0,sizeof(WDSPD));
  sd_f.write(writebuf, strlen(writebuf));

  sd_f.close();
}


void read_sd() {
  digitalWrite(LED_BUILTIN, HIGH);
  uint8_t ercode = 1;
  mobileStart();
  char readbuf[256];
  sprintf(readbuf,"%.8s.csv",lastFileName);
  sd_u = SD.open(readbuf);

  ercode = sendATcmd("AT#XFTP=\"open\",\"HumanComfort@bosl.com.au\",\"HumanComfort\",\"ftp.bosl.com.au\"", NULL);
  static const char* c[] = {"ERROR","Current restricted directory"};
  ercode = parseATcmd(c, 2, 10000);
  delay(100);
  switch(ercode){
  case 0:
    ercode = 11;
    break;
  case 1:
    urcWait("OK", 500);
  ercode = sendATcmd("AT#XDATACTRL=50", "OK", 3000,1);   
  ercode = sendATcmd("AT#XFTP=\"mput\",\"/databases/"SITEID".csv\"", "OK", 3000,1);   

  while (sd_u.available())
  {
    sd_u.read(buf,1500);
    Serial1.write(buf);  
    memset(&buf[0],0,sizeof(buf));
    delay(4000);
  }
  Serial1.write("+++");
  delay(3000);
  sendATcmd("AT#XFTP=\"close\"", "OK",3000);
  }
  
  sd_u.close();
  pull_net_time(); // resync clock
  sendATcmd("AT+CFUN=0", "OK", 3000);
  nRFrst(); // reset nrf chip
  digitalWrite(LED_BUILTIN, LOW);

}
