/**
 * image-generator.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 url = require('url');
var path = require('path');
var Package = require('./package');
var async = require('async');
var _ = require('underscore');
var fs = require('fs');
var extfs = require('fs-extra');

var Snapshot = require('../org.tizen.repository/snapshot.js');
var RemoteRepo = require('../org.tizen.repository/remote-repo.js');
var utils = require('../../lib/utils.js');
var FileSystem = require('../dibs.core/filesystem.js');
var Zip = require('../dibs.core/zip.js');
var dibs = require('../../core/dibs.js');

module.exports.makeImage = function makeImage(opts, callback) {
    var snapshotObj;
    var allPkgList;
    var metaPkgNameList = [];
    var installTypePkgNameList = [];
    var imgPkgList = [];
    var imgInfoContents = '';
    var os = opts.os;
    var workspace = opts.workspace;
    var imgName = opts.imgName;
    var pServerURL = opts.pServerURL;
    var snapshotName = opts.snapshotName;
    var distribution = opts.distributionName;
    var excludeList = opts.excludeList || [];
    var tempWorkspace = path.join(workspace, snapshotName + os);
    var imgInfo = opts.imgInfo || '';
    var userInputUID = opts.uid || '';

    // set image information
    if (imgInfo) {
        if (imgInfo.slice(-1) === '/') {
            imgInfo = imgInfo.substring(0, imgInfo.length - 1);
        }
        imgInfoContents = 'origin : ' + url.resolve(imgInfo, './') + '\n';
        imgInfoContents += 'distribution : ' + imgInfo.split('/')[imgInfo.split('/').length - 1];
    } else {
        imgInfoContents = 'origin : ' + url.resolve(pServerURL, './') + '\n';
        imgInfoContents += 'distribution : ' + distribution;
    }

    async.waterfall([
        function (cb) {
            console.log('<< Initialize >>');
            fs.exists(path.join(workspace, imgName), function (exists) {
                if (exists) {
                    FileSystem.remove(path.join(workspace, imgName), cb);
                } else {
                    cb(null);
                }
            });
        },
        function (cb) {
            initWorkspace(tempWorkspace, cb);
        },
        function (cb) {
            // get all pkg list
            if (pServerURL) {
                getSnapshotUsingURL(pServerURL, snapshotName, cb);
            } else {
                getSnapshotUsingRPC(distribution, snapshotName, cb);
            }
        },
        function (rst, cb) {
            // get meta,installtype pkg list
            snapshotObj = rst;
            allPkgList = _.pick(snapshotObj[0].osPackages, os)[os];
            for (var i in allPkgList) {
                var pkg = _.pick(allPkgList, i);
                if ((pkg[i].attr === 'root') || (pkg[i].attr === 'mandatory')) {
                    metaPkgNameList.push(pkg[i]);
                } else if (pkg[i].attr === 'install') {
                    installTypePkgNameList.push(pkg[i]);
                }
            }
            console.log('  Meta-package list');
            console.log('   - ' + _.pluck(_.values(metaPkgNameList), 'name'));
            console.log('  Installtype-package list');
            console.log('   - ' + _.pluck(_.values(installTypePkgNameList), 'name'));
            cb(null);
        },
        function (cb) {
            if (excludeList.length === 0) {
                imgPkgList = _.union(metaPkgNameList, installTypePkgNameList);
                cb(null, metaPkgNameList);
            } else {
                var excludeMetapackageList = [];
                console.log('  User input list');
                console.log('   - ' + _.values(excludeList));
                validateExcludeList(metaPkgNameList, excludeList, function (err) {
                    if (err) {
                        cb(err);
                    } else {
                        var tempList = _.filter(metaPkgNameList, function (metaPkgName) {
                            return (_.indexOf(excludeList, metaPkgName.name) !== -1);
                        });
                        getAllInstallDependentPackages(tempList, snapshotObj[0], function (err, rst) {
                            for (var i in rst) {
                                if (rst[i].attr === 'root') {
                                    excludeMetapackageList.push(rst[i]);
                                }
                            }
                            cb(null, _.difference(metaPkgNameList, _.union(tempList, excludeMetapackageList)));
                        });
                    }
                });
            }
        },
        function (rst, cb) {
            // get install dependency pkg list
            if (excludeList.length === 0) {
                getAllInstallDependentPackages(rst, snapshotObj[0], cb);
            } else {
                var tempList = _.filter(metaPkgNameList, function (metaPkgName) {
                    return (_.indexOf(excludeList, metaPkgName.name) !== -1);
                });
                getAllInstallDependentPackagesWithExcludeList(rst, snapshotObj[0], tempList, cb);
            }
        },
        function (rst, cb) {
            imgPkgList = _.union(imgPkgList, rst, installTypePkgNameList);
            var opts = {
                snapshotName: snapshotName,
                os: os,
                pkgList: _.union(imgPkgList, rst),
                workspace: workspace,
                destPath: path.join(tempWorkspace, 'binary')
            };
            // access repository server
            // 1. download remote packages
            // 2. get distribution uid
            if (pServerURL) {
                accessRepositoryUsingURL(pServerURL, distribution, opts, cb);
            } else {
                accessRepositoryUsingRPC(distribution, opts, cb);
            }
        },
        function (uid, cb) {
            // write distribution uid
            if (userInputUID) {
                imgInfoContents += '\ndistribution_id : ' + userInputUID;
            } else {
                if (uid) {
                    imgInfoContents += '\ndistribution_id : ' + uid;
                }
            }
            cb(null);
        },
        function (cb) {
            fs.writeFile(path.join(tempWorkspace, 'image.info'), imgInfoContents, cb);
        },
        function (cb) {
            // generate info files
            if (excludeList.length === 0) {
                fs.writeFile(path.join(tempWorkspace, 'pkg_list_' + os), Package.pkgListString(imgPkgList), cb);
            } else {
                _.each(imgPkgList, function (pkg) {
                    pkg.installDepList = _.reject(pkg.installDepList, function (depPkg) {
                        return _.contains(excludeList, depPkg.packageName);
                    });
                });
                fs.writeFile(path.join(tempWorkspace, 'pkg_list_' + os), Package.pkgListString(imgPkgList), cb);
            }
        },
        function (cb) {
            fs.writeFile(path.join(tempWorkspace, 'os_info'), os, cb);
        }, function (cb) {
            fs.writeFile(path.join(tempWorkspace, 'image.info'), imgInfoContents, cb);
        },function(cb){
            console.log("<< Compress >>");
            Zip.compress(path.join( workspace, imgName ), tempWorkspace, {}, cb);
        },function(cb){
            // Clean tempWorkspace
            utils.removePathIfExist(tempWorkspace, cb);
        }
    ],
    function (error) {
        if (error) {
            callback(error);
        } else {
            console.log('<< Information >>');
            console.log('  snapshot: ' + snapshotName);
            console.log('  os: ' + os);
            console.log('  location: ' + workspace);
            console.log('  image name: ' + imgName);
            callback(error);
        }
    });
};

function initWorkspace(inputPath, callback) {
    async.waterfall([
        function (cb) {
            utils.removePathIfExist(inputPath, cb);
        }, function (cb) {
            extfs.mkdirp(path.join(inputPath, 'binary'), cb);
        }], function (err) {
        callback(err);
    });
}

function validateExcludeList(metaPkgList, inputPkgList, callback) {
    async.eachSeries(inputPkgList, function (pkg, cb) {
        existsCheck(metaPkgList, pkg, function (err, rst) {
            if (rst === true) {
                cb(null);
            } else {
                cb(new Error(pkg + ' is not in package server.(We are case-sensitive).'));
            }
        });
    }, function (err) {
        callback(err);
    });
}
function existsCheck(metaPkgList, inputPkg, callback) {
    var flag = false;
    async.eachSeries(metaPkgList, function (pkg, cb) {
        if (pkg.name === inputPkg) {
            flag = true;
        }
        cb(null);
    }, function (err) {
        callback(err, flag);
    });
}

function getAllInstallDependentPackages(inputList, inputSnapshot, callback) {
    var outputList = [];
    async.eachSeries(inputList, function (pkgObj, cb) {
        Snapshot.getInstallDependentPackages([pkgObj], inputSnapshot, {
            installOrder: false
        }, function (err, rst) {
            outputList = _.union(outputList, rst, [pkgObj]);
            cb(err);
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(err, outputList);
        }
    });
}

function getAllInstallDependentPackagesWithExcludeList(inputList1, inputSnapshot, inputList2, callback) {
    var outputList = [];
    async.eachSeries(inputList1, function (pkgObj, cb) {
        Snapshot.getInstallDependentPackagesWithExcludeList([pkgObj], inputSnapshot, inputList2, {
            installOrder: false
        }, function (err, rst) {
            outputList = _.union(outputList, rst, [pkgObj]);
            cb(err);
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(err, outputList);
        }
    });
}

function getSnapshotUsingURL(url, snapshotName, callback) {
    Snapshot.loadRemote(url, {
        snapshotName: snapshotName
    }, callback);
}

function getSnapshotUsingRPC(distName, snapshotName, callback) {
    dibs.rpc.repo.searchSnapshots({name: snapshotName, repoType: 'tizen', distName: distName}, callback);
}

function accessRepositoryUsingURL(url, distName, opts, callback) {
    var repo;
    async.waterfall([
        function (cb) {
            // open repo
            repo = RemoteRepo.createRemoteRepo(path.dirname(url), {
                distName: distName,
                snapshotName: opts.snapshotName
            });
            repo.open(null, cb);
        },
        function (cb) {
            console.log('<< Download >>');
            async.eachLimit(opts.pkgList, 3, function (pkg, cb1) {
                process.stdout.write('  ' + pkg.name + ' ... ');
                repo.downloadPackage(pkg.name, {
                    distName: distName,
                    snapshotName: opts.snapshotName,
                    os: opts.os,
                    targetDir: opts.workspace
                }, function (err, rst) {
                    if (!err) {
                        var pkgName = path.basename(rst);
                        var sPath = path.join(opts.workspace, pkgName);
                        var dPath = path.join(opts.destPath, pkgName);
                        FileSystem.move(sPath, dPath, function (err1) {
                            console.log('done');
                            cb1(err1);
                        });
                    } else {
                        cb1(err);
                    }
                });
            },
            function (err) {
                if (err) {
                    console.log('failed');
                }
                cb(err);
            });
        }, function (cb) {
            repo.searchDistributions({
                repoType: 'tizen',
                distName: distName
            }, cb);
        }
    ], function (error, rst) {
        if (error) {
            callback(error);
        } else {
            callback(null, rst[0].uid);
        }
    });
}

function accessRepositoryUsingRPC(distName, opts, callback) {
    var repo;
    async.waterfall([
        function (cb) {
            console.log('<< Download >>');
            repo = dibs.getServersByType('repo')[0];
            async.eachLimit(opts.pkgList, 3, function (pkg, cb1) {
                process.stdout.write('  ' + pkg.name + ' ... ');
                repo.downloadPackage(pkg.name, {
                    distName: distName,
                    snapshotName: opts.snapshotName,
                    os: opts.os,
                    repoType: 'tizen',
                    targetDir: opts.destPath
                }, cb1);
            }, function (err) {
                if (err) {
                    console.log('failed');
                }
                cb(err);
            });
        },
        function (cb) {
            repo.searchDistributions({
                repoType: 'tizen',
                distName: distName
            }, cb);
        }
    ], function (error, rst) {
        if (error) {
            callback(error);
        } else {
            callback(null, rst[0].uid);
        }
    });
}
