#!/usr/bin/env node

/*
 *
 * Copyright 2014 Canonical Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/
var assert = require('assert');
var colors = require('colors');
var fs = require('fs');
var os = require('os');
var Q = require('q');
var path = require('path');
var shell = require('shelljs');

var ConfigParser = require('./config_parser');
var Constants = require('./constants');
var Utils = require('./utils');
var Manifest = require('./manifest');
var logger = require('./logger');

var PLATFORMS = Constants.PLATFORM_TYPES;

module.exports.build = function(rootDir, target, nobuild, architecture, framework, debug) {
    var ubuntuDir = path.join(rootDir, 'platforms', 'ubuntu');
    var campoDir = path.join(ubuntuDir, 'build');

    if (!architecture)
        architecture = Constants.DEFAULT_ARCH;
    if (!framework)
        framework = Constants.DEFAULT_FRAMEWORK;
    assert.ok(fs.existsSync(ubuntuDir));
    assert.ok(fs.existsSync(campoDir));

    if (target === PLATFORMS.PHONE)
        return buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug);
    if (target === PLATFORMS.DESKTOP)
        return buildNative(campoDir, ubuntuDir, nobuild, debug);
    if (target === PLATFORMS.ALL) {
        return buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug).then(function () {
            return buildNative(campoDir, ubuntuDir, nobuild);
        });
    }
};

function buildClickPackage(campoDir, ubuntuDir, nobuild, architecture, framework, debug) {
    logger.info('Building Phone Application...');

    assert.ok(architecture && architecture.match(/^[a-z0-9_]+$/));

    Manifest.generate(path.join(ubuntuDir, 'config.xml'), ubuntuDir);

    var archDir = path.join(ubuntuDir, framework, architecture);
    var prefixDir = path.join(archDir, 'prefix');

    if (!fs.existsSync(archDir))
        shell.mkdir('-p', archDir);

    if (nobuild && fs.existsSync(path.join(prefixDir, 'cordova-ubuntu'))) {
        return Q();
    }

    return checkChrootEnv(ubuntuDir, architecture, framework).then(function() {
        shell.rm('-rf', path.join(archDir, 'build'));

        shell.rm('-rf', prefixDir);
        shell.mkdir(path.join(archDir, 'build'));
        shell.mkdir(prefixDir);

        Utils.pushd(path.join(archDir, 'build'));

        var buildType = '"Debug"';
        if (!debug)
            buildType = '"Release"';

        var cmakeCmd = 'click chroot -a ' + architecture + ' -f ' + framework + ' run cmake ' + campoDir
            + ' -DCMAKE_INSTALL_PREFIX="' + prefixDir + '"' + ' -DCMAKE_BUILD_TYPE=' + buildType +' -DUBUNTU_TOUCH="1"';

        var deps = additionalBuildDependencies(ubuntuDir).join(' ').replace(/ARCH/g, architecture);
        if (deps.length)
            cmakeCmd += ' -DADDITIONAL_DEPENDECIES="' + deps + '"';
        return Utils.execAsync(cmakeCmd);
    }).then(function () {
        if (architecture != "i386" && framework != "ubuntu-sdk-15.04")
            Utils.execSync('find . -name AutomocInfo.cmake | xargs sed -i \'s;AM_QT_MOC_EXECUTABLE .*;AM_QT_MOC_EXECUTABLE "/usr/lib/\'$(dpkg-architecture -qDEB_BUILD_MULTIARCH)\'/qt5/bin/moc");\'');
        return Utils.execAsync('click chroot -a ' + architecture + ' -f ' + framework + ' run make -j ' + cpuCount());
    }).then(function () {
        return Utils.execAsync('click chroot -a ' + architecture + ' -f ' + framework + ' run make install');
    }).then(function () {
        Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(prefixDir, 'www'));
        Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(prefixDir, 'qml'));
        Utils.cp(path.join(ubuntuDir, 'cordova.desktop'), prefixDir);
        Utils.cp(path.join(ubuntuDir, 'config.xml'), prefixDir);

        var content = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'manifest.json'), {encoding: "utf8"}));
        content.architecture = architecture;
        content.framework = framework;
        fs.writeFileSync(path.join(prefixDir, 'manifest.json'), JSON.stringify(content));

        content = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'apparmor.json'), {encoding: "utf8"}));
        if (framework === "ubuntu-sdk-14.10") {
            content.policy_version = 1.2;
        } else {
            content.policy_version = 1.3;
        }
        content.policy_groups.push('webview');
        fs.writeFileSync(path.join(prefixDir, 'apparmor.json'), JSON.stringify(content));

        Utils.pushd(prefixDir);

        return Utils.execAsync('click build .');
    }).then(function () {
        checkClickPackage(prefixDir);
        Utils.popd();

        Utils.popd();
    });
}

function buildNative(campoDir, ubuntuDir, nobuild, debug) {
    logger.info('Building...');

    var nativeDir = path.join(ubuntuDir, 'native');
    var prefixDir = path.join(nativeDir, 'prefix');

    if (nobuild && fs.existsSync(path.join(prefixDir, 'cordova-ubuntu'))) {
        return Q();
    }

    return checkEnv(ubuntuDir).then(function() {
	Manifest.generate(path.join(ubuntuDir, 'config.xml'), ubuntuDir);

	shell.rm('-rf', prefixDir);

	shell.mkdir('-p', path.join(nativeDir, 'build'));
	shell.mkdir(prefixDir);

	Utils.pushd(path.join(nativeDir, 'build'));

	var buildType = '"Debug"';
	if (!debug)
            buildType = '"Release"';

	var cmakeCmd = 'cmake ' + campoDir + ' -DCMAKE_INSTALL_PREFIX="' + prefixDir + '"'
        + ' -DCMAKE_BUILD_TYPE=' + buildType;

	var deps = additionalBuildDependencies(ubuntuDir).join(' ').replace(/ARCH/g, '');
	if (deps.length)
            cmakeCmd += ' -DADDITIONAL_DEPENDECIES="' + deps + '"';

	var debDir;
	return Utils.execAsync(cmakeCmd).then(function () {
            return Utils.execAsync('make -j ' + cpuCount() + '; make install');
	}).then(function () {
            Utils.cp(path.join(ubuntuDir, 'config.xml'), prefixDir);
            Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(prefixDir, 'www'));
            Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(prefixDir, 'qml'));
	    
            Utils.popd();
	    
            var manifest = JSON.parse(fs.readFileSync(path.join(ubuntuDir, 'manifest.json'), {encoding: "utf8"}));
	    
            assert(manifest.name.length);
            assert(manifest.name.indexOf(' ') == -1);
	    
            debDir = path.join(nativeDir, manifest.name);
	    
            shell.rm('-rf', debDir);
	    
            shell.mkdir('-p', path.join(debDir, 'debian'));
            Utils.cp(path.join(campoDir, '*'), debDir);
	    
            Utils.cp(path.join(ubuntuDir, 'config.xml'), path.join(debDir, 'debian'));
            Utils.cp(path.join(ubuntuDir, 'www', '*'), path.join(debDir, 'www'));
            Utils.cp(path.join(ubuntuDir, 'qml', '*'), path.join(debDir, 'qml'));
	    
            var destDir = path.join('/opt', manifest.name);
	    
            var icon = fs.readFileSync(path.join(ubuntuDir, 'cordova.desktop'), {encoding: "utf8"}).match(/^Icon=(.+)$/m);
            icon = icon ? '/opt/' + manifest.name + '/' + icon[1] : 'qmlscene';
	    
            var maintainerEmail = manifest.maintainer.match('<.+>$');
            maintainerEmail = maintainerEmail?maintainerEmail[0]:'';
            var maintainerName = manifest.maintainer.replace(maintainerEmail, '').trim();
            maintainerEmail = maintainerEmail.replace('<', '').replace('>', '');
	    
            var props = { PACKAGE_NAME: manifest.name,
			  PACKAGE_TITLE: manifest.title,
			  PACKAGE_VERSION: manifest.version,
			  MAINTAINER_NAME: maintainerName,
			  MAINTAINER_EMAIL: maintainerEmail,
			  PACKAGE_DESCRIPTION: manifest.description,
			  PACKAGE_ICON: icon };
            var templateDir = path.join(campoDir, 'bin', 'templates', 'project', 'misc');
            var templates = [{source: path.join(templateDir, 'changelog'), dest: path.join(debDir, 'debian', 'changelog')},
                             {source: path.join(templateDir, 'compat'), dest: path.join(debDir, 'debian', 'compat')},
                             {source: path.join(templateDir, 'control'), dest: path.join(debDir, 'debian', 'control')},
                             {source: path.join(templateDir, 'rules'), dest: path.join(debDir, 'debian', 'rules')},
                             {source: path.join(templateDir, 'cordova.desktop'), dest: path.join(debDir, 'debian', 'cordova.desktop')},
                             {source: path.join(templateDir, 'install'), dest: path.join(debDir, 'debian', manifest.name + '.install')}];
            for (var i = 0; i < templates.length; i++) {
		fillTemplate(templates[i].source, templates[i].dest, props);
            }
            logger.warn('Note: to build a debian package, run: ');
            logger.warn(' cd ' + debDir + '; ' + 'debuild');
	});
    });
};



/**
*   Generates a file from a template source, injecting the values contained in the
*   provided js object.
*/
function fillTemplate(source, dest, obj) {
    var content = fs.readFileSync(source, {encoding: "utf8"});
    for (var prop in obj) {
        content = content.replace(new RegExp('{' + prop + '}', 'g'), obj[prop]);
    }

    fs.writeFileSync(dest, content);
}

function additionalBuildDependencies(ubuntuDir) {
    var config = new ConfigParser(path.join(ubuntuDir, 'config.xml'));

    var pkgConfig = [];
    config.etree.getroot().findall('./feature/deps/pkgConfig').forEach(function (element) {
        var list = JSON.parse(element.text);
        pkgConfig = pkgConfig.concat(list);
    });

    return pkgConfig;
}

function checkClickPackage(prefixDir) {
    Utils.pushd(prefixDir);

    if (fs.existsSync('/usr/bin/click-run-checks')) {
        var cmd = '/usr/bin/click-run-checks *.click';
        logger.debug(cmd);
        var output = shell.exec(cmd, { silent: true }).output;
        var json = '[', b = 0;
        for (var i = 0; i < output.length; i++) {
            if (output[i] == '{') {
                if (json.length > 1 && !b)
                    json += ', ';
                b++;
            } else if (output[i] == '}') {
                b--;
                if (b == 0)
                    json += '} ';
            }
            if (b > 0) {
                json += output[i];
            }
        }
        json += ']';
        var out = JSON.parse(json);
        for (var i = 0; i < out.length; i++) {
            if (out[i].warn) {
                for (var m in out[i].warn) {
                    if (out[i].warn[m].text) {
                        logger.warn(out[i].warn[m].text);
                        if (out[i].warn[m].link)
                            logger.warn(out[i].warn[m].link);
                    }
                }
            }
        }
        for (var i = 0; i < out.length; i++) {
            if (out[i].error) {
                for (var m in out[i].error) {
                    if (out[i].error[m].text) {
                        logger.warn(out[i].error[m].text);
                        if (out[i].error[m].link)
                            logger.warn(out[i].error[m].link);
                    }
                }
            }
        }
    }

    Utils.popd();
}

function cpuCount() {
    return os.cpus().length;
}

function checkEnv(ubuntuDir) {
    var deps = [
    'click',
    'cmake', 
    'libicu-dev', 
    'pkg-config', 
    'devscripts', 
    'qtbase5-dev', 
    'qtchooser', 
    'qtdeclarative5-dev', 
    'qtfeedback5-dev', 
    'qtlocation5-dev', 
    'qtmultimedia5-dev', 
    'qtpim5-dev', 
    'libqt5sensors5-dev', 
    'qtsystems5-dev'
    ];

    deps = deps.concat(additionalDependencies(ubuntuDir));
    deps = deps.join(' ');
    deps = deps.replace(/:ARCH/g, '');

    if (!deps.length)
        return;

    var cmd = "dpkg-query -Wf'${db:Status-abbrev}' " + deps;
    var installCmd = "sudo apt-get install " + deps;

    var res = shell.exec(cmd);

    if (res.code !== 0 || res.output.match(/[^i ]/)) {
        logger.error("Error: missing packages " + deps);

        var deferred = Q.defer();

        var readline = require('readline');
        var rl = readline.createInterface(process.stdin, process.stdout);
        rl.setPrompt('Install missing packages? (Yn)> ');
        rl.prompt();

        rl.on('line', function(line) {
            rl.close();
            if (line !== 'Y' && line !== 'y') {
                deferred.reject(new Error());
                return;
            }

            Utils.execAsync(installCmd).then(function() {
                deferred.resolve();
            }).catch(function (error) {
                deferred.reject(new Error());
            }).done();
        });

        return deferred.promise;
    }
    return Q();
}

function checkChrootEnv(ubuntuDir, architecture, framework) {
    var deps = "cmake libicu-dev:ARCH pkg-config qtbase5-dev:ARCH qtchooser qtdeclarative5-dev:ARCH qtfeedback5-dev:ARCH qtlocation5-dev:ARCH qtmultimedia5-dev:ARCH qtpim5-dev:ARCH libqt5sensors5-dev:ARCH qtsystems5-dev:ARCH ";
    deps += additionalDependencies(ubuntuDir).join(' ');
    deps = deps.replace(/ARCH/g, architecture);

    var chrootCreateCmd = "sudo click chroot -a " + architecture + " -f " + framework + " create";
    var chrootInstallCmd = "sudo click chroot -a " + architecture + " -f " + framework + " install " + deps;

    var cmd = "click chroot -a " + architecture + " -f " + framework + " run echo 1";
    var res = shell.exec(cmd);

    if (res.code !== 0) {
        logger.error("\nError: missing " + architecture + " chroot");
        logger.error("run:\n" + chrootCreateCmd);

        var deferred = Q.defer();
        deferred.reject(new Error());

        return deferred.promise;
    }

    cmd = "click chroot -a " + architecture + " -f " + framework + " run dpkg-query -Wf'${db:Status-abbrev}' " + deps;

    res = shell.exec(cmd);
    if (res.code !== 0 || res.output.match(/[^i ]/)) {
        logger.error("\nError: missing dependency inside " + architecture + " chroot");
        logger.error("run:\n" + chrootInstallCmd);

        var deferred = Q.defer();

        var readline = require('readline');
        var rl = readline.createInterface(process.stdin, process.stdout);
        rl.setPrompt('Do you want install it now? (Yn)> ');
        rl.prompt();

        rl.on('line', function(line) {
            rl.close();
            if (line !== 'Y' && line !== 'y') {
                deferred.reject(new Error());
                return;
            }

            Utils.execAsync(chrootInstallCmd).then(function() {
                deferred.resolve();
            }).catch(function (error) {
                deferred.reject(new Error());
            }).done();
        });

        return deferred.promise;
    }
    return Q();
}

function additionalDependencies(ubuntuDir) {
    var config = new ConfigParser(path.join(ubuntuDir, 'config.xml'));

    var deb = [];
    config.etree.getroot().findall('./feature/deps/deb').forEach(function (element) {
        var list = JSON.parse(element.text);
        deb = deb.concat(list);
    });

    return deb;
}
