/*! Data Events - v0.1.0 - 2016-01-18
* https://github.com/ssauermann/data-events
* Copyright (c) 2016 S. Sauermann; Licensed MIT */
( function( $ ) {
    "use strict";
    var debug = false;

    /*
    Returns reference id of element
    data-id@='abc12' -> 'abc12'
    */
    function getDataId( e ) {
        return e.data( "id@" );
    }

    /*
     Returns a object of all data attributes beginning with an @ and
     their values of a given dom element
     {@name: value1, @other: value2}
     */
    function getAttributes( e ) {
        /*
         Returns all HTML5 custom data attributes as an object:
         <div id='prod' data-id='10' data-cat='toy' data-cid='42'>blah</div>
         -> { "id":10, "cat":"toy", "cid":42 }
         jQuery (>=1.4.4)
         */
        var dataDict = e.data();

        //Filter data attributes for @ character
        $.each( dataDict, function( key ) {
            if ( key.indexOf( "@" ) !== 0 ) {
                delete dataDict[ key ];
            }
        } );

        return dataDict;

    }

    /*
    Returns an array of all @-data attribute objects of each child
    [[dom1, {@name: value1}], [dom2, {@name: value2, @other: value3}]]
    */
    function getChildAttributes( e ) {

        var arry = [];

        e.find( "*" ).each( function() {
            arry.push( [ $( this ), getAttributes( $( this ) ) ] );
        } );

        return arry;

    }

    /*
    Resolve cross references and validates structure
    */
    function resolveReference( childAttr ) {

        //[[dom1, {@name: value1}], [dom2, {@name: value2, @other: value3}]]
        $.each( childAttr, function( i, arry ) {
            if ( arry.length < 2 ) {
                throw "Missing data: [dom, attribute]";
            }

            //Var dom = arry[0],
              var attr = arry[ 1 ];

            //{@name: value2, @other: value3}
            $.each( attr, function( k, v ) {

                if ( typeof ( v ) !== "object" ) {
                    console.error( attr );
                    throw "Format has to be: data-@name={event, handler}" +
                        "or {id, attribute} for a reference'";
                }

                var cyclicTest = [ v ],

                //Search for reference until finding a non reference
                    searchForReference = function( ref ) {

                        if ( debug ) {
                            console.log( "Resolving reference for: " );
                            console.log( ref );
                        }

                        //Filter valid references
                        var validIds = $.grep( childAttr, function( arry ) {

                                var id = getDataId( arry[ 0 ] );
                                return ( id === ref.id );

                            } ),
                            refAttribute;

                        if ( validIds.length === 0 ) {
                            throw "Reference id not found: " + ref.id;
                        }
                        if ( validIds.length > 1 ) {
                            throw "Reference id is ambigous: " + ref.id;
                        }

                        //Get referenced attribute
                        refAttribute = validIds[ 0 ][ 0 ].data( ref.attribute );

                        //Found another reference or am I done?
                        if ( typeof ( refAttribute.event ) !== "undefined" ) {
                            if ( debug ) {
                                console.log( "Resolved to: " );
                                console.log( refAttribute );
                            }
                            return refAttribute;
                        }

                        //--> another reference
                        if ( debug ) {
                            console.log( refAttribute );
                        }

                        //Test for cyclic dependencies
                        if ( $.inArray( refAttribute, cyclicTest ) ) {
                            throw "References are cyclic";
                        }

                        //Continue search
                        searchForReference( refAttribute );
                    };

                //If attribute is a reference
                if ( typeof ( v.event ) === "undefined" ) {

                    //Replace reference with resolved value
                    attr[ k ] = searchForReference( v );
                }

            } );
        } );

        return childAttr;
    }

    /*
    Handle an event and update the attribute with the calculated value
    */
    function handleEvent( dom, attributename, event, handler, value ) {
        if ( debug ) {
            console.log( "Handling event:" );
            console.log( dom );
            console.log( attributename );
            console.log( handler );
            console.log( value );
        }

        var resultValue;

        if ( typeof ( handler ) === "object" ) {
            resultValue = handler[ value ];
        } else if ( typeof ( handler ) === "undefined" ) {
            resultValue = value;
        } else {
            resultValue = eval( "(" + handler + ")" )( event, value );
        }

        if ( typeof ( resultValue ) === "undefined" ) {
            throw "Handler is invalid: " + handler;
        }

        //Camel case format of jquery to dashed format helloWorld -> hello-world
        dom.attr( attributename.replace( /([a-z])([A-Z])/g, "$1-$2" ).toLowerCase(), resultValue );
    }

    /*
    Execute an event with the given value on an dom element
    */
    function executeEvent( at, event, eventValue ) {
        if ( debug ) {
            console.log( "Executing event:" );
            console.log( event );
            console.log( eventValue );
        }

        //Filter events from each object
        $.each( at, function( i, domAt ) {
            $.each( domAt[ 1 ], function( key, value ) {

                 //Multiple events given as array
                if ( typeof ( value.event ) === "object" ) {
                    $.each( value.event, function( i, val ) {
                        if ( val === event ) {
                            handleEvent( domAt[ 0 ], key.substring( 1 ),
                                        value.event, value.handler, eventValue );
                        }
                    } );
                } else {

                    //Single event given as string
                    if ( value.event === event ) {
                        handleEvent( domAt[ 0 ], key.substring( 1 ),
                                    value.event, value.handler, eventValue );
                    }
                }
            } );
        } );

        //Remove empty objects
        return $.grep( at, function( o ) {
            return $.isEmptyObject( o );
        } );

    }

    /*
    Update all dom elements which should be managed
    */
    $.fn.dataevent = function update( event, value ) {

        this.find( "[data-at]" ).each( function() {
            var dom = $( this );

            executeEvent( resolveReference( getChildAttributes( dom ) ), event, value );

        } );

        return this;
    };

}( jQuery ) );