/****************************************************************************
                Main ( main.cpp )
                -----------------------------------------

    begin      : 2015/03/18
    copyright  : (C) 2013 Solbox Inc.
    author     : Development Team
					- 2015/04/23 - 1st dadamin
    email      : storage.sd@solbox.com
    version    : 3.5

    CopyRight(C) 2005 Solbox Inc. All Rights reserved.
    Redistribution and use in source and binary forms, with or with out
    modification, are not permitted in outside of Solbox Inc.
*****************************************************************************/
#include <cstdlib>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <map>
#include <sstream>
#include <sys/types.h>
#include <sys/wait.h>

#include "Logger.h"
#include "Signal_handle.h"
#include "ArgParser.h"
#include "DaemonConfigs.h"
#include "Process.h"
#include "Worker.h"

using namespace std;

static int _pid = -1;
static bool _isDaemon = true;

// signal 
class SIGTerminate_Handler : public Event_Handler
{
public:
	SIGTerminate_Handler(void)
		: graceful_quit_(0) {}

	// Hook method.
	virtual int handle_signal(int signum)
	{
		this->graceful_quit_ = 1;
		return 0;
	}

	// Accessor.
	sig_atomic_t graceful_quit(void)
	{
		return this->graceful_quit_;
	}

	sig_atomic_t* get_signalhandel() 
	{
		return &graceful_quit_;
	}

private:
	sig_atomic_t graceful_quit_;
};

class SIGWorkerDead_Handler : public Event_Handler
{
public:
	SIGWorkerDead_Handler(void) {}

	// Hook method.
	virtual int handle_signal(int signum)
	{
		pid_t deadpid;
		int nstatus;

		while ((deadpid = waitpid(-1, &nstatus, WNOHANG)) > 0)
		{
			if (WIFEXITED(nstatus))
			{
				// 자식 프로세스가 정상적으로 종료되었는지 검사.
				LOG(LWAR, "Worker process [%d] killed by signal[SIGTERM]", deadpid);
			}
			else if (WIFSIGNALED(nstatus))
			{
				// 자식 프로세스가 Signal 에 의해 종료되었는지 검사.
				LOG(LWAR, "Worker process [%d] killed by signal[%d]", deadpid, WTERMSIG(nstatus));
			}
			else
			{
				LOG(LWAR, "Worker process [%d] killed. Not signal", deadpid);
			}

			if (WEXITSTATUS(nstatus) == EXIT_FAILURE)
			{
				// Main 종료 시그널 발생
				raise(SIGTERM);
				continue;
			}

			map<pid_t, CProcess*>::iterator it = m_child.find(deadpid);
			if ( it != m_child.end())
			{
				// find it
				CProcess *p = it->second;
				set_reprocess(p);
				m_child.erase(it);
			}
			else
			{
				LOG(LERR, "Worker process [%d] not found it. and can't be recreated.", deadpid);
			}
		}

		// 오류 발생시 해당 내역 로깅
		if (deadpid < 0)
		{
			LOG(LERR, "Main process error: SIG_CHLD receive but waitpid return error[%d][%s]", errno, strerror(errno));
		}

		return 0;
	}

	void add_child(pid_t pid, CProcess *process)
	{
		m_child.insert(pair<pid_t, CProcess*>(pid, process));
	}

	CProcess* get_reprocess()
	{
		
		if (m_vecreprocess.empty())
			return NULL;
		
		CProcess *p = m_vecreprocess.back();
		m_vecreprocess.pop_back();
		return p;
	}

	void set_reprocess(CProcess *p)
	{
		m_vecreprocess.push_back(p);
	}

	void show_all()
	{
		for (map<pid_t, CProcess*>::iterator it = m_child.begin(); it != m_child.end(); ++it)
		{
			LOG(LDEV2, "Child process[%d]", it->first);
			
		}
	}

	void kill_child()
	{
		for (map<pid_t, CProcess*>::iterator it = m_child.begin(); it != m_child.end(); ++it)
		{
			while (waitpid(it->first, NULL, WNOHANG) == 0)
			{
				LOG(LDEV2, "Child kill process [%d]", it->first);
				kill(it->first, SIGTERM);
				usleep(5000);
			}
		}
	}

	void clear_reprocess()
	{
		m_vecreprocess.clear();
	}

	void clear_all()
	{
		m_child.clear();
		m_vecreprocess.clear();
	}

private:
	map<pid_t, CProcess*>	m_child;
	vector<CProcess*>		m_vecreprocess;
};

// start log
static void StartLog()
{
	// Process 기동 관련 정보 기록 -> Log
	_LOG(LINF, "***********************************************************");
	_LOG(LINF, " %s Start. Version: %s", PROG_NAME, PROG_VERSION);
	_LOG(LINF, "***********************************************************");
	_LOG(LINF, "Config                  : %s", CDeamonConfig::GetInstance()->GetConfigFile());
	_LOG(LINF, "Log                     : %s/%s", CDeamonConfig::GetInstance()->GetAppLogRoot(), PROG_NAME);
	_LOG(LINF, "Log Bacup Server Host   : %s", CDeamonConfig::GetInstance()->GetLogServer());
	_LOG(LINF, "Log Bacup Server Port   : %d", CDeamonConfig::GetInstance()->GetLogServerPort());
	_LOG(LINF, "Log retention period    : %d", CDeamonConfig::GetInstance()->GetAppLogPeriod());
	_LOG(LINF, "***********************************************************");

}

// print Version
static void Version()
{
	fprintf( stderr, "\n" );
	fprintf( stderr, PROG_NAME " version: " PROG_VERSION "\n\n" );

}

// print usage 
static void Usage()
{
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "Usage: " PROG_NAME " [-h] [-v] [-D] [-c {file}]       \n" );
	fprintf( stderr, "Options:                                              \n" );
	fprintf( stderr, "  -h                       Display help information   \n" );
	fprintf( stderr, "  -v                       Display version            \n" );	
	fprintf( stderr, "  -D                       Run as console mode        \n" );
	fprintf( stderr, "  -c {file}                Use {file} as config file  \n" );
	fprintf( stderr, "\n" );

	fprintf( stderr, PROG_NAME " is Solbox Cloud Storage module.\n" );
	fprintf( stderr, "    - Log file transfer to log backup server(cc_collectd) \n" );
	fprintf( stderr, "    - Log file delete \n\n" );

}

// main function
int main(int argc, char * argv[])
{
	save_ps_display_args(argc, argv);

	string strConfPath = DEFAULT_CONFIG_FILE;

	// parse Input argument
	CArgParser argparser(argc, argv);

	if (argparser.checkvalue("-v"))
	{
		Version();
		return EXIT_SUCCESS;
	}

	if (argparser.checkvalue("-h"))
	{
		Usage();
		return EXIT_FAILURE;
	}

	if (argparser.checkvalue("-D"))
	{
		_isDaemon = false;
	}

	string val;
	if (argparser.checkvalue("-c", &val))
	{
#ifdef _DEBUG
		cout << "Change conf path " << strConfPath << " to " << val << endl;
#endif // _DEBUG
		strConfPath = val;
	}

	//check process
	if (CProcess::IsCurrentProcessRun(PROG_NAME))
		return EXIT_FAILURE;

	//initialized Config object
	if (CDeamonConfig::Init(PROG_NAME, strConfPath) == false)
	{
		cerr << "[ERR] Failed to initialize the config object." << endl;
		return EXIT_FAILURE;
	}

	// load config
	if (CDeamonConfig::GetInstance()->LoadConf() == false)
	{
		cerr << "[ERR] Config load error." << CDeamonConfig::GetInstance()->GetErrMessage() << endl;
		return EXIT_FAILURE;
	}

	if (CDeamonConfig::GetInstance()->CheckValue() == false)
	{
		cerr << "[ERR] Config load error." << CDeamonConfig::GetInstance()->GetErrMessage() << endl;
		return EXIT_FAILURE;
	}

	// initialized Log object
	if (CLogger::Init(PROG_NAME, CDeamonConfig::GetInstance()->GetAppLogRoot(),
		CDeamonConfig::GetInstance()->GetAppLogLevel()) == false)
	{
		cerr << "[ERR] Failed to initialize the log object." << endl;
		return EXIT_FAILURE;
	}
	
	// set signal
	SIGTerminate_Handler terminate;
	SIGWorkerDead_Handler workerdead;

	Signal_Handler::instance()->register_ignore(SIGPIPE);
	Signal_Handler::instance()->register_ignore(SIGHUP);
	Signal_Handler::instance()->register_ignore(SIGQUIT);

	Signal_Handler::instance()->register_handler(SIGTERM, &terminate);
	Signal_Handler::instance()->register_handler(SIGINT, &terminate);

	//  daemonize
	if (_isDaemon && CProcess::Daemon() == false)
	{
		return EXIT_FAILURE;
	}

	StartLog();

	int exitcode = EXIT_SUCCESS;
	_pid = getpid();

	// set work process
	// alive check dummy process create
	//CProcessDummy objAliveCheck;
	//objAliveCheck.SetPort(CDeamonConfig::GetInstance()->GetAliveCheckPort());
	//workerdead.set_reprocess(&objAliveCheck);

//	CProcessDummy dummy1, dummy2, dummy3;
//	dummy1.SetPort(7777);
//	workerdead.set_reprocess(&dummy1);
//	dummy2.SetPort(8888);
//	workerdead.set_reprocess(&dummy2);
//	workerdead.set_reprocess(&dummy3);

//	CProcessTest test;
//	workerdead.set_reprocess(&test);
	
	//  Worker
	CWorker worker;
	workerdead.set_reprocess(&worker);

	// Run the main event loop. 
	while (terminate.graceful_quit() == 0)
	{
		// make work process
		CProcess* p = NULL;
		while ((p = workerdead.get_reprocess()) != NULL )
		{
			p->Launcher(terminate.get_signalhandel());
			if (p->Getpid() > 0)
			{
				// main(parent) process 
				if (p->Is_launched() == false)
				{
					exitcode = EXIT_FAILURE;
					// error 
					cerr << "Worker Process create failed." << endl;
					LOG(LERR, "Worker Process create failed.");
					break;
				}

				workerdead.add_child(p->Getpid(), p);
			}
			else if (p->Getpid() == 0)
			{
				// work(child) process 
				workerdead.clear_all();
			}
			else
			{
				// error 
				exitcode = EXIT_FAILURE;
				cerr << "Worker Process create failed.(fork error)" << endl;
				LOG(LERR, "Worker Process create failed.(fork error)");
				break;
			}
		}

		if (exitcode == EXIT_FAILURE)
			break;
				
		if (_pid == getpid())
		{

			// main(parent) process 
			// wait 
			set_ps_display("main [log manager process]", false);
				
			Signal_Handler::instance()->register_handler(SIGCHLD, &workerdead);

			pause();
		}			
		else
		{
			// work(child) process 
			_pid = 0;
		}
	}

	if (exitcode == EXIT_SUCCESS)
		Signal_Handler::instance()->remove_handler(SIGCHLD);
	Signal_Handler::instance()->remove_handler(SIGTERM);
	Signal_Handler::instance()->remove_handler(SIGINT);

	if (_pid > 0)
	{
		if (terminate.graceful_quit())
		{
			// KILL - Child
			//kill(0, SIGTERM);

			// waitpid
			//while (waitpid(0, NULL, WNOHANG) > 0);
			workerdead.kill_child();
		}		
	}

	// end
	ostringstream msg;
	if (_pid == getpid())
		msg << "Main   Process [" << getpid() << "] exit job end. Good Bye..";
	else 
		msg << "Worker Process [" << getpid() << "] exit job end. Good Bye..";

	_LOG(LINF, msg.str().c_str());
	cerr << msg.str() << endl;

	CLogger::Exit();
	CDeamonConfig::Exit();

	return exitcode;
}
