/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $Id$ */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <getopt.h>

#include <signal.h>
#include <sys/param.h>
#include <sys/resource.h>

#include "platform.h"
#include "daemon/DaemonEngine.h"

#include "DaemonDM/IPCDaemonEngine.h"

#include "DataStorage/IDataStorage.h"
#include "Errors.h"

#include "Logger/Logger.h"

#include "../branding.h"

//------------------------------------------------------------------------------------------------------

volatile sig_atomic_t gGracefulShutdown = 0;
volatile sig_atomic_t gCaughtHupSignal = 0;
int gLockFileDesc = -1;

const char* const gLockFilePath = "/tmp/funambol_client.pid";

const char* const c_clSmallOptions = "hasprvct";
const struct option LongOptions[] =
{
 {"help",           0,      NULL,       c_clSmallOptions[0]},
 {"alive",          0,      NULL,       c_clSmallOptions[1]},
 {"start",          0,      NULL,       c_clSmallOptions[2]},
 {"stop",           0,      NULL,       c_clSmallOptions[3]},
 {"restart",        0,      NULL,       c_clSmallOptions[4]},
 {"version",        0,      NULL,       c_clSmallOptions[5]},
 {"compiler",       0,      NULL,       c_clSmallOptions[6]},
 {"time",           0,      NULL,       c_clSmallOptions[7]},
 {NULL,             0,      NULL,       0}
};


const size_t c_blockSignalsCount = 6;
int BlockSignals[c_blockSignalsCount] = {SIGUSR2, SIGALRM, SIGPIPE, SIGTSTP, SIGPROF, SIGCHLD};

#if defined(PLATFORM_LINUX)
const size_t c_handleSignalsCount = 15;
int HandleSignals[c_handleSignalsCount] = {SIGUSR1, SIGHUP, SIGTERM, SIGILL, SIGTRAP, SIGQUIT, SIGABRT, SIGIOT, SIGBUS, SIGFPE, SIGSEGV, SIGSTKFLT, SIGCONT, SIGPWR, SIGSYS};
#elif defined(PLATFORM_MAC)
const size_t c_handleSignalsCount = 13;
int HandleSignals[c_handleSignalsCount] = {SIGUSR1, SIGHUP, SIGTERM, SIGILL, SIGTRAP, SIGQUIT, SIGABRT, SIGIOT, SIGBUS, SIGFPE, SIGSEGV, SIGCONT, SIGSYS};
#endif

const int try_alive_check_count = 70;

struct sigaction SignalAction;
sigset_t SigMask;

volatile int iMaxFileDescriptors = 0;
volatile pid_t DaemonPID = 0;

//------------------------------------------------------------------------------------------------------

void CheckCommandLine(int argc, char** argv);
void Handler(int Signal);
int ConfigureSignalHandlers(void);
int BecomeDaemonProcess(void);
void DaemonFinalization();

void malloc_error_break()
{

}

//------------------------------------------------------------------------------------------------------
static void deamon_exit(int status)
{
//	NS_Logging::ReleaseLoggers();
	exit(status);
}
//------------------------------------------------------------------------------------------------------
bool IsStarted(pid_t& pid, bool checkAlive = false)
{
    bool res = false;
    int fd;
    if ((fd = open(gLockFilePath, O_RDONLY)) > 0)
    {
        const size_t c_procPreffixSize = 6;
        const size_t c_maxPidSize = 10;

        char pid_buf[c_procPreffixSize + c_maxPidSize] = {'/','p','r','o','c','/'};
        int len = read(fd, &pid_buf[c_procPreffixSize], 8);
        pid_buf[c_procPreffixSize + len - 1] = 0;

        pid = atoi(&pid_buf[c_procPreffixSize]);
        close(fd);

        #ifdef __MACH__ // mac os x
            res = false;    // todo: under construction
        #else // linux
            // try to find process in /proc
            struct stat st;
            res = (stat(pid_buf, &st) == 0);
            if ((!res) && checkAlive)
            {
                char error[20];
                switch(errno)
                {
                case EACCES:
                {
                    strcpy(error, "EACCES");
                    break;
                }
                case EIO:
                {
                    strcpy(error, "EIO");
                    break;
                }
                case ELOOP:
                {
                    strcpy(error, "ELOOP");
                    break;
                }
                case ENAMETOOLONG:
                {
                    strcpy(error, "ENAMETOOLONG");
                    break;
                }
                case ENOENT:
                {
                    strcpy(error, "ENOENT");
                    break;
                }
                case ENOTDIR:
                {
                    strcpy(error, "ENOTDIR");
                    break;
                }
                case EOVERFLOW:
                {
                    strcpy(error, "EOVERFLOW");
                    break;
                }
                default:
                {
                    strcpy(error, "Undefined");
                    break;
                }

                };
                //fprintf (stdout, "Check process on proc filesystem: %s using stat failed. Errno: %s\n", pid_buf, error);
            }
        #endif
    }
    else
    {
        if (checkAlive)
        {
            //fprintf (stdout, "Can't open lock file for process: %s\n", gLockFilePath);
        }
    }
    return res;
}
//------------------------------------------------------------------------------------------------------
bool IsAlive(pid_t& pid)
{
    #ifdef __MACH__ // mac os x
        return IsStarted(pid, true);
    #else // linux
        for (int i = 0; i < try_alive_check_count; ++i)
        {
            if (IsStarted(pid, true))
            {
                return true;
            }
            usleep(100000); // 1/10 sec
        }
        return false;
    #endif
    return false;
}

//------------------------------------------------------------------------------------------------------
void print_usage(FILE* stream, const char* prog_name)
{
    fprintf(stream, "Usage: %s options \n", prog_name);
    fprintf(stream,
        "   -h  --help              Display this usage information\n"
        "   -a  --alive             Check if daemon is alive\n"
        "   -s  --start             Start daemon\n"
        "   -p  --stop              Stop daemon\n"
        "   -r  --restart           Restart daemon\n"
        "   -v  --version           Display daemon version\n"
        "   -c  --compiler          Display compiller version\n"
        "   -t  --time              Display compilation time\n"
    );
}
//------------------------------------------------------------------------------------------------------
void CheckCommandLine(int argc, char** argv)
{
    int next_option;

    if (argc > 1)
    {
        // we have parameters in command line
        bool enableStartArg = false;
        do
        {
            next_option = getopt_long(argc, argv, c_clSmallOptions, LongOptions, 0);
            switch (next_option)
            {
                case 'h':
                    print_usage(stdout, argv[0]);
                    break;
                case 's':
                    enableStartArg = true;
                    break;
                case 'a':
                    pid_t pid;
                    fprintf(stdout, (IsAlive(pid) ? "Daemon is alive\n" : "Daemon is not alive\n"));
                    break;
                case 'p':
                    if (IsStarted(pid))
                    {
                        fprintf (stdout, "Start daemon stoping\n");
                        kill(pid, SIGUSR1);
                    }
                    else
                    {
                        fprintf(stdout, "Daemon is not runnign. Can't stop daemon\n");
                        deamon_exit (EXIT_FAILURE);
                    }
                    break;
                case 'r':
                    if (IsStarted(pid))
                    {
                        kill(pid, SIGHUP);
                        fprintf (stdout, "Start daemon restarting\n");
                    }
                    else
                    {
                        fprintf (stdout, "Daemon is not runnign. Can't restart daemon\n");
                        deamon_exit (EXIT_FAILURE);
                    }
                    break;
                case 'v':
                    fprintf(stdout, "Daemon version is: %s\n", NS_DM_Client::cApplicationVersion);
                    break;
                case 'c':
                    fprintf(stdout, "Compiller version is: %s\n", __VERSION__);
                    break;
                case 't':
                    fprintf(stdout, "Compilation date and time is: %s %s\n", __DATE__, __TIME__);
                    break;
                default:
                    fprintf (stdout, "No valid command line argument. Use -h for help\n");
                    break;
            }

            // only one parameter may be in command line
            if (!enableStartArg)
            {
                deamon_exit(EXIT_SUCCESS);
            }
            else
            {
                break;
            }
        }
        while (next_option != -1);
    }
    else
    {
        print_usage(stdout, argv[0]);
#ifndef PLATFORM_MAC
        deamon_exit(EXIT_SUCCESS);
#endif
    }
}
//------------------------------------------------------------------------------------------------------
void CheckIfSecondaryExecute()
{
    pid_t pid;
    if (IsStarted(pid))
    {
        printf ("Try to start daemon second time. Allowed only one instance of daemon\n");
        deamon_exit (EXIT_FAILURE);
    }
}
//------------------------------------------------------------------------------------------------------
void Handler(int Signal)
{
    syslog  (LOG_LOCAL0|LOG_INFO, "received signal %d by daemon", Signal);
    switch (Signal)
    {
        case SIGUSR1:
            syslog (LOG_LOCAL0|LOG_INFO, "stop signal caught by daemon");
            gGracefulShutdown = 1;
            break;
        case SIGHUP:
            syslog (LOG_LOCAL0|LOG_INFO, "HUP signal caught by daemon");
            gGracefulShutdown = gCaughtHupSignal = 1;
            break;
        case SIGTERM:
            DaemonFinalization();
            deamon_exit (EXIT_SUCCESS);
            break;
        default:
            #ifdef _GNU_SOURCE
            syslog (LOG_LOCAL0|LOG_INFO, "Caught by daemon signal %s - exiting", strsignal(Signal));
            #else
            syslog (LOG_LOCAL0|LOG_INFO, "Caught by daemon signal %d - exiting", Signal);
            #endif
            DaemonFinalization();
            deamon_exit (0);
            break;
    }
}
//------------------------------------------------------------------------------------------------------
int ConfigureSignalHandlers (void)
{
    size_t i;
    sigemptyset (&SigMask);
    for (i = 0; i < c_blockSignalsCount; i++)
    {
        sigaddset (&SigMask, *(BlockSignals + i));
    }

    SignalAction.sa_handler = Handler;
    SignalAction.sa_mask    = SigMask;
    SignalAction.sa_flags    = 0;
    sigaction (SIGUSR1, &SignalAction, NULL);
    for (i = 0; i < c_handleSignalsCount; i++)
    {
        sigaction (*(HandleSignals + i), &SignalAction, NULL);
    }
    return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------------------------------
int BecomeDaemonProcess (void)
{
    char cPIDString[7];
    int LockResult;
    struct flock Lock;

    iMaxFileDescriptors = sysconf (_SC_OPEN_MAX);

#if defined(PLATFORM_LINUX)
	int iCurrentPID;
    switch (iCurrentPID = fork ())
    {
        case 0:
            //Child process
            break;
        case -1:
            fprintf (stderr, "Daemon error: initial fork failed: %s\n", strerror (errno));
            return -1;
            break;
        default:
            deamon_exit (0);
            break;
    }
#endif

    //umask (0);
    DaemonPID = (int) getpid();
    sprintf (cPIDString, "%d\n", DaemonPID);
    openlog (NS_DM_Client::cApplicationName, LOG_NOWAIT, LOG_LOCAL0);
    if ((gLockFileDesc = creat (gLockFilePath, 0644)) < 0)
    {
        syslog (LOG_LOCAL0|LOG_INFO, "Daemon: couldn't open lock file, ERROR #%d", errno);
        return EXIT_FAILURE;
    }

    Lock.l_type = F_WRLCK;
    Lock.l_whence = SEEK_SET;
    Lock.l_start = Lock.l_start = 0;
    Lock.l_pid = 0;

    if ((LockResult = fcntl (gLockFileDesc, F_SETLK, &Lock)) < 0)
    {
        syslog (LOG_LOCAL0|LOG_INFO, "Daemon: couldn't set lock to file %s, ERROR #%d", gLockFilePath, errno);
    }

    if (write (gLockFileDesc, cPIDString, strlen(cPIDString)) <= 0)
    {
        syslog (LOG_LOCAL0|LOG_INFO, "Daemon: couldn't write PID to lock file, ERROR #%d", errno);
    }

  //  close(gLockFileDesc);
  //  gLockFileDesc = -1;

    if (setsid () < 0)
    {
        syslog (LOG_LOCAL0|LOG_INFO, "Daemon: couldn't get session ID (SID) from Kernel, ERROR #%d", errno);
        return EXIT_FAILURE;
    }

    if (chdir ("/") < 0)
    {
        syslog (LOG_LOCAL0|LOG_INFO, "Daemon: couldn't change directory to /, ERROR #%d", errno);
        return EXIT_FAILURE;
    }

    // Specific Daemon Initialization

    syslog (LOG_LOCAL0|LOG_INFO, "Daemon started with PID #%d, version: %s", DaemonPID, NS_DM_Client::cApplicationVersion);

    return EXIT_SUCCESS;
}
//------------------------------------------------------------------------------------------------------
void DaemonFinalization()
{
    if(gLockFileDesc != -1)
    {
        close(gLockFileDesc);
        unlink(gLockFilePath);
        gLockFileDesc = -1;
    }
}
//------------------------------------------------------------------------------------------------------
int main(int argc, char** argv)
{
    CheckCommandLine(argc, argv);
    CheckIfSecondaryExecute();

	int result;

daemon_restart:
#if defined(PLATFORM_LINUX)
    if ((result = BecomeDaemonProcess ()) < 0)
    {
        perror ("Failed to initialize deamon");
        unlink (gLockFilePath);
        deamon_exit (EXIT_FAILURE);
    }
#endif

    if ((result = ConfigureSignalHandlers()) < 0)
    {
        perror ("Failed to configure signal handlers for daemon");
        unlink (gLockFilePath);
        deamon_exit (EXIT_FAILURE);
    }

    // specific daemon initialization
    NS_DM_Client::IDaemonEngine* engine = NS_DM_Client::IDaemonEngine::GetInstance();
    if (engine)
    {
 	   syslog (LOG_LOCAL0|LOG_INFO, "BEFORE DaemonEngine START");
       if (engine->Start())
        {
	 	   syslog (LOG_LOCAL0|LOG_INFO, "AFTER DaemonEngine START");
            // start interprocess communication engine
            NS_DM_Client::IPCDaemonEngine* ipc_engine = NS_DM_Client::CreateDaemonIPCEngine(engine);
            if (!ipc_engine)
            {
                syslog(LOG_ERR, "Failed to get IPC Engine instance");
            }
            else
            {
		 	    syslog (LOG_LOCAL0|LOG_INFO, "BEFORE IPCEngine START");
                ipc_engine->Start();
		 	    syslog (LOG_LOCAL0|LOG_INFO, "AFTER IPCEngine START");
            }

            // main daemon cycle
            while (true)
            {
                // do something in cycle
                sleep(1);

                if( gGracefulShutdown == 1 )
                {
                    break;
                }
            }

		 	syslog (LOG_LOCAL0|LOG_INFO, "BEFORE IPCEngine STOP");
            if (ipc_engine && (!ipc_engine->Stop()))
            {
                syslog(LOG_WARNING, "can't release testing engine");
            }
		 	syslog (LOG_LOCAL0|LOG_INFO, "AFTER IPCEngine STOP");

		 	syslog (LOG_LOCAL0|LOG_INFO, "BEFORE DaemonEngine STOP");
            if (!engine->Stop())
            {
                syslog(LOG_WARNING, "can't stop daemon engine");
            }
		 	if (ipc_engine)
		 	{
		 	    ipc_engine->Release();
		 	}
		 	syslog (LOG_LOCAL0|LOG_INFO, "AFTER DaemonEngine STOP");
        }
        else
        {
            syslog(LOG_ERR, "can't start daemon engine");
        }
        delete engine;
    }
    else
    {
        syslog(LOG_ERR, "can't get daemon engine instance");
    }

    DaemonFinalization();
    syslog(LOG_INFO, "daemon stopped");

    if (gCaughtHupSignal == 1)
    {  // need restart daemon
        gGracefulShutdown = gCaughtHupSignal = 0;
		syslog(LOG_INFO, "daemon is restarting...");
		closelog();
        goto daemon_restart;
    }

    closelog();
    deamon_exit(EXIT_SUCCESS);
}
