/**
 * snapshot.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
**/

var async = require('async');
var _ = require('underscore');
// var util = require('util');

var DError = require('../../core/exception.js');
var webSnapshot = require('./lib/web-snapshot-api.js');
var utils = require('../../lib/utils.js');
var util = require('../org.tizen.ts.base.common/util.js');


module.exports = TizenSnapshot;

function TizenSnapshot(name) {
    var self = this;
    this.name = name;
    this.type = 'auto';
    this.time = null;
    this.path = null;
    this.changes = null;
    this.archivePackages = [];
    this.osPackages = {};
    this.isRemote = false;
    this.origin = null;
    this.uploader = null;
    this.newPkgs = [];
    this.updatedPkgs = [];
    this.removedPkgs = [];

    this.toString = function () {
        var originStr = '';
        if (self.origin) {
            originStr = 'origin : ' + self.origin + '\n';
        }
        return 'name : ' + self.name + '\n' +
            'time : ' + self.time + '\n' +
            'type : ' + self.type + '\n' +
            originStr +
            'path : ' + self.path + '\n';
    };
}


TizenSnapshot.prototype.downloadPackages = function (distPath, downloadPkgs, targetDir, monitor, callback) {

    if (utils.isURL(distPath)) {
        async.mapLimit(downloadPkgs, 4, function (pkg, cb) {
            monitor.updateProgress(' # - Download package - \'' + (typeof pkg === 'string' ? pkg : pkg.name + ' (' + pkg.os + ')') + '\'');
            webSnapshot.downloadPackages(distPath, pkg, targetDir, cb);
        },
        function (err, results) {
            callback(err, results);
        });
    } else {
        async.mapLimit(downloadPkgs, 1, function (pkg, cb) {
            monitor.updateProgress(' # - Get package - \'' + (typeof pkg === 'string' ? pkg : pkg.name) + '\'');

            var downloadPath;
            if (pkg.name === undefined) {
                // archive
                downloadPath = distPath + '/source/' + pkg;
            } else {
                // binary
                downloadPath = distPath + pkg.path;
            }
            cb(null, downloadPath);
        },
        function (err, results) {
            callback(err, results);
        });
    }
};


TizenSnapshot.prototype.getAllInstallDependentPackages = function (pkgList, options, monitor, callback) {
    var self = this;
    var results = [];

    var skipIntegrityCheck = false;
    if (options && options.skipIntegrityCheck) {
        skipIntegrityCheck = true;
    }

    async.eachSeries(pkgList, function (pkg, cb) {
        getInstallDependencies([pkg], self, {
            installOrder: false,
            skipIntegrityCheck: skipIntegrityCheck
        }, monitor, function (err1, rst) {
            if (err1) {
                monitor.updateProgress({ log: err1, logType: 'error' });
            }
            results = _.union(results, rst, [pkg]);
            cb(err1);
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(err, results);
        }
    });
};


TizenSnapshot.prototype.getAllInstallDependentPackagesWithExcludeList = function (pkgList, excludePkgList, monitor, callback) {
    var self = this;
    var results = [];

    async.eachSeries(pkgList, function (pkg, cb) {
        getInstallDependenciesWithExcludeList([pkg], self, excludePkgList, {
            installOrder: false
        }, function (err1, rst) {
            results = _.union(results, rst, [pkg]);
            cb(err1);
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(err, results);
        }
    });
};


function getInstallDependencies(pkgs, snapshot, options, monitor, callback) {

    getInstallDependentPackagesInternal(pkgs, [], snapshot, options, monitor, function (err, resultPkgs) {
        if (err) {
            monitor.updateProgress({ log: err, logType: 'error' });
            return callback(err, null);
        } else {
            for (var i = 0; i < pkgs.length; i++) {
                resultPkgs.splice(pkgs.indexOf(pkgs[i]), 1);
            }
            if (options.installOrder === true) {
                util.package.sortPackagesByInstallOrder(resultPkgs, [], callback);
            } else {
                return callback(null, resultPkgs);
            }
        }
    });
}

function getInstallDependentPackagesInternal(srcPkgs, depPkgs, snapshot, options, monitor, callback) {
    if (srcPkgs.length === 0) {
        callback(null, depPkgs);
        return;
    }

    // add sources to depPkgs
    for (var i = 0; i < srcPkgs.length; i++) {
        if (depPkgs.indexOf(srcPkgs[i]) === -1) {
            depPkgs.push(srcPkgs[i]);
        }
    }

    var newSrcPkgs = [];
    for (var j = 0; j < srcPkgs.length; j++) {
        var pkg = srcPkgs[j];

        var depList = pkg.installDepList;
        for (var k = 0; k < depList.length; k++) {
            var dep = depList[k];
            var os = (dep.os === undefined) ? pkg.os : dep.os;

            if (snapshot.osPackages[os] === undefined) {
                callback(new DError('TREPO019', {
                    os: os
                }), null);
                return;
            }
            var depPkg = snapshot.osPackages[os][dep.packageName];
            if (depPkg === undefined) {
                if (options && options.skipIntegrityCheck) {
                    monitor.updateProgress(' # - Skip check ' + dep.packageName + ' because of skipIntegrityCheck option');
                    continue;
                } else {
                    callback(new DError('TREPO020', {
                        pkg: dep.packageName
                    }), null);
                    return;
                }
            } else {
                if (depPkgs.indexOf(depPkg) === -1 && newSrcPkgs.indexOf(depPkg) === -1) {
                    newSrcPkgs.push(depPkg);
                }
            }
        }
    }

    // call with new src pkgs
    getInstallDependentPackagesInternal(newSrcPkgs, depPkgs, snapshot, options, monitor, callback);
}


function getInstallDependenciesWithExcludeList(pkgList, snapshot, excludePkgList, options, callback) {

    getInstallDependentPackagesWithExcludeListInternal(pkgList, [], snapshot, excludePkgList, function (err, resultPkgs) {
        if (!err) {
            for (var i = 0; i < pkgList.length; i++) {
                resultPkgs.splice(pkgList.indexOf(pkgList[i]), 1);
            }
            if (options.installOrder === true) {
                util.package.sortPackagesByInstallOrder(resultPkgs, [], callback);
            } else {
                return callback(null, resultPkgs);
            }
        } else {
            return callback(err, null);
        }
    });
}


function getInstallDependentPackagesWithExcludeListInternal(srcPkgs, depPkgs, snapshot, excludeList, callback) {
    if (srcPkgs.length === 0) {
        callback(null, depPkgs);
        return;
    }

    // add sources to depPkgs
    for (var i = 0; i < srcPkgs.length; i++) {
        if (depPkgs.indexOf(srcPkgs[i]) === -1) {
            depPkgs.push(srcPkgs[i]);
        }
    }

    var newSrcPkgs = [];
    for (var j = 0; j < srcPkgs.length; j++) {
        var pkg = srcPkgs[j];

        var depList = pkg.installDepList;
        for (var k = 0; k < depList.length; k++) {
            var dep = depList[k];
            var os = (dep.os === undefined) ? pkg.os : dep.os;

            if (snapshot.osPackages[os] === undefined) {
                callback(new DError('TREPO019', {
                    os: os
                }), null);
                return;
            }
            var depPkg = snapshot.osPackages[os][dep.packageName];
            if (depPkg === undefined) {
                callback(new DError('TREPO020', {
                    pkg: dep.packageName
                }), null);
                return;
            }

            if (depPkgs.indexOf(depPkg) === -1 && newSrcPkgs.indexOf(depPkg) === -1) {
                newSrcPkgs.push(depPkg);
            }
        }
    }

    // call with new src pkgs
    getInstallDependentPackagesWithExcludeListInternal(_.difference(newSrcPkgs, excludeList), depPkgs, snapshot, excludeList, callback);
}
