#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_PATH "/tmp/._timer_sock.socket"

using namespace std;

enum MessageType {
	MT_STOP,
	MT_STATUS,
	MT_WAYPOINT
};

void recvall(unsigned int sock,char *buf,int num){
	int ret,acc=0;
	do {
		ret=recv(sock,buf+acc,num-acc,0);
		if(ret==-1){
			perror("recv");
			exit(1);
		}
		if(ret==0){ //peer closed its side
			close(sock);
			exit(1);
		}
		acc+=ret;
	} while(acc<num);
}

void sendall(unsigned int sock,const char *buf,int num){
	int ret,acc=0;
	do {
		ret=send(sock,buf+acc,num-acc,0);
		if(ret==-1){
			perror("send");
			exit(1);
		}
		acc+=ret;
	} while(acc<num);
}
void sendall(unsigned int sock,const char *buf){
	sendall(sock,buf,strlen(buf));
}

void addwaypoint(vector<pair<time_t,string>> &waypoints,string desc){
	time_t tim=time(NULL);
	waypoints.push_back({tim,desc});
	char *buf=ctime(&tim);
	buf[strlen(buf)-1]='\0';
	cout<<"Added waypoint on "<<buf<<": "<<desc<<endl;
}

unsigned int parseUInt32BE(const unsigned char *buf){
	return (buf[0]<<24)|(buf[1]<<16)|(buf[2]<<8)|buf[3];
}
unsigned int parseUInt32BE(const char *buf){
	return parseUInt32BE((unsigned char*)buf);
}

bool strendequ(const char *str,const char *suffix){
	const int len1=strlen(str),len2=strlen(suffix);
	return len2>len1?false:strcmp(str+len1-len2,suffix)==0;
}

void unlink_socketpath(void){
	unlink(SOCKET_PATH);
}

void sendwaypoints(unsigned int sock,vector<pair<time_t,string>> &waypoints){
	stringstream ss;
	for(pair<time_t,string> &p : waypoints){
		char *buf=ctime(&p.first);
		buf[strlen(buf)-1]='\0';
		ss<<buf<<": "<<p.second<<endl;
	}
	string s=ss.str();
	int len=s.size();
	char buf[4];
	buf[0]=0xff&(len>>24);
	buf[1]=0xff&(len>>16);
	buf[2]=0xff&(len>>8);
	buf[3]=0xff&len;
	sendall(sock,buf,4);
	sendall(sock,s.c_str(),len);
}

unsigned int getclientsock(void){
	unsigned int s;
	struct sockaddr_un remote;
	s=socket(AF_UNIX,SOCK_STREAM,0);
	if(s==(unsigned int)-1){
		perror("socket");
		exit(1);
	}
	remote.sun_family=AF_UNIX;
	strcpy(remote.sun_path,SOCKET_PATH);
	if(connect(s,(struct sockaddr*)&remote,strlen(SOCKET_PATH)+1+sizeof(remote.sun_family))==-1){
		perror("connect");
		exit(1);
	}
	return s;
}

void act_server(int numargs,char **args){ //args == argv+1, numargs == argc-1
	vector<pair<time_t,string>> waypoints;
	if(numargs){
		stringstream ss;
		for(int i=0;i<numargs;i++)ss<<(i?" ":"")<<args[i];
		string line=ss.str();
		cout<<"Timer description: "<<line<<endl;
		addwaypoint(waypoints,line.c_str());
	} else addwaypoint(waypoints,"Start");
	pid_t pid=fork();
	if(pid==-1){
		perror("fork");
		exit(1);
	}
	if(pid!=0){ //parent
		cerr<<"Forking into background."<<endl;
		exit(0);
	}
	//child, becoming a daemon
	setsid();
	close(0); close(1); close(2);
	open("/dev/null",O_RDONLY);
	open("/dev/null",O_WRONLY);
	open("/dev/null",O_WRONLY);
	//now we're a daemon. Yay!
	atexit(unlink_socketpath);
	unsigned int s,s2;
	s=socket(AF_UNIX,SOCK_STREAM,0);
	if(s==(unsigned int)-1){
		perror("socket");
		exit(1);
	}
	struct sockaddr_un local;
	local.sun_family=AF_UNIX;
	strcpy(local.sun_path,SOCKET_PATH);
	if(::bind(s,(struct sockaddr*)&local,strlen(local.sun_path)+1+sizeof(local.sun_family))==-1){
		perror("bind");
		exit(1);
	}
	listen(s,2);
	while(true){
		struct sockaddr_un remote;
		unsigned int len=sizeof(struct sockaddr_un);
		s2=accept(s,(struct sockaddr*)&remote,&len);
		char buf[256];
		recvall(s2,buf,4);
		unsigned int msglen=parseUInt32BE(buf);
		if(msglen>256){
			close(s2);
			continue;
		}
		recvall(s2,buf,msglen);
		switch(buf[0]){
			case MT_STOP:
				addwaypoint(waypoints,"Stop");
				buf[0]=1;
				sendall(s2,buf,1);
				sendwaypoints(s2,waypoints);
				close(s2);
				close(s);
				exit(0);
			case MT_STATUS:
				buf[0]=1;
				sendall(s2,buf,1);
				sendwaypoints(s2,waypoints);
				break;
			case MT_WAYPOINT:
				addwaypoint(waypoints,string(buf+1,msglen-1));
				buf[0]=1;
				sendall(s2,buf,1);
				break;
			default:
				buf[0]=0;
				sendall(s2,buf,1);
				break;
		}
	}
}

void act_client_timerstop(void){
	unsigned int s=getclientsock();
	char buf[5];
	buf[0]=0;
	buf[1]=0;
	buf[2]=0;
	buf[3]=1;
	buf[4]=MT_STOP;
	sendall(s,buf,5);
	recvall(s,buf,1);
	if(buf[0]!=1){
		cerr<<"Didn't receive acknowledgement from server!"<<endl;
		exit(1);
	}
	recvall(s,buf,4);
	int len=parseUInt32BE(buf);
	char msg[len+1];
	recvall(s,msg,len);
	msg[len]='\0';
	cout<<"Results:"<<endl;
	cout<<msg<<flush;
}

void act_client_timerstatus(void){
	unsigned int s=getclientsock();
	char buf[5];
	buf[0]=0;
	buf[1]=0;
	buf[2]=0;
	buf[3]=1;
	buf[4]=MT_STATUS;
	sendall(s,buf,5);
	recvall(s,buf,1);
	if(buf[0]!=1){
		cerr<<"Didn't receive acknowledgement from server!"<<endl;
		exit(1);
	}
	recvall(s,buf,4);
	int len=parseUInt32BE(buf);
	char msg[len+1];
	recvall(s,msg,len);
	msg[len]='\0';
	cout<<"Status:"<<endl;
	cout<<msg<<flush;
	cout<<"Now:"<<endl;
	time_t tim=time(NULL);
	cout<<ctime(&tim)<<flush;
}

void act_client_timerwaypoint(int numargs,char **args){
	unsigned int s=getclientsock();
	string line;
	if(numargs){
		stringstream ss;
		for(int i=0;i<numargs;i++)ss<<(i?" ":"")<<args[i];
		line=ss.str();
		cout<<"Waypoint description: "<<line<<endl;
	} else {
		cout<<"Enter the description for this waypoint: "<<flush;
		getline(cin,line);
	}
	const int buflen=4+1+line.size();
	char buf[buflen];
	buf[0]=0;
	buf[1]=0;
	buf[2]=0;
	buf[3]=buflen-4;
	buf[4]=MT_WAYPOINT;
	memcpy(buf+5,line.c_str(),line.size());
	sendall(s,buf,buflen);
	recvall(s,buf,1);
	if(buf[0]!=1){
		cerr<<"Didn't receive acknowledgement from server!"<<endl;
		exit(1);
	}
	cout<<"Waypoint added."<<endl;
}

int main(int argc,char **argv){
	ios_base::sync_with_stdio(false);
	if(strendequ(argv[0],"timerstop")){
		act_client_timerstop();
	} else if(strendequ(argv[0],"timerwaypoint")||strendequ(argv[0],"timerwp")){
		act_client_timerwaypoint(argc-1,argv+1);
	} else if(strendequ(argv[0],"timerstatus")){
		act_client_timerstatus();
	} else if(strendequ(argv[0],"timer")||strendequ(argv[0],"timerstart")||strendequ(argv[0],"timerserver")){
		act_server(argc-1,argv+1);
	} else {
		cerr<<"Unknown calling name \""<<argv[0]<<"\"!"<<endl;
		return 1;
	}
	return 0;
}