/*  =BrowserDetect
  -----------------------------------------------
  The built-in dojo browser detection doesn't seem to include the version, so include the Quirksmode script.
  (http://www.quirksmode.org/js/detect.html)
  -----------------------------------------------  */
  var BrowserDetect = {
    unknownBrowser: "An unknown browser",
    unknownVersion: "An unknown version",
    
    init: function () {
      this.browser = this.searchString(this.dataBrowser) || this.unknownBrowser;
      this.version = this.searchVersion(navigator.userAgent)
        || this.searchVersion(navigator.appVersion)
        || this.unknownVersion;
    },
    searchString: function (data) {
      for (var i=0;i<data.length;i++)  {
        var dataString = data[i].string;
        var dataProp = data[i].prop;
        this.versionSearchString = data[i].versionSearch || data[i].identity;
        if (dataString) {
          if (dataString.indexOf(data[i].subString) != -1)
            return data[i].identity;
        }
        else if (dataProp)
          return data[i].identity;
      }
    },
    searchVersion: function (dataString) {
      var index = dataString.indexOf(this.versionSearchString);
      if (index == -1) return;
      return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
    },
    dataBrowser: [
      {
        string: navigator.vendor,
        subString: "Apple",
        identity: "Safari"
      },
      {
        prop: window.opera,
        identity: "Opera"
      },
      {
        string: navigator.userAgent,
        subString: "Firefox",
        identity: "Firefox"
      },
      {
        string: navigator.userAgent,
        subString: "MSIE",
        identity: "Explorer",
        versionSearch: "MSIE"
      }
    ]
  };
  BrowserDetect.init();

/*  =Common
  -----------------------------------------------  */
  dojo.addOnLoad(function() {
    dojo.addClass(dojo.doc.documentElement, 'scriptable');
    dojo.require("dojo.fx");
  
    // TODO: Find a way to do this instead of including dijit directly with a script tag, as we're doing now.
    // The problem so far has been that loading dijit using require causes "function not defined" errors 
    // (when it's not cached).
    //dojo.require("dijit.dijit");
  });

/*  =Ingredients
  -----------------------------------------------  */
  dojo.addOnLoad(function() {

    // IE7 doesn't support dynamic content produced by CSS, so add the ingredient list commas with JavaScript.
    if (BrowserDetect.browser == "Explorer" && Number(BrowserDetect.version) < 8) {
      var elements = [];
      elements = elements.concat(Page.getArrayFromQuery("#compare .recipe ul"));
      elements = elements.concat(Page.getArrayFromQuery(".recipe .ingredients ul"));
      for (var index = 0; index < elements.length; index++) {
        var list = elements[index];
        var items = dojo.query("li", list);
        for (var j = 0; j < items.length - 1; j++) {
          var item = items[j];
          
          // IE7 seems to add extra spaces within the items, which makes the comma we're
          // about to add look funny. So, remove the spaces before adding the comma.
          item.innerHTML = item.innerHTML.replace(/ /g, "");
          
          var comma = dojo.doc.createTextNode(", ");
          item.appendChild(comma);
        }
      }
    }
  });

/*  =Page (Static Class)
  -----------------------------------------------  */
  if (typeof(Page) != 'undefined') throw("Page is already defined.");
  var Page = {

  // Attributes
    name                : "Page",
    debugMode           : false,
    timer/*Interval*/   : null,
    bodyElement         : null,
    placebo/*Element*/  : null,
    lastState/*Object*/ : null,

  // Methods
    init: function() {
      if (this.debugMode) console.log(this.name + " has initialized");
      EventDispatcher.initialize(this);
      this.bodyElement = document.getElementsByTagName("body")[0];
      
      this.placebo = dojo.doc.createElement("div");
      this.bodyElement.appendChild(this.placebo);
      this.placebo.appendChild(document.createTextNode(" "));
      
      this.lastState = this.getCurrentState();
      
      // Check the page state periodically (to see if the page has changed its layout)
      if (this.timer == null) {
        this.timer = setInterval(dojo.hitch(this, this.check), 100);
      }
    },
    check: function() {
      var currentState = this.getCurrentState();
      
      // If the page has changed
      if (this.isEqualTo(currentState, this.lastState) == false) {
        this.dispatchEvent({type: "update"});
        this.lastState = this.getCurrentState();
      }
    },  
    getCurrentState: function() {
      var offset = this.cumulativeOffset(this.placebo);
      return {
        offsetTop: offset.top,
        offsetLeft: offset.left,
        width: this.placebo.offsetWidth,
        height: this.placebo.offsetHeight
      };
    },
    isEqualTo: function(a, b) {
      for (var prop in a) {
        if (a[prop] != b[prop]) {
          return false;
        }
      }
      return true;
    },
    // KUDOS: http://www.prototypejs.org
    // TBD: Dojo might have a built in function for this
    cumulativeOffset: function(element) {
      var valueT = 0, valueL = 0;
      do {
        valueT += element.offsetTop  || 0;
        valueL += element.offsetLeft || 0;
        element = element.offsetParent;
      } while (element);
      var result = {
        left: valueL,
        top: valueT
      };
      return result;
    },
    getArrayFromQuery: function() {
      // This is a wrapper function around dojo.query which returns an Array instead of a NodeList.
      // Use this function if you want to make multiple dojo queries and then combine them into one
      // array using Array.concat.
      
      var nodeList = dojo.query.apply(dojo, arguments);
      if (nodeList.length <= 0 || nodeList == null) return [];
      
      var array = [];
      for (var index = 0; index < nodeList.length; index++) {
        array[array.length] = nodeList[index];
      }
      return array;
    }
  };
  dojo.addOnLoad(dojo.hitch(Page, Page.init));

/*  =EventDispatcher (Template Class)
  -----------------------------------------------  */
  if (typeof(EventDispatcher) != 'undefined') throw("EventDispatcher is already defined.");
  EventDispatcher = {
    template: {
      // Attributes
        listenerStack/*Array*/  : null,
        nextListenerID          : 0,

      // Methods
        addEventListener: function(eventType/*String*/, eventHandler/*Function*/) {
          this.nextListenerID++;
          if (this.listenerStack == null) {
            this.listenerStack = [];
          }
          var listener = {
            type: eventType,
            handler: eventHandler,
            id: this.nextListenerID
          };
          this.listenerStack[this.listenerStack.length] = listener;
          return listener;
        },
        removeEventListener: function(listener/*Object*/) {
          if (this.listenerStack != null) {
            for (var index = 0; index < this.listenerStack.length; index++) {
              var nextListener = this.listenerStack[index];
              if (nextListener.id == listener.id) {
                this.listenerStack.splice(index, 1);
              }
            }
          }
        },
        dispatchEvent: function(e/*Object*/) {
          if (this.listenerStack != null) {
            for (var index = 0; index < this.listenerStack.length; index++) {
              var nextListener = this.listenerStack[index];
              if (nextListener.type == e.type) {
                nextListener.handler(e);
              }
            }
          }
        }
    },
    initialize: function(target) {
      dojo.mixin(target, this.template);
    }
  };

/*  =Compare (Static Class)
  -----------------------------------------------  */
  if (typeof(Compare) != 'undefined') throw("Page is already defined.");
  var Compare = {

  // Attributes
    name                : "Compare",
    debugMode           : false,
    id/*String*/        : null,
    element/*Element*/  : null,  // An HTML element which represents the "compare recipes" section
    slideshow           : null,
    count/*Element*/    : null,  // Displays the number of recipes in the "compare recipes" section

  // Methods
    init: function() {
      if (this.debugMode) console.log(this.name + " has initialized");

      this.onCreate();
    },
    setCount: function(count/*Number*/) {
      // Update the number in the comparison section (e.g. "2 recipes" ==> "3 recipes").
      if (this.count == null) {
        if (this.element != null) {
          var results = dojo.query(".count", this.element);
          if (results.length > 0) this.count = results[0];
        }
      }
      if (this.count != null) {
        this.count.innerHTML = String(count) + " recipes";
      }
    },
    onCreate: function() {
      if (this.element != null) return; // Prevent multiple calls to this function
      this.element = dojo.byId("compare");

      if (this.element != null) {
        this.slideshow = new Slideshow(dojo.query(".recipe", this.element));
      }
    },
    registerButtonForm: function(form) {
      dojo.connect(form, "onsubmit", dojo.hitch(this, this.onButtonFormClick));
    },
    onButtonFormClick: function(e) {
      var form = e.target;

      var handler = {
        context: this,
        form: form
      };
      
      var next;
      var results = dojo.query("input[name=next]", form);
      if (results.length > 0) next = results[0];
      var content = {};
      if (next) content = {next: next.value};

      // Make the request
      dojo.xhr('POST', {
        url: form.getAttribute("action"),
        content: content,
        handleAs: "json",
        handle: dojo.hitch(handler, this.onXhrResponse)
      });
      
      e.preventDefault();
    },
    toggleButtonForm: function(form) {
      var button;
      var results = dojo.query("button", form);
      if (results.length > 0) button = results[0];
      if (!button) return;
      
      if (button.innerHTML == "Unclip") {
        // Change the button to an "add" button
        form.setAttribute("action", String(form.getAttribute("action")).replace("/remove-earmark/", "/add-earmark/"));
        if (button) button.innerHTML = "Clip";
      } else {
        // Change the button to a "remove" button
        form.setAttribute("action", String(form.getAttribute("action")).replace("/add-earmark/", "/remove-earmark/"));
        if (button) button.innerHTML = "Unclip";
      }
    },
    onXhrResponse: function(data, args) {
      if (typeof data == "error") {
        console.warn("error!");
        console.warn(args);
      } else {
        this.context.onXhrSuccess(data, this.form);
      }
    },
    onXhrSuccess: function(data, form) {
      
      // If we just added a recipe
      if (data.html) {
        
        // If the comparison section does not exist, add it.
        if (this.element == null) {
          var container = dojo.doc.createElement("div");
          var search = dojo.byId("search");
          if (search != null) {
            search.parentNode.insertBefore(container, search);    // Insert the compare element just after the main search form
            container.innerHTML = data.html;
            this.onCreate();
          } else {
            console.warn(this.name + " said, 'I tried to create the comparison section, but I didn't find the expected insert point (the main search form could not be found)'.");
          }

        // Otherwise add the data to the existing comparison section.
        } else {
          this.slideshow.addItem(data.html);
        }
      
      // If we just removed a recipe
      } else if (data.id) {
        
        // If this is the only recipe in the compare section, remove the entire element
        if (this.slideshow.numItems() <= 1) {
          this.element.parentNode.removeChild(this.element);
          this.element = null;
          this.slideshow = null;
        } else {
          var compare = {
            id: data.id
          };
          this.slideshow.removeItem(dojo.hitch(compare, function(item) {
            return item.getAttribute("id") == this.id;
          }));
        }
        
      }
      
      if (this.slideshow != null) {
        this.setCount(this.slideshow.numItems());
      }
        
      this.toggleButtonForm(form);
    }
  };
  dojo.addOnLoad(dojo.hitch(Compare, Compare.init));

/*  =DropShadow (Class)
  -----------------------------------------------  */
  if (typeof(DropShadow) != 'undefined') throw("DropShadow is already defined.");
  dojo.declare("DropShadow", null, {

  // Attributes
    debugMode             : false,
    target/*Element*/     : null,
    container/*Element*/  : null,
    tl/*Element*/         : null,
    tr/*Element*/         : null,
    br/*Element*/         : null,
    bl/*Element*/         : null,
    shadowWidth           : 10,     // The width of the edge of the shadow image (i.e. the part that has a gradient or curve to it).
    shadowSize            : 6,      // How much larger the shadow should be than the target, on each side.
    offsetTop             : 3,      // Move the drop shadow away from the top edge by this amount.
    offsetBottom          : -2,     // Move the drop shadow away from the bottom edge by this amount.
    showing               : false,

  // Methods
    constructor: function(target/*Element*/) {
      this.name = this.declaredClass;
      if (arguments[1]) {
        for (var prop in arguments[1]) {
          this[prop] = arguments[1][prop];
        }
      }
      if (this.debugMode) console.log(this.name + " has initialized");

      this.target = target;
      if (this.target == null) {
        console.warn(this.name + " said, \"I was instantiated without a target HTML element.\"");
        return;
      }
      
      /*
      var clearBoth = dojo.doc.createElement("div");
      dojo.style(clearBoth, "clear", "both");
      this.target.appendChild(clearBoth);
      */
    },
    onPageUpdate: function() {
      if (this.showing == false) return;
      this.size();
      this.position();
    },
    show: function() {
      if (this.container == null) this.render();
      this.showing = true;
      this.size();
      this.position();
    },
    hide: function() {
      if (this.container == null) return;
      this.showing = false;
      dojo.style(this.container, "left", "-9999px");
    },
    render: function() {
      this.container = dojo.doc.createElement("div");
      dojo.addClass(this.container, "dropShadow");
      
      this.tl = dojo.doc.createElement("div");
      dojo.addClass(this.tl , "tl");
      
      this.tr = dojo.doc.createElement("div");
      dojo.addClass(this.tr, "tr");
      
      this.br = dojo.doc.createElement("div");
      dojo.addClass(this.br, "br");
      
      this.bl = dojo.doc.createElement("div");
      dojo.addClass(this.bl, "bl");
      
      this.target.parentNode.insertBefore(this.container, this.target);
      this.container.appendChild(this.tl);
      this.container.appendChild(this.tr);
      this.container.appendChild(this.br);
      this.container.appendChild(this.bl);
      
      dojo.style(this.container, "position", "absolute");
      Page.addEventListener("update", dojo.hitch(this, this.onPageUpdate));
    },
    size: function() {
      var width = this.target.offsetWidth + (this.shadowSize * 2);
      var height = this.target.offsetHeight + (this.shadowSize * 2) - this.offsetTop - this.offsetBottom;
      
      dojo.style(this.container , "width" , width   + "px");
      dojo.style(this.container , "height", height  + "px");
      dojo.style(this.tl        , "width" , width   - this.shadowWidth + "px");
      dojo.style(this.tl        , "height", height  - this.shadowWidth + "px");
      dojo.style(this.tr        , "height", height  - this.shadowWidth + "px");
      dojo.style(this.bl        , "width" , width   - this.shadowWidth + "px");
    },
    position: function() {
      var top = this.target.offsetTop - this.shadowSize;
      var left = this.target.offsetLeft - this.shadowSize;
      dojo.style(this.container, "top"  , String(top + this.offsetTop) + "px");
      dojo.style(this.container, "left" , left  + "px");
    }
  });
  dojo.addOnLoad(function() {
    var elements = [];
    elements = elements.concat(Page.getArrayFromQuery("#tweakForm"));                      // Tweak
    elements = elements.concat(Page.getArrayFromQuery("#register"));                       // Sign Up
    elements = elements.concat(Page.getArrayFromQuery("#accountInner"));                   // User Account
    elements = elements.concat(Page.getArrayFromQuery(".ingredients .tableContainer"));    // Recipe
    elements = elements.concat(Page.getArrayFromQuery("#recentRecipesInner"));             // Search Results
    elements = elements.concat(Page.getArrayFromQuery("#recipe .photo"));                  // Recipe Photo
    elements = elements.concat(Page.getArrayFromQuery("#recipe .notes"));                  // Recipe Notes
    elements = elements.concat(Page.getArrayFromQuery("#searchResultsInner"));             // Search Results
    elements = elements.concat(Page.getArrayFromQuery(".blog #content"));                  // Blog post
    for (var index = 0; index < elements.length; index++) {
      var dropShadow = new DropShadow(elements[index]);
      dropShadow.show();
    }
  });

/*  =FieldHint (Class)
  -----------------------------------------------  */
  if (typeof(FieldHint) != 'undefined') throw("FieldHint is already defined.");
  dojo.declare("FieldHint", null, {

  // Attributes
    debugMode           : false,
    element/*Element*/  : null,
    hint/*String*/      : null,

  // Methods
    constructor: function(element/*Element*/, hint/*String*/) {
      this.name = this.declaredClass;
      if (this.debugMode) console.log(this.name + " has initialized");

      this.element = element;
      if (this.element == null) {
        console.warn(this.name + " said, \"I was instantiated without a target HTML element.\"");
        return;
      }

      this.hint = hint;
      if (this.hint == null) {
        console.warn(this.name + " said, \"I was instantiated without a 'hint' to display.\"");
        return;
      }
      
      dojo.connect(this.element, "focus"  , this, this.hideHint);
      dojo.connect(this.element, "blur"   , this, this.showHint);
      
      // Hide the hint before the form is submitted (since we don't want to submit the hint text)
      if (this.element.form) dojo.connect(this.element.form, "submit", this, this.hideHint);
      
      this.showHint();
    },
    showHint: function() {
      if (this.element.value == "" || this.element.value == this.hint) {
        this.element.value = this.hint;
        dojo.addClass(this.element, "hint");
      }
    },
    hideHint: function() {
      if (this.element.value == this.hint) {
        this.element.value = "";
        dojo.removeClass(this.element, "hint");
      }
    },
    getText: function() {
      return this.hint;
    }
  });

/*  =Search
  ----------------------------------------------- */
  dojo.addOnLoad(function() {
    var initSearch = function(search) {
      var field = dojo.query("input[type=text]", search)[0];
      var headline = dojo.query("h2", search)[0];
      new FieldHint(field, headline.innerHTML);
      dojo.style(headline, "visibility", "hidden");
    };
    dojo.query("#search").forEach(initSearch);
    dojo.query("#searchFeature").forEach(initSearch);
  });

/*  =Alert
  ----------------------------------------------- */
  dojo.addOnLoad(function() {
    var alert = dojo.byId("user_alert");
    if (alert != null) {
      dojo.style(alert, "left", String((alert.parentNode.offsetWidth - alert.offsetWidth) / 2) + "px");
    }
  });

/*  =Search Results
  ----------------------------------------------- */
  dojo.addOnLoad(function() {
    var searchResults = dojo.byId("searchResults");
    if (searchResults != null) {
      dojo.query("form.compare", searchResults).forEach(dojo.hitch(this, function(form) {
        Compare.registerButtonForm(form);
      }));
    }
  });
