/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2008 OMC Denmark ApS.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  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 Affero 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/>.
 */

//**************************************************************************
//
// BASED ON ACE SAMPLE CODE BY:
//    ACE_wrappers/examples/NT_Service and
//    ACE_wrappers/TAO/orbsvcs/ImplRepo_Service
//
//**************************************************************************

#include "utils/fileops.h"
#include "utils/globals.h"
#include "utils/logging.h"
#include "server.h"
#include "ntsvc.h"

#include <ace/Get_Opt.h>

namespace BRUTUS
{
        class Service_Loader {
        public:
                enum SERVICE_COMMAND {
                        SC_NONE,
                        SC_INSTALL,
                        SC_REMOVE,
                        SC_START,
                        SC_STOP
                };

                Service_Loader(const char *progname);

                ~Service_Loader(void);

                int run(int argc,
                        ACE_TCHAR *argv[]);

                int parse_args(int argc,
                               ACE_TCHAR *argv[]);

                int run_service_command(void);

                int run_service(int argc,
				ACE_TCHAR *argv[]);

                int run_standalone(int argc,
				   ACE_TCHAR *argv[]);

                void print_usage_and_die(void);

                bool is_service(void);

                BRUTUS::BrutusServer *init_server(int argc,
						  ACE_TCHAR *argv[]); 

	private:
                ACE_CString program_name;

                /// SC_NONE, SC_INSTALL, SC_REMOVE, ...
                SERVICE_COMMAND service_command_;
                std::string config_file_;
                bool debug_;
        };
}

BRUTUS::Service_Loader::Service_Loader(const char *progname)
        : program_name(progname),
          service_command_(SC_NONE),
          debug_(false),
          config_file_(BRUTUS_CONF_FILE)
{
}

BRUTUS::Service_Loader::~Service_Loader(void)
{
}

void
BRUTUS::Service_Loader::print_usage_and_die(void)
{
        ACE_DEBUG((LM_INFO,
                   ACE_TEXT("Usage: %s")
                   ACE_TEXT(" -V -i -r -t -k -d -f -l\n")
                   ACE_TEXT(" -V: Print the version\n")
                   ACE_TEXT(" -i: Install this program as an NT service\n")
                   ACE_TEXT(" -r: Remove this program from the Service Manager\n")
                   ACE_TEXT(" -s: Start the service\n")
                   ACE_TEXT(" -k: Stop the service\n")
                   ACE_TEXT(" -d: Debug; run as a regular application\n")
                   ACE_TEXT(" -f: <file> Configuration file, default is \"%s\"\n")
                   ACE_TEXT(" -l: <level> Turn on Brutus Server debugging, default 0\n"),
                   program_name.c_str(), BRUTUS_CONF_FILE,
                   0));
        ExitProcess(EXIT_SUCCESS);
}

int
BRUTUS::Service_Loader::parse_args(int argc,
                                   ACE_TCHAR *argv[])
{
        if (!argc)
                return 0;

        ACE_Get_Opt get_opt(argc, argv, ACE_TEXT("Virtkdf:c:l:"));

        int c;
        const ACE_TCHAR *tmp;

        while ((c = get_opt()) != -1) {
                switch (c) {
                case 'i':
                        service_command_ = SC_INSTALL;
                        break;
                case 'r':
                        service_command_ = SC_REMOVE;
                        break;
                case 't':
                        service_command_ = SC_START;
                        break;
                case 'k':
                        service_command_ = SC_STOP;
                        break;
                case 'V':
			LOG_MANAGER->redirectToStderr();
                        ACE_OS::printf("Brutus Server version %s\n", BRUTUS_VERSION_STRING);
                        return 1;
                case 'd':
                        debug_ = true;
			LOG_MANAGER->redirectToStderr();
                        ACE_DEBUG((LM_INFO, ACE_TEXT("%N:%l - Brutus Server is in debug mode\n")));
                        break;
                case 'f':
                        config_file_ = get_opt.opt_arg();
                        ACE_DEBUG((LM_INFO, ACE_TEXT("%N:%l - Brutus Server configuration file = \"%s\"\n"), config_file_.c_str()));
                        break;
                case 'l':
                        tmp = get_opt.opt_arg();
                        if (tmp != 0) {
                                int brutus_debug_level = ACE_OS::atoi(tmp);
				ACE_DEBUG((LM_INFO, ACE_TEXT("%N:%l - Brutus Server debug level = %d\n"), brutus_debug_level));
			}
                        break;
                default:
			LOG_MANAGER->redirectToStderr();
                        print_usage_and_die();
                        break;
                }
        }

        return 0;
}

int
BRUTUS::Service_Loader::run_service_command(void)
{
        BRUTUS::SERVICE::instance()->name(ACE_TEXT("Brutus"),
                                          ACE_TEXT("Brutus Server"));

        if (service_command_ == SC_NONE)
                return 0;

        int result = 1;
        switch (service_command_) {
        case SC_INSTALL:
        {
                char pathname[_MAX_PATH] = { '\0' };
                DWORD length = ACE_TEXT_GetModuleFileName(NULL,
                                                          pathname,
                                                          _MAX_PATH);

                // Append the command used for running as a service
                ACE_OS::strcat(pathname, ACE_TEXT(" -s"));
                if (-1 == BRUTUS::SERVICE::instance()->insert(SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, pathname, 0, 0, "Lorica\0")) {
                        ACE_ERROR ((LM_ERROR, ACE_TEXT("%N:%l - error inserting service\n")));
                        result = -1;
                }

                break;
        }
        case SC_REMOVE :
                if (-1 == BRUTUS::SERVICE::instance()->remove()) {
                        ACE_ERROR ((LM_ERROR, ACE_TEXT("%N:%l - remove\n")));
                        result = -1;
                }
                break;
        case SC_START :
                if (-1 == BRUTUS::SERVICE::instance()->start_svc()) {
                        ACE_ERROR ((LM_ERROR, ACE_TEXT("%N:%l - start\n")));
                        result = -1;
                }
                break;
        case SC_STOP :
                if (-1 == BRUTUS::SERVICE::instance()->stop_svc()) {
                        ACE_ERROR ((LM_ERROR, ACE_TEXT("%N:%l - stop\n")));
                        result = -1;
                }
                break;
        default:
                result = -1;
        }

        return result;
}

// Define a function to handle Ctrl+C to cleanly shut this down.
static BOOL __stdcall
ConsoleHandler(DWORD ctrlType)
{
        ACE_UNUSED_ARG(ctrlType);
	ExitProcess(EXIT_SUCCESS);
	return TRUE;
}

ACE_NT_SERVICE_DEFINE(Brutus,
                      BRUTUS::NT_Service,
                      ACE_TEXT ("Brutus Server"));

BRUTUS::BrutusServer*
BRUTUS::Service_Loader::init_server(int argc,
                                    ACE_TCHAR *argv[])
{
        BRUTUS::Config config;
	config.load(this->config_file_.c_str());

        std::auto_ptr<BRUTUS::BrutusServer> brutus_server(new BRUTUS::BrutusServer(debug_, argc, argv));
        try {
                brutus_server->configure(config);

                return brutus_server.release();
        }
        catch (const BRUTUS::BrutusServer::InitError &) {
                ACE_ERROR((LM_ERROR, ACE_TEXT("%N:%l - initialization failed.\n")));
		throw;
        }

        return 0;
}

int
BRUTUS::Service_Loader::run_service(int argc,
				    ACE_TCHAR *argv[])
{
        try {
                BRUTUS::SERVICE::instance()->brutus_server(this->init_server(argc, argv));
        }
        catch (...) {
                BRUTUS_LOG_CRITICAL("Couldn't start Brutus Server - init_server() failed");
                return EXIT_FAILURE;
        }

        BRUTUS_LOG_INF("Starting Brutus Server service");
        ACE_NT_SERVICE_RUN(Brutus,
                           BRUTUS::SERVICE::instance(),
                           ret);
        if (0 == ret) {
                BRUTUS_LOG_CRITICAL("Couldn't start Brutus Server - Service start failed");
		return EXIT_FAILURE;
        }

        return EXIT_SUCCESS;
}

int
BRUTUS::Service_Loader::run_standalone(int argc,
				       ACE_TCHAR *argv[])
{
        std::auto_ptr<BrutusServer>brutus_server(this->init_server(argc, argv));

        ACE_DEBUG((LM_DEBUG, ACE_TEXT("Brutus Server [%P] running as a standalone application \n")));

        SetConsoleCtrlHandler(&ConsoleHandler, true);
        brutus_server->activate();
        brutus_server->wait();

        return EXIT_SUCCESS;
}

bool
BRUTUS::Service_Loader::is_service(void)
{
        return !debug_;
}

int
ACE_TMAIN(int argc,
          ACE_TCHAR *argv[])
{
        BRUTUS::Service_Loader brutus_server(argv[0]);
        int result = 0;

        { // set current working directory
                char *c = NULL;
                char cwd[_MAX_PATH] = { '\0' };

                if (!GetFullPathName(argv[0],
                                     _MAX_PATH,
                                     cwd,
                                     NULL))
                        ExitProcess(EXIT_FAILURE); 

                c = &cwd[strlen(cwd)-1];
                while ('\\' != *c)
                        *(c--) = '\0';
                *c = '\0';
                SetCurrentDirectory(cwd);

#if _WIN32_WINNT > 0x0501
		// use this when Windows 2000 has become irrelevant
		SetDllDirectory(cwd); 
#endif
        }

	// create directories
	create_file(true, TMP_DIR);
	create_file(true, LOG_DIR);
	create_file(true, IOR_DIR);
	create_file(true, MMP_DIR);
	create_file(false, BRUTUS_SERVER_LOG_FILE);

	LOG_MANAGER->redirectToFile(BRUTUS_SERVER_LOG_FILE);

        result = brutus_server.parse_args(argc, argv);
        if (result < 0)
                ExitProcess(EXIT_FAILURE);
        else if (result > 0)
                ExitProcess(EXIT_SUCCESS); // No error, but we should exit anyway.

        ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - Brutus Server %s initializing\n"), BRUTUS_VERSION_STRING));

        result = brutus_server.run_service_command();
        if (result < 0)
                ExitProcess(EXIT_FAILURE); 
        else if (result > 0)
                ExitProcess(EXIT_SUCCESS); // No error, but we should exit anyway.

	try {
		if (brutus_server.is_service())
			result = brutus_server.run_service(argc, argv);
		else
			result = brutus_server.run_standalone(argc, argv);
	}
	catch (...) {
	        ACE_DEBUG((LM_CRITICAL, ACE_TEXT("<%T > %N:%l - Brutus Server could not start\n")));
		result = EXIT_FAILURE;
	}
        ACE_DEBUG((LM_INFO, ACE_TEXT("<%T > %N:%l - Brutus Server is shutting down\n")));

        ExitProcess(result);
}
