/**
 * 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( './stand-alone-distribution.js' );
var Snapshot = require( './stand-alone-snapshot.js' );
var DError = require('../../core/exception.js');
var RemoteRepo = require('../org.tizen.repository/remote-repo.js');


module.exports.createRepo = function( parent, repoURL1, options ) {
    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();
    } 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; }

            if (exts[i].type === 'direct') { continue; }

            if (exts[i].type === 'stand-alone-direct') {
                exts[i].type = 'direct';
            }

            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 ) {
            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 ) {
                            cb( err );
                        } else if ( results.length < 1 ) {
                            cb( new DError("TREPO004",{dist:opts.distName})  );
                        } else {
                            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 ) {
                callback( err, null );
            } else {
                callback( err, snapshot );
            }
        });
    };


    // TODO: Need to process OS compatible packages
    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;
        }

        if ( opts.isTemp ){
            Distribution.registerTempFiles( rpaths, dist, opts, opts.progress, callback );
        } else if(opts.tempJobId){
            tempFiles2rpath(rpaths, dist, opts.tempJobId, opts.progress, function(newRpaths){
                Distribution.registerPackages( newRpaths, dist, opts, opts.progress, callback );
            });
        } else {
            Distribution.registerPackages( rpaths, dist, opts, opts.progress, callback );
        }
    };

    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){
                            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;
                                }
                            });
                            callback(newRpaths);
                        }
                    });
                });

            }else{
                callback(rpaths);
            }
        });
    };

    this.removePackages =  function( names, 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;
        }

        Distribution.removePackages( names, dist, opts, callback );
    };


    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(wcb){
                    // create
                    Distribution.create( name, opts, repoPath, wcb);
                },
                function(newDist,wcb){
                    // update repo configuration
                    updateDistributionConfigInternal( newDist, opts, function(err) {
                        // set & return
                        if ( !err ) { distributions[ name ] = newDist; }
                        wcb( err, 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 ( !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 );
        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 {
                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;
        }

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


    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) {
                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;
                }
                callback(null, dist.snapshots);
            }
        });
    }

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

        async.waterfall([
            function (cb) {
                if (opts.refresh) {
                    loadLocalRepository(cb);
                } else {
                    cb(null);
                }
            },
            function (cb) {
                //check distribution info 
                var dist = distributions[ opts.distName ];
                if ( dist === undefined ) {
                    callback( new DError("TREPO004",{dist:opts.distName}) );
                    return;
                }

                //check snapshot name fo option parameta
                //if snapshot name is skiped then latest snapshot is return 
                if ( opts.name === null && dist.latestSnapshot !== null )  {
                    cb( null, [ dist.latestSnapshot ] );
                    return; 
                }

                
                var snapshots = [];
                var newSnapshots = [];
                for( var i = 0; i < dist.snapshots.length; i++ ) {
                    var snapshot = dist.snapshots[i];

                    // check name if exists
                    if ( opts.name !== undefined && snapshot.name !== opts.name ) {
                        continue;
                    }

                    // check type if exists
                    if ( opts.type !== undefined && snapshot.type !== opts.type ) {
                        continue;
                    }
                    snapshots.push(snapshot);
                }

                // set changelogs
                if(opts.addChangeLog){
                    async.map(snapshots,function(snapshot,mcb){
                        Distribution.loadChangelog(dist, snapshot.name,function(changelog){
                            snapshot.changeLog = changelog;
                            mcb(null,snapshot);
                        });
                    },function(err,snapshotsWithChangelog){
                        if(!err){
                            snapshots = snapshotsWithChangelog;
                        }
                    });
                }

                // handle option for querying snapshot list without their package info
                if ( opts.snapshotInfoOnly ) {
                    cb(null, snapshots );
                    return;
                }

                async.map(snapshots, function(snapshot, mcb){
                    if ( isRemoteRepo ) {
                        Distribution.loadRemoteSnapshot( snapshot.name, dist, repoPath, function( err, nSnapshot ) {
                            if ( !err ) {
                                var cSnapshot = _.clone(snapshot);
                                cSnapshot.archivePackages = nSnapshot.archivePackages;
                                cSnapshot.osPackages = nSnapshot.osPackages;
                                mcb(err,cSnapshot);
                            } else {
                                mcb(null, null);
                            }

                        }); 
                    } else {
                        Distribution.loadSnapshot(snapshot.name, dist, function(err,nSnapshot){
                            if ( !err ) {
                                nSnapshot.name = snapshot.name;
                                nSnapshot.type = snapshot.type;
                                nSnapshot.time = snapshot.time;
                                nSnapshot.path = snapshot.path;
                                nSnapshot.origin = snapshot.orogin;

                                var changeLogPath = path.join(dist.path, "changes", snapshot.name + ".log");
                                fs.exists(changeLogPath, function(exist){
                                    if(exist){
                                        Snapshot.loadChangelog( changeLogPath, function(changelog){
                                            nSnapshot.changeLog = changelog;
                                            mcb(err,nSnapshot);
                                        });
                                    }else{
                                        mcb(err,nSnapshot);
                                    }
                                });
                            } else {
                                mcb(err, null);
                            }
                        });
                    }
                }, function(err,snapshots){
                    newSnapshots = _.compact(snapshots);
                    if (opts.include || opts.exclude) {
                        var include = strToArray(opts.include);
                        var exclude = strToArray(opts.exclude);
                        var includeAll = strToTruth(opts.includeAll);
                        var useInstallDep = strToTruth(opts.useInstallDep);
                        var useBuildDep = strToTruth(opts.useBuildDep);
                        newSnapshots = _.map(newSnapshots, function(newSnapshot) {
                            return Snapshot.getFilteredSnapshot(newSnapshot, include, exclude, includeAll, useInstallDep, useBuildDep);
                        });
                    }

                    if (opts.addProjectName === 'true') {
                        getPackageNameToProjectNameHash(opts.distName, function(err, hash) {
                            if (err) {
                                cb(null, newSnapshots);
                            } else {
                                cb(null, _.map(newSnapshots, function(newSnapshot) {
                                    return bindSnapshotAndProjectHash(newSnapshot, hash);
                                }));
                            }
                        });
                    } else {
                        cb(null, newSnapshots);
                    }
                });
            }],
            function (err, snapshots) {
                callback(err, snapshots);
            });


    }


    // 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 strToArray(obj) {
        var array = []
        if (_.isArray(obj)) {
            array = obj;
        } else if (_.isString(obj)) {
            array = obj.replace(/ /, '').split(',');
        } else {
            array = [];
        }
        return array;
    }

    function strToTruth(obj) {
        var truth = false;
        if (_.isBoolean(obj)) {
            truth = obj
        } else if ( _.isString(obj) && obj.replace(/ /, '').toLowerCase() === 'true') {
            truth = true;
        } else {
            truth = false;
        }
        return truth;
    }

    function createNewLocalRepository( callback ) {
        parent.log.info( "Creating new repository for Tizen..." );
        Utils.makeDirIfNotExist(repoPath,function(err){
            if(err){
                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 );

                            if (opts.INDEXING === undefined) {
                                opts.INDEXING = 'TRUE';
                            }

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

                            async.waterfall([
                                function(wwcb){
                                    // update configuration
                                    updateDistributionConfigInternal( dist, opts,
                                        function(err) {
                                            wwcb(err);
                                        } );
                                },
                                function(wwcb){
                                    // add index flag
                                    dist.indexIsReady = false;

                                    if (opts.INDEXING !== 'FALSE' && !dist.indexIsReady && !dist.indexIsWorking) {
                                        parent.log.info("Loading '" + dist.name + "' file indexing start");
                                        dist.indexIsWorking = true;
                                        Distribution.getAllPkgFileRefCountHash(dist.name, repoPath,
                                            function(err, refHash){
                                                if (err) {
                                                    parent.log.info("Loading '" + dist.name + "' file indexing failed");
                                                    parent.log.info("Error: " + err);
                                                } else {
                                                    parent.log.info("Loading '" + dist.name + "' file indexing done");
                                                    dist.pkgFileRefCountHash = refHash;
                                                    dist.indexIsReady = true;
                                                }
                                                dist.indexIsWorking = false;
                                            }
                                        );
                                    } else {
                                        parent.log.info("Skipping file indexing routine for '" + dist.name + "'");
                                    }
                                    wwcb(null);
                                } ],
                                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 getPackageNameToProjectNameHash(distName, callback){
        var hash = {};
        dibs.rpc.datamgr.searchProjects({distName:distName},function(err,projects){
            if(err){
                callback(err,hash);
            }
            else{
                _.each(projects,function(project){
                    // git build project
                    if(project.options.LAST_BUILD_INFO){
                        _.each(project.options.LAST_BUILD_INFO,function(info,os){
                            if(info.packageNames.length > 0){
                                _.each(info.packageNames, function(packageName){
                                    if(!hash[os]){ hash[os] = {}; }
                                    hash[os][packageName] = project.name;
                                });
                            }
                        });
                    }
                    // binary build project
                    else if(project.options.PKG_NAME && project.environments[0]){
                        if(!hash[project.environments[0]]){ hash[project.environments[0]] = {}; }
                            hash[project.environments[0]][project.options.PKG_NAME] = project.name;
                    }
                    else{
                        // do notting
                    }
                });
                callback(err,hash);
            }
        });
    }

    function bindSnapshotAndProjectHash(snapshot, projectHash){
        _.each(snapshot.osPackages,function(pkgs,os){
            _.each(pkgs,function(pkg){
                if(projectHash[os] && projectHash[os][pkg.name]){
                    snapshot.osPackages[os][pkg.name].projectName = _.clone(projectHash[os][pkg.name]);
                }
            });
        });
        return snapshot;
    }

}
