/**
 * package-pull-project.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@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
**/

var async = require('async');
var path = require('path');
var _ = require('underscore');

var DError = require('../../core/exception.js');
var dibs = require('../../core/dibs.js');
var utils = require('../../lib/utils.js');
var Job = require('../dibs.model.common/job.js');
var Package = require('../org.tizen.common/package.js');
var ReverseBuildChecker = require('../org.tizen.projects/reverse-build.js');
var Snapshot = require('../org.tizen.repository/snapshot.js');
var RemoteRepo = require('../org.tizen.repository/remote-repo.js');
var TizenCommon = require('../org.tizen.common/tizen_common.js');
var TizenUtils = require('../org.tizen.common/tizen_utils.js');

/**
 * Tizen package pull plugin
 * @module org.tizen.projects/package-pull
 */

module.exports.createJob = createJob;
module.exports.initializeJob = initializeJob;
module.exports.executeJob = executeJob;
module.exports.checkMutualExclusiveJob = checkMutualExclusiveJob;


/**
 * Tizen package pull job
 * @constructor
 * @augments module:models/job
 * @param {module:models/job~Job} baseJob - base job object
 */

function TizenPackagePullJob(baseJob) {
    Job.copy(this, baseJob);

    this.options.packages = [];
    this.initEnvironments = dibs.projectTypes['Tizen-Package-Pull'].environments
        .filter(function (e) {
            return (e.indexOf('ubuntu') >= 0);
        });
    this.execEnvironments = dibs.projectTypes['Tizen-Package-Pull'].environments
        .filter(function (e) {
            return (e.indexOf('ubuntu') >= 0);
        });
}


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) {
        callback(err, new TizenPackagePullJob(newJob));
    });
}


function initializeJob(job, options, callback) {

    callback(null, job);
}


function getInternalDistribution(distName, callback) {
    dibs.rpc.repo.searchDistributions({
        repoType: 'tizen',
        distName: distName
    }, function (err, dists) {
        if (err) {
            callback(err, null);
        } else if (dists.length === 0) {
            callback(new DError('TREPO003', { distName: distName }), null);
        } else {
            callback(null, dists[0]);
        }
    });
}


function checkUpdatedPackages(currSnapshot, sourceSnapshot, pkgConditions, sourceType, callback) {
    getSourcePackageNames(pkgConditions, sourceType, sourceSnapshot.distName, function (err, pkgNames) {
        pkgNames = _.compact(pkgNames);
        var uPkgs = Snapshot.getUpdatedPackages(sourceSnapshot, currSnapshot);
        var result = uPkgs.filter(function (pkg) {
            return pkg && (pkgNames.indexOf(pkg.name) >= 0);
        });
        callback(err, result);
    });
}


function getSourcePackageNames(pkgConditions, srcType, srcDistName, callback) {
    var pkgNames = [];
    async.eachSeries(pkgConditions,
        function (cond, cb) {
            if (srcType === 'INTERNAL' && cond.type === 'project') {
                getPackageNamesFromProjectName(cond.name, srcDistName, function (err, results) {
                    if (!err && results && _.isArray(results)) {
                        pkgNames = pkgNames.concat(results);
                    }
                    cb(err);
                });
            } else if (cond.type === 'package') {
                pkgNames.push(cond.name);
                cb(null);
            } else {
                // ignore
                cb(null);
            }
        },
        function (err) {
            callback(err, pkgNames);
        });
}


function getPackageNamesFromProjectName(projectName, srcDistName, callback) {
    // filter using binary project
    dibs.rpc.datamgr.searchProjects({
        distName: srcDistName,
        name: projectName
    }, function (err, prjs) {
        if (err) {
            callback(err, null);
        } else if (prjs.length === 0) {
            callback(null, []);
        } else {
            var prj = prjs[0];
            if (prj.options && prj.options.PKG_NAME) {
                callback(null, [prj.options.PKG_NAME]);
            } else if (prj.options && prj.options.LAST_BUILD_INFO) {
                var result = [];
                for (var envName in prj.options.LAST_BUILD_INFO) {
                    var bInfo = prj.options.LAST_BUILD_INFO[envName];
                    if (bInfo && _.isObject(bInfo)) {
                        result = result.concat(bInfo.packageNames);
                    }
                }
                callback(null, _.uniq(result));
            } else {
                callback(null, []);
            }
        }
    });
}


function checkPackageIntegrity(updatedPkgs, currSnapshot, callback) {
    // gather all deps
    var deps = Package.getAllDepsOfPackages(updatedPkgs);

    var unmetDeps = [];
    deps.forEach(function (dep) {
        if (!currSnapshot) {
            unmetDeps.push(dep);
        } else if (dep.os && (!currSnapshot.osPackages[dep.os] || !currSnapshot.osPackages[dep.os][dep.name])) {
            if (currSnapshot.archivePackages.indexOf(dep.name) < 0) {
                unmetDeps.push(dep);
            }
        } else if (!dep.os && currSnapshot.archivePackages.indexOf(dep.name) < 0) {
            unmetDeps.push(dep);
        } else {
            // matched
        }
    });

    if (unmetDeps.length > 0) {
        callback(
            new DError('TREPO026', {
                errMsg: unmetDeps.map(function (dep) {
                    return dep.name + '(' + dep.os + ')';
                }).join(',')
            }));
    } else {
        callback(null);
    }
}


function getSnapshotFromInternalRepository(distributionName, snapshotName, callback) {
    dibs.rpc.repo.searchSnapshots({
        name: snapshotName || null,
        repoType: 'tizen',
        distName: distributionName
    }, function (err, snapshotList) {
        if (err) {
            callback(err, null);
        } else if (snapshotList.length === 0) {
            callback(new DError('TREPO023', { ref: 'latest' }), null);
        } else {
            callback(null, snapshotList[0]);
        }
    });
}


function getExternalRepository(url, distributionName, snapshotName) {
    return RemoteRepo.createRemoteRepo(url, {
        distName: distributionName,
        snapshotName: snapshotName || null
    });
}


function getSnapshotFromExternalRepository(externalRepo, distributionName, snapshotName, callback) {
    externalRepo.open(null, function (err) {
        if (err) {
            callback(err, null);
        } else {
            externalRepo.searchDistributions({
                distName: distributionName
            }, function (err, dists) {
                if (err) {
                    callback(err, null);
                } else {
                    if (snapshotName) {
                        callback(null, dists[0].snapshots[snapshotName]);
                    } else {
                        callback(null, dists[0].latestSnapshot);
                    }
                }
            });
        }
    });
}


function downloadPackagesFromInternalRepo(packages, sourceSnapshot, monitor, callback) {
    async.mapLimit(packages, 5,
        function (pkg, cb) {
            monitor.updateProgress(' - downloading...' + pkg.name + '(' + pkg.os + ')');
            dibs.rpc.repo.downloadRemotePackage(pkg.name, {
                repoType: 'tizen',
                os: pkg.os,
                distName: sourceSnapshot.distName,
                snapshotName: sourceSnapshot.name
            }, function (err, rpath) {
                if (err) {
                    monitor.updateProgress(' - downloading fail... ' + pkg.name + '(' + pkg.os + ') from ' + sourceSnapshot.name);
                    return cb(err, null);
                }
                monitor.updateProgress(' - downloaded...' + pkg.name + '(' + pkg.os + ')');
                cb(null, rpath);
            });
        },
        function (err, rpaths) {
            callback(err, rpaths);
        });
}

function downloadPackageFromExternalRepo(server, repoServer, package, distributionName, snapshotName, monitor, callback) {
    async.waterfall([
        // create temp directory
        function (cb) {
            utils.genTemp(cb);
        },
        // download package
        function (tempDir, cb) {
            repoServer.downloadPackage(package.name, {
                repoType: 'tizen',
                distName: distributionName,
                snapshotName: snapshotName,
                os: package.os,
                targetDir: tempDir
            }, function (err, path) {
                if (err) {
                    monitor.updateProgress(' - downloading fail... ' + package.name + '(' + package.os + ') from ' + snapshotName);
                    return cb(err);
                }
                monitor.updateProgress(' - downloading done...' + path);
                cb(null, path);
            });
        }, function (path, cb) {
            server.dfsAddFile(null, path, {
                lifetime: 60 * 60 * 1000
            }, function (err, dfsPath) {
                if (err) {
                    monitor.updateProgress(' - Register DFS fail...' + err);
                    return cb(err);
                }
                monitor.updateProgress(' - Register DFS done...' + dfsPath);
                cb(null, dfsPath);
            });
        }
    ], function (err, dfsPath) {
        callback(err, dfsPath);
    });
}

function downloadPackagesFromExternalRepo(server, repoServer, packages, sourceSnapshot, monitor, callback) {
    async.mapLimit(packages, 5,
        function (pkg, cb) {
            monitor.updateProgress('- downloading...' + pkg.name + '(' + pkg.os + ')');
            downloadPackageFromExternalRepo(
                server,
                repoServer,
                pkg,
                sourceSnapshot.distName,
                sourceSnapshot.name,
                monitor, cb);
        },
        function (err, rpaths) {
            callback(err, rpaths);
        });
}

function upload(job, jobWorkPath, monitor, force, callback) {
    monitor.updateProgress('Uploading... ');

    var repoServers = dibs.getServersByType('repo');
    if (repoServers.length === 0) {
        var error = new DError('TIZENGITJOB007');
        callback(error);
        return;
    }

    // register the files of all sub-jobs
    if (job.resultFiles.length !== 0) {
        // NOTE. sync must consider compatibility
        repoServers[0].registerRemotePackages(job.resultFiles, {
            repoType: 'tizen',
            distName: job.distName,
            forceUpload: force,
            noChangeLogCheck: true,
            buildInfo: job.buildInfo,
            progress: function (msg) {
                monitor.updateProgress(' - ' + msg);
            }
        }, function (err) {
            callback(err);
        });
    } else {
        callback(null);
    }
}

/**
 * Execute Tizen sync distribution job
 * - it wait for sub-jobs finished & upload them
 * @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/tizenGit/project
 */
function executeJob(job, options, callback) {
    var jobWorkPath = options.jobWorkPath;
    var monitor = options.monitor;
    var server = dibs.getServer(options.parentServerId);
    var currSnapshot = null;
    var sourceSnapshot = null;
    var externalRepository = null;
    var updatedPkgs = null;

    async.waterfall([
        function (cb) {
            monitor.updateProgress('Get snapshot from current repository');
            getSnapshotFromInternalRepository(job.distName, null, function (err, snapshot) {
                if (err && err.errno === 'TREPO023') {
                    return cb(null, null);
                } else if (err) {
                    return cb(err);
                } else {
                    return cb(null, snapshot);
                }
            });
        },
        function (output, cb) {
            currSnapshot = output;
            monitor.updateProgress(' - ' + (currSnapshot === null ? null : currSnapshot.name));

            if (job.options.SOURCE_TYPE === 'EXTERNAL') {
                monitor.updateProgress('Get snapshot from external source repository');
                monitor.updateProgress(' - getting external repository');
                externalRepository = getExternalRepository(
                    job.options.SOURCE_URL,
                    job.options.SOURCE_DIST_NAME,
                    job.options.SOURCE_SNAPSHOT_NAME);
                monitor.updateProgress(' - getting snapshot');
                getSnapshotFromExternalRepository(
                    externalRepository,
                    job.options.SOURCE_DIST_NAME,
                    job.options.SOURCE_SNAPSHOT_NAME, cb);
            } else {
                monitor.updateProgress('Get snapshot from internal source repository');
                monitor.updateProgress(' - getting snapshot');
                getSnapshotFromInternalRepository(
                    job.options.SOURCE_DIST_NAME,
                    job.options.SOURCE_SNAPSHOT_NAME, cb);
            }
        },
        function (output, cb) {
            if (!output.distName) {
                output.distName = job.options.SOURCE_DIST_NAME;
            }
            sourceSnapshot = output;
            monitor.updateProgress(' - ' + sourceSnapshot.name);

            monitor.updateProgress('Check update packages');
            checkUpdatedPackages(currSnapshot,
                sourceSnapshot,
                job.options.PACKAGES,
                job.options.SOURCE_TYPE, cb);
        },
        function (pkgs, cb) {
            updatedPkgs = pkgs;
            if (updatedPkgs.length > 0) {
                monitor.updateProgress(' - ' + updatedPkgs.map(function (e) {
                    return e.name + '(' + e.os + ')';
                }).join(' '));
            }

            // check integrity before registered
            getInternalDistribution(job.options.SOURCE_DIST_NAME, cb);
        },
        function (distribution, cb) {
            if (distribution.options.SKIP_INTEGRITY_CHECK) {
                cb(null);
            } else {
                monitor.updateProgress('Checking package integrity...');
                checkPackageIntegrity(updatedPkgs, currSnapshot, cb);
            }
        },
        function (cb) {
            if (job.options.SOURCE_TYPE === 'EXTERNAL') {
                monitor.updateProgress('Downloading packages from external repository...');
                downloadPackagesFromExternalRepo(server, externalRepository, updatedPkgs, sourceSnapshot, monitor, cb);
            } else {
                monitor.updateProgress('Downloading packages from internal repository...');
                downloadPackagesFromInternalRepo(updatedPkgs, sourceSnapshot, monitor, cb);
            }
        },
        function (rpaths, cb) {
            monitor.updateProgress('Fill the build information...');
            job.resultFiles = rpaths;
            job.snapshot = currSnapshot;
            job.options.packages = updatedPkgs;

            job.buildInfo = [];
            _.each(rpaths, function (dfsPath) {
                var pInfo = TizenUtils.getInfoFromPackageFileName(path.basename(dfsPath));
                if (pInfo && pInfo.os && pInfo.name) {
                    var build = {};
                    var regex = /__[A-Z]/;
                    _.each(sourceSnapshot.osPackages[pInfo.os][pInfo.name].options, function (value, key) {
                        var match = regex.exec(key);
                        if (match) {
                            build[match.input] = value;
                        }
                    });
                    job.buildInfo.push({
                        rpath: dfsPath,
                        build: build
                    });
                }
            });
            cb(null);
        },
        function (cb) {
            monitor.updateProgress('Checking reverse build...');
            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) {
            monitor.updateProgress('Checking uploading...');
            monitor.updateProgress(' - uploading...');
            if (job.resultFiles.length > 0) {
                upload(job, jobWorkPath, monitor, false, cb);
            } else {
                monitor.updateProgress('WARNING! Nothing to be uploaded!');
                cb(null);
            }
        },
        function (cb) {
            monitor.updateProgress('Terminating pull-packages job...', cb);
        }], function (err) {
        // NOTE. MUST strip unnecessary information for reducing object size
        //       if not, DNODE RPC cannnot receive callback
        TizenCommon.stripJob(job);

        callback(err, job);
    });
}


function checkMutualExclusiveJob(srcJob, tarJob) {
    if (srcJob.distName !== tarJob.distName) {
        return false;
    }
    return true;
}


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);
        });
}
