'use strict';

var util = require('util');
var path = require('path');
var fs = require('fs');

/**
 * Function for creating a clone of an object
 *
 * @param o {Object}  object to clone
 * @return {Object}
 */
function clone(obj) {
    try {
        return JSON.parse(JSON.stringify(obj));
    } catch(ignore) {}

    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    var ret;
    if (util.isArray(obj)) {
        ret = [];
        obj.forEach(function (val) {
            ret.push(clone(val));
        });
        return ret;
    }
    ret = {};
    Object.keys(obj).forEach(function (key) {
        ret[key] = clone(obj[key]);
    });
    return ret;
}

/**
 * A extends B
 *
 * util.inherits works only with objects derived from Object
 *
 * @return {Object} Extended object
 */
function extend(a, b, noClone) { // A extends B
    a = a || {};

    if (typeof a !== 'object') {
        return noClone ? b : clone(b);
    }

    if (typeof b !== 'object') {
        return b;
    }

    if (!noClone) {
        a = clone(a);
    }

    var bk = Object.keys(b);
    var i, c;
    for (i = 0, c = bk.length; i < c; i++) {
        var key = bk[i];
        if (!a.hasOwnProperty(key) ||
            (!(typeof b[key] === 'object' && b[key] !== null && b[key].length === undefined) &&
                (typeof b[key] !== 'function'))) { // Simple types

            a[key] = b[key];
        } else { // Complex types
            a[key] = extend(a[key], b[key], noClone);
        }
    }
    return a;
}

/**
 * Function for mapping a folder and subfolders for certain file types
 * @param opts {Object} - path: folder to map, type: file extension to look for
 * @returns Object
 */
function mapFolder(opts) {
    var folders = [''];
    var map = {};
    Object.defineProperty(map, '_root', {value: opts.path});
    opts.sType = '.' + opts.type;
    var rel = '';
    function examine(file) {
        var stats = fs.statSync(path.join(map._root, rel, file));
        if(stats.isDirectory()) {
            folders.push(path.join(rel, file));
            return;
        }
        var ext = path.extname(file);
        if(ext === opts.sType || ext === opts.type) {
            map[path.join(rel, file)] = {
                _path: path.join(map._root, rel, file),
                _base: path.basename(file, path.extname(file))
            };
        }
    }
    folders.forEach(function (k) {
        rel = k !== '' ? k + path.sep : rel;
        var subfiles = fs.readdirSync(map._root + rel);
        subfiles.forEach(examine);
    });
    return map;
}


/**
 * Function for appending path.sep to the end of string if necessary
 * 
 * @param string {String}
 * @return {String}
 */
function fixPath(string) {
    if (string.charAt(string.length - 1) !== path.sep) {
        string += path.sep;
    }
    return string;
}

/**
 * Function for getting a value from object at index
 * @param obj
 * @param index - string for index, can be in format "a.b.c"
 * @returns {*}
 */
function getIndex(obj, index) {
    return index.split('.').reduce(function (thisOne, i) {
        return thisOne && thisOne[i] || undefined;
    }, obj);
}

/**
 * Function for seting a value in object at index
 * @param obj
 * @param index - string for index, can be in format "a.b.c"
 * @param value - value to set
 * @returns {*}
 */
function setIndex(obj, index, value) {
    if(typeof index === 'string') {
        index = index.split('.');
    }
    var current = index.shift();
    if(!index.length) {
        obj[current] = value;
    } else if(!obj[current]) {
        obj[current] = setIndex({}, index, value);
    } else {
        setIndex(obj[current], index, value);
    }
    return obj;
}

/**
 * Function for seting a value in object at index while igoring case
 * @param obj
 * @param index - string for index, can be in format "a.b.c"
 * @param value - value to set
 * @returns {*}
 */
function setCaseInsensitiveIndex(obj, index, value) {
    if(typeof index === 'string') {
        index = index.split('.');
    }
    var current = index.shift();
    var currentKey = current.toLowerCase();
    Object.keys(obj).some(function (key) {
        if(key.toLowerCase() === currentKey) {
            currentKey = key;
            return true;
        }
    });

    if(!index.length) {
        obj[currentKey] = value;
    } else if(!obj[currentKey]) {
        obj[currentKey] = setCaseInsensitiveIndex({}, index, value);
    } else {
        setCaseInsensitiveIndex(obj[currentKey], index, value);
    }
    return obj;
}

/**
 * Function for determining if value is a string representation
 * of boolean or number
 * @param value
 * @returns {*}
 */
function typeCast(value) {
    if(value === 'true') {
        return true;
    }
    if(value === 'false') {
        return false;
    }
    // Check integer values
    if(+value == value) {
        return +value;
    }
    // Check float values
    if(parseFloat(value) == value) {
        return parseFloat(value);
    }

    if(value.indexOf('[') === 0 || value.indexOf('{')) {
        var oldVal = value;
        try {
            value = JSON.parse(value);
        } catch(e) {
            value = oldVal;
        }
    }
    return value;
}

module.exports = {
    extend: extend,
    clone: clone,
    fixPath: fixPath,
    map: mapFolder,
    getIndex: getIndex,
    setIndex: setIndex,
    setCaseInsensitiveIndex: setCaseInsensitiveIndex,
    typeCast: typeCast
};
@@ -1051,24 +1051,280 @@
 (a);%0A    %7D%0A%0A
+    if (Array.isArray(b)) %7B%0A        if (Array.isArray(a)) %7B%0A            return a.concat(noClone ? b : b.map(function (item) %7B%0A                return clone(item);%0A            %7D));%0A        %7D else %7B%0A            return noClone ? b : clone(b);%0A        %7D%0A    %7D%0A%0A
     var bk =
@@ -1336,24 +1336,24 @@
 ct.keys(b);%0A
-
     var i, c
@@ -1462,16 +1462,16 @@
 key) %7C%7C%0A
+
         
@@ -1526,39 +1526,8 @@
 null
- && b%5Bkey%5D.length === undefined
 ) &&

Extend support for arrays