/**
 * dfs-lock.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 uuid = require('node-uuid');

DError = require('../../core/exception.js');
var dibs = require('../../core/dibs.js');

/**
 * @module core/dfs/dfs-lock
 */

module.exports = DFSLock;

/**
 * @constructor
 * @param {string} rPath
 * @param {object} opt
 * @memberOf module:core/dfs/dfs-lock
 */
function FileLock(dfsPath, opt) {
    /** type {string} */
    this.dfsPath = dfsPath;
    /** type {string} */
    this.option = opt;
    /** type {string} */
    this.id = uuid.v4();
}


/**
 * @constructor
 * @param {string} rPath
 * @param {object} opt
 * @memberOf module:core/dfs/dfs-lock
 */
function LockInfo(dfsPath, opt) {
    var self = this;

    /** @type {string} */
    this.dfsPath = dfsPath;
    /** @type {object} */
    this.option = opt;

    //
    /** @type {object} */
    var refTable = {};


    /**
     * @method hasRef
     * @param {module:core/dfs/dfs-lock.FileLock} oldLock
     * @returns {boolean}
     * @memberOf core/dfs/dfs-lock.LockInfo
     */
    this.hasRef = function (oldLock) {
        if (!refTable[oldLock.id]) {
            return false;
        } else {
            return true;
        }
    };


    /**
     * @method hasNoRef
     * @returns {boolean}
     * @memberOf core/dfs/dfs-lock.LockInfo
     */
    this.hasNoRef = function () {
        for (var ref in refTable) {
            if (refTable[ref]) {
                return false;
            }
        }
        return true;
    };


    /**
     * @method addRef
     * @returns {module:core/dfs/dfs-lock.FileLock}
     * @memberOf core/dfs/dfs-lock.LockInfo
     */
    this.addRef = function () {
        var newLock = new FileLock(self.dfsPath, self.option);

        refTable[newLock.id] = newLock;
        return newLock;
    };


    /**
     * @method removeRef
     * @param {module:core/dfs/dfs-lock.FileLock} oldLock
     * @returns {undefined}
     * @memberOf core/dfs/dfs-lock.LockInfo
     */
    this.removeRef = function (oldLock) {
        delete refTable[oldLock.id];
    };
}

/**
 * @callback callback_err_lock
 * @param {error|undefined} error
 * @param {module:core/dfs/dfs-lock.FileLock} callback
 * @memberOf module:core/dfs/dfs-lock
 */

/**
 * @typedef table
 * @property {string} rpath
 * @property {module:core/dfs/dfs-lock.LockInfo} lockinfo
 * @memberOf module:core/dfs/dfs-lock
 */

/**
 * @constructor
 * @memberOf module:core/dfs/dfs-lock
 */
function DFSLock() {
    var self = this;

    // private
    var table = {}; // dfsPath -> lockinfo

    /**
     * @method aquire
     * @param {string} rPath
     * @param {object} opt
     * @param {module:core/dfs/dfs-lock.callback_err_lock} callback
     * @memberOf module:core/dfs/dfs-lock.DFSLock
     */
    this.aquire = function (dfsPath, opt, callback) {
        aquireLockInternal(dfsPath, opt, function (err, ret) {
            callback(err, ret);
        });
    };


    /**
     * @method release
     * @param {module:core/dfs/dfs-lock.FileLock} lock
     * @return {boolean}
     * @memberOf module:core/dfs/dfs-lock.DFSLock
     */
    this.release = function (lock) {
        var dfsPath = lock.dfsPath;

        if (table[dfsPath] && table[dfsPath].hasRef(lock)) {

            table[dfsPath].removeRef(lock);
            if (table[dfsPath].hasNoRef()) {
                delete table[dfsPath];
            }
            return true;
        } else {
            return false;
        }
    };


    /**
     * @method getLockTable
     * @return {module:core/dfs/dfs-lock.table} table
     * @memberOf module:core/dfs/dfs-lock.DFSLock
     */
    this.getLockTable = function () {
        return table;
    };


    function checkLock(dfsPath, callback) {
        if (!table[dfsPath]) {
            callback(new DError('DFS011'), null);
        } else {
            callback(null, table[dfsPath]);
        }
    }


    function aquireLockInternal(dfsPath, opt1, callback) {
        if (!table[dfsPath]) {
            table[dfsPath] = new LockInfo(dfsPath, opt1);
            callback(null, table[dfsPath].addRef());
        } else {
            if (opt1.exclusive ||
                table[dfsPath].option.exclusive) {

                // wait for lock release( wait for 100ms )
                setTimeout(function () {
                    aquireLockInternal(dfsPath, opt1, callback);
                }, 100);
            } else {
                // increase refer count
                callback(null, table[dfsPath].addRef());
            }
        }
    }
}

