///////////////////////////////////////////////////////////////////////////////
//                                                                             
//  Copyright (C) 2008-2012  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>     
//                                                                             
//  See accompanying file COPYING.TXT file for licensing details.
//
///////////////////////////////////////////////////////////////////////////////
#if defined(__sun)
#define _POSIX_PTHREAD_SEMANTICS
#endif
#include "tcp_cache_server.h"
#include "cache_storage.h"
#ifndef CPPCMS_WIN32
#include <signal.h>
#include "daemonize.h"
#endif
#ifdef CPPCMS_WIN_NATIVE
#include "winservice.h"
#endif
#include <iostream>
#include <stdlib.h>
#include <stdexcept>
#include <booster/shared_ptr.h>
#include <booster/intrusive_ptr.h>
#include <booster/shared_object.h>
#include <booster/thread.h>
#include <booster/log.h>
#include "base_cache.h"
#include "logging.h"
#include <cppcms/session_storage.h>
#include <cppcms/json.h>
#include <cppcms/service.h>
#include <cppcms/cppcms_error.h>

#ifdef CPPCMS_WIN_NATIVE
#include "session_win32_file_storage.h"
#include "winservice.h"
#else
#include "session_posix_file_storage.h"
#endif
#include "session_memory_storage.h"


struct settings {
	std::string ip;
	int port;
	int threads;
	int gc;
	cppcms::json::value config;
	
	booster::shared_ptr<cppcms::sessions::session_storage_factory> sessions;
	booster::shared_object plugin;
	booster::intrusive_ptr<cppcms::impl::base_cache> cache;
	
	~settings()
	{
		sessions.reset(); // ensure that sessions object is destroyed before shared object
		cache = 0;
	}
	
	settings(int argc,char **argv)
	{
		try {
			config = cppcms::service::load_settings(argc,argv);
			setup(config);
		}
		catch(cppcms::cppcms_error const &) {
			help();
			throw;
		}
	}
	void help()
	{
		std::cerr <<
		"usage: cppcms_scale [ -c config.js ] [ parameters ]\n"
		"  -c config.js       JSON, Configuration file, also parameters may\n"
		"                     be set via command line rather then the file\n"
		" --ip=IP             IP to bind, default 0.0.0.0\n"
		" --port=N            Port to bind, mandatory\n"
		" --threads=N         Worker threads, default = # HW CPU\n"
		" --cache-limit=N     The size of the cache in items\n"
		" --session-storage=(memory|files|external)\n"
		"                     Session storage module\n"
		" --session-gc=N      The frequency of garbage collection\n"
		" --session-dir=/path The location of files for session storage\n"
		" --session-shared_object=/path\n"
		"                     The shared object/dll that is used as external\n"
		"                     storage"
		" --session-module=name\n"
		"                     The name of the module for example mymod for\n"
		"                     an external storage, renamed to shared object\n"
		"                     according to OS conventions, like libmymod.so\n"
#ifndef CPPCMS_WIN32
		" --daemon-enable=(true|false)\n"
		"                     Run the process as daemon, default false\n"
		" --daemon-lock=/path Location of the lock file that keeps process pid\n"
		" --daemon-user=user  User that the daemon should run under\n"
		" --daemon-group=grp  Group that the daemon should run under\n"
		" --daemon-chroot=/path\n"
		"                     Run the service in chroot jain\n"
		" --daemon-fdlimit=N  The limit of file descriptors for the service\n"
#endif
#ifdef CPPCMS_WIN_NATIVE
		" --winservice-mode=(install|uninstall)\n"
		"                     Install or uninstall windows service\n"
		" --winservice-name=XXX\n"
		"                     Name of windows service\n"
		" --winservice-display_name=XXX\n"
		"                     The service name shown in the UI\n"
		" --winservice-start=(auto|demand)\n"
		"                     Windows service start mode\n"
		" --winservice-user=XXX\n"
		" --winservice-password=XXX\n"
		"                     The user and password the service would run under\n"
#endif
		<< std::endl;
	}
	void setup(cppcms::json::value const &v)
	{		
		ip=v.get("ip","0.0.0.0");
		port=v.get<int>("port");
		threads=v.get("threads",booster::thread::hardware_concurrency());
		int items = v.get("cache.limit",-1);
		if(items!=-1){
			cache = cppcms::impl::thread_cache_factory(items);
		}
		gc=v.get("session.gc",10);
		std::string stor = v.get("session.storage","");
		if(!stor.empty()) {
			if(stor == "files") {
				std::string dir = v.get("session.dir","");
				#ifdef CPPCMS_WIN_NATIVE
				sessions.reset(new cppcms::sessions::session_file_storage_factory(dir));
				#else
				sessions.reset(new cppcms::sessions::session_file_storage_factory(dir,threads,1,false));
				#endif
			}
			else if(stor == "memory") {
				sessions.reset(new cppcms::sessions::session_memory_storage_factory());
			}
			else if(stor == "external") {
				std::string so = v.get<std::string>("session.shared_object","");
				std::string module = v.get<std::string>("session.module","");
				std::string entry_point = v.get<std::string>("session.entry_point","sessions_generator");
				if(so.empty() && module.empty())
					throw cppcms::cppcms_error(
								"session.storage=external "
								"and neither session.shared_object "
								"nor session.module is defined");
				if(!so.empty() && !module.empty())
					throw cppcms::cppcms_error(
								"both session.shared_object "
								"and session.module are defined");

				if(so.empty()) {
					so = booster::shared_object::name(module);
				}
				std::string error;
				if(!plugin.open(so,error)) {
					throw cppcms::cppcms_error("sessions_pool: failed to load shared object " + so + ": " + error);
				}
				cppcms::sessions::cppcms_session_storage_generator_type f=0;
				plugin.symbol(f,entry_point);
				sessions.reset(f(v.find("session.settings")));
			}
			else 
				throw cppcms::cppcms_error("Unknown session.storage:"+stor);
		}
		if(!sessions && !cache) {
			throw cppcms::cppcms_error("Neither cache.limit nor session.storage is defined");
		}
	}

};

#if !defined(CPPCMS_WIN32)
void main_posix(settings &par)
{
	cppcms::impl::daemonizer demon(par.config);

	cppcms::impl::tcp_cache_service srv(
		par.cache,
		par.sessions,
		par.threads,
		par.ip,
		par.port,
		par.gc);
	
	// Wait for signals for exit
	sigset_t wait_mask;
	sigemptyset(&wait_mask);
	sigaddset(&wait_mask, SIGINT);
	sigaddset(&wait_mask, SIGQUIT);
	sigaddset(&wait_mask, SIGTERM);
	pthread_sigmask(SIG_BLOCK, &wait_mask, 0);
	int sig = 0;
	sigwait(&wait_mask, &sig);
	
	BOOSTER_NOTICE("cppcms_scale")<<"Catch signal: exiting...";

	srv.stop();
}
#elif defined(CPPCMS_WIN_NATIVE)

static booster::shared_ptr<cppcms::impl::tcp_cache_service> the_server;
static settings *the_settings;
static booster::mutex done_lock;
static booster::condition_variable done_cond;
static bool done;

static void win_prepare()
{
	BOOSTER_NOTICE("cppcms_scale")<<"Starting service...";
	settings &par = *the_settings;
	the_server.reset(
		new cppcms::impl::tcp_cache_service(
			par.cache,
			par.sessions,
			par.threads,
			par.ip,
			par.port,
			par.gc)
	);
	
}

static void win_stop()
{
	booster::unique_lock<booster::mutex> guard(done_lock);
	done = true;
	done_cond.notify_all();
}

static void win_run()
{
	{
		booster::unique_lock<booster::mutex> guard(done_lock);
		while(!done) {
			done_cond.wait(guard);
		}
	}
	BOOSTER_NOTICE("cppcms_scale")<<"Stopping service...";
	the_server->stop();
	the_server.reset();
	the_settings = 0;
}

void main_win(settings &par,int argc,char **argv)
{
	using cppcms::impl::winservice;
	
	the_settings = &par;
	winservice::instance().prepare(win_prepare);
	winservice::instance().stop(win_stop);
	winservice::instance().exec(win_run);
	
	winservice::instance().run(par.config,argc,argv);
	
}

#endif

#ifdef CPPCMS_WIN32

void main_console(settings &par)
{
	cppcms::impl::tcp_cache_service srv(
		par.cache,
		par.sessions,
		par.threads,
		par.ip,
		par.port,
		par.gc);

	std::cout << "Press any key to stop..." << std::flush;
	std::cin.get();
	
	srv.stop();
}

#endif


int main(int argc,char **argv)
{
	try 
	{
		settings par(argc,argv);	
		cppcms::impl::setup_logging(par.config);
#ifndef CPPCMS_WIN32
		main_posix(par);
#elif defined CPPCMS_WIN_NATIVE
		if(cppcms::impl::winservice::is_console(par.config))
			main_console(par);
		else
			main_win(par,argc,argv);
#else // cygwin
		main_console(par);
#endif

	}
	catch(std::exception const &e) {
		BOOSTER_ERROR("cppcms_scale")<<e.what()<<booster::trace(e);
		return 1;
	}
	return 0;
}

