/**
 * repo.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>
 *
 * 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 path = require('path');
var async = require('async');
var fs = require('fs');
var _ = require('underscore');
var LockFile = require('lockfile');

var dibs = require('../../core/dibs');
var DError = require('../../core/exception.js');
var Utils = require('../../lib/utils');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var Distribution = require('./distribution.js');


module.exports.createRepo = function (parent, repoURL1, options) {
    return new VisualStudioRepository(parent, repoURL1, options);
};

function VisualStudioRepository(parent, repoURL1, options) {
    var self = this;
    var DIST_INFO_FILE = 'visualstudio-dist.idx';
    var distributions = {};
    var repoPath = null;

    this.lockFilePath = path.join(parent.lockFilePath);

    repoPath = parent.getRepositoryPath();

    this.getServer = function () {
        return parent;
    };

    this.open = function (repoConfig, callback) {
        self.repoConfig = repoConfig;

        isRepositoryExists(function (exist) {
            if (exist) {
                loadLocalRepository(callback);
            } else {
                createNewLocalRepository(callback);
            }
        });
    };

    this.close = function (callback) {
        // Every infomation must be saved in realtime,
        // So, nothing to be saved when close
        callback(null);
    };

    this.getRepositoryURL = function (callback) {
        parent.getRepositoryURL(null, callback);
    };

    this.createDistribution = function (name, opts, callback) {
        if (distributions[ name ] !== undefined) {
            callback(new DError('VSREPO005', {dist: name}), null);
            return;
        }

        async.waterfall([
            function (cb) {
                // create
                Distribution.create(name, opts, repoPath, cb);
            },
            function (newDist, cb) {
                // update repo configuration
                updateDistributionConfigInternal(newDist, opts, function (err) {
                    // set & return
                    if (!err) {
                        distributions[name] = newDist;
                    }
                    cb(err, newDist);
                });
            }
        ], callback);
    };

    this.removeDistribution = function (name, opts, callback) {
        if (!distributions[name]) {
            callback(new DError('VSREPO004', {dist: name}));
            return;
        }

        delete distributions[ name ];

        // remove distOptions in config.yaml
        removeDistributionOptionsToConfig(name);

        Distribution.remove(name, opts, repoPath, callback);
    };

    this.updateDistributionConfig = function (name, opts, callback) {
        if (!distributions[name]) {
            callback(new DError('VSREPO004', {dist: name}));
            return;
        }

        updateDistributionConfigInternal(distributions[name], opts, callback);
    };

    this.searchDistributions = function (opts, callback) {
        var results = [];

        for (var name in distributions) {
            var dist = distributions[name];
            if (opts.distName !== undefined && dist.name !== opts.distName) {
                continue;
            }

            results.push(dist);
        }
        callback(null, results);
    };

    this.generateSnapshot = function (name, opts, callback) {
        if (opts.distName === undefined) {
            callback(new DError('VSREPO003'));
            return;
        }

        var dist = distributions[ opts.distName ];
        if (dist === undefined) {
            callback(new DError('VSREPO004', {dist: opts.distName}));
            return;
        }

        async.waterfall([
            function (cb) {
                acquireLock(opts.distName, cb);
            },
            function (cb) {
                Distribution.generateSnapshot(dist, name, opts, cb);
            }
        ], function (err, snapshot) {
            releaseLock(opts.distName);
            callback(err, snapshot);
        });
    };

    this.searchSnapshots = function (conditions, callback) {
        if (conditions.distName === undefined) {
            return callback(new DError('VSREPO003'));
        }

        var dist = distributions[conditions.distName];
        if (dist === undefined) {
            return callback(new DError('VSREPO004', {dist: conditions.distName}));
        }

        if (conditions.name === null) {
            if (dist.latestSnapshot !== null) {
                return callback(null, [ dist.latestSnapshot ]);
            } else {
                delete conditions.name;
                conditions.attribute = 'auto';
                conditions.count = 1;
                conditions.order = 'DESC';
            }
        }

        Distribution.searchSnapshots(conditions, callback);
    };

    this.registerPackages = function (files, opts, callback) {
        var distName = opts.distName;
        if (distName === undefined) {
            return callback(new DError('VSREPO003'));
        }

        async.waterfall([
            // Add files to server
            function (cb) {
                parent.searchDistributions({repoType: 'tizen-visualstudio', distName: distName},
                    function (err, results) {
                        if (err) {
                            return cb(err);
                        } else if (results.length < 1) {
                            return cb(new DError('VSREPO004', {dist: distName}));
                        } else {
                            return cb(err);
                        }
                    });
            },
            // Add files to server
            function (cb) {
                addFiles(files, opts, cb);
            },
            function (rpaths, cb) {
                if (opts.buildInfo) {
                    _.each(opts.buildInfo, function (b) {
                        if (!b.files) {
                            return;
                        }
                        _.each(b.files, function (file) {
                            var dfs = _.find(rpaths, function (rpath) {
                                return path.basename(rpath) === file.name;
                            });
                            file.dfs = dfs;
                        });
                    });
                }
                parent.registerRemotePackages(rpaths, opts, cb);
            }
        ], function (err, snapshot) {
            if (err) {
                return callback(err, null);
            } else {
                return callback(err, snapshot);
            }
        });
    };

    this.registerRemotePackages = function (rpaths, opts, callback) {
        var distName = opts.distName;
        var distribution = distributions[distName];
        if (distribution === undefined) {
            callback(new DError('VSREPO004', {dist: distName}), null);
            return;
        }

        async.waterfall([
            function (cb) {
                acquireLock(distName, cb);
            },
            function (cb) {
                Distribution.registerArtifacts(distribution, rpaths, opts, cb);
            }
        ], function (err, snapshot) {
            releaseLock(distName);
            callback(err, snapshot);
        });
    };

    this.removeSnapshot = function (nameList, opts, callback) {
        if (opts.distName === undefined) {
            callback(new DError('VSREPO003'));
            return;
        }

        var dist = distributions[ opts.distName ];
        if (dist === undefined) {
            callback(new DError('VSREPO004', {dist: opts.distName}));
            return;
        }

        if (_.indexOf(nameList, dist.latestSnapshot.name) > -1) {
            callback(new DError('VSREPO008'));
            return;
        }

        Distribution.removeSnapshot(nameList, dist, opts, callback);
    };

    this.removePackages = function (names, opts, callback) {
        if (opts.distName === undefined) {
            return callback(new DError('VSREPO003'));
        }

        var dist = distributions[opts.distName];
        if (dist === undefined) {
            return callback(new DError('VSREPO004', {dist: opts.distName}));
        }

        async.waterfall([
            function (cb) {
                acquireLock(opts.distName, cb);
            },
            function (cb) {
                Distribution.removePackages(names, dist, opts, cb);
            }
        ], function (err, snapshot) {
            releaseLock(opts.distName);
            callback(err, snapshot);
        });
    };

    // PRIVATE
    function isRepositoryExists(callback) {
        // check DIST_INFO_FILE
        return fs.exists(path.join(repoPath, DIST_INFO_FILE), callback);
    }

    function loadLocalRepository(callback) {
        parent.log.info('Load distribution(s) ...');
        async.waterfall([
            function (wcb) {
                Distribution.load(repoPath, wcb);
            },
            function (dists, wcb) {
                distributions = {};
                async.eachLimit(dists, 5, function (dist, cb) {
                    var opts = getDistributionOptionsFromConfig(dist.name);

                    updateDistributionConfigInternal(dist, opts, function (err) {
                        if (!err) {
                            distributions[ dist.name ] = dist;
                        }
                        cb(err);
                    });
                }, function (err) {
                    wcb(err);
                });
            }
        ], function (err) {
            callback(err);
        });
    }

    function getDistributionOptionsFromConfig(distName) {
        var options = self.repoConfig.distOptions;
        if (!options) { return {}; }

        var filtered = options.filter(function (e) {
            return (e.distName === distName);
        });

        if (filtered.length > 0) {
            return filtered[0];
        } else {
            return {};
        }
    }

    function removeDistributionOptionsToConfig(distName) {
        var distOptions = self.repoConfig.distOptions;
        if (!distOptions) {
            return -1;
        }

        var distOptionsIndex = _.findIndex(distOptions, function (option) {
            if (option.distName) {
                return (option.distName === distName);
            }
        });

        if (distOptionsIndex > -1) {
            distOptions.splice(distOptionsIndex, 1);
        }
    }

    function setDistributionOptionsToConfig(distName, distOptions) {
        var optsList = self.repoConfig.distOptions;
        if (!optsList) {
            self.repoConfig.distOptions = [];
            optsList = self.repoConfig.distOptions;
        }

        var filtered = optsList.filter(function (e) {
            return (e.distName === distName);
        });
        if (filtered.length > 0) {
            optsList.splice(optsList.indexOf(filtered[0]), 1);
        }

        var newOptions = _.clone(distOptions);
        newOptions.distName = distName;

        optsList.push(newOptions);
    }

    function updateDistributionConfigInternal(dist, opts, callback) {
        dist.options = opts;
        setDistributionOptionsToConfig(dist.name, opts);
        callback(null);
    }

    function createNewLocalRepository(callback) {
        parent.log.info('Create new repository...');
        Utils.makeDirIfNotExist(repoPath, function (err) {
            if (err) {
                return callback(err);
            } else {
                parent.log.info('Initiating distribution info file...');
                Distribution.makeDistributionInfo(repoPath, function (err) {
                    if (!err) {
                        distributions = {};
                    }
                    callback(err);
                });
            }
        });
    }

    function acquireLock(distName, callback) {
        var lockFile = path.join(parent.lockFilePath, distName);
        LockFile.lock(lockFile, {wait: 1000 * 60 * 60}, function (err) {
            dibs.log.info('Lock distribution... >> ' + distName);
            callback(err);
        });
    }

    function releaseLock(distName) {
        var lockFile = path.join(parent.lockFilePath, distName);
        if (lockFile) {
            dibs.log.info('Unlock distribution... << ' + distName);
            LockFile.unlockSync(lockFile);
        }
    }

    function addFiles(lpaths, opts, callback) {
        async.mapSeries(lpaths,
            function (lpath, cb) {
                var fileName = path.basename(lpath);
                // if from client, upload to server directly, otherwise, add it to me
                if (dibs.thisServer === undefined || dibs.thisServer === null) {
                    dfs.addFileToServer(fileName, lpath, parent, function (err, dfsPath2) {
                        cb(err, dfsPath2);
                    });
                } else {
                    // NOTE. REGISTER must be finished in 1 hour after adding this file to DFS
                    //      Otherwise, added files will be removed on DFS
                    dfs.addFile(null, lpath, {lifetime: 60 * 60 * 1000}, function (err, dfsPath2) {
                        cb(err, dfsPath2);
                    });
                }
            },
            function (err, rpaths) {
                callback(err, rpaths);
            });
    }
}
