/*
 *
 * IOSControllerWiFi library (“The Software”) and the related documentation (“The Documentation”) are supplied to you 
 * by the Author in consideration of your agreement to the following terms, and your use or installation of The Software and the use of The Documentation 
 * constitutes acceptance of these terms.  
 * If you do not agree with these terms, please do not use or install The Software.
 * The Author grants you a personal, non-exclusive license, under author's copyrights in this original software, to use The Software. 
 * Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by the Author, including but not limited to any 
 * patent rights that may be infringed by your derivative works or by other works in which The Software may be incorporated.
 * The Software and the Documentation are provided by the Author on an "AS IS" basis.  THE AUTHOR MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT 
 * LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE SOFTWARE OR ITS USE AND OPERATION 
 * ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
 * REPRODUCTION AND MODIFICATION OF THE SOFTWARE AND OR OF THE DOCUMENTATION, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 
 * STRICT LIABILITY OR OTHERWISE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Author: Fabrizio Boco - fabboco@gmail.com
 *
 * All rights reserved
 *
 */

#include "IOSControllerBLE.h"
#include <avr/eeprom.h>


#define ALARMCHECKDELAY 		  90000
#define ALARMCHECKDELAY_CONNECTED 50

#define LEAP_YEAR(Y)     ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) )

static  const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0


static  unsigned long actualCheckDelay;

#ifdef ALARMS_SUPPORT
IOSControllerBLE::IOSControllerBLE(void (*doWork)(void), 
                             void (*doSync)(char *variable),
                             void (*processIncomingMessages)(char *variable, char *value),
                             void (*processOutgoingMessages)(void),
                             void (*processAlarms)(char *alarm),
                             void (*deviceConnected)(void),
							 void (*deviceDisconnected)(void)
                             )
{
	_var = true;
    _idx = 0;
    _doWork = doWork;
    _doSync = doSync;
    _processIncomingMessages = processIncomingMessages;
    _processOutgoingMessages = processOutgoingMessages;
    _processAlarms = processAlarms;
    _deviceConnected = deviceConnected;
    _deviceDisconnected = deviceDisconnected;
    
    _variable[0] = '\0';
    _value[0]    = '\0';
        
    _startTime = 0;
    _fireAlarmDelay = 0;
        
    _connected = false;    
    
    actualCheckDelay = ALARMCHECKDELAY;
        
	this->inizializeAlarms();	
}
#endif 


IOSControllerBLE::IOSControllerBLE(void (*doWork)(void), 
                             void (*doSync)(char *variable),
                             void (*processIncomingMessages)(char *variable, char *value),
                             void (*processOutgoingMessages)(void),
                             void (*deviceConnected)(void),
							 void (*deviceDisconnected)(void)
                             )
{
	_var = true;
    _idx = 0;
    _doWork = doWork;
    _doSync = doSync;
    _processIncomingMessages = processIncomingMessages;
    _processOutgoingMessages = processOutgoingMessages;
    _deviceConnected = deviceConnected;
    _deviceDisconnected = deviceDisconnected;
    
    _connected = false;  
    
    _variable[0] = '\0';
    _value[0]    = '\0';
}                             

void IOSControllerBLE::loop() {
	  	
#ifdef ALARMS_SUPPORT  	

  if (_processAlarms != NULL) {
  
	if (_fireAlarmDelay < actualCheckDelay) {
		_fireAlarmDelay++;
    }
	else {
    	_fireAlarmDelay=0;
  
  		//this->dumpAlarms();
  
		this->checkAndFireAlarms();
	}
	
	ble_do_events();
  }
  
#endif  	
   	  		
  _doWork();
  
  ble_do_events();

  this->readVariable();
    
  if (_variable[0]!='\0' && _value[0]!='\0') {
      
    int cmd = atoi(_variable);  
    
//Serial.print("\t");
//Serial.println(cmd);
    
    switch(cmd) {
    
    	case 100:
	    	_doSync(_value);
	    break;
	
#ifdef ALARMS_SUPPORT	
	    case 200:
	    	_startTime = atol(_value)-millis()/1000;
	    break;	
		    
	    case 201:
	    	strcpy(_id,_value);
	    break;
		    
	    case 202:
	    	_time=atol(_value);	    	
	    break;
	    
		case 203:
		{
			if (_time == 0)
				this->removeAlarm(_id);
			else
        		this->createUpdateAlarm(_id,_time,atoi(_value));   
#ifdef DEBUG
			this->dumpAlarms();
			//ble_do_events();
#endif              	 
		}
		break;
#endif

		default:
			_processIncomingMessages(_variable,_value);
    }
    
    ble_do_events();
  }

  if (ble_connected()) {

      if (!_connected) {
        
        _connected = true;
        
        if(_deviceConnected != NULL)
          _deviceConnected();
           
        ble_do_events();
        
        actualCheckDelay = ALARMCHECKDELAY_CONNECTED;
        
        delay(650);         // Increasing this, the chance of stuck is reduced
        //ble_do_events();
      }
      
      ble_do_events();      
      _processOutgoingMessages();
      
  }
  else {
    
    if (_connected) {
    
        _connected = false;
        
        actualCheckDelay = ALARMCHECKDELAY;
        
       if(_deviceConnected != NULL)
         _deviceDisconnected();
    }
    
    ble_do_events();
  }
  
  ble_do_events();
}

void IOSControllerBLE::readVariable(void) {
    
	_variable[0]='\0'; 
	_value[0]='\0';
	        
	while (ble_available()) {
        
        char c = (char)ble_read();
    
        if ((char)c == '=') {
                
            _variable[_idx]='\0'; 
            _var = false; 
            _idx = 0;
        }
        else {
                
        	if ((char)c == '#') {
                
                _value[_idx]='\0'; 
                _var = true; 
                _idx = 0; 
                            
                ble_do_events();         
                          
                return;
            }
            else {
                    
            	if (_var) {
                    
                    if(_idx==VARIABLELEN) 
	                	_variable[_idx] = '\0';
	                else
                      	_variable[_idx++] = c;
                }
                else {
                        
                	if(_idx==VALUELEN)
                    	_value[_idx] = '\0';
                    else
                    	_value[_idx++] = c;
                }
                    
            	ble_do_events(); 
            }
        }
            
        ble_do_events();  
    }    

}


void IOSControllerBLE::writeMessage(char *variable, float value)
{
    char buffer[VARIABLELEN+VALUELEN+3];
    char vbuffer[VALUELEN];
            
    dtostrf(value, 0, 3, vbuffer);    
    snprintf(buffer,VARIABLELEN+VALUELEN+3, "%s=%s#", variable, vbuffer); 
        
    ble_write_bytes((unsigned char *)buffer, strlen(buffer)*sizeof(char));  
}


void IOSControllerBLE::writeTxtMessage(char *variable, char *value) 
{
	int i = 0;
    while(variable[i] != '\0') 
    {
        ble_write(variable[i++]);
    }
    
    ble_write('=');
    
    i = 0;
    while(value[i] != '\0') 
    {
        ble_write(value[i++]);
    }

    ble_write('#');
}

void IOSControllerBLE::temporaryDigitalWrite(uint8_t pin, uint8_t value, unsigned long ms) {

	boolean previousValue = digitalRead(pin);

    digitalWrite(pin, value);
    delay(ms);
    digitalWrite(pin, previousValue);
}

void IOSControllerBLE::log(char *msg) 
{
	this->writeTxtMessage("$D$",msg);
}

void IOSControllerBLE::log(int msg)
{
	char buffer[11];
	itoa(msg, buffer, 10);
	
	this->writeTxtMessage("$D$",buffer);
}


void IOSControllerBLE::logLn(char *msg) 
{
	this->writeTxtMessage("$DLN$",msg);
}

void IOSControllerBLE::logLn(int msg)
{
	char buffer[11];
	itoa(msg, buffer, 10);
	
	this->writeTxtMessage("$DLN$",buffer);
}

void IOSControllerBLE::logLn(long msg)
{
	char buffer[11];
	ltoa(msg, buffer, 10);
	
	this->writeTxtMessage("$DLN$",buffer);
}

void IOSControllerBLE::logLn(unsigned long msg) {

	char buffer[11];
	ltoa(msg, buffer, 10);
	
	this->writeTxtMessage("$DLN$",buffer);
}


// Time Management 

#ifdef ALARMS_SUPPORT 

unsigned long IOSControllerBLE::now() {
	
	unsigned long now = _startTime + millis()/1000;
	
	ble_do_events();
	
	return now;
}

void IOSControllerBLE::breakTime(unsigned long time, int *seconds, int *minutes, int *hours, int *Wday, long *Year, int *Month, int *Day) {
  // break the given time_t into time components
  // this is a more compact version of the C library localtime function
  // note that year is offset from 1970 !!!

  unsigned long year;
  uint8_t month, monthLength;
  unsigned long days;

  *seconds = time % 60;
  time /= 60; // now it is minutes
  *minutes = time % 60;
  time /= 60; // now it is hours
  *hours = time % 24;
  time /= 24; // now it is days
  *Wday = ((time + 4) % 7) + 1;  // Sunday is day 1 

  year = 1970;  
  days = 0;
  while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
    year++;
    
    ble_do_events();
  }
  *Year = year; // year is offset from 1970 
  
  days -= LEAP_YEAR(year) ? 366 : 365;
  time -= days; // now it is days in this year, starting at 0

  days=0;
  month=0;
  monthLength=0;
  for (month=0; month<12; month++) {
  
    if (month==1) { // february
      if (LEAP_YEAR(year)) {
        monthLength=29;
      } 
      else {
        monthLength=28;
      }
    } 
    else {
      monthLength = monthDays[month];
    }

    if (time >= monthLength) {
      time -= monthLength;
    } 
    else {
      break;
    }
    
    ble_do_events();
  }
  *Month = month + 1;  // jan is month 1  
  *Day = time + 1;     // day of month
}


#ifdef DEBUG
void IOSControllerBLE::printTime(unsigned long time) {

    	int seconds;
   		int minutes;
   		int hours;
   		int Wday;
   		long Year;
   		int Month;
   		int Day;
		
		ble_do_events();
		
		this->breakTime(time, &seconds, &minutes, &hours, &Wday, &Year, &Month, &Day);

	   	Serial.print(Day);
	   	Serial.print("/");
	   	
	   	ble_do_events();
	   	
	   	Serial.print(Month);
	   	Serial.print("/");
	   	
	   	ble_do_events();
	   	
	   	Serial.print(Year);
	   	Serial.print(" ");
	   	
	   	ble_do_events();
	   	
	   	Serial.print(hours);
	   	Serial.print(":");
	   	
	   	ble_do_events();
	   	
	   	Serial.print(minutes);
	   	Serial.print(":");
	   	
	   	ble_do_events();
	   	
	   	Serial.print(seconds);
	   	
	   	ble_do_events();
}
#endif

void IOSControllerBLE::createUpdateAlarm(char *id, unsigned long time, bool repeat) {

	char lid[12];
	
#ifdef DEBUG

	Serial.print("Setting alarm ");
	Serial.println(id);
/*	
	this->printTime(time);
	Serial.println();
	Serial.flush();	
*/	
#endif	
	
	lid[0] = 'A';
	strcpy(&lid[1],id);

	// Update

	for(int i=0; i<5; i++) {
		
		alarm a;
		
		ble_do_events();
		
		eeprom_read_block((void*)&a, (void*)(i*sizeof(a)), sizeof(a));
		
		ble_do_events();
		
		if (strcmp(a.id,lid) == 0) {
				a.time = time;
				a.repeat = repeat;
				
				ble_do_events();
				
				eeprom_write_block((const void*)&a, (void*)(i*sizeof(a)), sizeof(a));
				
				ble_do_events();
				
				return;
		}
		
		ble_do_events();		
	}

	// Create
	
	for(int i=0; i<5; i++) {
	
		alarm a;
		
		ble_do_events();
		
		eeprom_read_block((void*)&a, (void*)(i*sizeof(a)), sizeof(a));
		
		ble_do_events();
	
		if(a.id[1]=='\0') {
		
			strcpy(a.id,lid);
			a.time = time;
			a.repeat = repeat;
			
			ble_do_events();
		
			eeprom_write_block((const void*)&a, (void*)(i*sizeof(a)), sizeof(a));
			
			ble_do_events();
			
			return;
		}
		
		ble_do_events();
	}
}

void IOSControllerBLE::removeAlarm(char *id) {

#ifdef DEBUG
	Serial.print("removeAlarm ");
	Serial.println(id);
	ble_do_events();
#endif
	char lid[12];
	
	lid[0] = 'A';
	strcpy(&lid[1],id);

	for(int i=0; i<5; i++) {
	
		alarm a;
		
		eeprom_read_block((void*)&a, (void*)(i*sizeof(a)), sizeof(a));
	
		if(strcmp(a.id,lid) == 0) {
		
			a.id[1]='\0';
			a.time = 0;
	        a.repeat = 0;
			
			eeprom_write_block((const void*)&a, (void*)(i*sizeof(a)), sizeof(a));
		}
	}
}

void IOSControllerBLE::inizializeAlarms() {

	for(int i=0; i<5; i++) {

		alarm a;
		
		eeprom_read_block((void*)&a, (void*)(i*sizeof(a)), sizeof(a));
	
		if(a.id[0] != 'A') {
		
			a.id[0]='A';
			a.id[1]='\0';
			a.time=0;
			a.repeat = 0;
			
			eeprom_write_block((const void*)&a, (void*)(i*sizeof(a)), sizeof(a));
Serial.println("i");			
		}
	} 	
}

#ifdef DEBUG
void IOSControllerBLE::dumpAlarms() {

	Serial.println("\t----Dump Alarms -----"); 
			
	for(int i=0; i<5; i++) {

		alarm al;			
		eeprom_read_block((void*)&al, (void*)(i*sizeof(al)), sizeof(al));

		Serial.print("\t");
    	Serial.print(al.id); 
    	Serial.print(" "); 
    	//Serial.println(al.time);
    	this->printTime(al.time);
    	
    	ble_do_events();
    	
    	Serial.print(" ");
    	Serial.println(al.repeat);
	}
}
#endif

void IOSControllerBLE::checkAndFireAlarms() {

	unsigned long now = _startTime + millis()/1000;

#ifdef DEBUG
	Serial.print("checkAndFireAlarms ");
	//this->printTime(now);
	Serial.println();
#endif

	for(int i=0; i<5; i++) {

		alarm a;

		eeprom_read_block((void*)&a, (void*)(i*sizeof(a)), sizeof(a));
					
		ble_do_events();					
						
	    if(a.id[1]!='\0' && a.time<now) {

#ifdef DEBUG
			Serial.println(a.id);
#endif				
			// First character of id is A and has to be removed
           	_processAlarms(&a.id[1]);

			ble_do_events();

	    	if(a.repeat) {
	    	
    	   		a.time += 86400; // Scheduled again tomorrow
    	    		
#ifdef DEBUG
				Serial.print("Alarm rescheduled at ");
	           	this->printTime(a.time);
	            Serial.println();	
#endif	            

				ble_do_events();
           	}
	       	else {
           		//     Alarm removed
            	
	           	a.id[1]='\0';
	           	a.time = 0;
	           	a.repeat = 0;
           	}
				
			eeprom_write_block((const void*)&a, (void*)(i*sizeof(a)), sizeof(a));
			
			ble_do_events();
		}
			
		//ble_do_events();
    }    	
}
#endif