/*
 * Copyright (C) 2017 ~ 2017 Deepin Technology Co., Ltd.
 *
 * Author:     Iceyer <me@iceyer.net>
 *
 * Maintainer: Iceyer <me@iceyer.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDebug>
#include <QTimer>

#include <DLog>

#include <Windows.h>

#include "service.h"
#include "legalizationagent.h"
#include "winsystemdetector.h"
#include "tool.h"

using namespace Dtk::Core;

SERVICE_STATUS g_ServiceStatus = {0};
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE;

VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv);
VOID WINAPI ServiceCtrlHandler(DWORD);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);

#define SERVICE_NAME  (L"Deepin Legalization Agent")

DWORD WINAPI ServiceWorkerThread(LPVOID /*lpParam*/)
{
    quint64 count = 0;
    while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) {
        // just feed as work
        if (0 == count % 1800) {
            qDebug() << "service work";
        }
        Sleep(1000);
        count++;
    }

    qApp->exit(ERROR_SUCCESS);
    return ERROR_SUCCESS;
}

VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode)
{
    switch (CtrlCode) {
    case SERVICE_CONTROL_STOP:

        if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) {
            break;
        }
        /*
         * Perform tasks necessary to stop the service here
         */
        g_ServiceStatus.dwControlsAccepted = 0;
        g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
        g_ServiceStatus.dwWin32ExitCode = 0;
        g_ServiceStatus.dwCheckPoint = 4;

        if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) {
            OutputDebugString(TEXT("Legalization Agent Start"));
        }

        // This will signal the worker thread to start shutting down
        SetEvent(g_ServiceStopEvent);

        break;

    default:
        break;
    }
}

VOID WINAPI ServiceMain(DWORD /*argc*/, LPTSTR * /*argv*/)
{
    // DWORD Status = E_FAIL;
    // Register our service control handler with the SCM
    g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);

    if (g_StatusHandle == NULL) {
        goto EXIT;
    }

    // Tell the service controller we are starting
    ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwControlsAccepted = 0;
    g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwServiceSpecificExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) {
        OutputDebugString(TEXT("Legalization Agent Start"));
    }

    /*
     * Perform tasks necessary to start the service here
     */

    // Create a service stop event to wait on later
    g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (g_ServiceStopEvent == NULL) {
        // Error creating event
        // Tell service controller we are stopped and exit
        g_ServiceStatus.dwControlsAccepted = 0;
        g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        g_ServiceStatus.dwWin32ExitCode = GetLastError();
        g_ServiceStatus.dwCheckPoint = 1;

        if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) {
            OutputDebugString(TEXT("Legalization Agent returned error"));
        }
        goto EXIT;
    }

    // Tell the service controller we are started
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;

    if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) {
        OutputDebugString(TEXT("Legalization Agent returned error"));
    }

    // Start a thread that will perform the main task of the service
    HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread, NULL, 0, NULL);

    // Wait until our worker thread exits signaling that the service needs to stop
    WaitForSingleObject(hThread, INFINITE);

    /*
     * Perform any cleanup tasks
     */
    CloseHandle(g_ServiceStopEvent);

    // Tell the service controller we are stopped
    g_ServiceStatus.dwControlsAccepted = 0;
    g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
    g_ServiceStatus.dwWin32ExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 3;

    if (SetServiceStatus(g_StatusHandle, &g_ServiceStatus) == FALSE) {
        OutputDebugString(TEXT("Legalization Agent returned error"));
    }

EXIT:
    ;
}

void ServiceWork::run()
{
    SERVICE_TABLE_ENTRY ServiceTable[] = {
        {(LPWSTR)SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)ServiceMain},
        {NULL, NULL}
    };

    qDebug() << "StartServiceCtrlDispatcher";
    if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
        qDebug() << "start service return" << GetLastError();
        qApp->exit(GetLastError());
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    app.setOrganizationName("deepin");
    app.setApplicationName("deepin-legalization-agent");

    DLogManager::setlogFilePath(app.applicationDirPath() + "/agent.log");
    DLogManager::setLogFormat("%{time}{yyyy-MM-dd, HH:mm:ss.zzz} [%{TypeOne}] [%{function:20} %{line:6}] %{message}\n");
    DLogManager::registerConsoleAppender();
    DLogManager::registerFileAppender();

    qDebug() << "Save log to:" << DLogManager::getlogFilePath();

    app.setApplicationVersion(Tool::getVersion());

    HRESULT hres;

    // Step 1: --------------------------------------------------
    // Initialize COM. ------------------------------------------

    hres =  CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres)) {
        qDebug() << "Failed to initialize COM library. Error code = 0x"
                 << SUCCEEDED(hres) << Tool::qHexStr1(hres);
        return 1;                  // Program has failed.
    }

    // Step 2: --------------------------------------------------
    // Set general COM security levels --------------------------

    hres =  CoInitializeSecurity(
                NULL,
                -1,                          // COM authentication
                NULL,                        // Authentication services
                NULL,                        // Reserved
                RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication
                RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
                NULL,                        // Authentication info
                EOAC_NONE,                   // Additional capabilities
                NULL                         // Reserved
            );


    if (FAILED(hres)) {
        qDebug() << "Failed to initialize security. Error code = 0x"
                 << SUCCEEDED(hres) << Tool::qHexStr1(hres);
        CoUninitialize();
        return 1;
    }

    QCommandLineParser parser;
    parser.setApplicationDescription("Deepin Legalization Agent");
    parser.addHelpOption();
    parser.addVersionOption();
    QCommandLineOption debugOption(QStringList() << "m" << "mode",
                                   QCoreApplication::translate("main", "enable test/debug/service mode"),
                                   QCoreApplication::translate("main", "daemon mode"),
                                   "service");
    parser.addOption(debugOption);
    parser.process(app);

    if ("test" == parser.value(debugOption)) {
        qDebug() << "!!! test mode !!!";
        WinSystemDetector d;
        d.test();
        qDebug() << "!!! test end !!!";
        return 0;
//        return app.exec();
    }


    if ("debug" == parser.value(debugOption)) {
        qDebug() << "!!! debug mode !!!";
        LegalizationAgent agent;
        agent.startServer();
        return app.exec();
    }

    qDebug() << "start daemon service";

    ServiceWork work;
    work.start();

    QTimer::singleShot(500, []() {
        auto agent = new LegalizationAgent ;
        agent->startServer();
    });

    return app.exec();
}
