/**
 * Created by TalhaAkkas on 16/02/15.
 */

//'use strict';

function addPropertyToClass(clas, name, prop, opts){
    var options = opts || {
            value: prop,
            writable: true,
            enumerable: false,
            configurable: true
        }
    if(!clas[name]){
        Object.defineProperty( clas, name, options);
    }else{
        // EWLog(name, "Property Allready Defined")
    }
}
module.exports = function(){

    addPropertyToClass(global, '__stack',undefined, {
        get: function(){
            var orig = Error.prepareStackTrace;
            Error.prepareStackTrace = function(_, stack){ return stack; };
            var err = new Error;
            Error.captureStackTrace(err, arguments.callee);
            var stack = err.stack;
            Error.prepareStackTrace = orig;
            return stack;
        }
    });
    addPropertyToClass(global, '__line', undefined,{
        gçet: function(){
            return __stack[1].getLineNumber();
        }
    });
    addPropertyToClass(global, '__callerlline',undefined, {
        get: function(){
            return __stack[2].getLineNumber();
        }
    });
    addPropertyToClass(global, '__caller', undefined, {
        get: function(){
            var stack = __stack[2];
            return stack.getFileName() + " " + stack.getLineNumber();
        }
    });
    addPropertyToClass(global, '__printStackTrace', function(){
        __stack.forEach(function(item){
            console.log(item.getFileName() + " " + item.getLineNumber())
        })
    });
    addPropertyToClass(global, "EWLog", function(){
        var date = new Date();
        var dateStr = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + ":"+ date.getMilliseconds()
        var args = [dateStr,__caller];
        for(var key in arguments){
            args.push(arguments[key]);
        }
        console.log.apply(null,args);
    });
    addPropertyToClass(EWLog, "levels", ["silly", "debug", "verbose", "info", "warn", "error", "none"]);
    addPropertyToClass(EWLog, "logLevel", "silly")
    addPropertyToClass(EWLog, "silly", function(){
        var activeLevel = this.levels.indexOf(this.logLevel)
        var thisLevel = this.levels.indexOf("silly")
        if(thisLevel >= activeLevel){
            EWLog(arguments)
        }
    });
    addPropertyToClass(EWLog, "verbose", function(){
        var activeLevel = EWLog.levels.indexOf(EWLog.logLevel)
        var thisLevel = EWLog.levels.indexOf("verbose")
        if(thisLevel >= activeLevel){
            EWLog(arguments)
        }
    });
    addPropertyToClass(EWLog, "info", function(){
        var activeLevel = EWLog.levels.indexOf(EWLog.logLevel)
        var thisLevel = EWLog.levels.indexOf("info")
        if(thisLevel >= activeLevel){
            EWLog(arguments)
        }
    });
    addPropertyToClass(EWLog, "warn", function(){
        var activeLevel = EWLog.levels.indexOf(EWLog.logLevel)
        var thisLevel = EWLog.levels.indexOf("warn")
        if(thisLevel >= activeLevel){
            EWLog(arguments)
        }
    });
    addPropertyToClass(EWLog, "error", function(){
        var activeLevel = EWLog.levels.indexOf(EWLog.logLevel)
        var thisLevel = EWLog.levels.indexOf("error")
        if(thisLevel >= activeLevel){
            EWLog(arguments)
        }
    });
    addPropertyToClass(global, "fwLog", EWLog)

    addPropertyToClass(Date.prototype, 'getStartOfDay',function(){
      var start = new Date(this.getTime())
      start.setHours(0,0,0,0);
      return start
    })

    addPropertyToClass(Date.prototype, 'getEndOfDay',function(){
      var start = new Date(this.getTime())
      start.setHours(23,59,59,59);
      return start
    })
    addPropertyToClass(Array.prototype, 'removeDuplicates',function(predicate){
        var set = [];
        for(var key in this){
            if(this[key] != undefined && this[key] != null &&
                (
                    (!predicate && set.indexOf(this[key]) == -1 ) ||
                    (predicate && set.indexInArray(this[key], predicate) == -1)
                )
            ){
                set.push(this[key])
            }
        }
        return set;
    });
    addPropertyToClass(Array.prototype, 'removeItem',function(item, predicate){
        var i = undefined;
        if(predicate){
            i = this.indexInArray(item, predicate);
        }else {
            i = this.indexOf(item);
        }
        if(i && i != -1) {
            this.splice(i, 1);
        }
        return i;
    });
    addPropertyToClass(Array.prototype,'findInArray',function(predicate) {
        if (this == null) {
            throw new TypeError('Array.prototype.find called on null or undefined');
        }
        if (typeof predicate !== 'function') {
            throw new TypeError('predicate must be a function');
        }
        var list = Object(this);
        var length = list.length >>> 0;
        var thisArg = arguments[1];
        var value;

        for (var i = 0; i < length; i++) {
            value = list[i];
            if (predicate.call(thisArg, value, i, list)) {
                return value;
            }
        }
        return undefined;
    });

    addPropertyToClass(Array.prototype,'indexInArray',function(item, predicate) {
        if (this == null) {
            throw new TypeError('Array.prototype.find called on null or undefined');
        }
        if (typeof predicate !== 'function') {
            throw new TypeError('predicate must be a function');
        }
        var list = Object(this);
        var length = list.length >>> 0;
        var thisArg = arguments[0];
        var value;

        for (var i = 0; i < length; i++) {
            value = list[i];
            if (predicate.call(thisArg, value, i, list)) {
                return i;
            }
        }
        return -1;
    });


    addPropertyToClass(Array.prototype, "blendWith", function(array, blend, key){
        return key ? this.blendIntoObject(array, blend, key) : this.blendIntoArray(array, blend);
    });

    addPropertyToClass(Array.prototype,"blendIntoArray",function(array, blend){
        var arr = [];
        for(var i = 0; i < this.length; i++){
            for(var j = 0; j < array.length; j++){
                var item = blend(this[i], array[j], i, j);
                if(item){
                    arr.push(item);
                }
            }
        }
        return arr;
    });
    addPropertyToClass(Array.prototype,"blendIntoObject", function(array, blend, keyFunc){
        var obj = {};
        for(var i = 0; i < this.length; i++){
            for(var j = 0; j < array.length; j++){
                var item = blend(this[i], array[j], i, j);
                var key = keyFunc(this[i], array[j], i, j);
                if(item && key){
                    obj[key] = item;
                }
            }
        }
        return obj;
    });
    addPropertyToClass(Array.prototype,"groupEW", function(keyFunc){
        var obj = {};
        for(var i = 0; i < this.length; i++){
            var item = this[i];
            var key = keyFunc(this[i], i);
            if(item && key && !obj[key]){
                obj[key] = []
            }

            if(item && key){
                obj[key].push(item);
            }
            
        }
        return obj;
    });
    addPropertyToClass(Array.prototype,"notIn", function(array, comp){
        var arr = [];
        for(var i = 0; i < this.length; i++){
            var isIn = false;
            for(var j = 0; j < array.length; j++){
                if(comp(this[i], array[j])){
                    isIn = true;
                    break;
                }
            }
            if(!isIn || array.length == 0){
                arr.push(this[i]);
            }
        }
        return arr;
    });
    addPropertyToClass(Array.prototype,"reduce",function(callback /*, initialValue*/) {
        if (this == null) {
            throw new TypeError('Array.prototype.reduce called on null or undefined');
        }
        if (typeof callback !== 'function') {
            throw new TypeError(callback + ' is not a function');
        }
        var t = Object(this), len = t.length >>> 0, k = 0, value;
        if (arguments.length == 2) {
            value = arguments[1];
        } else {
            while (k < len && !(k in t)) {
                k++;
            }
            if (k >= len) {
                throw new TypeError('Reduce of empty array with no initial value');
            }
            value = t[k++];
        }
        for (; k < len; k++) {
            if (k in t) {
                value = callback(value, t[k], k, t);
            }
        }
        return value;
    });
    addPropertyToClass(Array.prototype,"flatten",function() {
        return this.reduce(function(a, b) {
            if(a.isArray()){
                a.forEach(function(i){
                    if(i.isArray()){
                        a.concat(i.flatten());
                    }
                });
            }else{
                a = [a];
            }
            if(b.isArray()){
                b.forEach(function(i){
                    if(i.isArray()){
                        a.concat(i.flatten());
                    }else{
                        a.push(i);
                    }
                });
            }else{
                a.push(b);

            }
            return a;
        });
    });


    addPropertyToClass(Array.prototype,"mclone", function(){
        var obj = [];
        for(var key in this){
            obj[key] = this[key];
        }
        return obj;
    });
    addPropertyToClass(Array.prototype,"dclone", function(){
        return JSON.parse(JSON.stringify(this));
    });
    addPropertyToClass(Array.prototype,"isArray",function() {
        return Object.prototype.toString.call(this) === '[object Array]';
    });
    addPropertyToClass(Object.prototype,"isArray",function() {
        return Object.prototype.toString.call(this) === '[object Array]';
    });
    addPropertyToClass(Array.prototype,"walk",function(key, set) {
        if(set == undefined){
            var arr = key.split(/[\]\[]+/g).filter(function(item){return item.length > 0});
            elem = this[arr[0]];
            arr.splice(0,1)
            for(i in arr){
                if(elem == undefined) {break};
                k = arr[i];
                elem = elem[k];
            }
            return elem;
        }else{
            var arr = key.split(/[\]\[]+/g);
            if(arr.length == 1){
                this[arr[0]] = set;
                return;
            }
            var elem = this[arr[0]];
            arr.splice(0,1)
            var last = arr.pop() 
            for(i in arr){ 
                if(elem == undefined) {break};
                var k = arr[i];
                elem = elem[k];
                
                EWLog(elem);
            }
            if(elem != undefined){
                if(last.length > 0){
                    elem[last] = set
                }else if(elem.isArray()){
                    elem.push(set)
                }
            }
            return elem;
        }
    });
    addPropertyToClass(Object.prototype,"walk",function(key, set) {
        if(set == undefined){
            var arr = key.split(/[\]\[]+/g).filter(function(item){return item.length > 0});
            elem = this[arr[0]];
            arr.splice(0,1)
            for(i in arr){
                if(elem == undefined) {break};
                k = arr[i];
                elem = elem[k];
            }
            EWLog(elem, arr, key);
            return elem;
        }else{
            var arr = key.split(/[\]\[]+/g);
            if(arr.length == 1){
                this[arr[0]] = set;
                return;
            }
            var elem = this[arr[0]];
            arr.splice(0,1)
            var last = arr.pop()
            for(i in arr){ 
                if(elem == undefined) {break};
                var k = arr[i];
                elem = elem[k];
            }
            if(elem != undefined){
                if(last.length > 0){
                    elem[last] = set
                }else if(elem.isArray()){
                    elem.push(set)
                }
            }
            return elem;
        }
    });

    addPropertyToClass(Object.prototype, "flip", function() {
        var flip = {};
        for (var key in this) {
            flip[this[key]] = key;
        }
        return flip;
    });
    addPropertyToClass(Object.prototype,"sortByReference",function(ref, predicate) {
        var array = ref.map(function(item){return "";});
        for(var i = 0; i < this.length; i++){
            var index = undefined;
            if(predicate){
                index = ref.indexInArray(this[i], predicate);
            }else{
                index = ref.indexOf(this[i]);
            };
            if(index != undefined){
                array[index] = this[i];
            }
        }
        return array;
    });

    addPropertyToClass(Object.prototype,"inject", function(relatedData, keys){
        if(relatedData && keys){
            for (var i in keys) {
                var key = keys[i]
                this[key] = relatedData[key];
            }
        }else if (relatedData) {
            for (var key in relatedData) {
                this[key] = relatedData[key];
            }
        }
    });
    addPropertyToClass(Object.prototype,"keyIsEmpty", function(key){
        return typeof(this[key]) != "undefined" && this[key] !== null && this[key] != "";
    });

    addPropertyToClass(Object.prototype,"mclone", function(){
        var obj = {};
        for(var key in this){
            obj[key] = this[key];
        }
        return obj;
    });
    addPropertyToClass(Object.prototype,"dclone", function(){
        return JSON.parse(JSON.stringify(this));
    });
    addPropertyToClass(Object.prototype, "toArrayEW", function(){
        var arr = [];
        for(var key in this){
            arr.push(this[key]);
        }
        return arr;
    });
    addPropertyToClass(String.prototype, "toObjectId", function() {
        var ObjectId = (require('mongoose').Types.ObjectId);
        return new ObjectId(this.toString());
    });
    addPropertyToClass(Object.prototype, "associativeIndexOf", function(value) {
        for (var key in this) if (key == value) return key;
        return undefined;
    });
    addPropertyToClass(String.prototype, "allReplace",  function(obj) {
        var retStr = this
        for (var x in obj) {
            retStr = retStr.replace(new RegExp(x, 'g'), obj[x])
        }
        return retStr
    });
    addPropertyToClass(String.prototype, "fullTrim", function(){
        return this.replace(/^\s+|\s+$|\s+(?=\s)/g, "");
    });
    addPropertyToClass(String.prototype, "format", function() {
      a = this;
      for (k in arguments) {
        a = a.replace("{" + k + "}", arguments[k])
      }
      return a
    });
    addPropertyToClass(String.prototype, "capitalize", function() {
      return this.split(" ").map(function(s){return s[0].toUpperCase() + s.substring(1)}).join(" ")
    });

    addPropertyToClass(Object.prototype, "goAOPsync", function(){
        var _func = this;
        var me =  function(){
            me.preCall.apply(this, arguments);
            _func.apply(this, arguments);
            me.postCall.apply(this, arguments);
        }
        me.preCall = function(){
            //console.log("preCall", arguments);

        };
        me.postCall = function(){
            //console.log(arguments, "postCall");
        };
        return me;

    });

    addPropertyToClass(Object.prototype, "goAOP", function(){
        var _func = this;
        var args = getParamNames(this)
        //var me = new Function(paramNames, "EWLog(this);this.mainCall(arguments)")

 

        var functionBody = "arguments.callee.mainCall.apply(this,arguments)";
        //args.forEach(function(arg){ functionBody += "args.push(" + arg + ");\n" });
        //functionBody += "arguments.callee.controller.createEventData(req,res,'"+funcNm+"', '" + this.pluralName +"')\n;";
        //functionBody += "arguments.callee.controller['" + superNm + "'].apply(this, args)\n;";
        args.push(functionBody);
        var me = construct(Function,  args);


        me.mainCall = function(){
            var argsPre = arguments.toArrayEW();
            var cb = argsPre[argsPre.length -1];
            argsPre.push(function(){
                var argsOrigin = arguments.toArrayEW();
                argsOrigin.push(function(){
                    var argsPost = arguments.toArrayEW();
                    argsPost.push(cb);
                    me.postCall.apply(this, argsPost);
                });
                _func.apply(this, argsOrigin);
            });
            me.preCall.apply(this, argsPre);
        };
        me.preCall = function(){
            var argsPre = arguments.toArrayEW();
            var next = argsPre[argsPre.length -1];
            next.apply(this,argsPre);
        };
        me.postCall = function(){
            var argsPre = arguments.toArrayEW();
            var next = argsPre[argsPre.length -1];
            next.apply(this,argsPre);
        };
        return me;
    });
};

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;


function getParamNames (func) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
    if(result === null)
        result = [];
    return result;
}

function construct(constructor, args) {
    function F() {
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}