define([
    'overrides'
  , 'jquery'
  , 'jqm'
  , 'extensions'
  , 'polyfill'
  , 'text'
  , 'json'
  , 'renderjs'
  , 'i18next'
  , 'css!../css/jquery-mobile/jquery-mobile.latest'
  , 'css!../css/css'
  , 'css!../css/normalize/normalize'
], function () {
  "use strict";

  var priv = {};
  var that = {};

  /* ========================================================================
                                  ATTRIBUTES
   ======================================================================== */

  // document
  priv.doc = $.mobile.document || $(document);

  /* ========================================================================
                                  FLAGS
   ======================================================================== */
  // first page loaded
  priv.pageWrapLoaded;

  // ========================================================================
  //                            INTERNAL METHODS
  // ========================================================================

  //
  // add page header and footer (meta/scripts) to first page loaded (only!)
  // @method wrapFirstPage
  //
  priv.wrapFirstPage = function (spec) {
    // TODO: clean this up
    priv.doc.on('pagecreate.pageWrap', 'div.ui-page', function () {
      if (priv.pageWrapLoaded === undefined) {
        priv.pageWrapLoaded = true;
        priv.doc.off('.pageWrap');

        // add a callback to setup to allow hooking into settings to
        // setup page header and footer configuration
        priv.allset.resolve(spec);

//         .then(function(App, Config) {
//           console.log("this should be the 2nd callback run AFTER we have set configuration");
//           console.log(App);
//           console.log(Config);

        //           var widgetsForThisRequest = [
        //             "text!../gadgets/pageheader.html",
        //             "text!../gadgets/pagefooter.html"
        //           ],
        //           target = [$('head'),$('body')];
        //
        //
        //           require(
        //             widgetsForThisRequest,
        //             function( /* dynamic */ ) {
        //               var i, widget, template_target;
        //
        //               for ( i = 0; i < widgetsForThisRequest.length; i++){
        //                 template_target = target[i];
        //                 // if customization based on user data required (add logo/img urls, etc)
        //                 // it should be done here with widget being a string that can be
        //                 // modified with replace method
        //                 widget = arguments[i];
        //                 template_target.append( widget );
        //               }
        //             }
        //           );
//         });


      }
    });
  };

  // ============================ RenderJs related ========================
  //
  // get elements inside a gadget
  // @method getElements
  // @param  {object} gadget to search for recursive gadgets
  //
  priv.getElements = function (gadget, elements) {
    return gadget.dom.find(elements);
  };

  //
  // prefix element id with the parent id to allow instantiation
  // @method prefixId
  // @param  {string} id > of current gadget/element
  // @param  {object} parent gadget
  // @return {string} new id for gadget/element
  //
  priv.prefixElement = function (current_id, parent_id) {
    return parent_id + "__" + current_id;
  };

  //
  // set Attributes passed through data-gadget-properties
  // @method setAttributesOnGadgetElements
  // @param  {object} gadget
  // @param  {properties} properties to set on the gadget
  //
  priv.setAttributesOnGadgetElements = function (gadget, properties) {
    var key, values, i, object, method;

    for (key in properties) {
      if (properties.hasOwnProperty(key)) {
        values = properties[key];

        switch (key) {
          case "_links":
            // TODO: set dictionary here!
          break;
          case "_config":
            // interim: create a fake dictionary
            gadget[key] = values;
          break;
          case "_elements":
            for (i = 0; i < values.length; i += 1) {
              object = values[i];
              method = object[1];
              gadget.dom.find("." + object[0])[method](
                object[2], method === "attr" ? object[3] : null
              );
            }
          break;
        }
      }
    }
  };

  //
  // renderJs initializer and jQuery Mobile handler
  // @method initializeRenderJs
  //
  priv.initializeRenderJs = function () {
    var root, rootId, setFalse = false, rootUndefined = true;

    // TODO: try to live without all bindings... in renderjs 2
    priv.doc.on('pagebeforeshow', 'div.ui-page', function () {
      var page = $(this),
        pageId = page.attr('id');

      if (rootUndefined) {
        root = RenderJs.GadgetIndex.getRootGadget();
        if (root) {
          rootId = root.id;
          rootUndefined = false;
        }
      }

      // need to call this every time a page is shown...
      // unless we go to the root page, which is always in the GadgetIndex
      if (rootId !== pageId) {
        RenderJs.init(page);

        RenderJs.bindReady(function () {
          var gadget = RenderJs.GadgetIndex.getGadgetById(page.attr('id'));
          if (gadget.isEnhanced() === false) {
            //page.trigger("create");
            gadget.setEnhanced(true);
          }
        });
      }
    })
    .on('pagebeforehide', 'div.ui-page', function(e, data) {
      // pagebeforehide triggers before remove and data includes the new
      // page being loaded. This means we can override clearing the gadgetIndex
      // in case we are going to a page that has already been loaded. Instead
      // we setReady to false.
      var page = $(this),
        pageId = page.attr('id'),
        nextPageId = data.nextPage.attr('id'),
        pageGadget = RenderJs.GadgetIndex.getGadgetById(pageId);

      if (nextPageId === pageId) {
        // renderJs will try to parse the page before pageremove fires, so
        // if we don't set all gadgets on this page to false, renderJs does
        // nothing...
        if (page.attr("data-gadget") !== undefined) {
          pageGadget.setReady(false);
          pageGadget.setEnhanced(false);
        }
        // also, set all gadgets on this page to false
        setFalse  = true;
      }
    })
    .on('pageremove', 'div.ui-page', function (e, data) {
      var page = $(this),
        pageId = page.attr('id'),
        pageGadget;

      // remove gadgets when removing pages,
      // but not if we are doing a same page transition...
      page.find("div[data-gadget]").each(function (i, el) {
        var gadget = $(el);
        if (gadget.is('.main-interactor') === false && !setFalse) {
          RenderJs.GadgetIndex.getGadgetById(gadget.attr('id')).remove();
        }
      });

      // if the page is a gadget itself, it needs to be reset here
      if (page.attr("data-gadget") !== undefined && !setFalse) {
        pageGadget = RenderJs.GadgetIndex.getGadgetById(pageId);
        pageGadget.setReady(false);
        RenderJs.GadgetIndex.unregisterGadget(pageGadget);
      }
      setFalse = false;
    })
  };

  //
  // setup global application parameters
  // @method setupGlobalParameters
  //
  // TODO: this would be the place to load JSON configuration settings
  priv.setupGlobalParameters = function () {

    // initialize renderJs
    priv.initializeRenderJs();
  };

  //
  // initialize application
  // @method initialize
  //
  priv.initialize = function (spec) {

    // create new deferred for waiting until all initialization is done
    priv.allset = new $.Deferred();

    priv.allset.done(function(spec) {
      spec.configuration_setter(spec);
    });

    // > set up globals
    priv.setupGlobalParameters()

    // > add <head>er and footer to first page only
    priv.wrapFirstPage(spec);

    // initialize JQM
    // TODO: is there a better way with requireJS/renderJs?
    if ($.mobile.autoInitializePage === false) {
      $.mobile.initializePage();
    }
  };

  // ========================================================================
  //                      PUBLIC METHODS => window.App.method()
  // ========================================================================

  //
  // prefix instance ids and handle/forward JSON properties passed to gadget
  // @method renderGadgets
  // @param {object} gadget to be rendered
  //
  that.renderGadgets = function (gadget, forceRefresh) {
    var gadget_id = gadget.dom.attr('id'),
      gadget_parent = gadget.dom.parents("div[data-gadget]"),
      gadget_props,
      gadget_cleaned_id,
      propsToSet,
      new_id,
      fwd = [], i;

    if (!gadget_parent.is(".ui-page-active")) {
      return;
    }

    // so now we do it here...
    if (gadget.isEnhanced() === false || forceRefresh) {
      gadget.dom.parent().trigger("create");
      gadget.setEnhanced(true);
    }

    gadget_props = gadget["state"];
    gadget_cleaned_id = gadget_id.split("__").slice(-1)[0];

    // JSON property API
    //  "state": [
    //    {"gadget-id" {"element": ["method", "key/value", "value"] } },
    //    ...
    //  ]
    if (gadget_props !== undefined) {
      for (i = 0; i < gadget_props.length; i += 1) {
        propsToSet = gadget_props[i][gadget_cleaned_id];

        if (propsToSet !== undefined) {
          // properties for this gadget
          priv.setAttributesOnGadgetElements(gadget, propsToSet);
        } else {
          // fwd to new data-gadget-property object
          fwd.push(gadget_props[i]);
        }
      }
      // should we clean up?
      gadget.dom.removeAttr("data-gadget-property");
    }

    priv.getElements(
      gadget, "a[data-rel='popup'], a[data-rel='panel']"
    ).each(function () {
      var href = this.getAttribute('href').replace("#", ''),
        prefixedHref = priv.prefixElement(href, gadget_id);

      this.setAttribute("href", "#" + prefixedHref);

      // not nice... prevents panel getting prefix__prefix__gadget_id
      // TODO: find better way
      if (this.getAttribute("data-rel") !== "panel") {
        document
          .getElementById(href)
          .setAttribute("id", prefixedHref);
      }
    });

    // beware: need to get children only to prevent selecting gadgets
    // multiple levels deep
    priv.getElements(gadget, "> div[data-gadget]").each(function () {
      if (fwd.length > 0) {
        this.setAttribute(
          'data-gadget-property', JSON.stringify({
            "state": fwd
          })
        );
      }
    });

    // update this gadgets id with the parent gadget id. Make sure to also
    // add the new id inside the renderJs GadgetIndex. Previsouly the id
    // was set only on child gadgets, which worked because gadgets had not
    // been loaded at the time when the id was modified. Since this only
    // seems to work in FF with multiple nested gadget levels, we set the
    // id here, but must also update the id of the gadget in the gadgetIndex
    if (gadget_parent.length > 0) {
      new_id = gadget_parent.attr('id') + "__" + gadget_id;
      RenderJs.GadgetIndex.getGadgetById(gadget_id).id = new_id;
      gadget.dom.attr('id', new_id);
    }
  };

  // ========================================================================
  //                            INITIALIZE
  // ========================================================================
  that.initialize = priv.initialize;

  that.settings = priv.settings;

  // keep items in here, so all gadgets can access them vs. having
  // to query jIO over and over from every gadget
  that.memoryItems = [];

  // ========================================================================
  //                            PUBLISH APP
  // ========================================================================

  // return public methods
  return that;

});