/**
 * util-package.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 parser = require('./parser');
var _ = require('underscore');
var async = require('async');
var fs = require('fs');
var path = require('path');

var Zip = require('../dibs.core/zip.js');
var DError = require('../../core/exception.js');
var Package = require('./package.js');

module.exports.compareVersion = compareVersion;
module.exports.getPackage = getPackage;
module.exports.getPkgListFromString = getPkgListFromString;
module.exports.getPkgListFromFile = getPkgListFromFile;
module.exports.getPkgInfoFromPkgFile = getPkgInfoFromPkgFile;
module.exports.getPackageNameList = getPackageNameList;
module.exports.sortPackagesByInstallOrder = sortPackagesByInstallOrder;
module.exports.sortPackagesByPartialInstallOrder = sortPackagesByPartialInstallOrder;
module.exports.getBuildDepsOfPackages = getBuildDepsOfPackages;
module.exports.getInstallDepsOfPackages = getInstallDepsOfPackages;
module.exports.getSourceDepsOfPackages = getSourceDepsOfPackages;
module.exports.getAllDepsOfPackages = getAllDepsOfPackages;
module.exports.hasSamePackages = hasSamePackages;
module.exports.hasBuildDependency = hasBuildDependency;
module.exports.pkgListString = pkgListString;
module.exports.getInfoFromPackageFileName = getInfoFromPackageFileName;
module.exports.getPackageFileNameFromInfo = getPackageFileNameFromInfo;

// var PACKAGE_MANIFEST = 'pkginfo.manifest';

function getCustom(object) {
    var customList = _.select(_.keys(object), function (key) {
        return (key.substring(0, 2) === 'C-');
    });
    var result = {};
    _.each(customList, function (custom) {
        result[custom] = object[custom];
    });
    return result;
}

function compareVersion(srcVer, tarVer) {
    var srcVerArray = srcVer.split('-')[0].split('.');
    var tarVerArray = tarVer.split('-')[0].split('.');

    var srcLen = srcVerArray.length;
    var tarLen = tarVerArray.length;
    var maxLen = srcLen;
    var gab = null;

    if (srcLen > tarLen) {
        gab = srcLen - tarLen;
        for (var i = 0; i < gab; i++) {
            tarVerArray.push('0');
        }
    } else if (tarLen > srcLen) {
        gab = tarLen - srcLen;
        for (var j = 0; j < gab; j++) {
            srcVerArray.push('0');
        }
        maxLen = tarLen;
    }

    for (var k = 0; k < maxLen; k++) {
        var srcVal = parseInt(srcVerArray[k], 10);
        var tarVal = parseInt(tarVerArray[k], 10);
        if (srcVal > tarVal) {
            return 1;
        } else if (tarVal > srcVal) {
            return -1;
        }
    }

    return 0;
}


function depString(dep) {
    var string = dep.packageName;
    if (dep.comp) {
        string = string + ' (' + dep.comp + ' ' + dep.baseVersion + ')';
    }
    if (dep.os) {
        string = string + ' [' + dep.os + ']';
    }
    return string;
}

function depListString(depList) {
    return _.map(depList, function (dep) {
        return depString(dep);
    }).join(', ');
}

/**
 * @function toString
 * @memberof module:models/tizen-common/package
 */
function pkgString(pkg) {
    var string = 'Package : ' + pkg.name;
    if (pkg.label) {
        string += '\nLabel : ' + pkg.label;
    }
    if (pkg.version) {
        string += '\nVersion : ' + pkg.version;
    }
    if (pkg.osList) {
        string += '\nOS : ' + pkg.osList.join(', ');
    }
    if (pkg.buildHostOS) {
        string += '\nBuild-host-os : ' + pkg.buildHostOS;
    }
    if (pkg.maintainer) {
        string += '\nMaintainer : ' + pkg.maintainer.join(', ');
    }
    if (pkg.attr) {
        string += '\nAttribute : ' + pkg.attr;
    }
    if (pkg.installDepList.length > 0) {
        string += '\nInstall-dependency : ' + depListString(pkg.installDepList);
    }
    if (pkg.buildDepList.length > 0) {
        string += '\nBuild-dependency : ' + depListString(pkg.buildDepList);
    }
    if (pkg.srcDepList.length > 0) {
        string += '\nSource-dependency : ' + depListString(pkg.srcDepList);
    }
    if (pkg.conflicts) {
        string += '\nConflicts : ' + pkg.conflicts;
    }
    if (pkg.source) {
        string += '\nSource : ' + pkg.source;
    }
    if (pkg.srcPath) {
        string += '\nSrc-path : ' + pkg.srcPath;
    }
    if (pkg.path) {
        string += '\nPath : ' + pkg.path;
    }
    if (pkg.origin) {
        string += '\nOrigin : ' + pkg.origin;
    }
    if (pkg.checksum) {
        string += '\nSHA256 : ' + pkg.checksum;
    }
    if (pkg.size) {
        string += '\nSize : ' + pkg.size;
    }
    if (pkg.uncompSize) {
        string += '\nUncompressed-size : ' + pkg.uncompSize;
    }
    _.each(_.keys(pkg.custom), function (key) {
        string += '\n' + key + ' : ' + pkg.custom[key].replace(/\n\n/g, '\n.\n').replace(/\n/g, '\n ');
    });
    if (pkg.desc) {
        string += '\nDescription : ' + pkg.desc.replace(/\n\n/g, '\n.\n').replace(/\n/g, '\n ');
    }
    string += '\n';
    return string;
}


function pkgListString(pkgList) {
    return _.map(pkgList, function (pkg) {
        return pkgString(pkg);
    }).join('\n');
}


function getPackage(object) {
    return new Package(object);
}

function getPkgListFromString(string, callback) {
    parser.manifestStringParser(string, function (err, data) {
        if (err) {
            callback(new Error('Parse ERROR : ' + string + ' => [ ' + err.message + '=>' + err.stack + ']'), data);
        } else {
            callback(err, _.map(data, function (pkg) {
                return new Package(pkg);
            }));
        }
    });
}


function getPkgListFromFile(filePath, callback) {
    parser.manifestParser(filePath, function (err, data) {
        var result = [];
        if (!err) {
            result = _.map(data, function (pkg) {
                return new Package(pkg);
            });
        }
        callback(err, result);
    });
}


function getPkgInfoFromPkgFile(pkgFile, callback) {
    fs.exists(pkgFile, function (exist) {
        if (exist) {
            async.waterfall([
                function (cb) {
                    Zip.unzipAFileString(pkgFile, 'pkginfo.manifest', function (err, data) {
                        if (err) {
                            cb(new DError('TIZENCOMMON001', { pkg: pkgFile }, err), []);
                        } else {
                            cb(null, data);
                        }
                    });
                },
                function (data, cb) {
                    getPkgListFromString(data, function (err, pkgs) {
                        if (err) {
                            cb(new DError('TIZENCOMMON002', err), []);
                        } else {
                            cb(null, pkgs);
                        }
                    });
                },
                function (pkgs, cb) {
                    var os = getInfoFromPackageFileName(path.basename(pkgFile)).os;
                    _.each(pkgs, function (pkg, idx) {
                        if (pkg.osList.indexOf(os) > 0) {
                            pkgs[idx].os = os;
                            pkgs[idx].osList = _.union([os], _.without(pkgs[idx].osList, os));
                        }
                    });
                    Zip.unzipAFileString(pkgFile, 'changelog', function (err, data) {
                        if (!err) {
                            parser.changelogStringParser(data, function (err, result) {
                                if (err) {
                                    cb(new DError('TIZENCOMMON003', err), pkgs);
                                } else {
                                    pkgs.forEach(function (e) { e.changelog = result; });
                                    cb(null, pkgs);
                                }
                            });
                        } else {
                            // ignore if no changelog file
                            cb(null, pkgs);
                        }
                    });
                }
            ],
            function (err, pkgs) {
                callback(err, pkgs[0]);
            });
        } else {
            // console.log('error: ' + pkgFile);
            callback(new DError('TIZENCOMMON004', {pkg: pkgFile}));
        }
    });
}


function getPackageNameList(pkgList) {
    return _.map(pkgList, function (pkg) {
        return pkg.name;
    });
}


function getInfoFromPackageFileName(fileName) {
    var regExp = /(.*)_(.*)_(.*)\.zip/;

    var result = regExp.exec(path.basename(fileName));
    if (!result) {
        return null;
    }
    if (result[1] === '' || result[2] === '' || result[3] === '') {
        return null;
    }

    return {
        name: result[1],
        version: result[2],
        os: result[3]
    };
}


function getPackageFileNameFromInfo(name, version, os) {
    return name + '_' + version + '_' + os + '.zip';
}


function sortPackagesByInstallOrder(srcPkgs, installedPkgs, callback) {
    var result = [];
    var prevLength = -1;

    // loop unless no more increment of result
    while (result.length !== prevLength) {
        prevLength = result.length;

        for (var i = 0; i < srcPkgs.length; i++) {
            var srcPkg = srcPkgs[i];

            // skip if already exists
            if (result.indexOf(srcPkg) !== -1) {
                continue;
            }

            // check all dependency met
            var isDepMet = true;
            for (var j = 0; j < srcPkg.installDepList.length; j++) {
                var dep = srcPkg.installDepList[j];
                var os = (dep.os === undefined) ? [srcPkg.os] : dep.os;

                // check installed pkgs
                var depPkgs1 = installedPkgs.filter(function (e) {
                    return (e.name === dep.packageName && e.os === os);
                });
                // check result pkgs
                var depPkgs2 = result.filter(function (e) {
                    return (e.name === dep.packageName && e.os === os);
                });

                if (depPkgs1.length === 0 && depPkgs2.length === 0) {
                    isDepMet = false;
                    break;
                }
            }

            // if met, add it
            if (isDepMet) {
                result.push(srcPkg);
            }
        }
    }

    if (result.length !== srcPkgs.length) {
        callback(new Error('Install dependencies are not met'), null);
    } else {
        callback(null, result);
    }
}


function sortPackagesByPartialInstallOrder(srcPkgs) {
    var result = [];


    // loop unless no more increment of result
    var inputPkgs = _.clone(srcPkgs);
    while (true) {

        var nextPkgs = [];
        inputPkgs.forEach(function (inputPkg) {
            // check if there are install dependent pkgs in list
            var matched = inputPkgs.filter(function (pkg) {
                return inputPkg.installDepList.filter(function (dep) {
                    return (dep.packageName === pkg.name);
                }).length > 0;
            });
            if (matched.length === 0) {
                nextPkgs.push(inputPkg);
            }
        });

        if (nextPkgs.length > 0) {
            nextPkgs.forEach(function (e) {
                result.push(e);
                inputPkgs.splice(inputPkgs.indexOf(e), 1);
            });
        } else {
            break;
        }
    }

    return result;
}


function getBuildDepsOfPackages(pkgs, targetOS) {
    var result = [];

    for (var i = 0; i < pkgs.length; i++) {
        var pkg = pkgs[i];
        var os = pkg.os;
        if (targetOS) {
            os = targetOS;
        }

        if (pkg.osList.indexOf(os) === -1) {
            continue;
        }

        if (pkg.buildDepList) {
            for (var j = 0; j < pkg.buildDepList.length; j++) {
                var dep = pkg.buildDepList[j];
                var depOS = (!dep.os) ? os : dep.os;
                var filtered = result.filter(function (depPkg) {
                    return depPkg.name === dep.packageName && depPkg.os === os;
                });
                if (filtered.length === 0) {
                    result.push({
                        name: dep.packageName,
                        os: depOS
                    });
                }
            }
        }
    }

    return result;
}


function getInstallDepsOfPackages(pkgs, targetOS) {
    var result = [];

    for (var i = 0; i < pkgs.length; i++) {
        var pkg = pkgs[i];
        var os = pkg.os;
        if (targetOS) {
            os = targetOS;
        }

        if (pkg.osList.indexOf(os) === -1) {
            continue;
        }

        if (pkg.installDepList) {
            for (var j = 0; j < pkg.installDepList.length; j++) {
                var dep = pkg.installDepList[j];
                var depOS = (!dep.os) ? os : dep.os;
                var filtered = result.filter(function (depPkg) {
                    return depPkg.name === dep.packageName && depPkg.os === os;
                });
                if (filtered.length === 0) {
                    result.push({
                        name: dep.packageName,
                        os: depOS
                    });
                }
            }
        }
    }

    return result;
}


function getSourceDepsOfPackages(pkgs) {
    var result = [];

    for (var i = 0; i < pkgs.length; i++) {
        var pkg = pkgs[i];

        if (pkg.srcDepList) {
            for (var j = 0; j < pkg.srcDepList.length; j++) {
                var dep = pkg.srcDepList[j];
                var filtered = result.filter(function (depPkg) {
                    return depPkg.name === dep.packageName;
                });
                if (filtered.length === 0) {
                    result.push({
                        name: dep.packageName
                    });
                }
            }
        }
    }

    return result;
}


function getAllDepsOfPackages(pkgs) {
    var depList = [];
    _.each(pkgs, function (pkg) {
        if (pkg.buildDepList) {
            depList = depList.concat(pkg.buildDepList);
            depList.forEach(function (dep) {
                if (!dep.os) {
                    dep.os = pkg.os;
                }
            });
        }
        if (pkg.installDepList) {
            depList = depList.concat(pkg.installDepList);
            depList.forEach(function (dep) {
                if (!dep.os) {
                    dep.os = pkg.os;
                }
            });
        }
        if (pkg.srcDepList) {
            depList = depList.concat(pkg.srcDepList);
        }
    });

    var selfsupplyList = [];
    _.each(pkgs, function (pkg) {
        selfsupplyList = _.union(selfsupplyList, _.select(depList, function (dep) {
            if (pkg.name) { // install, build dependencies
                return (dep.packageName === pkg.name && pkg.osList.indexOf(dep.os) > -1);
            } else { // source dependencies
                return (dep.packageName === pkg);
            }
        }));
    });

    return _.difference(depList, selfsupplyList).map(function (dep) {
        return {
            name: dep.packageName,
            os: dep.os
        };
    });
}

function hasSamePackages(srcPkgs, tarPkgs) {
    return srcPkgs.filter(function (e1) {
        return tarPkgs.filter(function (e2) {
            return (e2.name === e1.name && _.intersection(e1.osList, e2.osList).length > 0 && e2.version === e1.version);
        }).length > 0;
    }).length > 0;
}


function hasBuildDependency(srcPkgs, tarPkgs, os) {
    var deps = getBuildDepsOfPackages(srcPkgs, os);

    var result = [];
    result = getBuildDepPkgsMatched(deps, tarPkgs);
    if (result.length > 0) {
        return true;
    }

    // check source dependency
    deps = getSourceDepsOfPackages(srcPkgs);
    result = deps.filter(function (dep) {
        var matches = tarPkgs.filter(function (pkg) {
            return (dep.name === pkg);
        });
        return matches.length > 0;
    });
    if (result.length > 0) {
        return true;
    }

    return false;
}

function getBuildDepPkgsMatched(deps, pkgs) {
    if (deps && pkgs) {
        return deps.filter(function (dep) {
            var matches = pkgs.filter(function (pkg) {
                return (dep.name === pkg.name &&
                    pkg.osList.indexOf(dep.os) !== -1);
            });
            return matches.length > 0;
        });
    } else {
        return [];
    }
}
