(function() {


  // YUI Libraries
  var Dom           = YAHOO.util.Dom;
  var Event         = YAHOO.util.Event;
  var Lang          = YAHOO.lang;
  var $             = YAHOO.util.Selector.query;
  var Anim          = YAHOO.util.Anim;
  var CustomEvent   = YAHOO.util.CustomEvent;


  // TIME Libraries
  var QueryString    = TIME.util.QueryString;
  var XSSTransport   = TIME.util.XSSTransport;
  var PageScroll     = TIME.util.PageScroll;
  var Glow           = TIME.widget.Effects.Glow;
  var CommentAppForm = TIME.widget.CommentAppForm;


  /**
   * CommentApp
   */
  var CommentApp = function() {



    // CommentApp constants
    var ASCENDING  = 'ASC',
        DESCENDING = 'DESC',
        FIRST_PAGE = 1,
        LAST_PAGE  = 'last';



    // CommentApp configuration variables
    var apiUri,
        order,
        page,
        perPage,
        expandAll,
        boardName,
        boardUrl,
        id,
        loadingMessage,
        backgroundColor,
        highlightColor,
        animationDuration;


    // Custom Events



    // DOM helper methods

    var getRelatedComment = function(el) {
      return Dom.getAncestorByClassName(el, 'comment');
    };

    var getRepliesToggle = function(comment) {
      return $('a.toggle-replies', comment, true);
    };

    var getRepliesCount = function(comment) {
      return $('span.comment-count', comment, true);
    };

    var getRepliesContainer = function(comment) {
      return $('ol.comments', comment, true);
    };

    var getReplyFormToggle = function(comment) {
      return $('a.toggle-reply-form', comment, true);
    };

    var getReplyFormContainer = function(comment) {
      return $('div.add-comment', comment, true);
    };

    var getReplyForm = function(container) {
      return $('div.add-comment form', container, true);
    };

    var isExpanded = function(el) {
      return Dom.hasClass(el, 'expanded');
    };

    var isLoaded = function(el) {
      return Dom.hasClass(el, 'loaded');
    };



    /**
     *
     *
     * @method highlightComment
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var highlightComment = function(commentId) {

      var comment = Dom.get(commentId);

      if (!Lang.isNull(comment)) {

        var fadeAnim = new Glow(comment, {
          backgroundColor: { from: backgroundColor, to: highlightColor }
        }, animationDuration, YAHOO.util.Easing.easeOut);

        fadeAnim.animate();
      }
    };



    /**
     *
     *
     * @method scrollToElement
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var scrollToElement = function(el) {

      var region = Dom.getRegion(el);
      var top    = region.top;
      var bottom = region.bottom;
      var height = bottom - top;

      var y = Math.min(Math.max(top - ((Dom.getViewportHeight() - height) / 2), Dom.getDocumentScrollTop()), top);

      var scrollAnim = new PageScroll(y, 0.5, YAHOO.util.Easing.easeOut);
      scrollAnim.animate();
    };



    /**
     *
     *
     * @method scrollToHighlighted
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var scrollToHighlighted = function(container) {
      var comment = $('.highlight', container, true);
      if (comment) {
        scrollToElement(comment);
        highlightComment(comment);
      }
    };



    /**
     *
     *
     * @method expandReplies
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var expandReplies = function(comment) {

      var toggle = getRepliesToggle(comment);
      var container = getRepliesContainer(comment);

      Dom.addClass([comment, toggle, container], 'expanded');
    };



    /**
     *
     *
     * @method collapseReplies
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var collapseReplies = function(comment) {

      var toggle = getRepliesToggle(comment);
      var container = getRepliesContainer(comment);

      Dom.removeClass([comment, toggle, container], 'expanded');

      // Scroll element back into view
      scrollToElement(comment);
    };



    /**
     *
     *
     * @method toggleReplies
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var toggleReplies = function(evt) {

      var target  = Event.getTarget(evt);
      var comment = getRelatedComment(target);

      // Collapse replies container
      if (isExpanded(target)) {
        collapseReplies(comment);
      } else {

        var container = getRepliesContainer(comment);

        // Expand replies container
        if (isLoaded(container)) {
          expandReplies(comment);

        // Load replies
        } else {
          loadReplies(target.href, container);
        }
      }
    };


    var getDepth = function(comment) {
      var depth = 1;
      while ((comment = Dom.getAncestorByClassName(comment, 'comment')) !== null) {
        depth++;
      }
      return depth;
    };

    /**
     *
     *
     * @method loadReplies
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var loadReplies = function(uri, container) {

      // Display loading indicator...
      container.innerHTML = loadingMessage;

      // Make XSS request to comment server
      XSSTransport.getRequest(uri, {

        success: function(html) {

          // TODO: Parse error messages

          // Populate comments
          container.innerHTML = html;
          Dom.addClass(container, 'loaded');

          var comment = getRelatedComment(container);

          // Comments depth is capped at 3
          if (getDepth(comment) === 2) {
            var parentHref = getReplyFormToggle(comment).href;

            // I feel dirty using setTimeout for these events!
            // TODO: implement with onContentReady?
            setTimeout(function() {
              Dom.getElementsByClassName('toggle-reply-form', 'a', container, function(el) {
                console.log('turning %s to %s', el.href, parentHref);
                el.href = parentHref;
              });
            }, 0);

          }

          updateReplyCount(comment);
          expandReplies(comment);


          setTimeout(function() { scrollToHighlighted(container); }, 250);
        },

        failure: function(error) {
          alert('Unable to load replies. Please wait a moment and try again.');
          // throw new Error(error);
        }
      });
    };



    /**
     *
     *
     * @method expandReplyForm
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var expandReplyForm = function(comment) {
      var toggle = getReplyFormToggle(comment);
      Dom.addClass(toggle, 'expanded');

      var container = getReplyFormContainer(comment);
      Dom.addClass(container, 'expanded');

      scrollToElement(container);

      // Focus first form input element
      var input = $('input[type=text]', container, true);
      input.focus();
    };



    /**
     *
     *
     * @method collapseReplyForm
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var collapseReplyForm = function(comment) {
      var toggle = getReplyFormToggle(comment);
      Dom.removeClass(toggle, 'expanded');

      var container = getReplyFormContainer(comment);
      Dom.removeClass(container, 'expanded');
    };



    /**
     *
     *
     * @method toggleReplyForm
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var toggleReplyForm = function(evt) {

      var target  = Event.getTarget(evt);
      var comment = getRelatedComment(target);

      // TODO: prevent double clicking - this may belong in XSSRequest

      // Collapse reply form container
      if (isExpanded(target)) {
        collapseReplyForm(comment);
      } else {

        var container = getReplyFormContainer(comment);

        // Expand reply form container
        if (isLoaded(container)) {
          expandReplyForm(comment);

        // Load reply form
        } else {

          var uri = target.href;

          // Make XSS request
          XSSTransport.getRequest(uri, {

            // Handle success
            success: function(html) {

              container.innerHTML = html;
              Dom.addClass(container, 'loaded');

              // Initialize form
              var form = getReplyForm(container);
              var commentAppForm = new CommentAppForm(form);


              // Handle successful reply submission
              commentAppForm.onSuccess.subscribe(function(html) {
                var re = /\<a name="?comment-(\d+)"?\>\<\/a\>/i;
                var commentId = html.match(re)[1];

                // If at max depth the parent comment replies container will need to be refreshed
                if (getDepth(comment) === 3) {
                  comment = Dom.getAncestorByClassName(comment, 'comment');
                }

                var uri = [getRepliesToggle(comment).href, '?highlight=', commentId].join('');
                var container = getRepliesContainer(comment);

                loadReplies(uri, container);
              });

              // Handle cancel event
              commentAppForm.onCancel.subscribe(function(evt) {
                collapseReplyForm(comment);
              });

              commentAppForm.onFailure.subscribe(function(evt) {
                alert('Unable to submit your comment. Please wait a moment and try again.');
              });

              expandReplyForm(comment);
            },

            failure: function(error) {
              alert('Unable to load reply form. Please wait a moment and try again.');
              // throw new Error(error);
            }
          });

        }
      }
    };



    /**
     *
     *
     * @method updateReplyCount
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var updateReplyCount = function(comment) {

      var container  = getRepliesContainer(comment);
      var numReplies = Dom.getChildren(container).length;
      var count      = getRepliesCount(comment);

      count.innerHTML = numReplies;

      if (numReplies > 0) {
        var el = getRepliesToggle(comment).parentNode;
        Dom.removeClass(el, 'hidden');
      }
    };



    /**
     *
     *
     * @method onClick
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var onClick = function(evt) {
      var target = Event.getTarget(evt);

      for (var className in clickHandlerMap) {
        if (Dom.hasClass(target, className)) {

          // Stop event propagation
          Event.stopEvent(evt);

          // Execute mapped callback
          clickHandlerMap[className](evt);
        }
      }
    };


    var clickHandlerMap = {};
    clickHandlerMap['add-comment-link'] = function() {
      var el = $('a[name=add-your-comment]', 'comment-app', true).parentNode;
      scrollToElement(el);
    };
    clickHandlerMap['toggle-replies'] = toggleReplies;
    clickHandlerMap['toggle-reply-form'] = toggleReplyForm;
    // TODO: refactor map properties into config


    /**
     *
     *
     * @method loadPage
     * @static
     * @param  {type} paramName  Parameter description
     * @return {type} Return value description
     */
    var loadPage = function(uri, container) {

      // Display loading indicator...
      // container.innerHTML = loadingMessage;

      var callback = {

        // Handle success
        success: function(html) {

          // TODO: Detect error status

          // Replace container content with response HTML
          container.innerHTML = html;

          // Replace href with internal link
          var addCommentLink = $('a.add-comment-link', container, true);
          if (!Lang.isNull(addCommentLink)) {
            addCommentLink.href = '#add-your-comment';
          }

          // Replace pagination href with only its query string
          Dom.batch($('.pagination a, .sort-comments a, a.expand-all', container), function(a) {
            a.href = a.href.substring(a.href.indexOf('?'));
          });

          // Defer scrolling to the internal anchor until content is loaded
          if (id) {
            var comment = Dom.get('comment-' + id);
            Dom.addClass(comment, 'highlight');
            scrollToHighlighted(container);
          }

          var form = $('div.add-comment form', container, true);
          var commentAppForm = new CommentAppForm(form);

          commentAppForm.onSuccess.subscribe(function(html) {

            var re = /\<a name="?comment-(\d+)"?\>\<\/a\>/i;

            var commentId = html.match(re)[1];

            var qs = new QueryString();
            qs.set('order', order);
            qs.set('page', (order === ASCENDING) ? LAST_PAGE : FIRST_PAGE);
            qs.set('id', commentId);

            location.href = qs.serialize(true);
          });

        },

        // Handle failure
        failure: function(error) {
          alert('Unable to load comments. Please wait a moment and refresh your browser.');
          // throw new Error(error);
        }
      };

      // Make XSS request to comment server
      XSSTransport.getRequest(uri, callback);
    };

    return {


      /**
       *
       *
       * @method init
       * @static
       * @param  {type} paramName  Parameter description
       * @return {type} Return value description
       */
      init: function(el, config) {

        // Set container for application
        var container = Dom.get(el);

        if (Lang.isNull(container)) {
          alert('Unable to initialize comment application.');
          // throw new Error('Unable to initialize CommentApp.');
        }


        // Main configuration
        apiUri    = config.apiUri;
        order     = config.order || ASCENDING;    // Sort comments by comment date
        perPage   = config.perPage || 10;         // Current page of results
        page      = config.page || FIRST_PAGE;    // Current page of results
        expandAll = config.expandAll || 'false';  // Current page of results
        boardName = config.boardName;             // Name of associated content
        boardUrl  = config.boardUrl;              // Return URL for associated content
        id        = config.id;                    // New comment ID


        // Assemble API URI
        var qs = new QueryString();
        qs.set('order', order);
        qs.set('per_page', perPage);
        qs.set('page', page);
        qs.set('expand_all', expandAll);
        qs.set('board_name', boardName);
        qs.set('board_url', boardUrl);

        var uri = apiUri + qs.serialize(true);


        // Animation/display configuration
        backgroundColor   = config.backgroundColor || '#fff';
        highlightColor    = config.highlightColor || '#fbf246';
        animationDuration = config.duration || 6;
        loadingMessage    = config.loadingMessage || '';


        // Attach event delegators which will route incoming events
        // from various DOM elements to specified CommentApp methods
        // We use the event delegation pattern to minimize memory
        // usage when adding many DOM elements
        Event.on(container, 'click',  onClick,  this, true);


        // Load the page
        loadPage(uri, container);
      },


      /**
       *
       *
       * @method destroy
       * @static
       * @param  {type} paramName  Parameter description
       * @return {type} Return value description
       */
      destroy: function() {
        // TODO: write destroy method for unload event
      }
    };
  }();


  // Add CommentApp to TIME.app namespace
  window.TIME = window.TIME || YAHOO.namespace('TIME');
  YAHOO.namespace('TIME.app');
  TIME.app.CommentApp = CommentApp;

})();
