/***************************************************************************
                          main.cpp  -  description
                             -------------------
    begin                : Mon Okt  1 19:59:45 CEST 2001
    copyright            : (C) 2001-2004 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <dclib/dcos.h>
#include <config.h>

#include <stdlib.h>
#ifndef WIN32
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#endif

#include <QApplication>
#include <QtGui>
#include <QFont>
#include <QString>
#include <QMessageBox>
#include <QTextCodec>
#include <QTranslator>
#include <QLabel>
#include <QStyleFactory>
#include <QFile>
#include <QDir>

#include "dcconfig.h"
#include "dcsplash.h"
#include "dcgui.h"
#include "dcpluginmanager.h"
#include "dcconnectionmanager.h"
#include "dctransferview.h"
#include "dcdebug.h"
#include "dciconloader.h"
#include "dcsigterm.h"
#include "dchubsearch.h"
#include "dchublinklistener.h"

#include <dclib/dclib.h>
#include <dclib/core/casyncdns.h>
#include <dclib/core/cmanager.h>
#include <dclib/cquerymanager.h>
#include <dclib/cfilemanager.h>
#include <dclib/clistenmanager.h>
#include <dclib/csearchmanager.h>

#ifndef WIN32

/** install signal handler */
void term_install_handlers(void) {
    // ignore pipe signal
    // signal(SIGPIPE, SIG_IGN);
    if (can_we_handle_signal(SIGPIPE)) {
        struct sigaction new_action;
        new_action.sa_handler = SIG_IGN;
        sigemptyset(&new_action.sa_mask);
        new_action.sa_flags = 0;
        if (sigaction(SIGPIPE, &new_action, NULL) == -1) {
            printf("Error ignoring SIGPIPE.\n");
        }
    }

    // install SIGTERM signal handler
    // signal(SIGTERM, sigterm_handler);

    // install handlers for SIGTERM, SIGHUP, SIGINT and SIGQUIT
    bool term = false, hup = false, sigint = false, quit = false;

    struct sigaction new_action;
    new_action.sa_handler = DCSigTerm::signal_handler;
    sigemptyset(&new_action.sa_mask);
    new_action.sa_flags = 0;

    term = can_we_handle_signal(SIGTERM);
    if (term) {
        sigaddset(&new_action.sa_mask, SIGTERM);
    }

    hup = can_we_handle_signal(SIGHUP);
    if (hup) {
        sigaddset(&new_action.sa_mask, SIGHUP);
    }

    sigint = can_we_handle_signal(SIGINT);
    if (sigint) {
        sigaddset(&new_action.sa_mask, SIGINT);
    }

    quit = can_we_handle_signal(SIGQUIT);
    if (quit) {
        sigaddset(&new_action.sa_mask, SIGQUIT);
    }

    if (term) {
        if (sigaction(SIGTERM, &new_action, NULL) == -1) {
            printf("Error installing SIGTERM handler\n");
        }
    }

    if (hup) {
        if (sigaction(SIGHUP, &new_action, NULL) == -1) {
            printf("Error installing SIGHUP handler\n");
        }
    }

    if (sigint) {
        if (sigaction(SIGINT, &new_action, NULL) == -1) {
            printf("Error installing SIGINT handler\n");
        }
    }

    if (quit) {
        if (sigaction(SIGQUIT, &new_action, NULL) == -1) {
            printf("Error installing SIGQUIT handler\n");
        }
    }
}
#endif

#ifndef WIN32
int fd_lock;

int get_lock() {
    struct flock fl;

    fl.l_type = F_WRLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start = 0;
    fl.l_len = 0;

    QString lock_file = QDir::homePath() + "/.eiskaltdc/eiskaltdc.lock";

    if ((fd_lock = open(lock_file.toAscii().data(), O_WRONLY | O_CREAT, 0666)) < 0) {
        perror("open");
        exit(1);
    }

    if (fcntl(fd_lock, F_SETLK, &fl) < 0) {
        perror("fcntl flock");
        return 0;
    }

    return 1;
}

#endif

/** */
int main(int argc, char *argv[]) {
    /* Nothing in QT works without a QApplication!!! */
    new QApplication(argc, argv);

    int i;
    int ret;
    CString configpath;
    CString debugopt;
    CString magnet;
    CString dchublink;
    bool bcrash = false;
    QTranslator tor(0);
    DCSplash * pSplash = 0;

    // translating Qt UI Interface
    QTranslator qtTranslator;
    qtTranslator.load("qt_" + QLocale::system().name(),
            QLibraryInfo::location(QLibraryInfo::TranslationsPath));
    qApp->installTranslator(&qtTranslator);
    // end of translating

    // startup parameter
    bool bThemeSupport = true;
    bool bSplash = true;
    bool bTray = false;
    bool bOverrideTray = false;
    bool bEnableTray = true;

#ifdef WIN32
#ifdef _DEBUG
    //_CrtSetDbgFlag ( _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF | _CRTDBG_CHECK_ALWAYS_DF );
#endif
#endif
    bool bBacktrace = true;

#ifndef WIN32
    DCDebug::arg_0 = qApp->applicationFilePath().toLocal8Bit().constData();
    DCDebug::startup_dir = qApp->applicationDirPath().toLocal8Bit().constData();
#endif

    printf("EiskaltDC: '%s' ", PACKAGE_VERSION);
    printf("using dclib: '%s'\n", dclibVersion());

    // check for Qt (TM) version
    printf("Checking Qt® version... ");
    printf("compiled for '%s' using '%s'\n", QT_VERSION_STR, qVersion());

    // makes QString::toAscii().constData() and fromAscii() return the correct local encoding not just ISO-8859-1
    QTextCodec::setCodecForCStrings(QTextCodec::codecForLocale());
    // QT4 just says "System" which is somewhat unhelpful
    //printf("QT thinks your system encoding is '%s'\n",QTextCodec::codecForLocale()->name().data());
    //printf("If this is incorrect, you may need to adjust your LANG environment variable which is currently '%s'\n",CString(getenv("LANG")).Data());

    // parameter stuff ...
    for (i = 1; i < argc; i++) {
        if (QString(argv[i]) == "--help") {
            QString s;

            s = "\nHelp:\n\n";
            s += "--help               - show this help\n";
            s += "--disable-splash     - disable splash screen\n";
            s += "--disable-theme      - disable theme support\n";
            s += "--enable-tray        - enable tray icon, overriding saved setting\n";
            s += "--disable-tray       - disable tray icon, overriding saved setting\n";
            s += "--check-theme        - check for broken theme support\n";
            s += "--disable-backtrace  - no backtrace\n";
            s += "-c <configpath>      - set another config path\n";
            s += "-v                   - verbose \n";
            s += "-vv                  - more verbose \n";
            s += "--trayicon           - start in tray icon mode\n";
            s += "--socketlog <param>  - log socket to dcsocket.log file (param = [none,send,recv,both])\n";
#ifndef WIN32
            s += "-m <magnet:?xt=link> - auto search file by magnet-link\n";
            s += "<magnet:?xt=link>    - auto search file by magnet-link\n";
            s += "<dchub://host:port>  - connect with host:port\n";
#endif
            s += "\n";

            printf("%s", s.toAscii().constData());
            delete qApp;
            return 0;
        } else if (QString(argv[i]) == "--disable-splash")
            bSplash = false;
        else if (QString(argv[i]) == "--disable-theme")
            bThemeSupport = false;
        else if (QString(argv[i]) == "--enable-tray") {
            printf("The system tray icon is now controlled within valknut.\nUsing --enable-tray overrides the saved setting.\n");
            bOverrideTray = true;
            bEnableTray = true;
        } else if (QString(argv[i]) == "--disable-tray") {
            printf("The system tray icon is now controlled within valknut.\nUsing --disable-tray overrides the saved setting.\n");
            bOverrideTray = true;
            bEnableTray = false;
        } else if (QString(argv[i]) == "--check-theme") {
            // check style/theme Qt (TM) support
            printf("Check theme support ... ");
            QStringList list = QStyleFactory::keys();
            printf("ok %d styles found\n", list.count());
            delete qApp;
            return 0;
        } else if (QString(argv[i]) == "--disable-backtrace") {
            bBacktrace = false;
        } else if (QString(argv[i]) == "-c") {
            i++;
            if (i < argc) {
                configpath = argv[i];
            } else {
                printf("Wrong parameter option -c\n");
                i--;
            }
        }
#ifndef WIN32
        else if (QString(argv[i]) == "-m") {
            i++;
            if (i < argc) {
                magnet = argv[i];
            } else {
                printf("Wrong parameter option -m\n");
                i--;
            }
        }
#endif
        else if (QString(argv[i]) == "--trayicon") {
            bTray = true;
        } else if (QString(argv[i]) == "-C") {
            i++;
            if (i < argc) {
                // crash handling
                printf("handle crash\n");
                debugopt = argv[i];
                bcrash = true;
            } else {
                printf("Wrong parameter option -C\n");
                i--;
            }
        } else if (QString(argv[i]) == "-v") {
            setdclibVerbose(1);
        } else if (QString(argv[i]) == "-vv") {
            setdclibVerbose(2);
        } else if (QString(argv[i]) == "--socketlog") {
            i++;
            if (i < argc) {
                if (QString(argv[i]) == "none")
                    CSocket::m_eSocketLog = eslNONE;
                else if (QString(argv[i]) == "send")
                    CSocket::m_eSocketLog = eslSEND;
                else if (QString(argv[i]) == "recv")
                    CSocket::m_eSocketLog = eslRECV;
                else if (QString(argv[i]) == "both")
                    CSocket::m_eSocketLog = eslBOTH;
                else {
                    printf("Wrong parameter option --socketlog!\n");
                    i--;
                }
            } else {
                printf("Wrong parameter option --socketlog\n");
                i--;
            }
        } else {
            QString some_arg(argv[i]);

            some_arg = some_arg.trimmed();

            if (some_arg.indexOf("dchub://") == 0) {
                dchublink = argv[i];
            } else if (some_arg.indexOf("magnet:?xt=") == 0) {
                magnet = argv[i];
            } else
                printf("Ignore wrong Parameter: '%s'\n", argv[i]);
        }
    }

    QString homepath = QDir::homePath();
    QString eis_path = homepath + "/.eiskaltdc";
    QDir eis_dir(eis_path);

    if (!eis_dir.exists()) {
        if (mkdir(eis_path.toAscii().data(), 0744) == -1) {
            perror("mkdir");

            exit(1);
        }
    }

#ifndef WIN32

    if (magnet.Length() > 0) {
        QFile *file = new QFile(QString(eis_path + "/eiskaltdc.magnet"));
        file->open(QIODevice::WriteOnly);
        char *block = magnet.Data();
        printf("Found magnet: %s\n", block);
        file->write(block, strlen(block));
        file->close();
        delete file;
    }

    if (dchublink.Length() > 0) {
        QFile *file = new QFile(QString(eis_path + "/eiskaltdc.hublink"));
        file->open(QIODevice::WriteOnly);
        char *block = dchublink.Data();
        printf("Found hublink: %s\n", block);
        file->write(block, strlen(block));
        file->close();
        delete file;
    }

    if (!get_lock() && !bcrash) {
        printf("Process already running! Terminating.\n");
        exit(0);
    }

#endif

    // fix wrong parameter
    if (bTray && (bOverrideTray && (bEnableTray == false))) {
        bTray = false;
    }

    dclibInitDepLibs(argv[0]);

    ret = 0;

    CManager::SetInstance(new CManager);
    CAsyncDns::SetInstance(new CAsyncDns);

    if (bcrash) {
        printf("CRASH: %s %s\n", configpath.Data(), debugopt.Data());
        DCDebug * dbg = new DCDebug();
        if (dbg->Init(configpath.Data(), debugopt.Data())) {
            dbg->exec();
        }
        delete dbg;
        printf("exit\n");
        delete qApp;
        _exit(0);
    }

#ifndef WIN32
    if (bBacktrace)
        crash_install_handlers();
#endif
    configpath = eis_path.toAscii().data();

    DCIconLoader::SetInstance(new DCIconLoader());

    DCConfig::SetInstance(new DCConfig(configpath));

    CConfig::SetInstance(DCConfig::Instance());

    if (DCConfig::Instance()->LoadDCLib() != 0) {
        ret = -1;
    }

    // set theme support
    DCConfig::Instance()->SetThemeSupport(bThemeSupport);

    if (DCConfig::Instance()->LoadDCGui() != 0) {
        ret = -1;
    }

    // needs to be done after config loading for icon themes
    if (DCIconLoader::Instance()->Load()) {
        QMessageBox::critical(0, "EiskaltDC",
                QString("Can't load all Icons. Please set correct Datapath and restart the Application."));
    }

    if (bOverrideTray) {
        DCConfig::Instance()->SetEnableTray(bEnableTray);
    }

    if (bSplash) {
        pSplash = new DCSplash(0);
        pSplash->show();
    }

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Load Hub-Profiles ...");
    qApp->processEvents();

    DCConfig::Instance()->LoadHubProfile();

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Load Hub-List ...");
    qApp->processEvents();

    DCConfig::Instance()->LoadDCHub();

    // create the filemanager
    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Init Filemanager ...");
    qApp->processEvents(); // was 1

    CFileManager::SetInstance(new CFileManager());

    if (DCConfig::Instance()->GetFilelistNamingSchemeVersion() < 4) {
        if (pSplash)
            pSplash->TextLabel_STATUS->setText("Renaming stored filelists ...");
        qApp->processEvents();

        DCConfig::Instance()->RenameStoredFilelists();
    }

    // create the querymanager
    CQueryManager::SetInstance(new CQueryManager());
    // create listenmanager
    CListenManager::SetInstance(new CListenManager());

    if (dclibSupportsSSL()) {
        CCryptoListenManager::SetInstance(new CCryptoListenManager());
    }

    // Create search manager which is no longer part of the search window
    // in preparation for more than one search window.
    // Strangely it was being created after all the other
    // dclib managers but being deleted after CFileManager
    // CQueryManager and CAsynDns were deleted.
    CSearchManager::SetInstance(new CSearchManager());

    // set application font
    if (DCConfig::Instance()->GetUseCustomFont()) {
        QFont appFont;
        if (appFont.fromString(DCConfig::Instance()->GetAppFont()) == false) {
            // In case the xml file is messed up
            appFont.fromString(DEFAULT_APP_FONT);
        }
        qApp->setFont(appFont);
    }

    // load translation files
    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Install translator ...");
    qApp->processEvents();

    if (!(DCConfig::Instance()->GetLanguageFile().isEmpty())) {
        if (tor.load(DCConfig::Instance()->GetLanguageFile()))
            qApp->installTranslator(&tor);
    } else {

        QString tr_file = QDir::toNativeSeparators(DCConfig::Instance()->GetValknutDataPath());
        QString lc_prefix = QLocale::system().name();

        lc_prefix = lc_prefix.left(lc_prefix.indexOf("_"));

        tr_file += "/translation/eiskaltdc." + lc_prefix + ".qm";

        if (QFile::exists(tr_file)) {
            if (tor.load(tr_file)) {
                qApp->installTranslator(&tor);

                DCConfig::Instance()->SetLanguageFile(tr_file);
            }
        }
    }

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Start ...");
    qApp->processEvents();

    DCGuiApp::SetInstance(new DCGuiApp());

#ifndef WIN32
    DCSigTerm * sighandler = new DCSigTerm(DCGuiApp::Instance());
    term_install_handlers();
#endif

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Load plugins ...");
    qApp->processEvents();

    DCPluginManager::SetInstance(new DCPluginManager());
    CPluginManager::SetInstance(DCPluginManager::Instance());
    // load dclib plugins
    DCPluginManager::Instance()->Load();
    // load valknut plugins
    /* 1. There are no valknut plugins.
     * 2. /usr/share/valknut is an architecture independent folder
     *    so is the wrong place to look for plugins
    if ( !(DCConfig::Instance()->GetValknutDataPath().isEmpty()) )
            DCPluginManager::Instance()->Load( (DCConfig::Instance()->GetValknutDataPath()+DIRSEPARATOR+"plugin").toAscii().constData());
     */

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Init plugins ...");
    qApp->processEvents();

    DCPluginManager::Instance()->InitPlugins();

    if (pSplash)
        pSplash->TextLabel_STATUS->setText("Start ...");
    qApp->processEvents();

    // hide splash
    if (pSplash)
        pSplash->hide();
    qApp->processEvents(); // was 10
    if (pSplash)
        delete pSplash;

    if (ret != 0) {
        // open options dialog ...
        DCGuiApp::Instance()->ShowOptionsDialog();
    }

    qApp->processEvents(); // was 10

    // load download manager queue
    DCTransferView::Instance()->DLM_LoadQueue();
    qApp->processEvents(); // was 10

    if (bTray && DCConfig::Instance()->GetEnableTray()) {
        DCGuiApp::Instance()->Dock();
    } else {
        StringMap * map;
        if (DCConfig::Instance()->GetMap("MAINVIEW", map) && ((*map)["MAXIMIZED"].toInt() == 1) && !DCConfig::Instance()->GetInTray()) {
            DCGuiApp::Instance()->showMaximized();
        } else if (!DCConfig::Instance()->GetInTray()) {
            DCGuiApp::Instance()->show();
        } else {
            DCGuiApp::Instance()->hide();
        }
    }

    qApp->processEvents(); // was 10

    DCGuiApp::Instance()->initWindows();
    qApp->processEvents(); // was 10

    // connect to hubs with the auto connect flag
    DCConnectionManager::Instance()->AutoConnect();
    qApp->processEvents();
#ifndef WIN32

    DCWaitMagnet::SetInstance(new DCWaitMagnet());

    DCHubLinkListener::SetInstance(new DCHubLinkListener());

#endif
    ret = qApp->exec();

    printf("Exiting...\n");

    DCPluginManager::Instance()->DeInitPlugins();

    delete DCPluginManager::Instance();

    if (CFileManager::Instance()) {
        delete CFileManager::Instance();
    }

    if (CQueryManager::Instance()) {
        delete CQueryManager::Instance();
    }

    if (CAsyncDns::Instance()) {
        delete CAsyncDns::Instance();
    }

#ifndef WIN32
    delete sighandler;

    if (DCWaitMagnet::Instance())
        delete DCWaitMagnet::Instance();

    if (DCHubLinkListener::Instance())
        delete DCHubLinkListener::Instance();
#endif

    delete DCGuiApp::Instance();

    // must be after dcgui deleted
    if (CSearchManager::Instance()) {
        delete CSearchManager::Instance();
    }

    // stop listen manager before deleting DCConfig::Instance()
    // avoids possible segfault on exit
    if (CListenManager::Instance()) {
        delete CListenManager::Instance();
    }

    if (CCryptoListenManager::Instance()) {
        delete CCryptoListenManager::Instance();
    }

    if (DCConfig::Instance()) {
        // save traffic
        DCConfig::Instance()->SaveDCLib();

        delete DCConfig::Instance();
    }

    if (CManager::Instance()) {
        delete CManager::Instance();
    }

    if (DCIconLoader::Instance())
        delete DCIconLoader::Instance();

    dclibDeInitDepLibs();

    delete qApp;

    printf("application exit ok %d\n", ret);

    //	if(bBacktrace)
    //		signal(SIGSEGV, 0);

#ifndef WIN32
    //signal(SIGTERM, 0);
#endif

#ifdef WIN32
#ifdef _DEBUG
    _CrtDumpMemoryLeaks();
#endif
#endif

    return ret;
}
