/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/extra/event_behavior
 * @require prototype.js, livepipe.js
 * @attribution http://www.adamlogic.com/2007/03/20/3_metaprogramming-javascript-presentation
 */

/*global Prototype, Class, Event, Try, $, $A, $H */

if(typeof(Prototype) == "undefined") {
    throw "Event.Behavior requires Prototype to be loaded."; }
if(typeof(Object.Event) == "undefined") {
    throw "Event.Behavior requires Object.Event to be loaded."; }
    
Event.Behavior = {
    addVerbs: function(verbs){
        var v;
        for (var name in verbs) { if (verbs.hasOwnProperty(name)) {
            v = new Event.Behavior.Verb(verbs[name]);
            Event.Behavior.Verbs[name] = v;
            Event.Behavior[name.underscore()] = Event.Behavior[name] = v.getCallbackForStack.bind(v);
        }}
    },
    addEvents: function(events){
        $H(events).each(function(event_type){
            Event.Behavior.Adjective.prototype[event_type.key.underscore()] = Event.Behavior.Adjective.prototype[event_type.key] = function(){
                this.nextConditionType = 'and';
                this.events.push(event_type.value);
                this.attachObserver(false);
                return this;
            };
        });
    },
    invokeElementMethod: function(element,action,args){
        if(typeof(element) == 'function'){
            return $A(element()).each(function(e){
                if(typeof(args[0]) == 'function'){
                    return $A(args[0]).each(function(a){
                        return $(e)[action].apply($(e),(a ? [a] : []));
                    });
                }else {
                    return $(e)[action].apply($(e),args || []); }
            });
        }else {
            return $(element)[action].apply($(element),args || []); }
    }
};

Event.Behavior.Verbs = $H({});

Event.Behavior.Verb = Class.create();
Object.extend(Event.Behavior.Verb.prototype,{
    originalAction: false,
    execute: false,
    executeOpposite: false,
    target: false,
    initialize: function(action){
        this.originalAction = action;
        this.execute = function(action,target,argument){
            return (argument) ? action(target,argument) : action(target);
        }.bind(this,action);
    },
    setOpposite: function(opposite_verb){
        var opposite_action = opposite_verb.originalAction;
        this.executeOpposite = function(opposite_action,target,argument){
            return (argument) ? opposite_action(target,argument) : 
                opposite_action(target);
        }.bind(this,opposite_action);
    },
    getCallbackForStack: function(argument){
        return new Event.Behavior.Noun(this,argument);
    }
});

Event.Behavior.addVerbs({
    call: function(callback){
        callback();
    },
    show: function(element){
        return Event.Behavior.invokeElementMethod(element,'show');
    },
    hide: function(element){
        return Event.Behavior.invokeElementMethod(element,'hide');
    },
    remove: function(element){
        return Event.Behavior.invokeElementMethod(element,'remove');
    },
    setStyle: function(element,styles){
        return Event.Behavior.invokeElementMethod(element,'setStyle',[(typeof(styles) == 'function' ? styles() : styles)]);
    },
    addClassName: function(element,class_name){
        return Event.Behavior.invokeElementMethod(element,'addClassName',[(typeof(class_name) == 'function' ? class_name() : class_name)]);
    },
    removeClassName: function(element,class_name){
        return Event.Behavior.invokeElementMethod(element,'removeClassName',[(typeof(class_name) == 'function' ? class_name() : class_name)]);
    },
    setClassName: function(element,class_name){
        var c = (typeof(class_name) == 'function') ? class_name() : class_name;
        if(typeof(element) == 'function'){
            return $A(element()).each(function(e){
                $(e).className = c;
            });
        }else {
            c = $(element).className;
            return c;
        }
    },
    update: function(content,element){
        return Event.Behavior.invokeElementMethod(element,'update',[(typeof(content) == 'function' ? content() : content)]);
    },
    replace: function(content,element){
        return Event.Behavior.invokeElementMethod(element,'replace',[(typeof(content) == 'function' ? content() : content)]);
    }
});
Event.Behavior.Verbs.show.setOpposite(Event.Behavior.Verbs.hide);
Event.Behavior.Verbs.hide.setOpposite(Event.Behavior.Verbs.show);
Event.Behavior.Verbs.addClassName.setOpposite(Event.Behavior.Verbs.removeClassName);
Event.Behavior.Verbs.removeClassName.setOpposite(Event.Behavior.Verbs.addClassName);

Event.Behavior.Noun = Class.create();
Object.extend(Event.Behavior.Noun.prototype,{
    verbs: false,
    verb: false,
    argument: false,
    subject: false,
    target: false,
    initialize: function(verb,argument){
        //this.verbs = $A([]);
        this.verb = verb;
        this.argument = argument;
    },
    execute: function(){
        return (this.target) ? this.verb.execute(this.target,this.argument) : 
            this.verb.execute(this.argument);
    },
    executeOpposite: function(){
        return (this.target) ? 
            this.verb.executeOpposite(this.target,this.argument) : 
            this.verb.executeOpposite(this.argument);
    },
    when: function(subject){
        this.subject = subject;
        return new Event.Behavior.Adjective(this);
    },
    getValue: function(){
        return Try.these(
            function(){return $(this.subject).getValue();}.bind(this),
            function(){return $(this.subject).options[$(this.subject).options.selectedIndex].value;}.bind(this),
            function(){return $(this.subject).value;}.bind(this),
            function(){return $(this.subject).innerHTML;}.bind(this)
        );
    },
    containsValue: function(match){
        var value = this.getValue();
        if(typeof(match) == 'function'){
            return $A(match()).include(value);
        }else {
            return value.match(match); }
    },
    setTarget: function(target){
        this.target = target;
        return this;
    },
    and: function(){

    }
});
Event.Behavior.Noun.prototype._with = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.on = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.of = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.to = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.from = Event.Behavior.Noun.prototype.setTarget;

Event.Behavior.Adjective = Class.create();
Object.extend(Event.Behavior.Adjective.prototype,{
    noun: false,
    lastConditionName: '',
    nextConditionType: 'and',
    conditions: $A([]),
    events: $A([]),
    attached: false,
    initialize: function(noun){
        this.conditions = $A([]);
        this.events = $A([]);
        this.noun = noun;
    },
    attachObserver: function(execute_on_load){
        if(this.attached){
            //this may call things multiple times, but is the only way to gaurentee correct state on startup
            if(execute_on_load) {
                this.execute(); }
            return;
        }
        this.attached = true;
        if(typeof(this.noun.subject) == 'function'){
            $A(this.noun.subject()).each(function(subject){
                (this.events.length > 0 ? this.events : $A(['change'])).each(function(event_name){
                    (subject.observe ? subject : $(subject)).observe(event_name,function(){
                        this.execute();
                    }.bind(this));
                }.bind(this));
            }.bind(this));
        }else{
            (this.events.length > 0 ? this.events : $A(['change'])).each(function(event_name){
                $(this.noun.subject).observe(event_name,function(){
                    this.execute();
                }.bind(this));
            }.bind(this));
        }
        if(execute_on_load) { this.execute(); }
    },
    execute: function(){
        if(this.match()) { return this.noun.execute(); }
        else if(this.noun.verb.executeOpposite) { this.noun.executeOpposite(); }
    },
    attachCondition: function(callback){
        this.conditions.push([this.nextConditionType,callback.bind(this)]);
    },
    match: function(){
        if(this.conditions.length === 0) { return true; }
        else {
            return this.conditions.inject(false, function (bool,condition) {
                return (condition[0] === 'or') ? 
                       (bool && condition[1]()) : (bool || condition[1]());
            });
        }
    },
    //conditions
    is: function(item){
        this.lastConditionName = 'is';
        this.attachCondition(function(item){
            return (typeof(item) == 'function' ? item() : item) == this.noun.getValue();
        }.bind(this,item));
        this.attachObserver(true);
        return this;
    },
    isNot: function(item){
        this.lastConditionName = 'isNot';
        this.attachCondition(function(item){
            return (typeof(item) == 'function' ? item() : item) != this.noun.getValue();
        }.bind(this,item));
        this.attachObserver(true);
        return this;
    },
    contains: function(item){
        this.lastConditionName = 'contains';
        this.attachCondition(function(item){
            return this.noun.containsValue(item);
        }.bind(this,item));
        this.attachObserver(true);
        return this;
    },
    within: function(item){
        this.lastConditionName = 'within';
        this.attachCondition(function(item){
            
        }.bind(this,item));
        this.attachObserver(true);
        return this;
    },
    //events
    change: function(){
        this.nextConditionType = 'and';
        this.attachObserver(true);
        return this;
    },
    and: function(condition){
        this.attached = false;
        this.nextConditionType = 'and';
        if(condition) { this[this.lastConditionName](condition); }
        return this;
    },
    or: function(condition){
        this.attached = false;
        this.nextConditionType = 'or';
        if(condition) { this[this.lastConditionName](condition); }
        return this;
    }
});

Event.Behavior.addEvents({
    losesFocus: 'blur',
    gainsFocus: 'focus',
    isClicked: 'click',
    isDoubleClicked: 'dblclick',
    keyPressed: 'keypress'
});

Event.Behavior.Adjective.prototype.is_not = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.include = Event.Behavior.Adjective.prototype.contains;
Event.Behavior.Adjective.prototype.includes = Event.Behavior.Adjective.prototype.contains;
Event.Behavior.Adjective.prototype.are = Event.Behavior.Adjective.prototype.is;
Event.Behavior.Adjective.prototype.areNot = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.are_not = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.changes = Event.Behavior.Adjective.prototype.change;
