/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
 * http://ugene.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 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include <AppContextImpl.h>
#include <AppSettingsImpl.h>
#include <ConsoleLogDriver.h>
#include <DocumentFormatRegistryImpl.h>
#include <IOAdapterRegistryImpl.h>
#include <PluginSupportImpl.h>
#include <ServiceRegistryImpl.h>
#include <SettingsImpl.h>
#include <TaskSchedulerImpl.h>
#include <crash_handler/CrashHandler.h>

#include <U2Algorithm/AlignmentAlgorithmsRegistry.h>
#include <U2Algorithm/AssemblyConsensusAlgorithmRegistry.h>
#include <U2Algorithm/CDSearchTaskFactoryRegistry.h>
#include <U2Algorithm/CudaGpuRegistry.h>
#include <U2Algorithm/DnaAssemblyAlgRegistry.h>
#include <U2Algorithm/GenomeAssemblyRegistry.h>
#include <U2Algorithm/MSAConsensusAlgorithmRegistry.h>
#include <U2Algorithm/MSADistanceAlgorithmRegistry.h>
#include <U2Algorithm/MolecularSurfaceFactoryRegistry.h>

#ifdef OPENCL_SUPPORT
#    include <U2Algorithm/OpenCLGpuRegistry.h>
#endif

#include <U2Algorithm/PWMConversionAlgorithmRegistry.h>
#include <U2Algorithm/PhyTreeGeneratorRegistry.h>
#include <U2Algorithm/RepeatFinderTaskFactoryRegistry.h>
#include <U2Algorithm/SWMulAlignResultNamesTagsRegistry.h>
#include <U2Algorithm/SWResultFilterRegistry.h>
#include <U2Algorithm/SecStructPredictAlgRegistry.h>
#include <U2Algorithm/SmithWatermanTaskFactoryRegistry.h>
#include <U2Algorithm/SplicedAlignmentTaskRegistry.h>
#include <U2Algorithm/StructuralAlignmentAlgorithmRegistry.h>
#include <U2Algorithm/SubstMatrixRegistry.h>

#include <U2Core/AnnotationSettings.h>
#include <U2Core/AppFileStorage.h>
#include <U2Core/AutoAnnotationsSupport.h>
#include <U2Core/CMDLineCoreOptions.h>
#include <U2Core/CMDLineRegistry.h>
#include <U2Core/CMDLineUtils.h>
#include <U2Core/ConsoleShutdownTask.h>
#include <U2Core/DBXRefRegistry.h>
#include <U2Core/DNAAlphabetRegistryImpl.h>
#include <U2Core/DNATranslation.h>
#include <U2Core/DataBaseRegistry.h>
#include <U2Core/DataPathRegistry.h>
#include <U2Core/ExternalToolRegistry.h>
#include <U2Core/LoadRemoteDocumentTask.h>
#include <U2Core/Log.h>
#include <U2Core/ResourceTracker.h>
#include <U2Core/ScriptingToolRegistry.h>
#include <U2Core/TaskStarter.h>
#include <U2Core/Timer.h>
#include <U2Core/U2DbiRegistry.h>
#include <U2Core/U2OpStatusUtils.h>
#include <U2Core/UserApplicationsSettings.h>
#include <U2Core/Version.h>
#include <U2Core/VirtualFileSystem.h>

#include <U2Formats/DocumentFormatUtils.h>

#include <U2Lang/LocalDomain.h>
#include <U2Lang/QueryDesignerRegistry.h>
#include <U2Lang/WorkflowEnvImpl.h>

#include <U2Test/GTestFrameworkComponents.h>

#include "UgeneContextWrapper.h"

int ARGC = 0;

namespace U2 {

static void setDataSearchPaths() {
    //set search paths for data files
    QStringList dataSearchPaths;
    const char *relativeDataDir = "/data";
    const char *relativeDevDataDir = "/../../data";

    if (QDir(AppContext::getWorkingDirectoryPath() + relativeDataDir).exists()) {
        dataSearchPaths.push_back(AppContext::getWorkingDirectoryPath() + relativeDataDir);
    } else if (QDir(AppContext::getWorkingDirectoryPath() + relativeDevDataDir).exists()) {
        coreLog.info("Added path: " + AppContext::getWorkingDirectoryPath() + relativeDevDataDir);
        dataSearchPaths.push_back(AppContext::getWorkingDirectoryPath() + relativeDevDataDir);
    }

#if (defined(Q_OS_UNIX)) && defined(UGENE_DATA_DIR)
    //using folder which is set during installation process on Linux
    const QString ugene_data_dir(UGENE_DATA_DIR);
    if (QDir(ugene_data_dir).exists()) {
        dataSearchPaths.push_back(QString(UGENE_DATA_DIR));
    }
#endif

    if (dataSearchPaths.empty()) {
        dataSearchPaths.push_back("/");
    }

    QDir::setSearchPaths(PATH_PREFIX_DATA, dataSearchPaths);
    //now data files may be opened using QFile( "data:some_data_file" )
}

static void setSearchPaths() {
    setDataSearchPaths();
}

UgeneContextWrapper::UgeneContextWrapper(const QString &workingDirectoryPath)
    : app(ARGC, NULL) {
    if (!Version::checkBuildAndRuntimeVersions()) {
        return;
    }
    GTIMER(c1, t1, "initialization of UGENE Context");

    appContext = AppContextImpl::getApplicationContext();
    appContext->setWorkingDirectoryPath(workingDirectoryPath);

    QCoreApplication::addLibraryPath(workingDirectoryPath);
    const QString devPluginsPath = QDir(workingDirectoryPath + "/../../installer/windows")
                                       .absolutePath();
    QCoreApplication::addLibraryPath(devPluginsPath);

    setSearchPaths();
    cmdLineRegistry = new CMDLineRegistry(QStringList());
    appContext->setCMDLineRegistry(cmdLineRegistry);

    globalSettings = new SettingsImpl(QSettings::SystemScope);
    appContext->setGlobalSettings(globalSettings);

    settings = new SettingsImpl(QSettings::UserScope);
    appContext->setSettings(settings);

    appSettings = new AppSettingsImpl();
    appContext->setAppSettings(appSettings);

    userAppSettings = AppContext::getAppSettings()->getUserAppsSettings();

    ConsoleLogDriver logs;
    coreLog.details(AppContextImpl::tr("UGENE script environment initialization started"));
    coreLog.trace(QString("UGENE script environment run at dir %1").arg(workingDirectoryPath));

    resTrack = new ResourceTracker();
    appContext->setResourceTracker(resTrack);

    ts = new TaskSchedulerImpl(appSettings->getAppResourcePool());
    appContext->setTaskScheduler(ts);

    asr = new AnnotationSettingsRegistry(DocumentFormatUtils::predefinedSettings());
    appContext->setAnnotationSettingsRegistry(asr);

    tf = new TestFramework();
    appContext->setTestFramework(tf);

    rfr = new RepeatFinderTaskFactoryRegistry();
    appContext->setRepeatFinderTaskFactoryRegistry(rfr);

    str = new ScriptingToolRegistry();
    appContext->setScriptingToolRegistry(str);

    qpr = new QDActorPrototypeRegistry();
    appContext->setQDActorFactoryRegistry(qpr);

    phyreg = new PhyTreeGeneratorRegistry();
    appContext->setPhyTreeGeneratorRegistry(phyreg);

    etr = new ExternalToolRegistry();
    appContext->setExternalToolRegistry(etr);

    dbiRegistry = new U2DbiRegistry();
    appContext->setDbiRegistry(dbiRegistry);

    dfr = new DocumentFormatRegistryImpl();
    appContext->setDocumentFormatRegistry(dfr);

    psp = new PluginSupportImpl();
    appContext->setPluginSupport(psp);

    sreg = new ServiceRegistryImpl();
    appContext->setServiceRegistry(sreg);

    io = new IOAdapterRegistryImpl();
    appContext->setIOAdapterRegistry(io);

    dtr = new DNATranslationRegistry();
    appContext->setDNATranslationRegistry(dtr);

    dal = new DNAAlphabetRegistryImpl(dtr);
    appContext->setDNAAlphabetRegistry(dal);

    dbxrr = new DBXRefRegistry();
    appContext->setDBXRefRegistry(dbxrr);

    msaConsReg = new MSAConsensusAlgorithmRegistry();
    appContext->setMSAConsensusAlgorithmRegistry(msaConsReg);

    msaDistReg = new MSADistanceAlgorithmRegistry();
    appContext->setMSADistanceAlgorithmRegistry(msaDistReg);

    assemblyConsReg = new AssemblyConsensusAlgorithmRegistry();
    appContext->setAssemblyConsensusAlgorithmRegistry(assemblyConsReg);

    pwmConvReg = new PWMConversionAlgorithmRegistry();
    appContext->setPWMConversionAlgorithmRegistry(pwmConvReg);

    smr = new SubstMatrixRegistry();
    appContext->setSubstMatrixRegistry(smr);

    swar = new SmithWatermanTaskFactoryRegistry();
    appContext->setSmithWatermanTaskFactoryRegistry(swar);

    msfr = new MolecularSurfaceFactoryRegistry();
    appContext->setMolecularSurfaceFactoryRegistry(msfr);

    swrfr = new SWResultFilterRegistry();
    appContext->setSWResultFilterRegistry(swrfr);

    sspar = new SecStructPredictAlgRegistry();
    appContext->setSecStructPedictAlgRegistry(sspar);

    cgr = new CudaGpuRegistry();
    appContext->setCudaGpuRegistry(cgr);

    alignmentAlgorithmRegistry = new AlignmentAlgorithmsRegistry();
    appContext->setAlignmentAlgorithmsRegistry(alignmentAlgorithmRegistry);

#ifdef OPENCL_SUPPORT
    oclgr = new OpenCLGpuRegistry();
    appContext->setOpenCLGpuRegistry(oclgr);
#endif

    rdc = new RecentlyDownloadedCache();
    appContext->setRecentlyDownloadedCache(rdc);

    vfsReg = new VirtualFileSystemRegistry();
    appContext->setVirtualFileSystemRegistry(vfsReg);

    Workflow::WorkflowEnv::init(new Workflow::WorkflowEnvImpl());
    Workflow::WorkflowEnv::getDomainRegistry()->registerEntry(
        new LocalWorkflow::LocalDomainFactory());

    assemblyReg = new DnaAssemblyAlgRegistry();
    appContext->setDnaAssemblyAlgRegistry(assemblyReg);

    genomeAssemblyReg = new GenomeAssemblyAlgRegistry();
    appContext->setGenomeAssemblyAlgRegistry(genomeAssemblyReg);

    dbr = new DataBaseRegistry();
    appContext->setDataBaseRegistry(dbr);

    cdsfr = new CDSearchFactoryRegistry();
    appContext->setCDSearchFactoryRegistry(cdsfr);

    saar = new StructuralAlignmentAlgorithmRegistry();
    appContext->setStructuralAlignmentAlgorithmRegistry(saar);

    splicedAlignmentTaskRegistry = new SplicedAlignmentTaskRegistry();
    appContext->setSplicedAlignmentTaskRegistry(splicedAlignmentTaskRegistry);

    workflowScriptRegistry = new WorkflowScriptRegistry();
    appContext->setWorkflowScriptRegistry(workflowScriptRegistry);

    appFileStorage = new AppFileStorage();
    U2OpStatusImpl os;
    appFileStorage->init(os);
    if (os.hasError()) {
        coreLog.error(os.getError());
        delete appFileStorage;
    } else {
        appContext->setAppFileStorage(appFileStorage);
    }

    dpr = new U2DataPathRegistry();
    appContext->setDataPathRegistry(dpr);

    GCOUNTER(cvar, "U2Script is ready");

    t1.stop();
    QObject::connect(psp, SIGNAL(si_allStartUpPluginsLoaded()), &app, SLOT(quit()));
    app.exec();
}

UgeneContextWrapper::~UgeneContextWrapper() {
    Workflow::WorkflowEnv::shutdown();

    delete dpr;
    appContext->setDataPathRegistry(NULL);

    delete genomeAssemblyReg;
    appContext->setGenomeAssemblyAlgRegistry(NULL);

    delete vfsReg;
    appContext->setVirtualFileSystemRegistry(NULL);

    delete rdc;
    appContext->setRecentlyDownloadedCache(NULL);

    delete swrfr;
    appContext->setSWResultFilterRegistry(NULL);

    delete swar;
    appContext->setSmithWatermanTaskFactoryRegistry(NULL);

    delete msfr;
    appContext->setMolecularSurfaceFactoryRegistry(NULL);

    delete smr;
    appContext->setSubstMatrixRegistry(NULL);

    delete sreg;
    appContext->setServiceRegistry(NULL);

    delete psp;
    appContext->setPluginSupport(NULL);

    delete dal;
    appContext->setDNAAlphabetRegistry(NULL);

    delete dbxrr;
    appContext->setDBXRefRegistry(NULL);

    delete dtr;
    appContext->setDNATranslationRegistry(NULL);

    delete io;
    appContext->setIOAdapterRegistry(NULL);

    delete dfr;
    appContext->setDocumentFormatRegistry(NULL);

    delete dbiRegistry;
    appContext->setDbiRegistry(NULL);

    delete ts;
    appContext->setTaskScheduler(NULL);

    delete asr;
    appContext->setAnnotationSettingsRegistry(NULL);

    delete resTrack;
    appContext->setResourceTracker(NULL);

    delete cgr;
    appContext->setCudaGpuRegistry(NULL);

#ifdef OPENCL_SUPPORT
    delete oclgr;
    appContext->setOpenCLGpuRegistry(NULL);
#endif

    delete appSettings;
    appContext->setAppSettings(NULL);

    delete settings;
    appContext->setSettings(NULL);

    delete globalSettings;
    appContext->setGlobalSettings(NULL);

    delete sspar;
    appContext->setSecStructPedictAlgRegistry(NULL);

    appContext->setExternalToolRegistry(NULL);
    delete etr;

    delete str;
    appContext->setScriptingToolRegistry(NULL);

    delete msaConsReg;
    appContext->setMSAConsensusAlgorithmRegistry(NULL);

    delete msaDistReg;
    appContext->setMSADistanceAlgorithmRegistry(NULL);

    delete assemblyConsReg;
    appContext->setAssemblyConsensusAlgorithmRegistry(NULL);

    appContext->setPWMConversionAlgorithmRegistry(NULL);
    delete pwmConvReg;

    delete dbr;
    appContext->setDataBaseRegistry(NULL);

    appContext->setQDActorFactoryRegistry(NULL);
    delete qpr;

    delete cdsfr;
    appContext->setCDSearchFactoryRegistry(NULL);

    appContext->setStructuralAlignmentAlgorithmRegistry(NULL);
    delete saar;

    delete splicedAlignmentTaskRegistry;
    appContext->setSplicedAlignmentTaskRegistry(NULL);

    appContext->setAlignmentAlgorithmsRegistry(NULL);
    delete alignmentAlgorithmRegistry;
}

int UgeneContextWrapper::processTask(Task *task) {
    coreLog.info(QObject::tr("%1-bit version of UGENE started")
                     .arg(Version::appArchitecture));
    ts->registerTopLevelTask(task);
    QObject::connect(ts, SIGNAL(si_topLevelTaskUnregistered(Task *)), &app, SLOT(quit()));
    return app.exec();
}

// TODO: fix this dummy check
bool UgeneContextWrapper::isAppContextInitialized() {
    return AppContextImpl::getApplicationContext()->getWorkingDirectoryPath().isEmpty();
}

}    // namespace U2
