/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

var FileInfo = require("qbs.FileInfo");
var ModUtils = require("qbs.ModUtils");

function quote(value)
{
    if (value.includes(" ") || value.includes("'") || value.includes('"')) {
        return '"' + value.replace(/(["'\\])/g, "\\$1") + '"';
    }
    return value;
}

function writeEntry(product, file, key, propertyName, required, additionalValues)
{
    var value = product.Exporter.pkgconfig[propertyName];
    if (additionalValues && additionalValues.length > 0)
        value = (value || []).concat(additionalValues);
    var valueIsNotEmpty = value && (!(value instanceof Array) || value.length > 0);
    if (valueIsNotEmpty) {
        if (value instanceof Array)
            value = value.join(' ');
        file.writeLine(key + ": " + value);
    } else if (required) {
        throw "Failure creating " + FileInfo.fileName(file.filePath()) + ": The entry '" + key
                + "' is required, but property Exporter.pkgconfig."
                + propertyName + " is not set.";
    }
}

function collectAutodetectedData(topLevelProduct)
{
    var data = {
        libs: [],
        cflags: [],
        requires: [],
        requiresPrivate: []
    };
    if (!topLevelProduct.Exporter.pkgconfig.autoDetect)
        return data;

    var excludedDeps = topLevelProduct.Exporter.pkgconfig.excludedDependencies || [];
    var explicitRequires = topLevelProduct.Exporter.pkgconfig.requiresEntry || [];
    var explicitRequiresPrivate = topLevelProduct.Exporter.pkgconfig.requiresPrivateEntry || [];

    var transformFunc = topLevelProduct.Exporter.pkgconfig.transformFunction;

    // Make use of the "prefix" convenience variable if applicable.
    function quoteAndPrefixify(value)
    {
        var quotedValue = quote(value);
        var installPrefix = topLevelProduct.qbs.installPrefix || "";
        if (!topLevelProduct.Exporter.pkgconfig._usePrefix || typeof value !== "string"
                || !value.startsWith(installPrefix)
                || (value.length > installPrefix.length && value[installPrefix.length] !== '/')) {
            return quotedValue;
        }
        return quotedValue.replace(topLevelProduct.qbs.installPrefix, "${prefix}");
    }

    function transformedValue(product, moduleName, propertyName)
    {
        var originalValue = product.exports[moduleName][propertyName];
        var value = transformFunc
                ? eval("(" + transformFunc + ")(product, moduleName, propertyName, originalValue)")
                : originalValue;
        if (value instanceof Array)
            value.forEach(function(v, i, a) { a[i] = quoteAndPrefixify(v); });
        else if (value)
            value = quoteAndPrefixify(value);
        return value;
    }

    function collectLibs(productOrModule)
    {
        var libs = [];
        var libArtifacts;
        var isProduct = !productOrModule.present;
        var considerDynamicLibs = !isProduct || (productOrModule.type
                && productOrModule.type.includes("dynamiclibrary"));
        if (considerDynamicLibs) {
            libArtifacts = productOrModule.artifacts.dynamiclibrary;
        } else {
            var considerStaticLibs = !isProduct || (productOrModule.type
                    && productOrModule.type.includes("staticlibrary"));
            if (considerStaticLibs)
                libArtifacts = productOrModule.artifacts.staticlibrary;
        }
        for (var i = 0; i < (libArtifacts || []).length; ++i) {
            var libArtifact = libArtifacts[i];
            if (libArtifact.qbs.install) {
                var installDir = FileInfo.path(ModUtils.artifactInstalledFilePath(libArtifact));
                installDir = installDir.slice(libArtifact.qbs.installRoot.length);
                libs.push("-L" + quoteAndPrefixify(FileInfo.cleanPath(installDir)),
                          "-l" + quote(productOrModule.targetName));
            }
        }
        if (!productOrModule.exports.cpp)
            return libs;
        var libPaths = transformedValue(productOrModule, "cpp", "libraryPaths");
        if (libPaths)
            libs.push.apply(libs, libPaths.map(function(p) { return "-L" + p; }));
        function libNamesToLibEntries(libNames) {
            return libNames.map(function(libName) { return "-l" + libName; });
        };
        var dlls = transformedValue(productOrModule, "cpp", "dynamicLibraries");
        if (dlls)
            libs.push.apply(libs, libNamesToLibEntries(dlls));
        var staticLibs = transformedValue(productOrModule, "cpp", "staticLibraries");
        if (staticLibs)
            libs.push.apply(libs, libNamesToLibEntries(staticLibs));
        var lFlags = transformedValue(productOrModule, "cpp", "linkerFlags");
        if (lFlags)
            libs.push.apply(libs, lFlags);
        lFlags = transformedValue(productOrModule, "cpp", "driverFlags");
        if (lFlags)
            libs.push.apply(libs, lFlags);
        lFlags = transformedValue(productOrModule, "cpp", "driverLinkerFlags");
        if (lFlags)
            libs.push.apply(libs, lFlags);
        return libs;
    }

    function collectCFlags(productOrModule)
    {
        if (!productOrModule.exports.cpp)
            return [];
        var flags = [];
        var defs = transformedValue(productOrModule, "cpp", "defines");
        if (defs)
            flags.push.apply(flags, defs.map(function(d) { return "-D" + d; }));
        var incPaths = transformedValue(productOrModule, "cpp", "includePaths");
        if (incPaths)
            flags.push.apply(flags, incPaths.map(function(p) { return "-I" + p; }));
        var cflags = transformedValue(productOrModule, "cpp", "commonCompilerFlags");
        if (cflags)
            flags.push.apply(flags, cflags);
        cflags = transformedValue(productOrModule, "cpp", "driverFlags");
        if (cflags)
            flags.push.apply(flags, cflags);
        cflags = transformedValue(productOrModule, "cpp", "cxxFlags")
                || transformedValue(productOrModule, "cpp", "cFlags");
        if (cflags)
            flags.push.apply(flags, cflags);
        return flags;
    }

    function collectAutodetectedDataRecursive(productOrModule, privateContext)
    {
        if (!privateContext) {
            data.libs.push.apply(data.libs, collectLibs(productOrModule));
            data.cflags.push.apply(data.cflags, collectCFlags(productOrModule));
        }
        var exportedDeps = productOrModule.exports ? productOrModule.exports.dependencies : [];
        var exportedDepNames = [];
        var privateDeps = [];
        for (var i = 0; i < exportedDeps.length; ++i)
            exportedDepNames.push(exportedDeps[i].name);
        for (i = 0; i < (productOrModule.dependencies || []).length; ++i) {
            var dep = productOrModule.dependencies[i];
            if (exportedDepNames.includes(dep.name))
                continue;
            privateDeps.push(dep);
        }

        function gatherData(dep) {
            if (dep.name === "Exporter.pkgconfig")
                return;
            var depHasPkgConfig = dep.Exporter && dep.Exporter.pkgconfig;
            if (depHasPkgConfig) {
                var entry = FileInfo.completeBaseName(dep.Exporter.pkgconfig.fileName);
                if (excludedDeps.includes(entry))
                    return;
                if (isPrivateDep && !data.requiresPrivate.includes(entry)
                        && !explicitRequiresPrivate.includes(entry)) {
                    data.requiresPrivate.push(entry);
                }
                if (!isPrivateDep && !data.requires.includes(entry)
                        && !explicitRequires.includes(entry)) {
                    data.requires.push(entry);
                }
            } else {
                if (excludedDeps.includes(dep.name))
                    return;
                if (isPrivateDep && explicitRequiresPrivate.includes(dep.name))
                    return;
                if (!isPrivateDep && explicitRequires.includes(dep.name))
                    return;
                collectAutodetectedDataRecursive(dep, isPrivateDep);
            }
        }
        var isPrivateDep = privateContext;
        exportedDeps.forEach(gatherData);
        isPrivateDep = true;
        privateDeps.forEach(gatherData);
    }

    collectAutodetectedDataRecursive(topLevelProduct, false);
    return data;
}
