/*! jQuery Ctrl - v0.1.0-1 - 2014-08-01
* https://github.com/zengohm/jquery-ctrl
* Copyright (c) 2014 Zeng Ohm; Licensed MIT */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        factory(require('jquery'));
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {
    var NODE_STATUS_NORMAL = 0;
    var NODE_STATUS_COVER = 1;
    var NODE_STATUS_LAST = 2;

    var bindingTypes = {};

    var createCtrl = function(modelData,$element){
      return {
          model:new ModelObject(modelData),
          ctrl: $element
      };
    };

    // Static method.
    $.ctrl = function (ctrlName, init) {
        var root = $('[jq-ctrl="' + ctrlName + '"]');
        if(root.length===0){
            return false;
        }
        var thisCtrl = {};
        var modelData = {
            __refresh_binding__:function(){
                if(thisCtrl && thisCtrl.onChangeTasks){
                    for(var i = 0;i<thisCtrl.onChangeTasks.length;i++){
                        thisCtrl.onChangeTasks[i]();
                    }
                }
            }
        };
        init(modelData);
        thisCtrl = createCtrl(modelData,root);
        bindingInit(thisCtrl);
        return true;
    };

    var bindingInit = function (thisCtrl,parentOnchangeTasks) {
        thisCtrl.bindings = [];
        thisCtrl.onChangeTasks = parentOnchangeTasks?parentOnchangeTasks:[];
        var elementArray = [];
        var initElement = function ($element) {
            $element.contents().andSelf().each(function () {
                if(elementArray.indexOf(this) === -1) {
                    elementArray.push(this);
                    var node_status = NODE_STATUS_NORMAL;
                    for (var type in bindingTypes) {
                        node_status |= bindingTypes[type](this, thisCtrl.model, thisCtrl.onChangeTasks);
                        if (node_status & NODE_STATUS_LAST) {
                            break;
                        }
                    }
                    if(!(node_status & NODE_STATUS_COVER)) {
                        initElement($(this));
                    }
                }
            });
        };
        initElement(thisCtrl.ctrl);
    };

    bindingTypes.jqRepeat = function(element,model,onChangeTasks){
        var query, match;
        if(typeof(element.getAttribute)!=='function' || !(query = element.getAttribute('jq-repeat')) || !(match = query.match(/(\w+)\s+in\s+([\w\.]+)/))){
            return NODE_STATUS_NORMAL;
        }
        var modelName = match[2];
        var rowName = match[1];
        var startComment = document.createComment('jq-if '+query+' start');
        var endComment = document.createComment('jq-if '+query+' end');
        var template = $(element).prop('outerHTML');
        var parent = $(element).parent()[0];
        parent.insertBefore(startComment,element);
        if(element.nextElementSibling){
            parent.insertBefore(endComment,element.nextElementSibling);
        }else{
            parent.appendChild(endComment);
        }

        var repeatRowDelegate = function(subModel){
            var $row = $(template.replace(/\{\{([\w\.]+)\}\}/g,function(match,key){
                return subModel.get(key)!==null?subModel.get(key):match;
            }));
            $row.removeAttr('jq-repeat');
            var subCtrl = createCtrl(subModel.getData(),$row);
            bindingInit(subCtrl, onChangeTasks);
            return $row;
        };


        onChangeTasks.push(function(){
            var currentNode = startComment.nextSibling;
            while(currentNode !== endComment){
                currentNode = currentNode.nextSibling;
                $(currentNode.previousSibling).remove();
            }
            var list = model.get(modelName);
            for(var i in list){
                var subModel = model.clone();
                subModel.set(rowName,list[i]);
                repeatRowDelegate(subModel).insertBefore(endComment);
            }

        });
        onChangeTasks[onChangeTasks.length-1](null);
        return NODE_STATUS_COVER;
    };

    bindingTypes.text = function(node,model,onChangeTasks){
        if(node.nodeName.substr(0,1)==='#' && node.data.match(/\{\{[\w\.]+\}\}/)){
            var template = node.data;
            onChangeTasks.push(function(){
                node.data = template.replace(/\{\{([\w\.]+)\}\}/g, function (match, key) {
                    return model.get(key);
                });
            });
            onChangeTasks[onChangeTasks.length-1]();
            return NODE_STATUS_COVER | NODE_STATUS_LAST;
        }
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.model = function(element,model,onChangeTasks){
        var modelName;
        if(element.getAttribute && (modelName = element.getAttribute('jq-model'))){
            onChangeTasks.push(function(srcElement){
                if(element === srcElement){
                    return false;
                }
                if(element.type==='checkbox'){
                    $(element).attr({'checked':model.get(modelName)?'checked':false});
                }else {
                    $(element).val(model.get(modelName));
                }
            });
            onChangeTasks[onChangeTasks.length-1]();
            $(element).bind('keypress change keyup',function(){
                if(this.type==='checkbox') {
                    model.set(modelName, this.checked);
                }else{
                    model.set(modelName, $(this).val());
                }

                for(var i in onChangeTasks){
                    onChangeTasks[i](this);
                }
            });
            return NODE_STATUS_COVER;
        }
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.jqAttribute = function(element,model,onChangeTasks){
        if(typeof(element.getAttribute)!=='function'){
            return NODE_STATUS_NORMAL;
        }
        var allowAttributes = ['jq-src','jq-href'];
        var onChangeDelegate = function(element,attribute,template) {
            onChangeTasks.push(function(){
                element.setAttribute(attribute.substr(3),template.replace(/\{\{([\w\.]+)\}\}/g, function (match, key) {
                    return model.get(key);
                }));
            });
        };
        for (var i = 0; i < allowAttributes.length; i++) {
            var template;
            var attribute = allowAttributes[i];
            if(template = element.getAttribute(allowAttributes[i])){
                onChangeDelegate(element,attribute,template);
                onChangeTasks[onChangeTasks.length-1]();
            }
        }
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.otherAttribute = function(element,model,onChangeTasks){
        if(typeof(element.getAttribute)!=='function'){
            return NODE_STATUS_NORMAL;
        }
        var attributes = element.attributes;

        var onChangeDelegate = function(element,attribute,template) {
            onChangeTasks.push(function () {
                element.setAttribute(attribute, template.replace(/\{\{([\w\.]+)\}\}/g, function (match, key) {
                    return model.get(key);
                }));
            });
        };

        for (var i = 0; i < attributes.length; i++) {
            if(attributes[i].name.substr(0,3)==='jq-'){
                continue;
            }
            var template = attributes[i].value;
            var attribute = attributes[i].name;
            if(template.match(/\{\{([\w\.]+)\}\}/)){
                onChangeDelegate(element,attribute,template);
                onChangeTasks[onChangeTasks.length-1](null);
            }
        }
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.jqStyle = function(element,model,onChangeTasks){
        var modelName;
        if(typeof(element.getAttribute)!=='function' || !(modelName = element.getAttribute('jq-style'))){
            return NODE_STATUS_NORMAL;
        }
        onChangeTasks.push(function () {
            $(element).css(model.get(modelName));
        });
        onChangeTasks[onChangeTasks.length-1](null);
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.jqDisabled = function(element,model,onChangeTasks){
        var modelName;
        if(typeof(element.getAttribute)!=='function' || !(modelName = element.getAttribute('jq-disabled'))){
            return NODE_STATUS_NORMAL;
        }
        onChangeTasks.push(function () {
            $(element).attr('disabled',model.get(modelName));
        });
        onChangeTasks[onChangeTasks.length-1](null);
        return NODE_STATUS_NORMAL;
    };
    bindingTypes.jqSelect = function(element,model,onChangeTasks){
        var query;
        if(element.nodeName!=='SELECT' || !(query = element.getAttribute('jq-options'))){
            return NODE_STATUS_NORMAL;
        }
        var match;
        var keyName,valueName,listName;
        if(match = query.match(/^(\w+)$/)){
            keyName = null;
            valueName = null;
            listName = match[1];
        }else if(match = query.match(/^(\w+),(\w+)\s+in\s+(\w+)$/)){
            keyName = match[1];
            valueName = match[2];
            listName = match[3];
        }else{
            return NODE_STATUS_NORMAL;
        }

        onChangeTasks.push(function(){
            $(element).empty();
            var modelName = element.getAttribute('jq-model');
            var list = model.get(listName);
            if(list && typeof(list[0])==='object' && Object.keys(list[0]).length===1 && list[0][Object.keys(list[0])[0]]  instanceof Array ) {
                for (var g in list){
                    var $group = $('<optgroup></optgroup>');
                    var groupKey = Object.keys(list[g])[0];
                    $group.attr('label',groupKey);
                    var subList = list[g][groupKey];
                    if(keyName === null){
                        for(var gi in subList){
                            $group.append('<option>' + subList[gi] + '</option>');
                        }
                    }else{
                        for (var gk in subList) {
                            $group.append('<option value="' + subList[gk][keyName] + '">' + subList[gk][valueName] + '</option>');
                        }
                    }
                    $(element).append($group);
                }
            }else{
                if(keyName === null) {
                    for(var i in list){
                        $(element).append('<option>' + list[i] + '</option>');
                    }
                }else{
                    for (var k in list) {
                        $(element).append('<option value="' + list[k][keyName] + '">' + list[k][valueName] + '</option>');
                    }
                }
            }
            $(element).val(model.get(modelName));
        });
        onChangeTasks[onChangeTasks.length-1](null);
        $(element).removeAttr('jq-options');
        return NODE_STATUS_COVER;
    };
    bindingTypes.jqIf = function(element,model,onChangeTasks){
        var query;
        if(typeof(element.getAttribute)!=='function' || !(query = element.getAttribute('jq-if'))){
            return NODE_STATUS_NORMAL;
        }

        var startComment = document.createComment('jq-if '+query+' start');
        var endComment = document.createComment('jq-if '+query+' end');
        var parent = $(element).parent()[0];
        parent.insertBefore(startComment,element);
        if(element.nextElementSibling){
            parent.insertBefore(endComment,element.nextElementSibling);
        }else{
            parent.appendChild(endComment);
        }
        var fun = new Function('model','with(model){return ' + query + ';}');
        onChangeTasks.push(function(){
            $(element).remove(null,true);
            if(fun(model.getData())){
                $(element).insertAfter(startComment);

            }
        });
        onChangeTasks[onChangeTasks.length-1](null);
        return NODE_STATUS_NORMAL;
    };

    bindingTypes.jqEvent = function(element,model,onChangeTasks){
        var types = ['click','keypress','keyup','keydown','dbclick','mousedown','mouseup','mouseover','mousemove','mouseout','change','submit'];
        var query;
        var action = function(scope,funName,params){
            return function() {
                var rVal = model.get(funName).apply(scope, params);
                for (var i in onChangeTasks) {
                    onChangeTasks[i](scope);
                }
                return rVal;
            };
        };
        for(var i in types) {
            var type = types[i];
            if (element.getAttribute && (query = element.getAttribute('jq-' + type))) {
                var match = query.match(/^([\w\.]+)\((.*?)\)$/);
                var funName = match[1];
                var params = eval('['+match[2]+']');
                $(element).bind(type, action(this,funName,params));
            }
        }
        return NODE_STATUS_NORMAL;
    };

    var ModelObject = function(data){
        if(typeof(data) === 'undefined' || !data){
            data = {};
        }
        var model = data;

        this.set = function(key,value){
            var keys = key.split('.');
            var lastKey = keys.pop();
            var current = model;
            for(var i in keys){
                if(typeof(current[keys[i]]) === 'undefined'){
                    current[keys[i]] = {};
                }
                current = current[keys[i]];
            }
            current[lastKey] = value;
            return value;
        };

        this.get = function(key){
            var keys = key.split('.');
            var current = model;
            for(var i in keys){
                current = current[keys[i]];
                if(typeof(current)==='undefined'){
                    return null;
                }
            }
            return current;
        };

        this.subModel = function(key){
            return new ModelObject(this.get(key));
        };

        this.getData = function(){
            return model;
        };

        this.clone = function(){
            return new ModelObject($.extend(true,{},model));
        };
    };
}));