/*
 * GpioControl.cpp:
 *	Simple command-line interface for the Raspberry Pi's GPIO using cron jobs.
 *	Copyright (c) 2018-2019 Thomas Messmer
 ***********************************************************************
 * This file uses wiringPi and gpio:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    GpioControl is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    GpioControl is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with GpioControl.  If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */


// Include wiringPi api 
#include <wiringPi.h>

// include default C libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <sys/types.h>
#include <unistd.h>
//#include <chrono>
#include <ctime>
#include <iostream>


#define VERSION "1.0"
#define VERSION_MAJOR 1
#define VERSION_MINOR 0

/* 
 * Define the numbering of the ports
 * We only use 8 of them for the 8-ch relay module
 */
#define GPIN1 25
#define GPIN2 24
#define GPIN3 23
#define GPIN4 22
#define GPIN5 21
#define GPIN6 29
#define GPIN7 28
#define GPIN8 27

#define SET_PORT 				1
#define SET_CRONJOB_RAW 		2
#define SET_CRONJOB_FROM_NOW 	3
#define ALL_PORTS_OFF 			122

#define GetCurrentDir 			getcwd

using namespace std;

int option = 0;
int portnumber = 0;
int optval = 0;

struct tm nowT;
struct tm nextT;

void init();
void printHelp(char *argv[]);
void printInfo(char *argv[]);
void allPortsOff();
void setPort(int portNumber, int portState);
void calcDate( struct tm* date, int years, int month, int days, int hours, int minutes, int seconds );



int main(int argc, char* argv[] ) {

	// Get path of executable
	char cCurrentPath[1024];
    int ret;
    ret = readlink("/proc/self/exe",cCurrentPath,sizeof(cCurrentPath)-1);
    if(ret ==-1) {
        fprintf(stderr,"Error: path of executable not found\n");
        return errno;
    }
    cCurrentPath[ret] = 0;
    //cout << cCurrentPath << endl;

	
	// Get actual time
	time_t t = time(0);   
    nowT = *localtime( & t );
	nextT = *localtime( & t );
    /*cout << (nowT.tm_year + 1900) << '-' 
         << (nowT.tm_mon + 1) << '-'
         <<  nowT.tm_mday << ' '
		 <<  nowT.tm_hour << ':'
		 <<  nowT.tm_min << ':'
		 <<  nowT.tm_sec
         << endl;
	*/	 

	
	if(argc == 1)
	{
		printHelp(argv);
		return 1;
	}
	//In some cases only one parameter is also okay
	else if (argc == 2)
	{ 
		cout << "argc == 2" << endl;
		const char *p;
		int x;

		//Check the parameters
		for(x = 0; x < argc; x++) 
		{
			p = argv[x];
			if(*p == '-') 
			{
				p++;
				switch(*p) 
				{    
					case 'h':
						printInfo(argv);
						return 0;
					break;
					
					case '0':
						option = ALL_PORTS_OFF;
					break;
					
					default:
						printHelp(argv);
						return 0;
					break;
				}
			}
        
		}
		if(option == 0){
			printHelp(argv);
			return 0;
		}
	}
	/*else if (argc == 3)
	{ 
		const char *p;
		int x;

		for(x = 0; x < argc; x++) {
			p = argv[x];
			if(*p == '-') {
				p++;
			}
			switch(*p) {    
				case 'h':
					printInfo(argv);
					return 0;
				break;
				default:
					printHelp(argv);
					return 0;
				break;
			}
		}
	}*/
	else if(argc == 4)
	{
		cout << "argc == 4" << endl;
		const char *p;
		int x;

		for(x = 0; x < argc; x++) 
		{
			p = argv[x];
			if(*p == '-') 
			{
				p++;
			}
			switch(*p) 
			{    
				case 'h':
					printHelp(argv);
					return 0;
				break;
				case 'p':
					option = SET_PORT;
					portnumber = atoi(argv[2]);
					optval = atoi(argv[3]);
					if(portnumber <1 || portnumber >8){
						printf("port numbers must be between 1-8\n");
						return 0;
					}
					if(optval <0 || optval >1){
						printf("port states must be 0 or 1 (low or high)\n");
						return 0;
					}
				break;
				default:
					printInfo(argv);
				break;
			}
		}
	}
	else if(argc == 5)
	{
		cout << "argc == 5" << endl;
		const char *p;
		int x;

		for(x = 0; x < argc; x++) 
		{
			p = argv[x];
			if(*p == '-') 
			{
				p++;
			}
			switch(*p) 
			{    
				case 'c':
				{
					option = SET_CRONJOB_RAW;
					
					portnumber = atoi(argv[3]);
					optval = atoi(argv[4]);
					if(portnumber <1 || portnumber >8){
						printf("port numbers must be between 1-8\n");
						return 0;
					}
					if(optval <0 || optval >1){
						printf("port states must be 0 or 1 (low or high)\n");
						return 0;
					}
					
					break;
				}
				default:
					printInfo(argv);
				break;
			}
		}
	}
	else if(argc == 6)
	{
		cout << "argc == 6" << endl;
		const char *p;
		int x;

		for(x = 0; x < argc; x++) 
		{
			p = argv[x];
			if(*p == '-') 
			{
				p++;
			}
			switch(*p) 
			{
			case 'n':
				{
					option = SET_CRONJOB_FROM_NOW;
						
					portnumber = atoi(argv[4]);
					optval = atoi(argv[5]);
					if(portnumber <1 || portnumber >8){
						printf("portnumbers must be between 1-8\n");
						return 0;
					}
					if(optval <0 || optval >1){
						printf("port states must be 0 or 1 (low or high)\n");
						return 0;
					}
					
					calcDate( &nextT, 0, 0, 0, atoi(argv[2]), atoi(argv[3]), 0 );
						
					cout << (nowT.tm_year + 1900) << '-' 
						 << (nowT.tm_mon + 1) << '-'
						 <<  nowT.tm_mday << ' '
						 <<  nowT.tm_hour << ':'
						 <<  nowT.tm_min << ':'
						 <<  nowT.tm_sec
						 << endl;						
					
					cout << (nextT.tm_year + 1900) << '-' 
						 << (nextT.tm_mon + 1) << '-'
						 <<  nextT.tm_mday << ' '
						 <<  nextT.tm_hour << ':'
						 <<  nextT.tm_min << ':'
						 <<  nextT.tm_sec
						 << endl;	

					break;
				}
				default:
					printInfo(argv);
				break;
			}
		}
	}
	else
	{ 
		printHelp(argv);
		return 0;
	}
	
	
	// Start the WiringPi-Api (important)
	if (wiringPiSetup() == -1)
		return 1;

	//Initialize all necessary ports.
	init();

	
	
	switch(option)
	{
		case ALL_PORTS_OFF:
			printf("All ports will be shut down...\n");
			allPortsOff();		
		break;
		
		case SET_PORT:
			printf("Port %d will be set to %d\n",portnumber,optval);
			setPort(portnumber, optval);	
		break;
		
		case SET_CRONJOB_RAW:
		{
			string jobString(argv[2]);
			string portnumberS = std::to_string(portnumber);
			string optvalS = std::to_string(optval);
			
			stringstream  commandStringS;
			commandStringS << "(crontab -l ; echo '" << jobString << " " << cCurrentPath << " -p " << portnumberS << " " << optvalS << " ')| crontab -";
			string commandString = commandStringS.str();
			
			cout << commandString << endl;
			cout << "Use: \"crontab -l\" to get the cron list for the actual user." << endl;
			cout << "Use: \"crontab -r\" to remove the cron list for the actual user." << endl;
			
			system(commandString.c_str());
			break;
		}
		
		case SET_CRONJOB_FROM_NOW:
		{
			//cout << "SET_CRONJOB_FROM_NOW: " <<SET_CRONJOB_FROM_NOW<<endl;
			string minuteString = std::to_string(nextT.tm_min);
			string hourString = std::to_string(nextT.tm_hour);
			string dayString = std::to_string(nextT.tm_mday);
			string monString = std::to_string(nextT.tm_mon+1);
			
			string jobString(argv[2]);
			string portnumberS = std::to_string(portnumber);
			string optvalS = std::to_string(optval);
			
			stringstream  commandStringS;
			commandStringS << "(crontab -l ; echo '" << minuteString << " " << hourString << " " << dayString << " " << monString << " * " << cCurrentPath << " -p " << portnumberS << " " << optvalS << " ')| crontab -";
			string commandString = commandStringS.str();

			cout << commandString << endl;
			cout << "Use: \"crontab -l\" to get the cron list for the actual user." << endl;
			cout << "Use: \"crontab -r\" to remove the cron list for the actual user." << endl;

			system(commandString.c_str());
			break;
		}
		
	}

	return 0;
}


void init(){
	
	pinMode(GPIN1, OUTPUT);
	pinMode(GPIN2, OUTPUT);
	pinMode(GPIN3, OUTPUT);
	pinMode(GPIN4, OUTPUT);
	pinMode(GPIN5, OUTPUT);
	pinMode(GPIN6, OUTPUT);
	pinMode(GPIN7, OUTPUT);
	pinMode(GPIN8, OUTPUT);
	
}

void allPortsOff(){
	
	digitalWrite(GPIN1, 0);
	digitalWrite(GPIN2, 0);
	digitalWrite(GPIN3, 0);
	digitalWrite(GPIN4, 0);
	digitalWrite(GPIN5, 0);
	digitalWrite(GPIN6, 0);
	digitalWrite(GPIN7, 0);
	digitalWrite(GPIN8, 0);
	
	return;
}

void setPort(int portNumber, int portState)
{
	printf("PortNumber: %d - PortState: %d\n",portNumber,portState);
	switch(portNumber){
		
		case 1:
		digitalWrite(GPIN1, portState);
		break;
		case 2:
		digitalWrite(GPIN2, portState);
		break;
		case 3:
		digitalWrite(GPIN3, portState);
		break;
		case 4:
		digitalWrite(GPIN4, portState);
		break;
		case 5:
		digitalWrite(GPIN5, portState);
		break;
		case 6:
		digitalWrite(GPIN6, portState);
		break;
		case 7:
		digitalWrite(GPIN7, portState);
		break;
		case 8:
		digitalWrite(GPIN8, portState);
		break;
	}
	
	return;
}


void printHelp(char* argv[]){
	
	printf("%s: wrong or missing operand\n", argv[0]);
	printf("Try '%s -h' for more information.\n", argv[0]);
	
	return;
}


void printInfo(char* argv[]){
	
	printf("Use the tool with the following options:\n");
	printf("%s -h   #Shows this information text\n", argv[0]);
	printf("%s -p 1 0   #Sets port 1 to 0\n", argv[0]);
	printf("%s -0   #Sets all ports to 0\n", argv[0]);
	printf("%s -c \"30 1 28 7 *\" 6 0   #Sets port 6 to 0 on 28.07 1:30 every year\n", argv[0]);
	printf("%s -n 10 37 5 1   #Sets port 5 to 1 in 10 hours 37 minutes from now and then every year\n", argv[0]);
	
	return;
}


void calcDate( struct tm* date, int years, int month, int days, int hours, int minutes, int seconds ){
	
	date->tm_year += years;
	date->tm_mon += month;
	date->tm_mday += days;
	date->tm_hour += hours;
	date->tm_min += minutes;
	date->tm_sec += seconds;
	date->tm_isdst = -1;        // don't know if DST is in effect, please determine this for me

	mktime(date);
	
	return;
}