//todo add check for if i2c device is present to change error code
#include <avr/power.h>
#include <SoftwareSerial.h>
#include <LowPower.h>

#define SITE_ID "RainGuage_X" //ERIC PLEASE CHANGE THIS!
#define SITE_DIR "UoG"
#define APN_NAME "hologram"
#define NETWORK_NUMBER "302720" //where 302720 is for Canada ROGERS, 302220 is for Telus, 50501 is Australia telstra, 310410 is USA AT&T, 310260 is for T mobile america, 310260 for verizon

#define SIMCOM_7000 // SIM7000A/C/E/G
#define BAUDRATE 9600 // MUST be below 19200 (for stability) but 9600 is more stable

#define CHARBUFF 196 //SIM7000 serial response buffer //longer than 255 will cause issues

// For SIM7000 BoSL board
#define PWRKEY 4
#define DTR 5 // Connect with solder jumper
#define BOSL_RX 9 // Microcontroller RX 9 for new bosl 0.4; 3 for 0.3 
#define BOSL_TX 8 // Microcontroller TX 8 for new bosl 0.4; 2 for 0.3

//Site specific config

#define Upload_Interval 2  // units in mins for non-reading 

//millis timer variable 
extern volatile unsigned long timer0_millis;

//gobal variables 
char response[CHARBUFF]; //sim7000 serial response buffer
uint32_t lstTransmit = 0; //timestamp of last transmit (milli seconds)
String dataStr; //Transmit URL

//reponse stings
char datetime[64];
char flips[8];
char cumflips[8];
char CBC[5];
char SS_DTM[2];

//sensor variables
volatile int flipcount = 0;
float flipnum = 0 ;
float CumFlipCount = 0;
volatile unsigned long lastInterrupt;
bool FirstTimeLoad = 1;

//SIM7000 serial object
SoftwareSerial simCom = SoftwareSerial(BOSL_RX, BOSL_TX);

//declare function for atcmd
bool sendATcmd(String ATcommand, char* expctAns, unsigned int timeout, int repeatsDTM=5);

void setup() {

  pinMode(3, INPUT);
  attachInterrupt(digitalPinToInterrupt(3), flip, CHANGE);
  
  //begin serial
  Serial.begin(BAUDRATE);
  simCom.begin(BAUDRATE);

  //turn on power to SIM MODULE
  pinMode(PWRKEY,OUTPUT);
  digitalWrite(PWRKEY, HIGH);
	delay(3000);

  simCom.listen();
  simOn();
  sendATcmd(F("AT+IPR=9600"),"OK",1000);
  sendATcmd(F("ATE0"),"OK",1000);
  netReg();
  sendATcmd(F("AT&W0"),"OK",1000);
  sendATcmd(F("AT+CFUN=0"), ":", 3000); //unregister from netwrok
  simOff();
}
  
void loop() {

  Serial.print("TIPS: ");
  Serial.println(flipcount);
  Serial.print("Cumulative TIPS: ");
  Serial.println(CumFlipCount);

  memset(CBC, '\0', 5);
  memset(flips, '\0', 8);
  memset(cumflips, '\0', 8);
  flipnum = flipcount;
  flipcount = 0;
  dtostrf(flipnum, 2, 2, flips);
  dtostrf(CumFlipCount,2,2,cumflips);

  simOn();

  sendATcmd(F("AT+IPR=9600"),"OK",1000);
  sendATcmd(F("ATE0"),"OK",1000);
  sendATcmd(F("AT&W0"),"OK",1000);

  netReg();

  openbearer();

  CBCread();

  sendATcmd(F("AT+CSQ"),"OK",4000);

  SS_DTM[0] = response[8];
  SS_DTM[1] = response[9];

  Serial.println(SS_DTM[0] + SS_DTM[1]);

  Transmit(); 

  sendATcmd(F("AT+CFUN=0"), ":", 3000); //unregsiter from network

  simOff();

  Sleepy(Upload_Interval * 60);
}


void flip() {
  noInterrupts();
  if(millis() - lastInterrupt > 500){
    flipcount++;
    CumFlipCount++;
    // Serial.println("Interrupt happened");
  }
  
  lastInterrupt = millis();
  interrupts();
}

////TRANSMITS LAST GPS CORDINATES TO WEB////
void Transmit(){
    
  dataStr = "AT+HTTPPARA=\"URL\",\"http://www.bosl.com.au/IoT/";
  dataStr += SITE_DIR;
  dataStr += "/scripts/WriteMe_v2.php?SiteName=";
  dataStr += SITE_ID;
  dataStr += ".csv";
  dataStr += "&Battery=";
  dataStr += CBC;
  dataStr += "&CumulativeTips=";
  dataStr += cumflips;
  dataStr += "&TipsInLastTimestep=";
  dataStr += flips;
  dataStr += "&CellStrength=";
  dataStr += SS_DTM[0];
  dataStr += SS_DTM[1];
  dataStr += "\"";

  sendATcmd(F("AT+HTTPINIT"), "OK",1000);
  sendATcmd(F("AT+HTTPPARA=\"CID\",1"), "OK",1000);
   
  sendATcmd(dataStr, "OK",1000);
   
  sendATcmd(F("AT+HTTPACTION=0"), "200",5000);
  sendATcmd(F("AT+HTTPTERM"), "OK",1000);

}

void CBCread(){
    //get GNSS data
    if (sendATcmd(F("AT+CBC"), "OK",1000)){
        
          bool end = 0;
      uint8_t x = 0;
      uint8_t j = 0;
      
      memset(CBC, '\0', 5);
      
      //loop through reponce to extract data
      for (uint8_t i=0; i < CHARBUFF; i++){

          //string splitting cases
          switch(response[i]){
          case ':':
              x = 9;
              j = 0;
              i += 2;
              break;

          case ',':
              x++;
              j=0;
              break;

          case '\0':
              end = 1;
              break;
          case '\r':
              x++;
              j=0;
              break;
          }
          //write to char arrays
          if (response[i] != ','){
              switch(x){
                  case 11:
                      CBC[j] = response[i];
                  break;             
              }
              //increment char array counter
              j++;
          }
          //break loop when end flag is high
          if (end){
              i = CHARBUFF; 
          }
      }
        
    }
}

void Sleepy(uint16_t tsleep){ //Sleep Time in seconds
  //SLEEPS FOR SET TIME
  simCom.flush(); // must run before going to sleep
  Serial.flush(); // ensures that all messages have sent through serial before arduino sleeps
  LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); //8 seconds dosen't work on the 8mhz
  //advance millis timer as it is paused in sleep
  noInterrupts();
  timer0_millis += 4000;
  interrupts(); 

  while(tsleep >= 8){
    LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); //8 seconds dosen't work on the 8mhz
    //advance millis timer as it is paused in sleep
    noInterrupts();
    timer0_millis += 4000;
    interrupts();
    tsleep -= 4;
  }
}

void openbearer(){
  //close open bearer
  sendATcmd(F("AT+SAPBR=2,1"), "OK",3000);
  if (strstr(response, "1,1") == NULL){
    if (strstr(response, "1,3") == NULL){
      sendATcmd(F("AT+SAPBR=0,1"), "OK",3000);
    }
    String AAABBB;
    AAABBB = "AT+SAPBR=3,1,\"APN\",\"";
    AAABBB += APN_NAME;
    AAABBB += "\"";
    sendATcmd(AAABBB, "OK",3000); //set bearer apn
    sendATcmd(F("AT+SAPBR=1,1"), "OK",3000);
  }
}

void netReg(){
  //Regsiter to network
  sendATcmd(F("AT+CFUN=1"), "OK", 1000);//enable the modem
  String AAABBB;
  AAABBB = "AT+CGDCONT=1,\"IP\",\"";
  AAABBB += APN_NAME;
  AAABBB += "\"";
  sendATcmd(AAABBB, "OK", 2000);//set IPv4 and apn
  
  String senderAA = "AT+COPS=4,2,";
  senderAA += NETWORK_NUMBER;

  //check if it is registered on a network, if not try our best  
  sendATcmd(F("AT+CREG?"),"+CREG: 0,5",5000);
  if((strstr(response,"+CREG: 0,5") != NULL) || (strstr(response,"+CREG: 0,1") != NULL)){
    Serial.println(F("Connected to network"));
  }else{
    Serial.println(F("Unable to connect to network. Second last ditch attempt."));
    sendATcmd(senderAA, "OK",130000);
    sendATcmd(F("AT+CREG?"),"+CREG: 0,5",2000);
    if((strstr(response,"+CREG: 0,5") == NULL) && (strstr(response,"+CREG: 0,1") == NULL)){
      Serial.println(F("Still Unable to connect to network. Last ditch attempt."));
      sendATcmd(F("AT+COPS=0"), "OK",130000);
      sendATcmd(F("AT+CREG?"),"+CREG: 0,5",2000);
    }
  }
  sendATcmd(F("AT&W0"),"OK",2000);
}

bool sendATcmd(String ATcommand, char* expctAns, unsigned int timeout, int repeatsDTM){
  //sends at command, checks for reply
  uint32_t timeStart;
  bool answer;
  uint8_t a=0;
  
  do{a++;
    Serial.println();
    Serial.println(ATcommand);
    answer=0;
    timeStart = 0;
    delay(100);
    while( simCom.available() > 0) {
      simCom.read();    // Clean the input buffer
    }
    simCom.println(ATcommand);    // Send the AT command 
    uint8_t i = 0;
    timeStart = millis();
    memset(response, '\0', CHARBUFF);    // Initialize the string

    // this loop waits for the answer
    do{
      if(simCom.available() != 0){    
        response[i] = simCom.read();
        i++;
        // check if the desired answer is in the response of the module
        if (strstr(response, expctAns) != NULL)    
        {
          answer = 1;
        }
      }
    } while((answer == 0) && ((millis() - timeStart) < timeout)); 

    if (expctAns == "\0"){
      answer = 1;
    }
    Serial.print(response);
    
  } while(answer == 0 && a < repeatsDTM);
  a = 0;
  return answer;
}

void simOn() {
  //POWERS ON SIM7000
  Serial.println(F("Turning on Sim7000"));

  //check if sim is already on
  sendATcmd(F("AT"),"OK",250);  
  if(strstr(response, "OK")!= NULL){
    Serial.println(F("SIM7000 is already turned on. Turning off now."));
    simOff();
  }else{
    Serial.println(F("SIM7000 is off. Turning on now."));
  } 

  pinMode(PWRKEY, OUTPUT);
  pinMode(BOSL_TX, OUTPUT);
	digitalWrite(BOSL_TX, HIGH);
	pinMode(BOSL_RX, INPUT_PULLUP);
  digitalWrite(PWRKEY, HIGH);
	digitalWrite(PWRKEY, LOW);
	xDelay(1500); // For SIM7000
	digitalWrite(PWRKEY, HIGH);
  xDelay(5000);
  sendATcmd(F("AT"),"OK",5000);
  if(strstr(response, "OK")!= NULL){
    Serial.println(F("SIM7000 turned on"));
  }else{
    Serial.println(F("SIM7000 failed to turn on. Trying one last time."));
    digitalWrite(PWRKEY, HIGH);
    xDelay(3000);
    digitalWrite(PWRKEY, LOW);
    xDelay(2000); // For SIM7000
    digitalWrite(PWRKEY, HIGH);
    xDelay(5000);
  }
}

void simOff() {
  //POWERS ON SIM7000
  Serial.println(F("Turning off Sim7000"));

  //CHECK SIM IS CURRENTLY RESPONSIVE
  sendATcmd(F("AT"),"OK",250);  
  if(strstr(response, "OK")!= NULL){
    Serial.println(F("Checked that SIM7000 is on. Will begin turning off now."));
  }else{
    Serial.println(F("SIM7000 is already off. Turning on now."));
    simOn();
  }

  sendATcmd(F("AT+CPOWD=1"),"",2000);
  xDelay(2000);
  sendATcmd(F("AT"),"OK",250);
  if(strstr(response, "OK")== NULL){
    Serial.println(F("SIM7000 turned off"));
  }else{
    Serial.println(F("SIM7000 failed to turn off. Trying one last time..."));
    sendATcmd(F("AT+CPOWD=0"),"",250);
  }
  digitalWrite(BOSL_TX, LOW);
	digitalWrite(BOSL_RX, LOW);
}

void xDelay(uint32_t tmz){
	//like delay but lower power and more dodgy
  uint32_t tmzslc = tmz/64; //64 chosen as ballance between power savings and time spent in full clock mode
	clock_prescale_set(clock_div_64);
		delay(tmzslc);
	clock_prescale_set(clock_div_1);
	cli();
	timer0_millis += 63*tmzslc;	
	sei();
	delay(tmz-64*tmzslc);
}
