#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>

#define NO_VDR_TOOLS_H

#include "../common.h"

#include "tools.h"
#include "cstring.h"
#include "csocket.h"
#include "cfile.h"
#include "cselect.h"
#include "cbuffer.h"

#define MPLAYER_SEND_FILE "send.xxx"
#define MPLAYER_GRAB_FILE "grab.mpg"

#define DEBUG_DIR
//#define DEBUG_DATAFLOW
	
static bool        stopDem     = false;

static void sighandler(int signal) {
	if (stopDem == true) {
		switch (signal) {
		case SIGINT:
			LOGi << "STRG-C pressed again, exiting ungracefully" << endl;
			break;
		
		case SIGQUIT:
			LOGi << "QUIT command issued again, exiting ungracefully" << endl;
			break;

		case SIGTERM:
			LOGi << "TERM command issued again, exiting ungracefully" << endl;
			break;
		}
	}
	
	stopDem = true;

	switch (signal) {
	case SIGINT:
		LOGi << "STRG-C pressed, exiting gracefully" << endl;
		break;
	
	case SIGQUIT:
		LOGi << "QUIT command issued, exiting gracefully" << endl;
		break;

	case SIGTERM:
		LOGi << "TERM command issued, exiting gracefully" << endl;
		break;
	}
}

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

	cString     tempdir;
	cString     sendFilename;
	cString	    videoFilename;
	cString     grabFilename;
	char       *playerscript=NULL;

	cTBSocket   clientListener;
	cTBSocket   commandListener;
	cTBSocket  *client;
	cTBSocket  *command;
	cTBFile     sendFile;
	cTBFile     grabFile;
	cTBFile     mpSlave;
	cTBSelect   fiSelect;

	programName = argv[0];

	signal(SIGINT, sighandler);
	signal(SIGQUIT, sighandler);
	signal(SIGTERM, sighandler);

	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, sighandler);

	static struct option long_options[] = {
		{ "mplayer", required_argument, NULL, 'M' },
		{ "help", no_argument, NULL, 'h' },
		{ NULL }
	};

  	int getarg;
	while ((getarg=getopt_long(argc, argv, "M:h", long_options, NULL)) != -1) {
		switch (getarg) {
		case 'M': 
			playerscript=optarg;
			break;
		case 'h':
			cout << "Usage: " << programName << " [OPTION]..." << endl
				 << "Start mplayer recoding server" << endl << endl
				 << "  -M play.sh, --mplayer=play.sh Call script for playback" << endl
				 << "  -h, --help                    This help." << endl;
			return 0;
        default: 
			return 1;
		}
	}


	LOGi << "Initializing directory environment" << endl;



#if !defined(DEBUG_DIR)
	do {
		tempdir = tempnam("/tmp", ".stre");
	} while (stat(tempdir, &statbuf) == 0);
	
	if (mkdir(tempdir, 0700) == -1) {
		LOGe << "Couldn't create directory: " << strerror(errno) << endl;
		exit(1);
	}
#else
	tempdir = ".";
#endif

	sendFilename = tempdir + "/" + MPLAYER_SEND_FILE;
	grabFilename = tempdir + "/" + MPLAYER_GRAB_FILE;
	
	if ((mkfifo(sendFilename, 0600) == -1) || 
			(mkfifo(grabFilename, 0600) == -1)) {
		fprintf(stderr, "%s: Couldn't create fifos: %s\n", argv[0], 
				strerror(errno));
		unlink(sendFilename);
		unlink(grabFilename);
		rmdir(tempdir);
		exit(1);
	}

	if (!clientListener.Listen(2003, 0)) {
		LOGe << "Couldn't listen on port 2003: " << strerror(errno) << endl;
		unlink(sendFilename);
		unlink(grabFilename);
		rmdir(tempdir);
		exit(1);
	}
	
	while (!stopDem) {
		char       rawBuffer[KILOBYTE(16)];
		char       mpgBuffer[KILOBYTE(16)];
		char       cmdBuffer[KILOBYTE(4)];

		size_t     rawBufferFill = 0;
		size_t     mpgBufferFill = 0;
		size_t     cmdBufferFill = 0;

		ssize_t    bytes;

		bool       readRawData = true;
		bool       readCmdData = true;
		bool       writeRawData = true;
		bool       writeCmdData = true;
		bool       readMpgData = true;
		bool       writeMpgData = true;

		uint16_t   commandPort;
		char       initPacket[6];
		size_t     initPacketFill;


		uint16_t   filenameLen;
		char      *filename;
		size_t     filenameFill;

		int        mpPipes[2];

		LOGi << "Listening on port 2003" << endl;

		fiSelect.Clear();
		fiSelect.Add(clientListener, false);

		if (fiSelect.Select(-1) <= 0) {
			LOGe << "Couldn't listen for a client: " << strerror(errno) << endl;
			continue;
		}

		if ((client = clientListener.Accept()) == 0L) {
			LOGe << "Couldn't accept client: " << strerror(errno) << endl;
			continue;
		}

		LOGi << "Connected to client (fileno = " << *client << ")" << endl;

		if (!commandListener.Listen(0, 0)) {
			LOGe << "Couldn't listen for command connection: " 
				 << strerror(errno) << endl;
			client->Close();
			continue;
		}
		
		LOGi << "Listening for command connection (port = "
			 << commandListener.LocalPort() << ")" << endl;

		commandPort = htons(commandListener.LocalPort());

		memcpy(initPacket, "MPCL", 4);
		memcpy(initPacket + 4, &commandPort, 2);

		initPacketFill = PKT_HEADERLEN;
		while (initPacketFill > 0) {
			fiSelect.Clear();
			fiSelect.Add(*client, true);

			if (fiSelect.Select(-1) <= 0) {
				LOGe << "Couldn't send greeting packet: " << strerror(errno)
					 << endl;
				client->Close();
				continue;
			}

			if (!client->Write(initPacket, initPacketFill)) {
				LOGe << "Couldn't send greeting packet: " << strerror(errno)
					 << endl;
				client->Close();
				continue;
			}
		}

		fiSelect.Clear();
		fiSelect.Add(commandListener, false);

		if (fiSelect.Select(-1) <= 0) {
			LOGe << "Couldn't establish command connection: " << strerror(errno)
				 << endl;
			client->Close();
			continue;
		}

		if ((command = commandListener.Accept()) == NULL) {
			LOGe << "Couldn't establish command connection: " << strerror(errno)
				 << endl;
			client->Close();
			continue;
		}

		LOGi << "Command connection established" << endl;

		initPacketFill = 0;
		while (initPacketFill < PKT_HEADERLEN) {
			fiSelect.Clear();
			fiSelect.Add(*client, false);

			if (fiSelect.Select(-1) <= 0) {
				LOGe << "Couldn't listen for greeting packet: " << strerror(errno) 
						 << endl;
				client->Close();
				continue;
			}

			if (!client->Read(initPacket, PKT_HEADERLEN, initPacketFill)) {
				LOGe << "Couldn't listen for greeting packet: " << strerror(errno) 
						 << endl;
				client->Close();
				continue;
			}
		}

		if (memcmp(initPacket, "MPCL", 4) != 0) {
			LOGe << "Corrupt greeting from peer" << endl;
			client->Close();
			continue;
		}

		memcpy(&filenameLen, initPacket + 4, 2);
		filenameLen = ntohs(filenameLen);
		filenameFill = 0;

		if (filenameLen == 0) {
			videoFilename = sendFilename;
		
			LOGi << "Receiving video data from client" << endl;
		} else {
			filename = new char[filenameLen + 1];
			while (filenameFill < filenameLen) {
				fiSelect.Clear();
				fiSelect.Add(*client, false);

				if (fiSelect.Select(-1) <= 0) {
					LOGe << "Couldn't listen for filename: " << strerror(errno) 
							 << endl;
					client->Close();
					continue;
				}

				if (!client->Read(filename, filenameLen, filenameFill)) {
					LOGe << "Couldn't listen for filename: " << strerror(errno) 
							 << endl;
					client->Close();
					continue;
				}
			}

			filename[filenameLen] = 0;

			readRawData = false;
			writeRawData = false;

			videoFilename = filename;

			LOGi << "Reading video data from file " << (const char*)videoFilename
					 << endl;
		}

		if (::pipe(mpPipes) == -1) {
			LOGe << "Couldn't create mplayer slave pipe: " << strerror(errno)
				 << endl;
			client->Close();
			command->Close();
			continue;
		}

		if ((mplayerpid = fork()) == -1) {
			LOGe << "Couldn't fork off mplayer: " << strerror(errno) << endl;
			client->Close();
			command->Close();
			break;
		}

		if (mplayerpid == 0) {
			::close(mpPipes[1]);
			::dup2(mpPipes[0], 0);

			if (playerscript!=NULL) {
				char cmd[64+PATH_MAX*2];
				snprintf(cmd,sizeof(cmd),"%s \"%s\"",playerscript,(const char*)videoFilename);

				::execl("/bin/sh",  "sh", "-c", cmd, (char*)NULL);
			} else {
				::execl("/usr/local/bin/mplayer", "mplayer", "-vo", "mpegpes", "-ao",
						"mpegpes", "-vop", "lavc=10000,expand=-1:576:-1:-1:1,scale=-1:0,dvbscale", 
						"-slave", "-sws", "5", (const char*)videoFilename, (char*)NULL);
			}

			LOGe << "Couldn't start mplayer: " << strerror(errno) << endl;
			exit(127);
		}

		LOGi << "mplayer started as PID " << mplayerpid << endl;

		::close(mpPipes[0]);
		mpSlave.Open(mpPipes[1]);

		if (writeRawData && !sendFile.Open(sendFilename, O_WRONLY)) {
			LOGe << "Couldn't open mplayer input: " << strerror(errno) << endl;
			client->Close();
			command->Close();
			break;
		}

		if (!grabFile.Open(grabFilename, O_RDONLY + O_NONBLOCK)) {
			LOGe << "Couldn't open mplayer output: " << strerror(errno) << endl;
			client->Close();
			command->Close();
			sendFile.Close();
			break;
		}

#if defined(DEBUG_DATAFLOW)
		int count = 0;
#endif
		while ((!stopDem) && client->IsOpen()) {
			fiSelect.Clear();

			if (readRawData && rawBufferFill < sizeof(rawBuffer))
				fiSelect.Add(*client, false);

			if (readCmdData && cmdBufferFill < sizeof(cmdBuffer))
				fiSelect.Add(*command, false);

			if (writeRawData && rawBufferFill > 0)
				fiSelect.Add(sendFile, true);

			if (writeCmdData && cmdBufferFill > 0)
				fiSelect.Add(mpSlave, true);
			
			if (readMpgData && mpgBufferFill < sizeof(mpgBuffer))
				fiSelect.Add(grabFile, false);
			
			if (writeMpgData && mpgBufferFill > 0)
				fiSelect.Add(*client, true);

			if (fiSelect.Select(1000) == -1) {
				LOGe << "Couldn't get pipe's or socket's status: " 
					 << strerror(errno) << endl;
				break;
			}

			if (readRawData && fiSelect.CanRead(*client)) {
				if (((bytes = client->Read(rawBuffer, sizeof(rawBuffer), 
						rawBufferFill)) == -1) && (errno != EAGAIN)) {
					LOGe << "Couldn't read video data: " << strerror(errno) 
						 << endl;
					break;
				}

				if (bytes == 0) {
					LOGi << "All video data received..." << endl;
					readRawData = false;
					client->Shutdown(SHUT_RD);
				}
			}

			if (readCmdData && fiSelect.CanRead(*command)) {
				if (((bytes = command->Read(cmdBuffer, sizeof(cmdBuffer), 
						cmdBufferFill)) == -1) && (errno != EAGAIN)) {
					LOGe << "Couldn't read command: " << strerror(errno) 
						 << endl;
					break;
				}
			}
			
			if (readMpgData && fiSelect.CanRead(grabFile)) {
				if (((bytes = grabFile.Read(mpgBuffer, sizeof(mpgBuffer),
						mpgBufferFill)) == -1) && (errno != EAGAIN)) {
					LOGe << "Couldn't read mpeg data: " << strerror(errno) 
						 << endl;
					break;
				}

				if (bytes == 0) {
					LOGi << "All mpeg data received..." << endl;
					readMpgData = false;
					grabFile.Close();
				}
			}

			if (writeRawData && fiSelect.CanWrite(sendFile)) {
				if (((bytes = sendFile.Write(rawBuffer, rawBufferFill)) == -1)
						&& (errno != EAGAIN)) {
					LOGe << "Couldn't send mpeg data: " << strerror(errno)
						 << endl;
					break;
				}
			}
				
			if (writeRawData && !readRawData && rawBufferFill == 0) {
				LOGi << "All video data written..." << endl;
				writeRawData = false;
				sendFile.Close();
			}

			if (writeCmdData && fiSelect.CanWrite(mpSlave)) {
				if (((bytes = mpSlave.Write(cmdBuffer, cmdBufferFill)) == -1)
						&& (errno != EAGAIN)) {
					LOGe << "Couldn't send command data: " << strerror(errno)
						 << endl;
					break;
				}
			}

			if (writeMpgData && fiSelect.CanWrite(*client)) {
				if (((bytes = client->Write(mpgBuffer, mpgBufferFill)) == -1) 
						&& (errno != EAGAIN)) {
					LOGe << "Couldn't write video data: " << strerror(errno) 
						 << endl;
					break;
				}
			}

			if (writeMpgData && !readMpgData && mpgBufferFill == 0) {
				LOGi << "All mpeg data sent..." << endl;
				writeMpgData = false;
				client->Shutdown(SHUT_WR);
				client->Close();
				command->Close();
			}

			if (waitpid(mplayerpid, &mplayerstatus, WNOHANG) == mplayerpid) {
				LOGi << "mplayer died unexpected..." << endl;
				mplayerpid=0;
				break;
			}
		
#if defined(DEBUG_DATAFLOW)
			if (++count == 10) {
				LOGi << "RAW(R): " << client->BytesRead() / KILOBYTE(1) 
					 << "kB, RAW(W): " << sendFile.BytesWritten() / KILOBYTE(1) 
					 << "kB, MPG(R): " << grabFile.BytesRead() / KILOBYTE(1) 
					 << "kB, MPG(W): " << client->BytesWritten() / KILOBYTE(1) 
					 << "kB\r";
				fflush(stdout);
				count = 0;
			}
#endif
		}

		sendFile.Close();
		grabFile.Close();
		client->Close();
		strcpy(cmdBuffer,"quit\n");
		cmdBufferFill=5;
		mpSlave.Write(cmdBuffer,cmdBufferFill);
		mpSlave.Close();
		command->Close();
		delete client;
		delete command;

		LOGi << "Waiting for mplayer process" << endl;
		if (mplayerpid!=0 && waitpid(mplayerpid, &mplayerstatus, WNOHANG) != mplayerpid) {
			sleep(2);
			if (waitpid(mplayerpid, &mplayerstatus, WNOHANG) != mplayerpid) {
				LOGi << "Terminating mplayer process" << endl;
				kill(mplayerpid, SIGTERM);
				sleep(2);
				kill(mplayerpid, SIGKILL);
				waitpid(mplayerpid, &mplayerstatus, WNOHANG);
			}
		}
	}

	clientListener.Close();

	unlink(sendFilename);
	unlink(grabFilename);
#if !defined(DEBUG_DIR)
	rmdir(tempdir);
#endif
	exit(1);
}

