/**
 * 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>
 *
 * 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 dibs = require('../../core/dibs');
var utils = require('../../lib/utils');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var DError = require('../../core/exception.js');
var Distribution = require('./distribution.js');


module.exports.createRepo = function (parent) {
    return new Repository(parent);
};


function Repository(parent) {
    var self = this;

    var DIST_INFO_FILE = 'dists.idx';
    var distributions = {};
    var repoPath = null;

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


    this.registerPackages = function (paths, opts, callback) {

        async.waterfall([
            // Add files to server
            function (cb) {
                if (!opts.distName) {
                    cb(new DError('DREPO003'));
                    return;
                }
                parent.searchDistributions({
                    repoType: 'dibs',
                    distName: opts.distName
                },
                    function (err, results) {
                        if (err) {
                            cb(err);
                        } else if (results.length < 1) {
                            cb(new DError('DREPO004', {
                                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, sname) {
            if (err) {
                callback(err, null);
            } else {
                callback(err, sname);
            }
        });
    };


    this.registerRemotePackages = function (rpaths, opts, callback) {

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

        Distribution.registerPackages(rpaths, dist, opts, null, callback);
    };


    this.removePackages = function (names, opts, callback) {

        if (!opts.distName) {
            callback(new DError('DREPO003'));
            return;
        }

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

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


    this.createDistribution = function (name, opts, callback) {

        // check
        if (distributions[name]) {
            callback(new DError('DREPO005', {
                dist: name
            }), null);
            return;
        }

        // create
        var newDist = Distribution.create(name, opts, repoPath);

        distributions[name] = newDist;

        callback(null, newDist);
    };


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

        // create
        delete distributions[name];
        Distribution.remove(name, opts, repoPath);

        callback(null);
    };


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

        callback(null);
    };


    this.synchronizeDistribution = function (name, opts, callback) {
        callback(new DError('REPO003'), 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 (!opts.distName) {
            callback(new DError('DREPO003'));
            return;
        }

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

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


    this.removeSnapshot = function (name, opts, callback) {

        if (!opts.distName) {
            callback(new DError('DREPO003'));
            return;
        }

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

        if (name === dist.latestSnapshot.name) {
            callback(new DError('DREPO008'));
            return;
        }

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


    this.searchSnapshots = function (opts, callback) {

        if (!opts.distName) {
            callback(new DError('DREPO003'));
            return;
        }

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

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

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

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

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

            results.push(snapshot);
        }

        callback(null, results);
    };


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

        // check snapshot
        if (!opts.snapshotName) {
            callback(new DError('DREPO009'), null);
            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) {

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

        Distribution.prepareToDownloadPackage(name, dist, opts, 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...');
        if (!fs.existsSync(repoPath)) {
            fs.mkdirSync(repoPath);
        }

        parent.log.info('Initiating dists.idx...');
        Distribution.init(repoPath, function (err, dists) {
            if (!err) {
                distributions = dists;
            }
            callback(err);
        });
    }


    function loadLocalRepository(callback) {
        parent.log.info('Loading existing distributions...');

        Distribution.load(repoPath, function (err, dists) {
            if (!err) {
                distributions = {};
                async.eachSeries(dists, function (dist, cb) {
                    distributions[dist.name] = dist;
                    cb(null);
                },
                    function (err) {
                        callback(err);
                    });
            } else {
                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);
                });
            }
        }
    }

}
