/**
 * basic-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 extfs = require('fs-extra');
var async = require('async');
var fs = require('fs');

var dibs = require('../../core/dibs');
var dfs = require('../../plugins/dibs.dist-fs/dist-fs.js');
var DError = require('../../core/exception.js');
var FileSystem = require('../dibs.core/filesystem.js');

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


function BasicRepository(parent) {
    var self = this;

    var fileIndex = null;
    var INDEX_FILE = 'files.idx';
    var SNAPSHOT_INDEX_FILE = 'snapshots.idx';
    var SNAPSHOT_DIR = 'snapshots';
    var ROOT_DIR = 'root';
    var snapshotIndex = null;
    var repoPath = parent.getRepositoryPath();


    this.open = function (repoConfig, callback) {

        // check repo exists
        var indexFilePath = path.join(repoPath, INDEX_FILE);
        if (!fs.existsSync(indexFilePath)) {
            parent.log.info('Creating new repository...');
            var snapshotDir = path.join(repoPath, SNAPSHOT_DIR);
            var rootDir = path.join(repoPath, ROOT_DIR);

            // create directory
            extfs.mkdirsSync(snapshotDir);
            extfs.mkdirsSync(rootDir);

            // save index
            parent.log.info('Initiating file index...');
            fileIndex = {};
            saveIndex();

            parent.log.info('Initiating snapshot index...');
            snapshotIndex = [];
            saveSnapshotIndex();
        } else {
            // if exists, load repository
            loadIndex();
            loadSnapshotIndex();
        }
        callback(null);
    };


    this.close = function (callback) {
        // MUST BE OPENED BEFORE CLOSE
        if (repoPath !== null) {
            saveIndex();
        }
    };


    this.registerPackages = function (paths, opts, callback) {
        async.waterfall([
            // 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) {

        async.map(rpaths,
            function (dfsPath, cb) {
                var fname = path.basename(dfsPath);
                var tpath = path.join(getRootPath(), fname);

                // get file from remote
                dfs.getFile(tpath, dfsPath, function (err) {
                    cb(err, fname);
                });
            },
            function (err, fnames) {
                if (err) {
                    callback(err, null);
                } else {
                    // update file index
                    for (var i in fnames) {
                        addIndex(fnames[i], {
                            path: fnames[i]
                        });
                    }
                    saveIndex();

                    self.generateSnapshot(null, opts, callback);
                }
            });

    };

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

        for (var i = 0; i < names.length; i++) {
            var fname = names[i];

            // check if the file exist
            if (!existsIndex(fname)) {
                callback(new DError('REPO002', {
                    file: fname
                }), null);
                return;
            }

            deleteIndex(fname);
        }

        // save index file
        saveIndex();

        // generate auto snapshot
        self.generateSnapshot(null, {}, function (err, sname) {
            // remove file from repo
            if (!err) {
                for (var i = 0; i < names.length; i++) {
                    var fname = names[i];
                    var tpath = path.join(getRootPath(), fname);
                    fs.unlinkSync(path.join(getRootPath(), fname));
                }
            }

            callback(err, sname);
        });
    };


    this.createDistribution = function (name, opts, callback) {
        callback(null, {
            name: name,
            options: opts
        });
    };


    this.removeDistribution = function (name, opts, callback) {
        callback(null);
    };


    this.updateDistributionConfig = function (name, opts, callback) {
        callback(null);
    };


    this.synchronizeDistribution = function (name, opts, callback) {
        callback(new DError('REPO003'), null);
    };


    this.searchDistributions = function (opts, callback) {
        callback(null, []);
    };


    this.generateSnapshot = function (name, opts, callback) {
        if (name === null) {
            name = (new Date()).getTime().toString();
        }

        var snapshotFound = false;
        for (var i = 0; i < snapshotIndex.length; i++) {
            if (snapshotIndex[i].name === name) {
                snapshotFound = true;
                break;
            }
        }
        if (snapshotFound) {
            callback(new DError('REPO004', {
                snapshot: name
            }));
            return;
        }

        sfile = name + '.idx';

        // copy file index to snapshots/{sname}.idx
        var repoPath = parent.getRepositoryPath();
        var snapshotPath = path.join(repoPath, SNAPSHOT_DIR, sfile);
        FileSystem.copyFileSync(path.join(repoPath, INDEX_FILE), snapshotPath);

        // update snapshot index
        snapshotIndex.push({
            name: name,
            path: path.join(SNAPSHOT_DIR, sfile)
        });

        // save snapshot index file
        var snapshotIndexPath = path.join(repoPath, SNAPSHOT_INDEX_FILE);
        fs.writeFileSync(snapshotIndexPath, JSON.stringify(snapshotIndex));

        callback(null, name);
    };


    this.removeSnapshot = function (name, opts, callback) {
        var newIndex = [];
        var snapshotFound = false;
        for (var i = 0; i < snapshotIndex.length; i++) {
            if (snapshotIndex[i].name !== name) {
                newIndex.push(snapshotIndex[i]);
            } else {
                snapshotFound = true;
            }
        }


        if (snapshotFound) {
            snapshotIndex = newIndex;

            // save snapshot idex
            saveSnapshotIndex();

            // remove snapshot file
            var tpath = path.join(repoPath, SNAPSHOT_DIR, name + '.idx');
            fs.unlinkSync(tpath);

            callback(null);
        } else {
            callback(new DError('REPO005', {
                snapshot: name
            }));
        }
    };


    this.searchSnapshots = function (opts, callback) {
        callback(null, []);
    };


    this.downloadPackage = function (name, opts, callback) {
        callback(null);
    };

    this.downloadRemotePackage = function (name, opts, callback) {
        callback(null);
    };

    // PRIVATE

    function getRootPath() {
        return path.join(repoPath, ROOT_DIR);
    }


    function addIndex(fname, info) {
        fileIndex[fname] = info;
    }


    function deleteIndex(fname) {
        delete fileIndex[fname];
    }


    function existsIndex(fname) {
        return (fileIndex[fname] !== undefined);
    }


    function saveIndex() {

        parent.log.info('Saving file index...');
        var indexPath = path.join(repoPath, INDEX_FILE);
        fs.writeFileSync(indexPath, JSON.stringify(fileIndex));
    }


    function loadIndex() {

        parent.log.info('Loading file index...');
        var indexPath = path.join(repoPath, INDEX_FILE);
        fileIndex = JSON.parse(fs.readFileSync(indexPath, {
            encoding: 'utf8'
        }));
    }


    function deleteSnapshotIndex(name) {
        delete snapshotIndex[name];
    }


    function saveSnapshotIndex() {

        parent.log.info('Saving snapshot index...');
        var snapshotIndexPath = path.join(repoPath, SNAPSHOT_INDEX_FILE);
        fs.writeFileSync(snapshotIndexPath, JSON.stringify(snapshotIndex));
    }


    function loadSnapshotIndex() {

        parent.log.info('Loading snapshot index...');
        var snapshotIndexPath = path.join(repoPath, SNAPSHOT_INDEX_FILE);
        snapshotIndex = JSON.parse(fs.readFileSync(snapshotIndexPath, {
            encoding: 'utf8'
        }));
    }


    function addFiles(lpaths, opts, callback) {
        async.map(lpaths,
            function (lpath, cb) {
                var dfsPath = path.basename(lpath);
                // if from client, upload to server directly, otherwise, add it to me
                if (dibs.thisServer === undefined || dibs.thisServer === null) {
                    dfs.addFileToServer(dfsPath, lpath, parent, function (err, dfsPath2) {
                        cb(err, dfsPath2);
                    });
                } else {
                    dfs.addFile(null, lpath, function (err, dfsPath2) {
                        cb(err, dfsPath2);
                    });
                }
            },
            function (err, rpaths) {
                callback(err, rpaths);
            });
    }

}
