/*global console, require, $, localStorage, document */
"use strict";

/*
 * RenderJs - Generic Gadget library renderer.
 * http://www.renderjs.org/documentation
 */

// by default RenderJs will render all gadgets when page is loaded
// still it's possible to override this and use explicit gadget rendering
var RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING = true;

// by default RenderJs will examine and bind all interaction gadgets
// available 
var RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND = true;

// fallback for IE
if (typeof console === "undefined" || typeof console.log === "undefined") {
  console = {};
  console.log = function () {};
}

var RenderJs = (function () {
    // a variable indicating if current gadget loading is over or not
    var is_ready = false;

    return {

        init: function () {
          /*
           * Do all initialization
           */
          if (RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING) {
              RenderJs.bootstrap($('body'));
          }
          if (RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND) {
            var root_gadget = RenderJs.GadgetIndex.getRootGadget();
            // We might have a page without gadgets.
            // Be careful, right now we can be in this case because
            // asynchronous gadget loading is not finished
            if (root_gadget !== undefined) {
              RenderJs.bindReady(
                function () {
                  // examine all Intaction Gadgets and bind accordingly
                  $("div[data-gadget-connection]").each( function (index, element) {
                    RenderJs.InteractionGadget.bind($(element));
                  });
                });
            }
          }
        },

        bootstrap: function (root) {
            /*
             * Load all gadgets for this DOM element
             * (including recursively contained ones)
             */
            var gadget_id, is_gadget;
            gadget_id = root.attr("id");
            is_gadget = root.attr("data-gadget") !== undefined;
            // this will make RenderJs fire "ready" event when all gadgets are loaded.
            RenderJs.setReady(false);
            if (is_gadget && gadget_id !== undefined ) {
              // bootstart root gadget only if it is indeed a gadget
              RenderJs.loadGadget(root);
            }
            RenderJs.loadRecursiveGadget(root);
        },

        loadRecursiveGadget: function (root) {
            /*
             * Load all contained gadgets inside passed DOM element.
             */
            var gadget_list, gadget, gadget_id, gadget_js;
            gadget_list = root.find("[data-gadget]");

            // register all gadget in advance so checkAndTriggerReady
            // can have accurate information for list of all gadgets
            gadget_list.each(function () {
              gadget = $(this);
              gadget_id = gadget.attr("id");
              gadget_js = new RenderJs.Gadget(gadget_id, gadget);
              RenderJs.GadgetIndex.registerGadget(gadget_js);
            });

            // Load chilren
            gadget_list.each(function () {
                RenderJs.loadGadget($(this));
            });
        },

        setGadgetAndRecurse: function (gadget, data) {
            /*
             * Set gadget data and recursively load it in case it holds another
             * gadgets.
             */
            gadget.append(data);
            // a gadget may contain sub gadgets
            RenderJs.loadRecursiveGadget(gadget);
        },

        loadGadget: function (gadget) {
            /*
             * Load gadget's SPECs from URL
             */
            var url, gadget_id, gadget_property, cacheable, cache_id,
                app_cache, data, gadget_js, is_update_gadget_data_running;
            url = gadget.attr("data-gadget");
            gadget_id = gadget.attr("id");
            gadget_js = RenderJs.GadgetIndex.getGadgetById(gadget_id);
            if (gadget_js === undefined) {
              // register gadget in javascript namespace if not already registered
              gadget_js = new RenderJs.Gadget(gadget_id, gadget);
              RenderJs.GadgetIndex.registerGadget(gadget_js);
            }

            // update Gadget's instance with contents of "data-gadget-property"
            gadget_property = gadget.attr("data-gadget-property");
            if (gadget_property !== undefined) {
              gadget_property = $.parseJSON(gadget_property);
              $.each(gadget_property, function (key, value) {
                gadget_js[key] = value;
              });
            }

            if (url !== undefined && url !== "") {
                cacheable = gadget.attr("data-gadget-cacheable");
                cache_id = gadget.attr("data-gadget-cache-id");
                if (cacheable !== undefined && cache_id !== undefined) {
                    cacheable = Boolean(parseInt(cacheable, 10));
                }
                //cacheable = false ; // to develop faster
                if (cacheable) {
                    // get from cache if possible, use last part from URL as
                    // cache_key
                    app_cache = RenderJs.Cache.get(cache_id, undefined);
                    if (app_cache === undefined || app_cache === null) {
                        // not in cache so we pull from network and cache
                        $.ajax({
                            url: url,
                            yourCustomData: {
                                "gadget_id": gadget_id,
                                "cache_id": cache_id
                            },
                            success: function (data) {
                                cache_id = this.yourCustomData.cache_id;
                                gadget_id = this.yourCustomData.gadget_id;
                                RenderJs.Cache.set(cache_id, data);
                                RenderJs.GadgetIndex.getGadgetById(gadget_id).
                                    setReady();
                                RenderJs.setGadgetAndRecurse(gadget, data);
                                RenderJs.checkAndTriggerReady();
                                RenderJs.updateGadgetData(gadget);
                            }
                        });
                    } else {
                        // get from cache
                        data = app_cache;
                        gadget_js.setReady();
                        this.setGadgetAndRecurse(gadget, data);
                        this.checkAndTriggerReady();
                        RenderJs.updateGadgetData(gadget);
                    }
                } else {
                    // not to be cached
                    $.ajax({
                        url: url,
                        yourCustomData: {"gadget_id": gadget_id},
                        success: function (data) {
                            gadget_id = this.yourCustomData.gadget_id;
                            RenderJs.GadgetIndex.getGadgetById(gadget_id).
                                setReady();
                            RenderJs.setGadgetAndRecurse(gadget, data);
                            RenderJs.checkAndTriggerReady();
                            RenderJs.updateGadgetData(gadget);
                        }
                    });
                }
            }
            else {
                // gadget is an inline (InteractorGadget or one using
                // data-gadget-source / data-gadget-handler) so no need
                // to load it from network
                is_update_gadget_data_running = RenderJs.updateGadgetData(gadget);
                if (!is_update_gadget_data_running) {
                  // no update is running so gadget is basically ready
                  // if update is running then it should take care and set status
                  gadget_js.setReady();
                }
                RenderJs.checkAndTriggerReady();
            }
        },

        isReady: function () {
            /*
             * Get rendering status
             */
            return is_ready;
        },

        setReady: function (value) {
            /*
             * Update rendering status
             */
            is_ready = value;
        },

        bindReady: function (ready_function) {
            /*
             * Bind a function on ready gadget loading.
             */
            $("body").one("ready", ready_function);
        },

        checkAndTriggerReady: function () {
            /*
             * Trigger "ready" event only if all gadgets were marked as "ready"
             */
            var is_gadget_list_loaded;
            is_gadget_list_loaded = RenderJs.GadgetIndex.isGadgetListLoaded();
            if (is_gadget_list_loaded) {
                if (!RenderJs.isReady()) {
                    // backwards compatability with already written code
                    RenderJs.GadgetIndex.getRootGadget().getDom().
                        trigger("ready");
                    // trigger ready on root body element
                    $("body").trigger("ready");
                    // this set will make sure we fire this event only once
                    RenderJs.setReady(true);
                }
            }
            return is_gadget_list_loaded;
        },

        updateGadgetData: function (gadget) {
            /*
             * Gadget can be updated from "data-gadget-source" (i.e. a json)
             * and "data-gadget-handler" attributes (i.e. a namespace Javascript) 
             */
            var data_source, data_handler;
            data_source = gadget.attr("data-gadget-source");
            data_handler = gadget.attr("data-gadget-handler");
            // acquire data and pass it to method handler
            if (data_source !== undefined && data_source !== "") {
                $.ajax({
                    url: data_source,
                    dataType: "json",
                    yourCustomData: {"data_handler": data_handler,
                                     "gadget_id": gadget.attr("id")},
                    success: function (result) {
                              var data_handler, gadget_id;
                              data_handler = this.yourCustomData.data_handler;
                              gadget_id = this.yourCustomData.gadget_id;
                              if (data_handler !== undefined) {
                                  // eval is not nice to use
                                  eval(data_handler + "(result)");
                                  gadget = RenderJs.GadgetIndex.getGadgetById(gadget_id);
                                  // mark gadget as loaded and fire a check
                                  // to see if all gadgets are loaded
                                  gadget.setReady();
                                  RenderJs.checkAndTriggerReady();
                              }
                             }
                });
                // asynchronous update happens and respective thread will update status
                return true;
            }
            return false;
        },

        addGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
                            gadget_data_source) {
            /*
             * add new gadget and render it
             */
            var html_string, tab_container, tab_gadget;
            tab_container = $('#' + dom_id);
            tab_container.empty();
            html_string = [
                '<div  id="' + gadget_id + '"',
                'data-gadget="' + gadget + '"',
                'data-gadget-handler="' + gadget_data_handler + '" ',
                'data-gadget-source="' + gadget_data_source + '"></div>'
            ].join('\n');

            tab_container.append(html_string);
            tab_gadget = tab_container.find(".gadget");

            // render new gadget
            RenderJs.bootstrap(tab_container);

            return tab_gadget;
        },

        Cache: (function () {
            /*
             * Generic cache implementation that can fall back to local
             * namespace storage if no "modern" storage like localStorage
             * is available
             */
            return {
                ROOT_CACHE_ID: 'APP_CACHE',

                getCacheId: function (cache_id) {
                    /*
                     * We should have a way to 'purge' localStorage by setting a
                     * ROOT_CACHE_ID in all browser instances
                     */
                    return this.ROOT_CACHE_ID + cache_id;
                },

                hasLocalStorage: function () {
                    /*
                     * Feature test if localStorage is supported
                     */
                    var mod;
                    mod = 'localstorage_test_12345678';
                    try {
                        localStorage.setItem(mod, mod);
                        localStorage.removeItem(mod);
                        return true;
                    } catch (e) {
                        return false;
                    }
                },

                get: function (cache_id, default_value) {
                    /* Get cache key value */
                    cache_id = this.getCacheId(cache_id);
                    if (this.hasLocalStorage()) {
                        return this.LocalStorageCachePlugin.
                            get(cache_id, default_value);
                    }
                    //fallback to javscript namespace cache
                    return this.NameSpaceStorageCachePlugin.
                        get(cache_id, default_value);
                },

                set: function (cache_id, data) {
                    /* Set cache key value */
                    cache_id = this.getCacheId(cache_id);
                    if (this.hasLocalStorage()) {
                        this.LocalStorageCachePlugin.set(cache_id, data);
                    } else {
                        this.NameSpaceStorageCachePlugin.set(cache_id, data);
                    }
                },

                LocalStorageCachePlugin: (function () {
                    /*
                     * This plugin saves using HTML5 localStorage.
                     */
                    return {
                        get: function (cache_id, default_value) {
                            /* Get cache key value */
                            if (cache_id in localStorage) {
                              return JSON.parse(localStorage.getItem(cache_id));
                            }
                            else {
                              return default_value;
                            }
                        },

                        set: function (cache_id, data) {
                            /* Set cache key value */
                            localStorage.setItem(cache_id, JSON.stringify(data));
                        }
                    };
                }()),

                NameSpaceStorageCachePlugin: (function () {
                    /*
                     * This plugin saves within current page namespace.
                     */
                    var namespace = {};

                    return {
                        get: function (cache_id, default_value) {
                            /* Get cache key value */
                            return namespace[cache_id];
                        },

                        set: function (cache_id, data) {
                            /* Set cache key value */
                            namespace[cache_id] = data;
                        }
                    };
                }())
            };
        }()),

        Gadget: (function (gadget_id, dom) {
            /*
             * Javascript Gadget representation
             */
            this.id = gadget_id;
            this.dom = dom;
            this.is_ready = false;

            this.getId = function () {
                return this.id;
            };

            this.getDom = function () {
                return this.dom;
            };

            this.isReady = function () {
                /*
                 * Return True if remote gadget is loaded into DOM.
                 */
                return this.is_ready;
            };

            this.setReady = function () {
                /*
                 * Return True if remote gadget is loaded into DOM.
                 */
                this.is_ready = true;
            };

            this.remove = function () {
                /*
                 * Remove gadget (including its DOM element).
                 */
                // unregister from GadgetIndex
                RenderJs.GadgetIndex.unregisterGadget(this);
                // remove its DOM element
                $(this.getDom()).remove();
            };
        }),

        TabbularGadget: (function () {
            /*
             * Generic tabular gadget
             */
            return {
                toggleVisibility: function (visible_dom) {
                    /*
                     * Set tab as active visually and mark as not active rest.
                     */
                    $(".selected").addClass("not_selected");
                    $(".selected").removeClass("selected");
                    visible_dom.addClass("selected");
                    visible_dom.removeClass("not_selected");
                },

                addNewTabGadget: function (dom_id, gadget_id, gadget, gadget_data_handler,
                                          gadget_data_source) {
                    /*
                     * add new gadget and render it
                     */
                    var tab_gadget;
                    tab_gadget = RenderJs.addGadget(
                        dom_id, gadget_id, gadget, gadget_data_handler, gadget_data_source
                    );
                    // XXX: we should unregister all gadgets (if any we replace now in DOM)
                }
            };
        }()),

        GadgetIndex: (function () {
            /*
             * Generic gadget index placeholder
             */
            var gadget_list = [];

            return {

                getGadgetIdListFromDom: function (dom) {
                  /*
                   * Get list of all gadget's ID from DOM
                   */
                  var gadget_id_list = [];
                  $.each(dom.find('[data-gadget]'),
                         function (index, value) {
                           gadget_id_list.push($(value).attr("id"));}
                    );
                  return gadget_id_list;
                },

                setGadgetList: function (gadget_list_value) {
                    /*
                     * Set list of registered gadgets
                     */
                    gadget_list = gadget_list_value;
                },

                getGadgetList: function () {
                    /*
                     * Return list of registered gadgets
                     */
                    return gadget_list;
                },

                registerGadget: function (gadget) {
                    /*
                     * Register gadget
                     */
                    gadget_list.push(gadget);
                },

                unregisterGadget: function (gadget) {
                    /*
                     * Unregister gadget
                     */
                    var index = $.inArray(gadget, gadget_list);
                    if (index !== -1) {
                        gadget_list.splice(index, 1);
                    }
                },

                getGadgetById: function (gadget_id) {
                    /*
                     * Get gadget javascript representation by its Id
                     */
                    var gadget;
                    gadget = undefined;
                    $(RenderJs.GadgetIndex.getGadgetList()).each(
                        function (index, value) {
                            if (value.getId() === gadget_id) {
                                gadget = value;
                            }
                        }
                    );
                    return gadget;
                },

                getRootGadget: function () {
                    /*
                     * Return root gadget (always first one in list)
                     */
                    return this.getGadgetList()[0];
                },

                isGadgetListLoaded: function () {
                    /*
                     * Return True if all gadgets were loaded from network or
                     * cache
                     */
                    var result;
                    result = true;
                    $(this.getGadgetList()).each(
                        function (index, value) {
                            if (value.isReady() === false) {
                                result = false;
                            }
                        }
                    );
                    return result;
                }
            };
        }()),

        InteractionGadget : (function () {
            /*
             * Basic gadget interaction gadget implementation.
             */
            return {
                bind: function (gadget_dom) {
                    /*
                     * Bind event between gadgets.
                     */
                    var gadget_id, gadget_connection_list, 
                      createMethodInteraction = function (
                        original_source_method_id, source_gadget_id,
                        source_method_id, destination_gadget_id,
                        destination_method_id) {
                        var interaction = function () {
                            // execute source method
                            RenderJs.GadgetIndex.getGadgetById(
                                source_gadget_id)[original_source_method_id].
                                apply(null, arguments);
                            // call trigger so bind can be asynchronously called
                            RenderJs.GadgetIndex.getGadgetById(
                                destination_gadget_id).dom.trigger(source_method_id);
                        };
                        return interaction;
                    },
                    createTriggerInteraction = function (
                        destination_gadget_id, destination_method_id) {
                        var interaction = function () {
                            RenderJs.GadgetIndex.getGadgetById(
                                destination_gadget_id)[destination_method_id].
                                apply(null, arguments);
                        };
                        return interaction;
                    };
                    gadget_id = gadget_dom.attr("id");
                    gadget_connection_list = gadget_dom.attr("data-gadget-connection");
                    gadget_connection_list = $.parseJSON(gadget_connection_list);
                    $.each(gadget_connection_list, function (key, value) {
                        var source, source_gadget_id, source_method_id,
                        source_gadget, destination, destination_gadget_id,
                        destination_method_id, destination_gadget,
                        original_source_method_id;
                        source = value.source.split(".");
                        source_gadget_id = source[0];
                        source_method_id = source[1];
                        source_gadget = RenderJs.GadgetIndex.
                            getGadgetById(source_gadget_id);

                        destination = value.destination.split(".");
                        destination_gadget_id = destination[0];
                        destination_method_id = destination[1];
                        destination_gadget = RenderJs.GadgetIndex.
                            getGadgetById(destination_gadget_id);

                        if (source_gadget.hasOwnProperty(source_method_id)) {
                            // direct javascript use case
                            original_source_method_id = "original_" +
                                source_method_id;
                            source_gadget[original_source_method_id] =
                                source_gadget[source_method_id];
                            source_gadget[source_method_id] =
                                createMethodInteraction(
                                    original_source_method_id,
                                    source_gadget_id,
                                    source_method_id,
                                    destination_gadget_id,
                                    destination_method_id
                                );
                            // we use html custom events for asyncronous method call so
                            // bind destination_gadget to respective event
                            destination_gadget.dom.bind(
                                source_method_id,
                                createTriggerInteraction(
                                    destination_gadget_id, destination_method_id
                                )
                            );
                        }
                        else {
                            // this is a custom event attached to HTML gadget
                            // representation
                            source_gadget.dom.bind(
                                source_method_id,
                                createTriggerInteraction(
                                    destination_gadget_id, destination_method_id
                                )
                            );
                        }
                    });
                }
            };
        }())
    };
}());

// impliticly call RenderJs bootstrap
//  $(document).ready(function () {
//      RenderJs.init();
//  });