/**
 * git-project.js
 * Copyright (c) 2000 - 2017 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jonghwan Park <iwin100.park@samsung.com>
 * Kitae Kim <kt920.kim@samsung.com>
 *
 * 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.
 *
 * Contributors:
 * - S-Core Co., Ltd
**/

/**
 * Tizen git project plugin
 * @module models/tizen-project/git-project
 */

var fs = require('fs');
var extfs = require('fs-extra');
var path = require('path');
var async = require('async');
var _ = require('underscore');

var DError = require('../../core/exception.js');
var dibs = require('../../core/dibs.js');
var Job = require('../dibs.model.common/job.js');
var Builder = require('./builder.js');
var Installer = require('./installer.js');
var Tutils = require('../org.tizen.common/tizen_utils.js');
var Package = require('../org.tizen.common/package.js');
var Parser = require('../org.tizen.common/parser.js');
var TizenGit = require('./git-control.js');
var ReverseBuildChecker = require('./reverse-build.js');
var Snapshot = require('../org.tizen.repository/snapshot.js');
var utils = require('../../lib/utils.js');
var FileSystem = require('../dibs.core/filesystem.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');

module.exports.createJob = createJob;
module.exports.initializeJob = initializeJob;
module.exports.executeJob = executeJob;
module.exports.resumeJob = resumeJob;
module.exports.pendingJobHandler = pendingJobHandler;
module.exports.checkMutualExclusiveJob = TizenCommon.checkMutualExclusiveJob;

module.exports.saveBuildInfo = saveBuildInfo;

/**
 * Tizen git job that is specialized on tizen git project
 * @constructor
 * @augments module:models/job
 * @param {module:models/job~Job} baseJob - base job object
 */

function TizenSourceJob(baseJob) {
    Job.copy(this, baseJob);
    /**
     * compat job
     * @type {module:models/job~Job}
     */
    this.compatJob = null;
    /**
     * package infos from pkginfo.manifest
     * @type {Array}
     */
    if (!this.options.packages) {
        this.options.packages = [];
    }
    /**
     * will have git repository path if succeeded
     * @type {Array}
     */
    this.gitRepo = null;
    /**
     * will have git commit id if succeeded
     * @type {string}
     */
    this.gitCommit = null;
    /**
     * snapshot infomation
     * @type {string}
     */
    this.snapshot = null;
    this.distribution = null;
    /**
     * tizen environments
     * @type {environment}
     */
    // NOTE. Initializing on windows is very slow
    this.initEnvironments = dibs.projectTypes['Tizen-Source'].environments
        .filter(function (e) {
            return (e.indexOf('windows') === -1);
        });
}

/**
 * Create tizen git job instance
 * @function createJob
 * @param {string} userEmail - user email
 * @param {string} distName - distibution name
 * @param {string} prjName - project name
 * @param {string} prjType - project type
 * @param {string} environmentName - environment name
 * @param {string} parentId - parent Id
 * @param {string} distType - distibution type
 * @param {module:models/job.options} options - job options
 * @param {module:models/job~Job} baseJob - base job object
 * @memberOf module:models/tizen-project/git-project
 */

function createJob(userEmail, distName, prjName, prjType, environmentName, parentId, distType, options, callback) {
    Job.create(userEmail, distName, distType, prjName, prjType, environmentName, parentId, null, options,
        function (err, newJob) {
            if (err) {
                callback(err, null);
            } else {
                TizenCommon.checkReleasePhase(new TizenSourceJob(newJob), callback);
            }
        });
}


/**
 * Initialize Tizen Git job
 * @function initializeJob
 * @param {module:models/job~Job} job - job
 * @param {string} jobWorkPath - jobWorkPath
 * @param {module:core/base-server.BaseServer} server - server
 * @param {module:lib/monitor.monitor} monitor - monitor
 * @param {module:lib/utils.callback_error} callback - callback(error)
 * @memberOf module:models/tizen-project/git-project
 */
function initializeJob(job, options, callback) {
    /*
     *  1. state check
     */
    if ((job.status !== 'INITIALIZING')) {
        return callback(new DError('TIZENJOB008', {
            jobId: job.id
        }), job);
    }

    /*
     *  2. set variable
     */
    // gloval error variable
    var error = null;
    // pkg info pkginfo.manifest path => 5. section
    var pkgInfoPath = null;
    // change log path => 5. section
    var changelogPath = null;
    // parseed pkginfo.manifest => 5. section
    var pkgs = null;

    var monitor = options.monitor;

    /*
     *  3. options check
     */
    // check target OS
    if (!job.options.TARGET_OS) {
        error = new DError('TIZENGITJOB003', {
            jobId: job.id,
            prjName: job.projectName,
            option: 'TARGET_OS'
        });
        callback(error, job); return;
    }

    async.waterfall([
        /*
         *  4. prepare target
         */
        // git clone
        function (wcb) {
            monitor.updateProgress('Preparing source code... ' + job.options.GIT_REPO, wcb);
        },
        function (wcb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'git clone'
            });
            TizenGit.getSourceCode(job, options, wcb);
        },
        /*
         *  5. get infomation from target
         */
        // set info paths
        function (srcPathInfo, wcb) {
            monitor.updateProgress('get source info ' + srcPathInfo);

            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'git clone done'
            });

            pkgInfoPath = path.join(srcPathInfo, 'package', 'pkginfo.manifest');
            changelogPath = path.join(srcPathInfo, 'package', 'changelog');
            fs.exists(pkgInfoPath, function (exists) {
                wcb(((!exists) ? new DError('TIZENJOB001') : null));
            });
        },
        function (wcb) {
            monitor.updateProgress('Preparing source code is done!', wcb);
        },
        // parse pkginfo.manifest
        function (wcb) {
            monitor.updateProgress('Parsing package/pkginfo.manifest...', wcb);
        },
        function (wcb) {
            Package.getPkgListFromFile(pkgInfoPath, function (err, pkgList) {
                if (err) {
                    error = new DError('TIZENJOB002', {
                        error: err.message
                    });
                }
                wcb(error, pkgList);
            });
        },
        // check package infomation (version, commit id)
        function (pkgList, wcb) {
            pkgs = pkgList;
            if (job.options.BUILD_ONLY || job.options.FORCE_REBUILD) {
                wcb(null);
            } else if (job.options.UPLOAD_TEMP) {
                wcb(null);
            } else {
                monitor.updateProgress('Checking package information...');
                checkPackageInfo(job, pkgs, function (err) {
                    wcb(err);
                });
            }
        },
        function (wcb) {
            monitor.updateProgress(' - [' + _.uniq(_.map(pkgs, function (pkg) {
                return pkg.name;
            })).toString() + ']', wcb);
        },
        // target os setting
        function (wcb) {
            monitor.updateProgress('Checking target OS information...', wcb);
        },
        function (wcb) {
            var osPkgs = pkgs.filter(function (pkg) {
                return pkg.osList.indexOf(job.options.TARGET_OS) !== -1;
            });

            // if no packages are defined for target OS, invoke error
            if (osPkgs.length === 0) {
                wcb(new DError('TIZENGITJOB001', {
                    targetOS: job.options.TARGET_OS
                })); return;
            } else {
                var pkg = osPkgs[0];
                //*** job.execEnvironments ***
                job.execEnvironments = [Tutils.EnvironmentName2Id('Tizen-Source', pkg.buildHostOS)];
                //*** job.packages ***
                var InvalidInstallDep = null;
                job.options.packages = pkgs.filter(function (pkg) {
                    return pkg.osList.indexOf(job.options.TARGET_OS) !== -1;
                }).map(function (pkg) {
                    pkg.os = job.options.TARGET_OS;
                    pkg.buildDepList = _.map(pkg.buildDepList, function (dep) {
                        if (!dep.os) {
                            dep.os = job.options.TARGET_OS;
                        }
                        return dep;
                    });
                    pkg.installDepList = _.map(pkg.installDepList,
                        function (dep) {
                            if (!dep.os) {
                                dep.os = job.options.TARGET_OS;
                            } else {
                                if (dep.os !== pkg.os) {
                                    InvalidInstallDep = _.clone(pkg);
                                    InvalidInstallDep.wrongOS = dep.os;
                                }
                            }
                            return dep;
                        });

                    return pkg;
                });
                if (InvalidInstallDep) {
                    wcb(new DError('TIZENJOB015', {
                        pkgName: InvalidInstallDep.name,
                        targetOs: InvalidInstallDep.os,
                        depOs: InvalidInstallDep.wrongOS
                    }));
                } else {
                    wcb(null);
                }
            }
        },
        function (wcb) {
            monitor.updateProgress('Checking build server information...', wcb);
        },
        function (wcb) {
            if (isServerPrepared(dibs.getAllServers(), job.execEnvironments)) {
                wcb(null);
            } else {
                wcb(new DError('TIZENJOB010', {
                    envName: job.execEnvironments.join(',')
                }));
            }
        },
        function (wcb) {
            if (job.options.REVERSE_BUILD) {
                job.options.description = 'Reverse Build';
            }
            wcb(null);
        },
        /*
         *  6. check change log
         */
        function (wcb) {
            monitor.updateProgress('Checking change-log option of distriubtion...', wcb);
        },
        function (wcb) {
            dibs.rpc.datamgr.searchDistributions({
                name: job.distName
            }, function (err, data) {
                if (err) {
                    error = new DError('TIZENJOB014', {
                        distName: job.distName
                    }, err);
                }
                wcb(error, data);
            });
        },
        function (distributions, wcb) {
            job.distribution = distributions[0];
            if (job.distribution.options &&
                job.distribution.options.CHECK_CHANGE_LOG) {
                checkChangelog(pkgs, changelogPath, wcb);
            } else {
                wcb(null);
            }
        },
        /*
         *  7. check dependency list in repo
         */
        function (wcb) {
            monitor.updateProgress('Checking dependency information...', wcb);
        },
        //build dependencies and install dependencies check from repo server
        function (wcb) {
            job.depList = TizenCommon.getDepList(job.options.packages);
            wcb(null);
        },
        function (wcb) {
            monitor.updateProgress(' - [' + _.map(job.depList, function (dep) {
                return dep.packageName + '(' + dep.os + ')';
            }).toString() + ']', wcb);
        },
        function (wcb) {
            if (job.parentId === null && job.depList.length > 0) {
                monitor.updateProgress('Checking if dependent packages exist in repo or not ...');
                TizenCommon.checkDependencyExistInRepo(job, wcb);
            } else {
                monitor.updateProgress('Skipping packages check in repo ...');
                wcb(null);
            }
        }], function (err) {
        if (err) {
            finalize(err, job, monitor, callback);
        } else {
            callback(err, job);
        }
    });
}


function checkChangelog(pkginfoList, changelogPath, callback) {
    var error = null;
    fs.exists(changelogPath, function (exist) {
        if (exist) {
            Parser.changelogParser(changelogPath, function (err, changelogList) {
                if (err || !changelogList) {
                    error = new DError('TIZENJOB012', err);
                    callback(error);
                } else {
                    TizenCommon.checkChangelog(pkginfoList, changelogList, callback);
                }
            });
        } else {
            error = new DError('TIZENJOB011');
            callback(error);
        }
    });
}


function isServerPrepared(servers, envNames) {
    return (_.select(servers, function (svr) {
        return (svr.type === 'builder' &&
            svr.status === 'RUNNING' &&
            envNames &&
            _.intersection(envNames, _.map(svr.environments, function (e) {
                return e.id;
            })).length > 0);
    }).length > 0);
}


/**
 * Execute tizen git job
 * - it include build, packaging and upload
 * @function executeJob
 * @param {module:models/job~Job} job - job
 * @param {string} jobWorkPath - jobWorkPath
 * @param {module:core/base-server.BaseServer} server - server
 * @param {module:lib/utils.callback_error} callback - callback(error)
 * @memberOf module:models/tizen-project/git-project
 */
function executeJob(job, options, callback) {
    var jobSvc = options.jobSvc;
    var parentServerId = options.parentServerId;
    var jobWorkPath = options.jobWorkPath;
    var monitor = options.monitor;

    var server = dibs.getServer(parentServerId);

    var srcPath = path.join(jobWorkPath, 'source');
    async.waterfall([
        // get latest snapshots of repository
        function (cb) {
            monitor.updateProgress('Getting the latest snapshot of repository ...');
            if (job.snapshot && job.snapshot.distName && job.snapshot.options) {
                monitor.updateProgress(' - ' + job.snapshot.name);
                cb(null);
            } else {
                job.board.push({
                    type: 'STAMP',
                    time: utils.getTimeString(),
                    name: 'get repository'
                });
                dibs.rpc.repo.searchSnapshots({
                    name: null,
                    repoType: 'tizen',
                    distName: job.distName
                },
                function (err, snapshots) {
                    job.board.push({
                        type: 'STAMP',
                        time: utils.getTimeString(),
                        name: 'get repository done'
                    });
                    if (!err && snapshots.length > 0) {
                        job.snapshot = snapshots[0];
                        monitor.updateProgress(' - ' + job.snapshot.name);
                        job.snapshot.distName = job.distName;
                    }
                    cb(err);
                });
            }
        },
        function (cb) {
            monitor.updateProgress('Checking initialization server matched...', cb);
        },
        // check
        function (cb) {
            // get source code
            // NOTE.If exec-server is different from init-server,
            //      this job must get source code
            if (job.initServerId !== parentServerId) {
                monitor.updateProgress(' - build server is different from init server');
                monitor.updateProgress(' - preparing source code...' + job.options.GIT_REPO);
                job.board.push({
                    type: 'STAMP',
                    time: utils.getTimeString(),
                    name: 'git clone'
                });
                TizenGit.getSourceCode(job, options, function (err) {
                    job.board.push({
                        type: 'STAMP',
                        time: utils.getTimeString(),
                        name: 'git clone done'
                    });
                    cb(err);
                });
            } else {
                monitor.updateProgress(' - build server is same as init server', cb);
            }
        },
        function (cb) {
            monitor.updateProgress('Checking build options...', cb);
        },
        // check compatible OS packages
        function (cb) {
            // check 'compatJob' flag set by multi-build
            if (job.compatJob) {
                monitor.updateProgress('Downloading compat job packages from job #' + job.compatJob);
                downloadCompatJobPackages(server, job.compatJob, srcPath, job.options.TARGET_OS, cb);
            }
            // if 'FORCE_REBUILD', must do build
            else if (job.options.FORCE_REBUILD) {
                monitor.updateProgress('Building project using FORCE_REBUILD option...');
                buildGitProject(job, options, cb);
            } else if (job.options.UPLOAD_TEMP) {
                monitor.updateProgress('Building project using UPLOAD_TEMP option...');
                buildGitProject(job, options, cb);
            } else {
                monitor.updateProgress('Checking compatible os packages...');
                checkCompatPackagesFromSnapshot(job.options.packages, job.snapshot, function (err, pkgs) {
                    if (!err && pkgs.length > 0) {
                        monitor.updateProgress('Downloading compatible packages...');
                        downloadCompatiblePackages(pkgs, job.snapshot, srcPath, job.options.TARGET_OS, cb);
                    } else {
                        monitor.updateProgress('Building source project...');
                        buildGitProject(job, options, cb);
                    }
                });
            }
        },
        function (cb) {
            if (!job.options.BUILD_ONLY) {
                monitor.updateProgress('Preparing to upload result files...');
                job.board.push({
                    type: 'STAMP',
                    time: utils.getTimeString(),
                    name: 'prepare to upload'
                });
                prepareToUpload(server, srcPath, monitor, function (err, rpaths) {
                    if (!err) {
                        job.resultFiles = rpaths;
                    }
                    job.board.push({
                        type: 'STAMP',
                        time: utils.getTimeString(),
                        name: 'prepare to upload done '
                    });
                    cb(err);
                });
            } else {
                cb(null);
            }
        },
        function (cb) {
            if (!job.options.BUILD_ONLY) {
                monitor.updateProgress('Collecting build information...');
                job.buildInfo = [];

                _.each(job.resultFiles, function (rpath) {
                    job.buildInfo.push({
                        rpath: rpath,
                        build: {
                            __BUILD_DISTRIBUTION: job.distName,
                            __PROJECT_TYPE: job.projectType,
                            __PROJECT_NAME: job.projectName,
                            __GIT_COMMIT_ID: job.options.GIT_CURRENT_COMMIT,
                            __GIT_REPO: job.options.GIT_REPO,
                            __GIT_BRANCH: job.options.GIT_BRANCH,
                            __USE_GIT_SUBMODULES: job.options.USE_GIT_SUBMODULES,
                            __PRE_BUILD: job.options.PRE_BUILD,
                            __POST_BUILD: job.options.POST_BUILD
                        }
                    });
                });

                // save build informations for recover
                job.options.buildInfo = job.buildInfo;
            }
            cb(null);
        },
        function (cb) {
            monitor.updateProgress('Checking reverse build...');
            if (job.options.BUILD_ONLY || job.options.NO_REVERSE_CHECK) {
                return cb(null);
            } else {
                ReverseBuildChecker.check(job, monitor, function (err, result) {
                    updateReverseJobs(job, result, function (err1) {
                        if (err && !err1) {
                            monitor.updateProgress('WARNING! Reverse build failed!');
                            cb(err);
                        } else if (!err && err1) {
                            monitor.updateProgress('WARNING! Job information update failed!');
                            cb(err1);
                        } else if (err && err1) {
                            monitor.updateProgress('WARNING! Reverse build failed!');
                            monitor.updateProgress('WARNING! Job information update failed!');
                            cb(err);
                        } else {
                            cb(null);
                        }
                    });
                });
            }
        },
        function (cb) {
            // set job info with git commit id and so on
            monitor.updateProgress('Setting the lastest source info...', cb);
        },
        function (cb) {
            TizenCommon.set_commit_to_jobinfo(job, srcPath, cb);
        },
        function (cb) {
            // set job info with changelog
            monitor.updateProgress('Setting change-log information...', cb);
        },
        function (cb) {
            if (job.options.REVERSE_BUILD) {
                cb(null);
            } else {
                var changelogPath = path.join(srcPath, 'package', 'changelog');
                fs.access(changelogPath, function (err) {
                    if (err) {
                        return cb(null);
                    } else {
                        Parser.changelogParser(changelogPath, function (err, changelogList) {
                            if (err) {
                                cb(err);
                            } else {
                                TizenCommon.set_changelog_to_jobinfo(job, changelogList, cb);
                            }
                        });
                    }
                });
            }
        },
        function (cb) {
            if (job.options.packages[0]) {
                job.options.PACKAGES = job.options.packages.map(function (pkg) {
                    return pkg.name;
                });
                job.options.VERSION = job.options.packages[0].version;
            }
            cb(null);
        },
        function (cb) {
            monitor.updateProgress('Checking uploading...');
            TizenCommon.binaryUploadApprovalProcess(jobSvc, job, monitor, cb);
        }
    ], function (err, job1) {
        job1 = job1 || job;

        if (!err && job1.status === 'PENDING') {
            return callback(null, job1);
        }

        finalizeBuild(err, job1, monitor, callback);
    });
}


function updateReverseJobs(parentJob, reverseJobIds, callback) {
    async.mapLimit(reverseJobIds, 5,
        function (jobId, cb) {
            dibs.rpc.jobmgr.queryJob(jobId, cb);
        },
        function (err, rjobs) {
            if (!err) {
                parentJob.subJobs = _.union(parentJob.subJobs, rjobs);
            }
            callback(err);
        });
}


function finalizeBuild(err, job, monitor, callback) {
    console.log(job);
    async.series([
        function (cb) {
            if (job.options.UPLOAD) {
                saveBuildInfo(job, cb);
            } else {
                cb(null);
            }
        },
        function (cb) {
            finalize(err, job, monitor, cb);
        }
    ], function (err) {
        callback(err, job);
    });
}

function finalize(err, job, monitor, callback) {
    // NOTE. MUST strip unnecessary information for reducing object size
    //       if not, DNODE RPC cannnot receive callback
    TizenCommon.stripJob(job);

    if (dibs.getServersByType('messenger')[0] && job.parentId === null && job.userEmail !== 'admin@user') {
        monitor.updateProgress('sending email');
        TizenCommon.sendEmail(err, job, monitor, function (error) {
            if (error) {
                monitor.updateProgress('sending email failed');
                monitor.updateProgress('error: ' + error.message);
            }
            callback(err, job);
        });
    } else {
        monitor.updateProgress('ignore sending email');
        callback(err, job);
    }
}

function pendingJobHandler(job, callback) {
    TizenCommon.pendingJobHandler(job, callback);
}

function resumeJob(job, options, callback) {
    var jobSvc = options.jobSvc;
    var monitor = options.monitor;
    var resumeOptions = options.resumeOptions;

    // recover build information
    job.buildInfo = job.options.buildInfo;

    TizenCommon.resumeJob(jobSvc, job, resumeOptions, monitor, function (err) {
        finalizeBuild(err, job, monitor, callback);
    });

}

function downloadCompatJobPackages(server, jobId, downloadDir, targetOS, callback) {
    async.waterfall([
        function (cb) {
            downloadJobPackages(server, jobId, downloadDir, cb);
        },
        function (compatPkgPaths, cb) {
            async.eachSeries(compatPkgPaths,
                function (oldPath, cb2) {
                    var pInfo = Tutils.getInfoFromPackageFileName(path.basename(oldPath));
                    var newPath = path.join(path.dirname(oldPath), pInfo.name + '_' + pInfo.version + '_' + targetOS + '.zip');
                    FileSystem.move(oldPath, newPath, cb2);
                },
                function (err) {
                    cb(err);
                });
        }], function (err) {
        callback(err);
    });
}


//callback(err, pkgs)
function checkCompatPackagesFromSnapshot(pkgs, snapshot, callback) {
    async.map(pkgs,
        function (pkg, cb) {
            if (pkg.osList.length < 1) {
                cb(new Error('No compatible OS'), null);
                return;
            }
            var compatPkg = checkCompatPkgFromSnapshot(pkg, snapshot);
            if (compatPkg) {
                cb(null, compatPkg);
            } else {
                cb(new Error('Compatible package not found!'), null);
            }
        },
        function (err, pkgs) {
            callback(err, pkgs);
        });
}


function checkCompatPkgFromSnapshot(pkg, snapshot) {
    for (var i = 0; i < pkg.osList.length; i++) {
        var os = pkg.osList[i];
        if (pkg.os === os) {
            continue;
        }

        if (snapshot && snapshot.osPackages && snapshot.osPackages[os] &&
            snapshot.osPackages[os][pkg.name] &&
            snapshot.osPackages[os][pkg.name].version === pkg.version) {

            return snapshot.osPackages[os][pkg.name];
        }
    }

    return null;
}


function downloadCompatiblePackages(pkgs, snapshot, srcPath, targetOS, callback) {
    var repo = dibs.getServersByType('repo')[0];

    async.each(pkgs,
        function (pkg, cb) {
            repo.downloadPackage(pkg.name, {
                repoType: 'tizen',
                distName: snapshot.distName,
                snapshotName: snapshot.name,
                os: pkg.os,
                targetDir: srcPath
            }, function (err) {
                if (!err) {
                    var oldPath = path.join(srcPath, pkg.name + '_' + pkg.version + '_' + pkg.os + '.zip');
                    var newPath = path.join(srcPath, pkg.name + '_' + pkg.version + '_' + targetOS + '.zip');
                    FileSystem.move(oldPath, newPath, cb);
                } else {
                    cb(err);
                }
            });
        },
        function (err) {
            callback(err);
        });
}

function buildGitProject(job, options, callback) {
    var server = dibs.getServer(options.parentServerId);
    var jobWorkPath = options.jobWorkPath;
    //var workspaceTempPath = options.workspaceTempPath;
    var monitor = options.monitor;
    var environments = options.environments;

    var srcPath = path.join(jobWorkPath, 'source');
    var host = Tutils.getHost(environments);
    var buildRoot = path.join(jobWorkPath, 'buildRoot');
    var buildDepPkgs = null;
    var sourceDepPkgs = null;
    var localpkgs = null;
    var repo = null;

    async.waterfall([
        // get build dependencies
        function (cb) {
            monitor.updateProgress('Calculating build dependency... ', cb);
        },
        function (cb) {
            getBuildDependenciesFromSrcDir(srcPath, host.os, job.options.TARGET_OS, cb);
        },
        function (pkgs, cb) {
            buildDepPkgs = pkgs;
            monitor.updateProgress('- [' + _.uniq(_.map(buildDepPkgs, function (pkg) {
                return pkg.name;
            })).toString() + ']', cb);
        },
        // download packages of dependent jobs
        function (cb) {
            monitor.updateProgress('Downloading build dependent packages... ', cb);
        },
        function (cb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'download dep packages'
            });
            var downloadDir = path.join(jobWorkPath, 'local-packages');
            if (job.options.DEP_FILES) {
                var depFiles = job.options.DEP_FILES.split(',').filter(function (f) {
                    // NOTE. This code will decrease the number of download count
                    var fileInfo = Tutils.getInfoFromPackageFileName(path.basename(f));

                    var depPkgs = buildDepPkgs.filter(function (p) {
                        // Check OS information only
                        return p.os === fileInfo.os;
                    /*
                    return p.name === fileInfo.name && p.os === fileInfo.os;
                    */
                    });
                    return depPkgs.length > 0;
                });
                downloadRemoteFiles(server, depFiles, downloadDir, cb);
            } else {
                downloadDependentJobPackages(server, job, downloadDir, cb);
            }
        },
        function (lPkgs, cb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'download dep packages done'
            });
            localpkgs = lPkgs;
            monitor.updateProgress(' - downloading is done', cb);
        },
        // install build dependent packages
        function (cb) {
            monitor.updateProgress('Installing build dependent packages... ', cb);
        },
        function (cb) {
            repo = dibs.getServersByType('repo')[0];
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'install dep packages'
            });
            var installOpts = {
                clean: true,
                localPkgs: localpkgs
                //extractCacheDir: workspaceTempPath
            };
            if (job.distribution.options.PARENT_REPO_URL) {
                installOpts.parentRepoURL = job.distribution.options.PARENT_REPO_URL;
            }
            Installer.installMultiplePackages(buildDepPkgs, buildRoot, repo,
                job.distName, host, installOpts, monitor, cb);
        },
        function (wcb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'install dep packages done'
            });
            monitor.updateProgress(' - installing is done', wcb);
        },
        // download archive packages to ROOTDIR
        function (cb) {
            monitor.updateProgress('Calculating source archive package dependency... ', cb);
        },
        function (cb) {
            getArchiveDependenciesFromSrcDir(srcPath, cb);
        },
        function (sDepPkgs, cb) {
            sourceDepPkgs = sDepPkgs;
            monitor.updateProgress(' - [' + _.uniq(_.map(sourceDepPkgs, function (pkg) {
                return pkg.name;
            })).toString() + ']', cb);
        },
        // download archive packages of dependent jobs
        function (cb) {
            monitor.updateProgress('Downloading dependent source archive packages... ', cb);
        },
        function (cb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'download dep archive packages'
            });
            // download archive packages from repo
            downloadDependentArchivePackages(sourceDepPkgs, repo, job, buildRoot, cb);
        },
        function (wcb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'download dep archive packages done'
            });
            monitor.updateProgress(' - downloading is done', wcb);
        },
        // build
        function (wcb) {
            monitor.updateProgress('Building source project is started... ', wcb);
        },
        function (wcb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'build'
            });

            /*
             * add DISTRIBUTION environment.
             * this option allows users to upload packages into specified distributions
             */
            var buildOptions = job.options;
            buildOptions.DISTRIBUTION = job.distName;
            Builder.build(srcPath, buildRoot, host, buildOptions, monitor, wcb);
        },
        function (wcb) {
            job.board.push({
                type: 'STAMP',
                time: utils.getTimeString(),
                name: 'build done'
            });
            monitor.updateProgress('Building source project is done', wcb);
        }
    ], function (err) {
        callback(err);
    });
}

function downloadDependentArchivePackages(archivePkgs, repo, job, downloadDir, callback) {
    async.each(archivePkgs, function (archive, ecb) {
        // download archive package
        repo.downloadPackage(archive.name, {
            repoType: 'tizen',
            distName: job.snapshot.distName,
            snapshotName: job.snapshot.name,
            targetDir: downloadDir
        }, function (err) {
            ecb(err);
        });
    }, function (err) {
        callback(err);
    });
}

function getBuildDependenciesFromSrcDir(srcDir, hostOS, targetOS, callback) {
    var pkgFile = path.join(srcDir, 'package', 'pkginfo.manifest');
    Package.getPkgListFromFile(pkgFile, function (err, pkgs) {
        var result = [];
        if (!err) {
            result = Package.getBuildDepsOfPackages(pkgs, targetOS);
        }
        callback(err, result);
    });
}


function getArchiveDependenciesFromSrcDir(srcDir, callback) {
    var pkgFile = path.join(srcDir, 'package', 'pkginfo.manifest');
    Package.getPkgListFromFile(pkgFile, function (err, pkgs) {
        var result = [];
        if (!err) {
            result = Package.getSourceDepsOfPackages(pkgs);
        }
        callback(err, result);
    });
}

function downloadDependentJobPackages(server, job, downloadDir, callback) {
    // create 'downloaded' directory
    extfs.mkdirs(downloadDir, function (err) {
        if (err) {
            callback(err, []);
        } else {
            // download
            var result = [];
            async.eachSeries(job.depJobs,
                function (jobId, cb) {
                    async.waterfall([
                        function (wcb) {
                            downloadJobPackages(server, jobId, downloadDir, wcb);
                        }, Tutils.checkCancelBeforeCallback(job),
                        function (pkgPaths, wcb) {
                            result = result.concat(pkgPaths);
                            wcb(null);
                        }, Tutils.checkCancelBeforeCallback(job)
                    ], cb);
                },
                function (err) {
                    callback(err, result);
                });
        }
    });
}


function downloadJobPackages(server, jobId, downloadDir, callback) {
    async.waterfall([
        function (cb) {
            dibs.rpc.jobmgr.queryJob(jobId, cb);
        },
        function (job, cb) {
            downloadRemoteFiles(server, job.resultFiles, downloadDir, cb);
        }], function (err, result) {
        callback(err, result);
    });
}


function downloadRemoteFiles(server, rpaths, downloadDir, callback) {
    var result = [];
    async.eachSeries(rpaths,
        function (dfsPath, cb) {
            var tpath = path.join(downloadDir, path.basename(dfsPath));
            result.push(tpath);
            server.dfsGetFile(tpath, dfsPath, cb);
        },
        function (err) {
            callback(err, result);
        });
}


function prepareToUpload(server, srcPath, monitor, callback) {
    async.waterfall([
        function (wcb) {
            monitor.updateProgress('Preparing to upload result files...', wcb);
        },
        function (wcb) {
            fs.readdir(srcPath, wcb);
        },
        function (files, wcb) {
            var zipFiles = _.select(files, function (file) {
                return (file.indexOf('.zip') > -1);
            });
            var zipPaths = _.map(zipFiles, function (file) {
                return path.join(srcPath, file);
            });
            monitor.updateProgress(' - [' + zipPaths.map(function (e) {
                return path.basename(e);
            }).join(',') + ']');
            addToDFS(server, zipPaths, wcb);
        }
    ], function (err, result) {
        if (err) {
            monitor.updateProgress(' - ' + err.message);
        } else {
            monitor.updateProgress(' - done ');
        }
        callback(err, result);
    });
}

function addToDFS(server, lpaths, callback) {
    if (lpaths.length === 0) {
        callback(null, []); return;
    }
    async.mapSeries(lpaths,
        function (lpath, cb) {
            server.dfsAddFile(null, lpath, {
                lifetime: 60 * 60 * 1000
            }, cb);
        },
        function (err, result) {
            callback(err, result);
        });
}


function saveBuildInfo(job, callback) {
    async.waterfall([
        // search project
        function (cb) {
            dibs.rpc.datamgr.searchProjects({
                name: job.projectName,
                distName: job.distName
            },
                function (err, results) {
                    if (!err) {
                        if (results.length > 0) {
                            cb(null, results[0]);
                        } else {
                            cb(new Error('Job\'s environment not found!'), null);
                        }
                    } else {
                        cb(err, null);
                    }
                });
        },
        // save build info
        function (prj, cb) {
            if (!prj.options.LAST_BUILD_INFO) {
                prj.options.LAST_BUILD_INFO = {};
            }

            prj.options.LAST_BUILD_INFO[job.environmentName] = {
                version: job.options.VERSION,
                gitRepo: job.options.GIT_REPO,
                gitCommit: job.options.GIT_CURRENT_COMMIT,
                packageNames: job.options.PACKAGES,
                useGitSubmodule: job.options.USE_GIT_SUBMODULES
            };
            dibs.rpc.datamgr.updateProject(prj, function (err) {
                // not need newProject
                cb(err);
            });

        }], function (err) {
        callback(err);
    });
}


function checkPackageInfo(job, pkgs, callback) {
    async.series([
        // check package version
        function (cb) {
            if (pkgs.length === 0) {
                cb(new DError('TIZENJOB003')); return;
            }

            dibs.rpc.repo.searchSnapshots({
                name: null,
                repoType: 'tizen',
                distName: job.distName
            }, function (err, snaps) {
                if (err) {
                    cb(err); return;
                }
                if (snaps.length === 0) {
                    cb(null); return;
                }

                for (var i = 0; i < pkgs.length; i++) {
                    var pkg = pkgs[i];
                    var oldPkg = Snapshot.getPackage(snaps[0], pkg.name, job.options.TARGET_OS);

                    if (oldPkg && Package.compareVersion(pkg.version, oldPkg.version) <= 0) {
                        cb(new DError('TIZENJOB004', {
                            name: pkg.name,
                            version: pkg.version,
                            oldVersion: oldPkg.version
                        }));
                        return;
                    }
                }
                cb(null);
            });
        },
        // check package commit
        function (cb) {
            dibs.rpc.datamgr.searchProjects({
                name: job.projectName,
                distName: job.distName
            },
                function (err, prjs) {
                    if (err) {
                        cb(err); return;
                    }
                    if (prjs.length === 0) {
                        cb(new DError('TIZENJOB005', {
                            name: job.projectName,
                            distName: job.distName
                        }));
                        return;
                    }
                    if (!prjs[0].options.LAST_BUILD_INFO) {
                        cb(null); return;
                    }
                    var lastBuildInfos = prjs[0].options.LAST_BUILD_INFO;
                    for (var os in lastBuildInfos) {
                        var bInfo = lastBuildInfos[os];
                        if (!bInfo.version || bInfo.version !== pkgs[0].version) {
                            continue;
                        }

                        if (bInfo.gitRepo && bInfo.gitRepo !== job.gitRepo) {
                            cb(new DError('TIZENJOB006', {
                                repo: job.gitRepo,
                                oldRepo: bInfo.gitRepo
                            }));
                            return;
                        }
                        if (bInfo.gitCommit && bInfo.gitCommit !== job.gitCommit) {
                            cb(new DError('TIZENJOB007', {
                                commit: job.gitCommit,
                                oldCommit: bInfo.gitCommit
                            }));
                            return;
                        }
                    }
                    cb(null);
                });
        }], function (err) {
        callback(err);
    });
}
