(function(context, $) {

  function renderCss(featureDisplay) {
    var skin = featureDisplay.skin;
    var head = document.getElementsByTagName("head")[0];
    var link = document.createElement("link");
    link.setAttribute("rel", "stylesheet");
    link.setAttribute("type", "text/css");
    link.setAttribute("media", "all");
    link.setAttribute("href", featureDisplay.cssPath + "featuredisplay." + skin + ".css");
    head.appendChild(link);
    link = document.createElement("link");
    link.setAttribute("rel", "stylesheet");
    link.setAttribute("type", "text/css");
    link.setAttribute("media", "print");
    link.setAttribute("href", featureDisplay.cssPath + "featuredisplay." + skin + ".print.css");
    head.appendChild(link);
    if (featureDisplay.content.featured && featureDisplay.content.featured.skin) {
      link = document.createElement("link");
      link.setAttribute("rel", "stylesheet");
      link.setAttribute("type", "text/css");
      link.setAttribute("media", "all");
      link.setAttribute("href", featureDisplay.cssPath + "featuredisplay." + featureDisplay.content.featured.skin + ".css");
      head.appendChild(link);
    }
  }

  var index = 0;

  var markupTemplate = [
    // {cp} : classPrefix property.  Allows user to set their own prefix on classes in stylesheets.
    // {id} : id property.  Allows for multiple rotators on one page.
    // {ic} : itemClass property.  Additional user class assigned to items.
    '<div id="{id}_container" class="{cp}_container">',
    '  <div id="{id}_featured" class="{cp}_featured {ic}"></div>',
    '  <div id="{id}_nav_container" class="{cp}_nav_container">',
    '    <ul id="{id}_nav" class="{cp}_nav">',
    '      <li id="{id}_nav_prev" class="{cp}_nav_item {cp}_btn {cp}_nav_prev"></li>',
    '      <li id="{id}_nav_status" class="{cp}_nav_item {cp}_nav_status"></li>',
    '      <li id="{id}_nav_next" class="{cp}_nav_item {cp}_btn {cp}_nav_next"></li>',
    '    </ul>',
    '  </div>',
    '  <div id="{id}_items" class="{cp}_item_container">',
    '    <ul id="{id}_item_list" class="{cp}_item_list">',
    '    </ul>',
    '  </div>',
    '</div>'

  ].join('\n');

  var markupTemplateForAbsoluteNav = [
    // {cp} : classPrefix property.  Allows user to set their own prefix on classes in stylesheets.
    // {id} : id property.  Allows for multiple rotators on one page.
    // {ic} : itemClass property.  Additional user class assigned to items.
    '<div id="{id}_container" class="{cp}_container">',
    '  <div id="{id}_featured" class="{cp}_featured {ic}"></div>',
    '  <div id="{id}_items" class="{cp}_item_container">',
    '    <ul id="{id}_item_list" class="{cp}_item_list">',
    '    </ul>',
    '  </div>',
    '  <div id="{id}_nav_prev" class="{cp}_nav_item {cp}_btn {cp}_nav_prev"></div>',
    '  <div id="{id}_nav_next" class="{cp}_nav_item {cp}_btn {cp}_nav_next"></div>',
    '</div>'

  ].join('\n');

  var itemTemplate = '<li class="{cp}_item {ic}">{contents}</li>';

  var statusItemTemplate = '<div id="{id}_nav_status_item_{ix}" class="{cp}_nav_status_item {active}"></div>';

  var featureOverlayTemplate = [
    '<div id="{id}_feature_overlay" class="{cp}_featured_content">',
    //'  <div id="{id}_fo_close" class="{cp}_fo_close"></div>',
    '  {contents}',
    '</div>'].join('\n');

  function getHeight(jQueryObj, property) {
    var h = jQueryObj.css(property),
        i = h.indexOf("px");
    if (i > 0) {
      return h.substring(0, i) - 0;
    }
  }

  var itemHoverOn = function(event) {
    var fd = event.data.featureDisplay;
    fd.overItem = true;
    fd.stop();
    if (fd.featureOnHover) {
      var timer = event.data.featureDisplay.itemHoverTimer;
      var item = $(this);
      var index = item.attr("index") - 0;
      //console && console.log("item is " + item + " and index is " + index);
      var hasFeature = fd.content.items[index].feature || false;


      if (hasFeature) {
        if (!timer) {
          event.data.featureDisplay.itemHoverTimer = setInterval(function() {
            event.data.uri = item.attr("uri");
            event.data.target = item.attr("target");
            event.data.index = item.attr("index") - 0;
            clearTimeout(event.data.featureDisplay.itemHoverTimer);
            event.data.featureDisplay.itemHoverTimer = null;
            performAction(event);
          }, 500);
        }
      }
    }
  };
  var itemHoverOff = function(event) {
    var fd = event.data.featureDisplay;
    fd.overItem = false;
    var timer = fd.itemHoverTimer;
    if (timer) {
      //console && console.log("Clearing item hover timer.");
      clearTimeout(timer);
      fd.itemHoverTimer = null;
    }
    checkResetFeature(fd);
  };

  function constructItem(featureDisplay, contentItem, visibleIndex) {
    var item = $(itemTemplate.replace(/\{cp\}/g, featureDisplay.classPrefix)
      .replace(/\{ic\}/g, featureDisplay.itemClass)
      .replace(/\{contents\}/g, contentItem.markup));
    if (featureDisplay.overlayCaption && contentItem.caption) {
      item.append('<div class="{cp}_overlay_label">{caption}</div>'
        .replace(/\{cp\}/g, featureDisplay.classPrefix)
        .replace(/\{caption\}/g, contentItem.caption));
    }
    if (featureDisplay.useBorderedItems && visibleIndex > 0) {
      item.addClass(featureDisplay.classPrefix+"_item_bordered");
    }
    item.attr("uri", contentItem.actionUri);
    item.attr("target", contentItem.target || "")
    item.attr("index", contentItem.index);

    if (featureDisplay.featureOnClick) {
      item.bind('click', {featureDisplay:featureDisplay}, performAction);
    }

    item.bind('mouseover', {featureDisplay:featureDisplay}, itemHoverOn);
    item.bind('mouseout', {featureDisplay:featureDisplay}, itemHoverOff);

    return item;
  }

  function closeFeatureOverlay(featureDisplay) {
    featureDisplay.closingFeature = true;
    var overlay = $('#'+featureDisplay.id+"_feature_overlay");
    var fContainer = $('#'+featureDisplay.id+"_featured");
    if (featureDisplay.previousFeatures.length > 0 && featureDisplay.allowFeatureClosure) {// && !featureDisplay.openingFeature) {
      //console.log("Closing feature overlay");
      overlay.fadeOut(200, function() {
        overlay.remove();
        featureDisplay.featureShowing = false;
        var previous = $(featureDisplay.previousFeatures.pop()).hide();
        fContainer.empty().html(previous).bind("click", {featureDisplay: featureDisplay}, performAction);
        //featureDisplay.allowFeatureClosure = true;
        previous.fadeIn(200, function() {
          featureDisplay.closingFeature = false;
        });
      });
    }
  }

  function checkResetFeature(featureDisplay) {
    setTimeout(function() {
      if (! featureDisplay.overFeature && ! featureDisplay.overItem && !featureDisplay.closingFeature) {// && ! featureDisplay.openingFeature) {
        closeFeatureOverlay(featureDisplay);
        featureDisplay.start();
      }
    }, 50);
  };

  function constructFeatureOverlay(featureDisplay, contents) {
    if( Object.prototype.toString.call( contents ) === '[object Array]' ) {
      contents = contents.join("\n");
    }
    var fContainer = $('#'+featureDisplay.id+"_featured");
    featureDisplay.previousFeatures.push(fContainer.html());
    var overlay = $(featureOverlayTemplate.replace(/\{id\}/g, featureDisplay.id)
        .replace(/\{cp\}/g, featureDisplay.classPrefix)
        .replace(/\{contents\}/g, contents));
    //console.log("Constructing feature overlay");
    featureDisplay.openingFeature = true;
    fContainer.fadeOut(200, function() {
      fContainer.empty().unbind("click", performAction).html(overlay).fadeIn(200, function() {
        featureDisplay.featureShowing = true;
        featureDisplay.openingFeature = false;
        //console.log("Feature ovelay done.");
        // $('#'+featureDisplay.id+"_fo_close").bind("click", function(event) {
        //   closeFeatureOverlay(featureDisplay);
        // });
      });
    });
  }

  function constructStatusItem(featureDisplay, index, isActive) {
    return statusItemTemplate.replace(/\{id\}/g, featureDisplay.id)
      .replace(/\{cp\}/g, featureDisplay.classPrefix)
      .replace(/\{ix\}/g, index)
      .replace(/\{active\}/g, (isActive ? featureDisplay.classPrefix+"_nav_status_active" : ""));
  }

  function getNextItemIndex(featureDisplay, direction) {
    var nextIndex;
    var lastContentIndex = featureDisplay.content.items.length-1;
    
    if (direction === "next") {
      
      var lastVisibleIndex = featureDisplay.visibleItems[featureDisplay.visibleItems.length-1];
      nextIndex = lastVisibleIndex+1;
      if (nextIndex > lastContentIndex) {
        nextIndex = 0;
      }
    } else if (direction === "prev") {
      var firstVisibleIndex = featureDisplay.visibleItems[0];
      nextIndex = firstVisibleIndex-1;
      if (nextIndex < 0) {
        nextIndex = lastContentIndex;
      }
    }
    return nextIndex;
  }

  function getNextItemData(featureDisplay, direction) {
    var nextIndex = getNextItemIndex(featureDisplay, direction);
    return getItemData(featureDisplay, nextIndex);
  }

  function getItemData(featureDisplay, index) {
    var nextItem = featureDisplay.content.items[index];
    var ret= {
      type: nextItem.type,
      actionUri: nextItem.actionUri,
      target: nextItem.target || "",
      index: index,
      caption: nextItem.caption,
      feature: nextItem.feature
    };
    switch(nextItem.type) {
      case "image":
        ret.markup = '<img src="' + nextItem.uri + '" />';
        break;
      case "html":
        if(typeof(nextItem.data) === "object") {
          ret.markup = nextItem.data.join('\n');
        }
        else {
          ret.markup = nextItem.data;
        }
        break;
    }
    return ret;
  }

  function highlightVisibleItems(featureDisplay) {
    if (featureDisplay.showStatus && featureDisplay.content.items.length <= 10) {
      var activeClass = featureDisplay.classPrefix+"_nav_status_active";
      var statusItemIdPrefix = "#"+featureDisplay.id+"_nav_status_item_";

      $("."+activeClass).removeClass(activeClass);
      for (var i = 0; i < featureDisplay.visibleItems.length; i++) {
        $(statusItemIdPrefix + featureDisplay.visibleItems[i]).addClass(activeClass);
      }
    }
  }

  function getFeaturedItem(featureDisplay) {
    var fiConfig = featureDisplay.content.featured;
    var featuredItem = {};
    if (fiConfig.type === "image") {
      featuredItem = {
        markup: '<img id="' + featureDisplay.id + '_featured_content" class="' + featureDisplay.itemClass + '" src="' + fiConfig.uri + '" />',
        actionUri: fiConfig.actionUri
      };
    } else if(fiConfig.type === "html") {
      var configData = fiConfig.data;
      if(typeof(configData) === "object") {
        configData = configData.join('\n');
      }

      featuredItem = {
        markup: configData,
        actionUri: null
      }
    }
    return featuredItem;
  }

  function performAction(event) {
    var fd = event.data.featureDisplay;
    var uri = event.data.uri || $(this).attr("uri");
    var target = event.data.target || $(this).attr("target");
    var index = event.data.index;
    if (typeof index !== "number") index = $(this).attr("index") - 0;
    var feature = false;
    if(isNaN(index) == false) {
      feature = fd.content.items[index].feature || false;  
    }
    
    if (fd.actionFn) {
      fd.actionFn(fd, { uri: uri, target: target, feature: feature });
    } else if (feature) {
      fd.stop();
      // Display the feature content in a div that overlays the permanent feature.  This should have a "close" X on it.
      if (fd.featureShowing) {
        closeFeatureOverlay(fd);
      }
      constructFeatureOverlay(fd, feature);

    } else if( uri ) {

      if (target && target === "new") {
        window.open(uri);
      } else {
        window.location.href = uri;
      }
    }
  }

  function rotateItems(featureDisplay, direction) {
    if (featureDisplay.animationRunning) return;
    var item = getNextItemData(featureDisplay, direction);
    var newItem = constructItem(featureDisplay, item, 0);
    var list = $("#" + featureDisplay.id + "_item_list");
    var leavingItem;

    //newItem.bind("click", {featureDisplay: featureDisplay, uri:item.actionUri}, performAction);
    // newItem.bind('mouseover', {featureDisplay:featureDisplay}, function(event) {
    //   event.data.featureDisplay.stop();
    // });
    // newItem.bind('mouseout', {featureDisplay:featureDisplay}, function(event) {
    //   var fd = event.data.featureDisplay;
    //   if (fd.interval) { // Don't start the timer unless someone manually started it before.
    //     fd.start();
    //   }
    // });
    
    var marginAmount;
    var listItems = $("#" + featureDisplay.id + "_item_list li");
    if (direction === "prev") {
      // get the last list item
      leavingItem = listItems.last();
      if (featureDisplay.useBorderedItems) {
        listItems.first().addClass(featureDisplay.classPrefix+"_item_bordered");
      }
      marginAmount = getHeight(leavingItem, "height");

      // append the next invisible item (if no more, grab the first again)
      newItem.css('marginTop', -marginAmount);
      list.prepend(newItem);
      
      featureDisplay.animationRunning = true;
      newItem.animate(
        {marginTop: 0}, 
        featureDisplay.rotateOptions.animation.duration, 
        featureDisplay.rotateOptions.animation.easing,
        function() {
          leavingItem.remove();
          featureDisplay.animationRunning = false;
        }
      );

      featureDisplay.visibleItems.pop();
      featureDisplay.visibleItems.unshift(item.index);
      highlightVisibleItems(featureDisplay);
      
    } else if (direction === "next") { // next
      
      leavingItem = listItems.first();
      marginAmount = getHeight(leavingItem, "height");
      if (featureDisplay.useBorderedItems) {
        newItem.addClass(featureDisplay.classPrefix+"_item_bordered");
        $(listItems[1]).removeClass(featureDisplay.classPrefix+"_item_bordered");
      }

      // append the next item to the bottom of the list and animate it up.  When done, remove the old one.
      list.append(newItem);

      featureDisplay.animationRunning = true;
      leavingItem.animate(
        {marginTop: -marginAmount}, 
        featureDisplay.rotateOptions.animation.duration, 
        featureDisplay.rotateOptions.animation.easing,
        function() {
          leavingItem.remove();
          featureDisplay.animationRunning = false;
        }
      );

      featureDisplay.visibleItems.shift();
      featureDisplay.visibleItems.push(item.index);
      highlightVisibleItems(featureDisplay);
      
    }
    leavingItem.unbind('click', performAction);
  }

  var defaultContent = {
    featured: {
      type:"image",
      uri:"http://dummyimage.com/610x380/300000/ffd0c7.jpg&text=Featured",
      actionUri: "http://dummyimage.com/" // called on "action" (e.g. click)
    },
    items: [
      {
        type:"image",
        uri:"http://dummyimage.com/270x150/800000/ffd0c7.jpg&text=Item 1",
        actionUri: "http://dummyimage.com/"
      },
      {
        type:"image",
        uri:"http://dummyimage.com/270x150/800000/ffd0c7.jpg&text=Item 2",
        actionUri: "http://dummyimage.com/"
        // data: "" not allowed.  uri is required.
      },
      {
        type:"html",
        data:"<div style=\"width:270px;height:150px;background-color:#900000;color:white;padding:10px;\">Item 3</div>",
        // or uri: "" for ajax loading of content.
        actionUri: "http://google.com/"
      },
      {
        type:"image",
        uri:"http://dummyimage.com/270x150/400000/ffd0c7.jpg&text=Item 4",
        actionUri: "http://dummyimage.com/"
        // data: "" not allowed.  uri is required.
      },
      {
        type:"image",
        uri:"http://dummyimage.com/270x150/600000/ffd0c7.jpg&text=Item 5",
        actionUri: "http://dummyimage.com/"
        // data: "" not allowed.  uri is required.
      }
    ]
  };


  // ===============================================================================================


  var FeatureDisplay = function(options) {
    options = options || {};
    this.id = options.id || "featureDisplay" + (++index).toString();
    this.classPrefix = options.classPrefix || "fd";
    this.itemClass = options.itemClass || "";
    this.skin = options.skin || "default";
    this.cssPath = options.cssPath || "";
    this.actionFn = options.actionFn || false;
    this.showStatus = options.showStatus || false;
    this.overlayCaption = options.overlayCaption || false;
    this.useBorderedItems = options.useBorderedItems || false;
    this.absoluteNavLayout = options.absoluteNavLayout || false;
    this.allowFeatureClosure = true;
    this.featureOnClick = options.featureOnClick || false;
    this.featureOnHover = options.featureOnHover || false;

    this.rotateOptions = options.rotateOptions || {
      interval: 5000,
      animation: {
        easing: "easeOutBack", // See http://jqueryui.com/demos/effect/easing.html
        duration: 1000
      }
    };
    this.autoRotate = options.autoRotate || false;
    this.content = options.content || defaultContent;
    this.appendOnRender = options.renderAppends || false;
    this.init();    
  };

  FeatureDisplay.prototype = {

    init: function() {
      this.rendered = false;
      this.visibleItems = [];
      this.animationRunning = false;
      for (var i = 0; i < Math.min(3, this.content.items.length); i++) {
        this.visibleItems.push(i);
      }
      this.previousFeatures = [];
    },

    setContent: function(content) {
      this.content = content;
      this.setUseBorderedItems(content.useBorderedItems);
    },

    setUseBorderedItems: function(bool) {
      this.useBorderedItems = bool;
      if (! this.useBorderedItems) {
        $("#" + this.id + "_item_list li").removeClass(this.classPrefix+"_item_bordered");
      }
    },
    
    previous: function(event) {
      var fd = this;
      if (event && event.data) {
        fd = event.data.featureDisplay;
      }
      rotateItems(fd, "prev");
    },

    next: function(event) {
      
      var fd = this;
      if (event && event.data) {
        fd = event.data.featureDisplay;
      }
      rotateItems(fd, "next");
    },

    getDefaultItem: function() {
      var item = {
        type: "html",
        dataId: "",
        data: '<div class="fd-item-html ${classes}">{{html content}}</div>',
        feature: [
          "<div class='fd_featured_content'>",
          "  <div class='${vertical} ${horizontal}'>",
          "    <span class='title'>${title}</span>",
          "    <div class='description ${description_text_align}'>{{html description}}</div>",
          "  </div>",
          "</div>"
        ].join("\n"),
        actionUri: "#"
      };
      return item;
    },

    getMarkupTemplate: function() {
      var template = markupTemplate;
      if (this.absoluteNavLayout) {
        template = markupTemplateForAbsoluteNav;
      }
      return template.replace(/\{id\}/g, this.id)
        .replace(/\{cp\}/g, this.classPrefix)
        .replace(/\{ic\}/g, this.itemClass);
    },

    invalidate: function() {
      this.rendered = false;
    },

    start: function(interval) {
      var me = this;
      me.interval = interval || me.rotateOptions.interval;
      me.stop();
      me.rotateTimer = setInterval(function() {
        me.next({
          data: {
            featureDisplay: me
          }
        });
      }, me.interval);
    },

    stop: function() {
      if (this.rotateTimer) {
        clearInterval(this.rotateTimer);
        this.rotateTimer = null;
      }
    },

    render: function(container, cb) {
      var me = this, i = 0;
      if (! me.rendered || ! me.appendOnRender) {
        var el, go = false;
        if (typeof container === "string") {
          if (container.charAt(0) === "#") {
            el = $(container);
            //el = document.getElementById(container.substring(1));
            go = true;
          } else {
            cb && cb("Container element specified is not a valid ID.  Consider sending #"+container);
          }
        } else if (typeof container === "object") { // assume it's a dom element
          el = $(container);
          go = true;
        } else {
          cb && cb("Invalid container submitted.  Should be string or object, but was " + (typeof container));
        }
        if (go) {
          renderCss(me);
          if (! me.appendOnRender) {
            el.html("");
          }
          el.append(me.getMarkupTemplate());

          // Render content
          var item = getFeaturedItem(this);
          $("#"+me.id+"_featured").append(item.markup).attr("uri", item.actionUri);
          me.currentFeature = item;

          var list = $("#" + me.id + "_item_list");

          for (i = 0; i < me.visibleItems.length; i++) {
            item = getItemData(me, me.visibleItems[i]);
            var el = constructItem(me, item, i);
            // el.attr("uri", item.actionUri);
            // el.attr("target", item.target || "")
            // el.attr("index", item.index);
            el.appendTo(list);
            // $(document).ready(function() {
            //   el.bind("click", {featureDisplay: me, uri:item.actionUri}, performAction);
            // });
          }

          // Render the status pipe
          if (me.showStatus && me.content.items.length <= 10) {
            var statusCol = $("#"+me.id+"_nav_status");
            for (i = 0; i < me.content.items.length; i++) {
              statusCol.append(constructStatusItem(me, i, i < 3));
            }
          }

          me.rendered = true;

          // Now bind the controls
          setTimeout(function() {
            $("#"+me.id+"_featured").bind("click", {featureDisplay: me}, performAction);

            // Center the status pipe vertically.
            var statusItem = $("#"+me.id+"_nav_status_item_0");
            if (statusItem && statusItem.length > 0) {
              var totalHeight = getHeight(statusItem, "height") + getHeight(statusItem, "marginBottom");
              statusItem.css("marginTop", (getHeight($("."+me.classPrefix+"_nav_status"), "height") - (totalHeight * me.content.items.length)) / 2);
            }

            // Bind the nav buttons.
            $('#'+me.id+'_nav_prev').bind('click', {featureDisplay:me}, me.previous);
            $('#'+me.id+'_nav_next').bind('click', {featureDisplay:me}, me.next);

            // Bind the rotator items.
            var items = $('.'+me.classPrefix+'_item');
            var navHoverOff = function(event) {
              event.data.featureDisplay.start();
            };

            $('#'+me.id+"_featured").bind("mouseout", {featureDisplay:me}, function(event) {
              var fd = event.data.featureDisplay;
              fd.start();
              fd.overFeature = false;
              checkResetFeature(fd);
            });
            $('#'+me.id+"_featured").bind("mouseover", {featureDisplay:me}, function(event) {
              event.data.featureDisplay.overFeature = true;
            });

            // Start the rotator, but only if more than 3 are available.
            if (me.autoRotate && me.content.items.length > 3) {

              var navHoverOn = function(event) {
                var fd = event.data.featureDisplay;
                //itemHoverOff(event);
                fd.stop();
                fd.overItem = true;
              };
              
              var navButtons =  $("."+me.classPrefix+"_nav_next, ." + me.classPrefix + "_nav_prev");
              navButtons.bind('mouseover', {featureDisplay:me}, navHoverOn);
              navButtons.bind('mouseout', {featureDisplay:me}, navHoverOff);

              me.start(me.rotateOptions.interval);
            }
            cb && cb();
          }, 50);
        }
      } else {
        cb && cb();
      }
    }
  };

  if (context) context.FeatureDisplay = FeatureDisplay;
  return FeatureDisplay;
})(window, jQuery);
