/**
 * repo.js
 * Copyright (c) 2000 - 2015 Samsung Electronics Co., Ltd. All rights reserved.
 *
 * Contact:
 * DongHee Yang <donghee.yang@samsung.com>
 * Sungmin Kim <sm.art.kim@samsung.com>
 * Jiil Hyoun <jiil.hyoun@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 path = require('path');
var async = require('async');
var fs = require('fs');
var _ = require('underscore');

var dibs = require('../../core/dibs');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var Utils = require('../../lib/utils');
var Distribution = require('./distribution.js');
var DError = require('../../core/exception.js');
var RemoteRepo = require('../org.tizen.repository/remote-repo.js');
var LockFile = require('lockfile');


module.exports.createRepo = function (parent, repoURL1, options) {
    if ((parent === null) || (parent && parent.standAlone)) {
        var standAloneRepo = require('./stand-alone-repo.js');
        return standAloneRepo.createRepo(parent, repoURL1, options);
    } else {
        if (repoURL1 && options) {
            return new TizenRepository(parent, repoURL1, options);
        } else {
            return new TizenRepository(parent);
        }
    }
};


function TizenRepository(parent, repoURL1, options) {
    var self = this;

    var DIST_INFO_FILE = 'distribution.info';
    var distributions = {};
    var repoPath = null;
    var isRemoteRepo = false;
    var repoOptions = {};
    var syncMethods = {};

    if (parent !== null) {
        repoPath = parent.getRepositoryPath();
        this.lockFilePath = path.join(parent.lockFilePath);
    } else {
        repoPath = repoURL1;
        repoOptions = options;
        isRemoteRepo = true;
    }


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


    this.open = function (repoConfig, callback) {

        self.repoConfig = repoConfig;

        // init extentions
        var exts = dibs.plugin.getExtensions('dibs.distribution.sync-method');
        for (var i = 0; i < exts.length; i++) {
            if (exts[i].distributionType !== 'tizen') {continue;}
            syncMethods[ exts[i].type ] = exts[i].module;
        }

        // load remote repository
        // -> set only snapshot infos
        if (!isRemoteRepo) {
            isRepositoryExists(function (exist) {
                if (exist) {
                    loadLocalRepository(callback);
                } else {
                    createNewLocalRepository(callback);
                }
            });
        } else {
            loadRemoteRepository(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) {
        if (isRemoteRepo) {
            return callback(null, repoPath);
        } else {
            parent.getRepositoryURL(null, callback);
        }
    };


    this.registerPackages = function (paths, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

        async.waterfall([
            // Add files to server
            function (cb) {
                if (opts.distName === undefined) {
                    cb(new DError('TREPO003'));
                    return;
                }
                parent.searchDistributions({repoType: 'tizen', distName: opts.distName},
                    function (err, results) {
                        if (err) {
                            return cb(err);
                        } else if (results.length < 1) {
                            return cb(new DError('TREPO004', {dist: opts.distName}));
                        } else {
                            return cb(err);
                        }
                    });
            },
            // Add files to server
            function (cb) {
                addFiles(paths, opts, cb);
            },
            // request repo server to get files
            function (rpaths, cb) {
                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) {
        if (isRemoteRepo) {
            callback(new DError('TREO002'), null);
            return;
        }

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

        async.waterfall([
            function (cb) {
                acquireLock(opts.distName, cb);
            },
            function (cb) {
                if (opts.tempJobId) {
                    tempFiles2rpath(rpaths, dist, opts.tempJobId, opts.progress, function (newRpaths) {
                        Distribution.registerPackages(newRpaths, dist, opts, opts.progress, cb);
                    });
                } else {
                    Distribution.registerPackages(rpaths, dist, opts, opts.progress, cb);
                }
            }
        ], function (err, snapshot) {
            releaseLock(opts.distName);
            callback(err, snapshot);
        });
    };

    function tempFiles2rpath(rpaths, dist, tempJobId, progress, callback) {
        if (!tempJobId) {
            return callback(rpaths);
        }

        var tempPath = path.join(dist.path, 'temp', 'jobs', '' + tempJobId);
        fs.exists(tempPath, function (exists) {
            if (exists) {
                var tempFilePaths = _.map(rpaths, function (rpath) { return path.join(tempPath, path.basename(rpath)); });
                async.filter(tempFilePaths, fs.exists, function (existFiles) {
                    addFiles(existFiles, {}, function (err, dfsPaths) {
                        if (err) {
                            return callback(rpaths);
                        } else {
                            var repoPath = {};
                            _.each(dfsPaths, function (dpath) { repoPath[path.basename(dpath)] = dpath; });
                            var newRpaths = _.map(rpaths, function (rpath) {
                                if (repoPath[path.basename(rpath)]) {
                                    return repoPath[path.basename(rpath)];
                                } else {
                                    return rpath;
                                }
                            });
                            return callback(newRpaths);
                        }
                    });
                });
            } else {
                return callback(rpaths);
            }
        });
    }

    this.removePackages = function (names, opts, callback) {
        if (isRemoteRepo) {
            return callback(new DError('TREPO002'), null);
        }

        if (opts.distName === undefined) {
            return callback(new DError('TREPO003'));
        }

        var dist = distributions[opts.distName];
        if (dist === undefined) {
            return callback(new DError('TREPO004', {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);
        });
    };


    this.createDistribution = function (name, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

        // check
        if (distributions[ name ] !== undefined) {
            callback(new DError('TREPO005', {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);
                });
            },
            function (newDist, cb) {
                if (opts.restore) {
                    dibs.log.info('Restore distribution...' + name);
                    dibs.log.info('[Restore distribution] create projects...');
                    Distribution.restore(name, opts.restore, function (err) {
                        if (err) {
                            return cb(err);
                        }
                        cb(err, newDist);

                        dibs.log.info('[Restore distribution] get packages...');
                        self.synchronizeDistribution(name, {
                            repoType: 'tizen',
                            SYNC_METHOD: 'internal',
                            SYNC_SNAPSHOT: opts.restore.snapshotName,
                            SYNC_DIST_NAME: opts.restore.distName}, function (err1) {
                                if (err) {
                                    dibs.log.error(err1);
                                    dibs.log.info('[Restore distribution] get packages... failed');
                                } else {
                                    dibs.log.info('[Restore distribution] get packages... success');
                                }
                            });
                    });
                } else {
                    return cb(null, newDist);
                }
            }
        ], callback);
    };


    this.removeDistribution = function (name, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

        // check if it is exists
        if (!distributions[name]) {
            callback(new DError('TREPO004', {dist: name}));
            return;
        }

        // remove
        delete distributions[ name ];

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

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


    this.updateDistributionConfig = function (name, opts, callback) {
        // check if it is exists
        if (!distributions[name]) {
            callback(new DError('TREPO004', {dist: name}));
            return;
        }

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


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

        // update synchronizing distribution
        if (opts.SYNC_DIST_NAME) {
            if (!opts.SYNC_METHOD) { opts.SYNC_METHOD = 'direct'; }

            dist.syncMethod = opts.SYNC_METHOD;
            if (opts.SYNC_URL) { dist.syncURL = opts.SYNC_URL; }
            dist.syncDistName = opts.SYNC_DIST_NAME;
            if (opts.SYNC_PERIOD) {
                dist.syncPeriod = parseInt(opts.SYNC_PERIOD.toString(), 10);
            }
        } else {
            dist.syncMethod = null;
            dist.syncURL = null;
            dist.syncDistName = null;
            dist.syncPeriod = null;
        }
        if (opts.SYNC_METHOD) {
            syncMethods[ opts.SYNC_METHOD ].updateSyncOptions(dist, opts, self, callback);
        } else {
            syncMethods.direct.updateSyncOptions(dist, opts, self, callback);
        }
    }


    this.setSyncToConfig = function (distName, syncMethod, syncURL, syncDistName, syncPeriod, syncPrefix) {
        var syncs = self.repoConfig.synchronization;
        if (!syncs) {
            self.repoConfig.synchronization = [];
            syncs = self.repoConfig.synchronization;
        }

        var filtered = syncs.filter(function (e) { return (e.distName === distName); });
        if (filtered.length > 0) {
            syncs.splice(syncs.indexOf(filtered[0]), 1);
        }
        if (syncDistName) {
            var sync = {
                distName: distName,
                syncMethod: syncMethod,
                syncDistName: syncDistName
            };
            if (syncURL) {sync.syncURL = syncURL;}
            if (syncPeriod) {sync.syncPeriod = syncPeriod;}
            if (syncPrefix) {sync.syncPrefix = syncPrefix;}
            syncs.push(sync);
        }
    };


    function getSyncFromConfig(distName) {
        var syncs = self.repoConfig.synchronization;
        if (!syncs) {
            return null;
        }
        var filtered = syncs.filter(function (e) { return (e.distName === distName); });
        if (filtered.length > 0) {
            return filtered[0];
        } else {
            return null;
        }
    }


    function removeSyncToConfig(distName) {
        var syncConfig = self.repoConfig.synchronization;
        var syncIndex = _.findIndex(syncConfig, function (sync) {
            if (sync.distName) {
                return (sync.distName === distName);
            }
        });

        if (syncIndex > -1) {
            syncConfig.splice(syncIndex, 1);
        }
    }

    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);
    }


    this.synchronizeDistribution = function (name, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

        // check if it is exists
        var dist = distributions[ name ];
        if (dist === undefined) {
            callback(new DError('TREPO004', {dist: name}), null);
            return;
        }

        // overide sync options
        if (opts.SYNC_METHOD) {
            dist.syncMethod = opts.SYNC_METHOD;
        } else {
            if (!dist.syncMethod) { dist.syncMethod = 'direct'; }
        }
        if (opts.SYNC_URL) { dist.syncURL = opts.SYNC_URL; }
        if (opts.SYNC_DIST_NAME) { dist.syncDistName = opts.SYNC_DIST_NAME; }
        if (opts.SYNC_SNAPSHOT) { dist.syncSnapshot = opts.SYNC_SNAPSHOT; }

        if (!dist.syncDistName) {
            callback(new DError('TREPO006', {dist: name}), null);
            return;
        }

        if (opts.SYNC_PREFIX) {
            dist.syncPrefix = opts.SYNC_PREFIX;
        }
        parent.log.info(' - SYNC_PREFIX: ' + dist.syncPrefix);

        parent.log.info(' - SYNC_METHOD: ' + dist.syncMethod);
        if (dist.syncURL) {
            parent.log.info(' - SYNC_URL: ' + dist.syncURL);
        }
        parent.log.info(' - SYNC_DIST_NAME: ' + dist.syncDistName);
        if (dist.syncSnapshot) {
            parent.log.info(' - SYNC_SNAPSHOT: ' + dist.syncSnapshot);
        }

        var newOpts = _.clone(opts);
        if (!newOpts.progress) {
            newOpts.progress = function (msg) { parent.log.info(' ' + msg); };
        }
        syncMethods[ dist.syncMethod ].synchronize(dist, newOpts, self, function (err, dist) {
            if (!err) {
                dist.options.SYNC_METHOD = dist.syncMethod;
                if (dist.syncURL) {
                    dist.options.SYNC_URL = dist.syncURL;
                }
                dist.options.SYNC_DIST_NAME = dist.syncDistName;
                if (dist.syncPrefix) {
                    dist.options.SYNC_PREFIX = dist.syncPrefix;
                }
                updateDistributionConfigInternal(dist, dist.options, function (err) {
                    callback(err, dist);
                });
            } else {
                return callback(err, null);
            }
        });
    };


    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 (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

        if (opts.distName === undefined) {
            callback(new DError('TREPO003'));
            return;
        }

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

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


    this.removeSnapshot = function (nameList, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'));
            return;
        }

        if (opts.distName === undefined) {
            callback(new DError('TREPO003'));
            return;
        }

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

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

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


    // mandatory options
    // * opts.distName
    this.searchSnapshots = function (opts, callback) {
        if (opts.latest) {
            opts.name = null; //null is latest list
        }

        if (opts.remote) {
            remoteSearchSnapshots(opts, callback);
        } else {
            localSearchSnapshots(opts, callback);
        }
    };


    //for web search
    function remoteSearchSnapshots(opts, callback) {
        var distName = opts.remoteDistName;
        var repoURL = opts.remote;
        var snapshotName;
        if (!opts.name) {
            snapshotName = null;
        } else {
            snapshotName = opts.name;
        }


        //check distribution name of option parameta
        if (distName === undefined) {
            callback(new DError('TREPO003'));
            return;
        }
        RemoteRepo.getRemoteDistribution(repoURL, distName, {
            snapshotName: null
        }, function (err, dist) {
            if (err) {
                return callback(err, null);
            } else {
                if (dist === undefined) {
                    callback(new DError('TREPO004', {dist: opts.distName}), null);
                    return;
                }

                if (snapshotName === null && dist.latestSnapshot !== null) {
                    callback(null, [ dist.latestSnapshot ]);
                    return;
                }
                return callback(null, dist.snapshots);
            }
        });
    }

    function localSearchSnapshots(opts, callback) {
        //check distribution name of option parameta
        if (opts.distName === undefined) {
            callback(new DError('TREPO003'));
            return;
        }

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

        if (opts.name === null && dist.latestSnapshot !== null) {
            callback(null, [ dist.latestSnapshot ]);
            return;
        }

        Distribution.searchSnapshots(opts, callback);
    }


    // callback ( err, path )
    this.downloadPackage = function (name, opts, callback) {
        // check distribution
        if (opts.distName === undefined) {
            callback(new DError('TREPO003'), null);
            return;
        }

        // check snapshot
        if (opts.snapshotName === undefined) {
            callback(new DError('TREPO009'), null);
            return;
        }

        if (isRemoteRepo) {
            downloadPackageFromRemoteRepository(name, opts, function (err, path) {
                callback(err, path);
            });
            return;
        }

        async.waterfall([
            function (cb) {
                parent.downloadRemotePackage(name, opts, cb);
            },
            // get files from server
            function (dfsPath, cb) {
                getFile(dfsPath, opts, cb);
            }
        ], function (err, tpath) {
            callback(err, tpath);
        });
    };


    this.downloadRemotePackage = function (name, opts, callback) {
        if (isRemoteRepo) {
            callback(new DError('TREPO002'), null);
            return;
        }

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

        Distribution.prepareToDownloadPackage(name, dist, opts, callback);
    };


    this.createRelease = function (releaseName, distName, opts, callback) {

        var dist = distributions[ distName ];
        if (!dist) {
            return callback(new DError('TREPO004', {dist: distName}));
        }

        Distribution.createRelease(releaseName, dist, opts, callback);
    };


    this.removeRelease = function (releaseName, distName, opts, callback) {

        var dist = distributions[ distName ];
        if (!dist) {
            return callback(new DError('TREPO004', {dist: distName}));
        }

        Distribution.removeRelease(releaseName, dist, opts, callback);
    };


    this.updateRelease = function (release, distName, opts, callback) {

        var dist = distributions[ distName ];
        if (!dist) {
            return callback(new DError('TREPO004', {dist: distName}));
        }

        Distribution.updateRelease(release, dist, opts, callback);
    };


    this.searchReleases = function (distName, opts, callback) {

        var dist = distributions[ distName ];
        if (!dist) {
            return callback(new DError('TREPO004', {dist: distName}));
        }

        Distribution.searchReleases(dist, opts, callback);
    };


    this.getRelease = function (releaseName, distName, callback) {

        var dist = distributions[ distName ];
        if (!dist) {
            return callback(new DError('TREPO004', {dist: distName}));
        }
        Distribution.getRelease(releaseName, dist, callback);
    };


    // PRIVATE

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

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


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

                        var sync = getSyncFromConfig(dist.name);
                        if (sync && sync.syncDistName) {
                            if (!sync.syncMethod) { sync.syncMethod = 'direct'; }
                            opts.SYNC_METHOD = sync.syncMethod;
                            if (sync.syncURL) {
                                opts.SYNC_URL = sync.syncURL;
                            }
                            opts.SYNC_DIST_NAME = sync.syncDistName;
                            opts.SYNC_PERIOD = (sync.syncPeriod && sync.syncPeriod >= 0) ? sync.syncPeriod : -1;
                            if (sync.syncPrefix) {
                                opts.SYNC_PREFIX = sync.syncPrefix;
                            }
                        }

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


    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);
            });
    }


    // callback( err, tpath)
    function getFile(dfsPath, opts, callback) {
        var targetDir = opts.targetDir === undefined ? '.' : opts.targetDir;

        if (Utils.isURL(dfsPath)) {
            Utils.download(dfsPath, targetDir, function (err, path) {
                callback(err, path);
            });
        } else {
            var tpath = path.join(targetDir, path.basename(dfsPath));
            if (dibs.thisServer === undefined || dibs.thisServer === null) {
                dfs.getFileFromServer(tpath, dfsPath, parent, function (err) {
                    callback(err, tpath);
                });
            } else {
                dfs.getFile(tpath, dfsPath, function (err) {
                    callback(err, tpath);
                });
            }
        }
    }


    function loadRemoteRepository(callback) {
        Distribution.loadRemote(repoPath, repoOptions, function (err, dists) {
            if (!err) {
                distributions = {};
                for (var i = 0; i < dists.length; i++) {
                    var dist = dists[ i ];
                    distributions[ dist.name ] = dist;
                }
            }
            callback(err);
        });
    }


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

        Distribution.downloadPackageFromRemote(name, dist, opts, callback);
    }

    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);
        }
    }
}
