/**
 * ts-pull.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
**/

'use strict';

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

var TsRepo = require('../../org.tizen.ts.cli.repository/ts-repo.js');
var util = require('../../org.tizen.ts.base.common/util.js');
var utils = require('../../../lib/utils.js');
var Monitor = require('../../../lib/monitor.js');

// synchronize packages between local and web repo
module.exports.pull = function (argv, log) {
    var webRepoURL = argv.rr;
    var localFsRepo = argv.lr;
    var isForce = argv.force;
    var targetOS = argv.os;
    var pkgCachePath = argv.cache;

    if (!localFsRepo) {
        log.error('Required --local-repo option!');
        log.error('Usage: ts-cli pull --remote-repo <remote repository URL> --local-repo <repository path> [--force] [--os <os name | all>] [--base-snapshot <snapshot name>]');
        process.exit(-1);
    } else {
        // convert localFsRepo into absolute path in case of relative
        localFsRepo = path.resolve(localFsRepo);
    }

    if (!webRepoURL) {
        log.error('Required --remote-repo option!');
        log.error('Usage: ts-cli pull --remote-repo <remote repository URL> --local-repo <repository path> [--force] [--os <os name | all>] [--base-snapshot <snapshot name>]');
        process.exit(-1);
    }
    var destDistName = path.basename(localFsRepo);
    var srcDistName = path.basename(webRepoURL);

    var srcDist = null;
    var destDist = null;
    var srcSnapshot = null;
    var destSnapshot = null;

    log.info('Tizen Studio Pull Process - Start');

    var monitor = new Monitor({
        onProgress: function (info, cb) {
            if (info.logType) {
                log[info.logType](info.log);
            }
            cb(null);
        }
    });

    var destRepo = new TsRepo({
        location: localFsRepo
    });

    var srcRepo = new TsRepo({
        location: webRepoURL
    });

    async.waterfall([
        function (cb) {
            log.info('# - Open repository: \'' + localFsRepo + '\'');
            destRepo.load(function (err) {
                if (err) {
                    log.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            log.info('# - Open repository: \'' + webRepoURL + '\'');
            srcRepo.load(function (err) {
                if (err) {
                    log.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            log.info('# - Check updated packages');
            srcDist = srcRepo.distributions[srcDistName];
            destDist = destRepo.distributions[destDistName];

            if (!srcDist) {
                return cb(new Error('# Failed to get \'' + srcDistName + '\''));
            }

            if (!destDist) {
                return cb(new Error('# - Failed to get \'' + destDist + '\''));
            }

            srcSnapshot = srcDist.latestSnapshot;
            var pullSnapshot = argv['base-snapshot'];
            if (pullSnapshot) {
                srcDist.searchSnapshots({ distPath: srcDist.path, name: pullSnapshot }, function (err, results) {
                    if (!err) {
                        srcSnapshot = results[0];
                    }
                    cb(err);
                });
            } else {
                cb(null);
            }
        },
        function (cb) {
            if (!srcSnapshot) {
                return cb(new Error('# - Failed to get \'' + srcSnapshot + '\''));
            }
            log.info('# - Use remote snapshot \'' + srcSnapshot.name + '\' for package-pull');

            destSnapshot = destDist.latestSnapshot;
            if (!destSnapshot) {
                log.warn('# - Any snapshot does not exist in \'' + destDist.name + '\' distribution.');
            }

            var options;
            if (targetOS) {
                if (targetOS === 'all') {
                    targetOS = [ 'ubuntu-32', 'ubuntu-64', 'windows-32', 'windows-64', 'macos-64' ];
                    // targetOS = util.getEnvironments('Tizen-Source');
                }
                options = { osList: targetOS };
            }

            if (pkgCachePath) {
                // TODO: need to validate cached packages.
                fs.readdir(pkgCachePath, function (err, pkgs) {
                    var results = _.map(pkgs, function (pkg) {
                        return path.join(pkgCachePath, pkg);
                    });
                    cb(null, results);
                });
            } else {
                getUpdatedPackages(srcDist.path, srcSnapshot, destDist.path, destSnapshot, options, cb);
            }
        },
        function (updatedPkgs, cb) {
            if (updatedPkgs.length === 0) {
                log.warn('# - Stop pulling packages because of no update between remote(' + srcSnapshot.name + ') and local(' + destSnapshot.name + ')');
                return cb(null);
            }

            var archives = [];
            var binaryPkgs = [];

            _.each(updatedPkgs, function (pkg) {
                if (_.isString(pkg)) {
                    archives.push(path.basename(pkg));
                } else {
                    binaryPkgs.push(pkg);
                }
            });

            var pkgNameList = _.map(_.pluck(binaryPkgs, 'path'), function (pkgPath) {
                return path.basename(pkgPath);
            });
            pkgNameList = pkgNameList.concat(archives);


            async.waterfall([
                function (cb1) {
                    if (pkgCachePath) {
                        log.info('# - Use \'' + pkgCachePath + '\' as cache repository.');
                        cb1(null, pkgCachePath);
                    } else {
                        utils.genTemp(function (err1, tempDir) {
                            cb1(null, tempDir);
                        });
                    }
                },
                function (tempDir, cb1) {
                    // write updated package into a file.
                    var updatedPkgListFilePath = path.join(tempDir, 'updated-packages.txt');
                    log.info('# - Write updated packages into ' + updatedPkgListFilePath);
                    var pkgList = _.clone(pkgNameList);
                    fs.writeFile(updatedPkgListFilePath, pkgList.join('\n'), function (err1) {
                        cb1(err1, tempDir);
                    });
                },
                function (tempDir, cb1) {
                    log.info('# - Using \'' + tempDir + '\'');
                    if (pkgCachePath) {
                        var pkgsPath = _.map(pkgNameList, function (pkgName) {
                            return path.join(pkgCachePath, pkgName);
                        });

                        log.info('# - Checking ' + updatedPkgs.length + ' updated packages from \'' + pkgCachePath + '\'');
                        async.map(pkgsPath, function (pkgPath, cb2) {
                            fs.exists(pkgPath, function (exists) {
                                if (exists) {
                                    cb2(null, pkgPath);
                                } else {
                                    // error
                                    cb2(new Error(' # - \'' + pkgPath + '\' does not exists.'));
                                }
                            });
                        },
                        function (err1, results) {
                            cb1(err1, _.compact(results));
                        });
                    } else {
                        log.info('# - Downloading ' + updatedPkgs.length + ' updated packages from \'' + webRepoURL + '\'');
                        srcSnapshot.downloadPackages(srcDist.path, updatedPkgs, tempDir, monitor, function (err1, downloadPath) {
                            cb1(err1, downloadPath);
                        });
                    }
                },
                function (pkgsPath, cb1) {
                    log.info('# - Registering updated packages into ' + destDist.name);
                    destDist.registerPackages(pkgsPath, { force: isForce }, monitor, cb1);
                }
            ],
            function (err) {
                cb(err);
            });
        }
    ],
    function (err) {
        if (err) {
            log.error('Package-Pull - Failure!');
            log.error(err);
            process.exit(-1);
        }
        log.info('Package-Pull - Success');
        process.exit(0);
    });
};


function getArchivePackages(newDistPath, newSnapshot, oldDistPath, oldSnapshot, callback) {
    if (!newSnapshot) {
        return callback(null);
    }

    var oldArchives = [];
    var newArchives = [];

    async.waterfall([
        function (cb) {
            var destSourcePath = path.join(oldDistPath, 'source');

            if (oldSnapshot) {
                async.eachLimit(oldSnapshot.archivePackages, 1, function (archive, cb1) {
                    var archivePath = path.join(destSourcePath, archive);
                    fs.stat(archivePath, function (err1, stat) {
                        if (!err1) {
                            oldArchives.push({ name: archive, size: stat.size });
                        }
                        cb1(err1);
                    });
                },
                function (err) {
                    if (err) {
                        console.error(err);
                    }
                    cb(err);
                });
            } else {
                cb(null);
            }
        },
        function (cb) {
            var srcSourcePath = newDistPath + '/source';

            async.eachLimit(newSnapshot.archivePackages, 1, function (archive, cb1) {
                var archivePath = srcSourcePath + '/' + archive;

                http.get(archivePath, function (response) {
                    var statusCode = response.statusCode;
                    var size = response.headers['content-length'];

                    if (statusCode !== 200) {
                        return cb1(new Error('Request Failed. Status Code: ' + statusCode));
                    }

                    // response.on('end', function () {
                    //     newArchives.push({ name: archive, size: Number(size) });
                    //     cb1(null);
                    // });
                    newArchives.push({ name: archive, size: Number(size) });
                    cb1(null);
                });
            },
            function (err) {
                if (err) {
                    console.error(err);
                }
                cb(err);
            });
        },
        function (cb) {
            var results = [];
            _.each(newArchives, function (archive) {
                var pkgs = _.where(oldArchives, { name: archive.name });
                if (_.isEmpty(pkgs)) {
                    results.push(archive.name);
                }
            });
            cb(null, results);
        }
    ],
    function (err, results) {
        callback(err, results);
    });
}


function getBinaryPackage(newSnapshot, oldSnapshot, options) {
    var pkgs = [];

    for (var os in newSnapshot.osPackages) {
        // skip when os is in 'osList' option
        if (options.osList && options.osList.indexOf(os) < 0) {
            continue;
        }

        var packages = newSnapshot.osPackages[os];
        for (var pname in packages) {
            var newPkg = _.clone(packages[pname]);
            // NOTE. This is the guard code for preventing confused compatible OS name
            if (newPkg.os !== os) {
                newPkg.os = os;
            }

            var oldPackages = (oldSnapshot !== null) ? oldSnapshot.osPackages : null;
            if (oldPackages === null || oldPackages[os] === undefined) {
                pkgs.push(newPkg);
            } else {
                var oldPkg = oldPackages[os][pname];
                if (oldPkg === undefined) {
                    pkgs.push(newPkg);
                } else {
                    if (util.package.compareVersion(newPkg.version, oldPkg.version) > 0) {
                        pkgs.push(newPkg);
                    } else if (options.SYNC_FORCE && util.package.compareVersion(newPkg.version, oldPkg.version) === 0) {
                        if (newPkg.size !== oldPkg.size && newPkg.checksum !== oldPkg.checksum) {
                            pkgs.push(newPkg);
                        }
                    } else {

                    }
                }
            }
        }
    }

    return pkgs;
}

function getUpdatedPackages(newDistPath, newSnapshot, oldDistPath, oldSnapshot, options, callback) {
    // 'options' is optional argument
    if (!options) {
        options = {};
    }

    // if no new snapshot, return emtpy
    if (!newSnapshot) {
        return [];
    }

    async.waterfall([
        function (cb) {
            // check archive packages
            getArchivePackages(newDistPath, newSnapshot, oldDistPath, oldSnapshot, function (err, results) {
                cb(err, results);
            });
        },
        function (results, cb) {
            var pkgs = [];
            if (newSnapshot.osPackages) {
                pkgs = getBinaryPackage(newSnapshot, oldSnapshot, options);
            }
            cb(null, results.concat(pkgs));
        }
    ],
    function (err, results) {
        callback(err, results);
    });
}
