/**
 * @projectDescription Widget that embeds a Sitelife comments widget into a page.
 * @author             JSkinner
 * @version            0.3
 */

(function() { // closure to prevent namespace pollution

  //if (typeof console == "undefined")
  var console = {}, fns = "log|info|warn|error|dir|group|groupEnd".split("|");
  for (var i = 0; i < fns.length; i++) {
    if (typeof console[fns[i]] != "function") console[fns[i]] = function() { }
  }
  
  var FMT_REGEX = /{([^{}]*)}/g;
  String.prototype.fmt = function fmt() {
    var data = (arguments.length == 1 && arguments[0] instanceof Object) ? arguments[0] : jQuery.makeArray(arguments);
  
    return this.replace(FMT_REGEX, function(all, k) {
      try {
        return /\d/.test(k) ? data[parseInt(k)] : eval("data." + k);
      }
      catch (ex) {
        return all;
      }
    });
  }
  
  if (typeof Date.prototype.getMonthName == "undefined") {
    var months = "January|February|March|April|May|June|July|August|September|October|November|December".split("|");
    Date.prototype.getMonthName = function() {
      return months[this.getMonth()];
    }
  }

  function parseATCookie() {
    var items = /AT=(.*?)(?:;|$)/.exec(document.cookie);
    if (!items) return null;
    var b = unescape(items[1]).split("&"), res = {};
    for (var i = 0; i < b.length; i++) {
      var kv = b[i].split("=");
      res[kv[0]] = unescape(kv[1]);
    }
    return {
      userid: parseInt(res.u, 10),
      screenname: res.a.replace(/\+/g, " "),
      email: res.e,
      firstname: res.f,
      surname: res.l,
      fullname: jQuery.trim(res.f + " " + res.l),
      profileurl: doc2docUrl + "/profile.html?userId={0}&plckUserId={0}".fmt(res.u)
    };
  }

  /*var NIC_EDITOR_CONFIG = {
    buttonList: "bold|italic|underline|ol|ul|link|unlink".split("|"),
    iconsPath: "http://student.bmj.com/images/nicEditorIcons.gif",
    toolTipOn: true,
    toolTipText: "Comment on this article"
  }*/

  var COMMENT_TEMPLATE = "\n" +
    "<a name='{CommentKey.Key}'></a>\n" +
    "<div class='article-comment clearfix'><span class='pointer'></span><div class='response-b'><div class='response-m'>\n" +
      "<div class='head'>" +
        "<p class='date'>{PostedAtTime}</p>" + // pattern="dd MMMMMMM yyyy"
        "<p class='name'><strong>{Author.DisplayName} says:</strong></p>" + // ${fn:substringBefore(response.name, " ")}
      "</div>\n" +
      "<p>{CommentBody}</p>\n" +
      /*"<div class='poster'>" +
        "<p><strong><a href='{Author.PersonaUrl}'>{Author.DisplayName}</a></strong></p>" +
      "</div>" +*/
      "<div class='clear'></div>" +
    "</div></div></div>\n\n";

  // Allows us to do jQuery("#user-message").scrollTo
  jQuery.fn.scrollTo = function() {
    var targetTop = jQuery(this).offset().top;
    targetTop -= parseInt(jQuery(this).css("margin-top").replace("px", ""));
    jQuery(window).scrollTop(targetTop);
    return this;
  }

  function setUserMessage(msg, msg_opts) {
    console.info("setUserMessage(%o, %o)", msg, msg_opts);
    var options = {
      text: "",
      style: "simple",
      scroll: true
    };
    if (typeof msg === "string") {
      options.text = loadmsg;
    }
    else {
      jQuery.extend(options, msg || {});
    }
    jQuery.extend(options, msg_opts || {});
    console.info("setUserMessage: options are:");
    console.dir(options);

    jQuery("#user-message")
      .attr("class", options.style)
      .html("<span>" + options.text + "</span>");
    
    if (options.scroll) {
      jQuery("#user-message").scrollTo();
    }
  }

  function clearUserMessage() {
    jQuery("#user-message").attr("class", "").empty();
  }

  function doRequest(actions, loadmsg, callback) {
    if (loadmsg) {
      setUserMessage({ style: "loading" }, loadmsg);
    }
    
    actions = jQuery.makeArray(actions);
    callback = callback || function(responseBatch) { };

    var requestBatch = new RequestBatch();
    for (var i = 0; i < actions.length; i++) {
      requestBatch.AddToRequest(actions[i]);
    }
    requestBatch.BeginRequest(serverUrl, callback);
  }

  function checkResponse(response, errormsg) {
    var ok = response.Responses.length > 0 || response.Messages[0].Message == "ok";
    
    if (!ok && errormsg) {
      setUserMessage(errormsg, { style: "error" });
    }
    
    return ok;
  }
  
  /**
   * Given an element, tries to find the comment it is contained in and
   * return the comment key by parsing the name of the anchor that should
   * precede it.
   *
   * @param {DOMElement,jQuery} elem    where to start looking from
   * @return {String}                   a comment key, or null if not found
   */
  function getCommentKey(elem) {
    var anchorname = jQuery(elem).parents("div.article-comment").prev("a[name]").attr("name");
    // Will be either "CommentKey:XXXXX" or undefined.
    return typeof anchorname === "string" ? anchorname.substr(11) : null;
  }
  
  
  function getDateString(pluckDate) {
      function pad(n) {
        return n < 10 ? "0" + n : "" + n;
      }
      var d = new Date(pluckDate);
      return "{0}:{1} {2} {3} {4}".fmt(pad(d.getHours()), pad(d.getMinutes()), d.getDate(), d.getMonthName(), d.getFullYear())
  }

  var _userInfo = {}, _widget = null;

  /**
   * Constructor function for the comment widget.
   * 
   * @param {Object} articleKey
   * @param {Object} commentPageSize
   */
  function CommentWidget(articleKey, commentPageSize) {
    this.articleKey = articleKey;
    this.commentPageSize = commentPageSize;
    
    this.totalNumberOfComments = 0;
    this.numberOfCommentPages = 1;
    this.currentPage = 1;
    this.dbg = 0;
    
    // Parse cookie for user details, and store those and a reference
    // to this instance outside of the CommentWidget's scope for use
    // in callback functions later.
    _userInfo = parseATCookie();
    _widget = this;
    console.dir(_userInfo);
    
    // Initialise nicEdit on comment box.
    this.editor = new nicEditor().panelInstance("comment-edit");
    new nicEditor().panelInstance("competingInterests");
        
    jQuery(".nicEdit-main, #occupation, #affiliation").keyup(this.checkFields);

    this.button = jQuery("#post-comment");
    this.leftOffset = this.button.offsetParent().css("margin-left") + this.button.outerWidth();

    this.message = jQuery("#user-message");
    
    jQuery("select.interest")
      .bind("change.sbmj", function(ev) {
        jQuery(".comInterest").toggle();
        
        if (jQuery(this).val() == "yes") {
          var ciEditor = nicEditors.findEditor("competingInterests");
          ciEditor.setContent("");
          ciEditor.elm.focus();
        }
      });

    jQuery("#post-comment")
      .attr("disabled", true)
      .bind("click", function(ev) {
        var editor = nicEditors.findEditor("comment-edit"),
          bodyText = editor.getContent(),
          occ = jQuery("#occupation").val(),
          aff = jQuery("#affiliation").val(),
          ci = jQuery("select.interest").val() == "yes" ? nicEditors.findEditor("competingInterests").getContent() : null;
        console.warn("submit clicked sel = {1} cic = {0}".fmt(ci, jQuery("select.interest").val()));
  
        if (bodyText.length > 0 && occ.length > 0 && aff.length > 0) {
          editor.saveContent();
          editor.setContent("");
          nicEditors.findEditor("competingInterests").setContent("");
          jQuery("#occupation, #affiliation, #competingInterests").val("");
          jQuery("select.interest").val("no");
          jQuery(".comInterest").hide();
          _widget.disableForm();

          _widget.postNewComment(bodyText, occ, aff, ci);
        }
      });
      
    jQuery("<link rel='stylesheet' type='text/css' href='/student/css/doc2doc.css' />").appendTo("head");

    // Intialise comment count from any archived comments, load comments in background.
    this.updateNumberOfComments(numArchivedComments);
    this.loadComments(true);
    //this.setCurrentPage(1, true);
  }
  
  CommentWidget.prototype = {

    checkFields: function() {
      var bodyText = nicEditors.findEditor("comment-edit").getContent(),
        occ = jQuery("#occupation").val(),
        aff = jQuery("#affiliation").val();
      var ci = jQuery("select.interest").val() == "no" || nicEditors.findEditor("competingInterests").getContent().length > 0;
      jQuery("#post-comment").attr("disabled", bodyText.length == 0 || occ.length == 0 || aff.length == 0 || !ci);
    },    

    enableForm: function(enable) {
      if (typeof enabled == "undefined") enable = true;
      jQuery("#comment-edit, #post-comment, #occupation, #affiliation, select.interest").attr("disabled", !enable);
      jQuery("#post-comment").val(enable ? "Submit" : "Posting...");
      if (enable)
        this.checkFields();
    },
    disableForm: function() { this.enableForm(false); },
  
    /**
     * Makes a request to Sitelife for a page of comments for the
     * current article, and passes the ResponseBatch object to
     * the specified callback function.
     * @param {Boolean} load_in_background?  if true, do not jump to user message at start or
     *                                       after comments have loaded
     */
    loadComments: function(load_in_background) {
      load_in_background = !!load_in_background;
      console.info("loadComments(cur page is #%d, load_in_background is %s)", this.currentPage, load_in_background);
  
      var action = new CommentPage(new ArticleKey(this.articleKey),
        this.commentPageSize, this.currentPage, "TimeStampDescending");

      function get_msg(text, style) {
        return {
          text: text,
          style: style || "loading",
          scroll: !load_in_background
        };
      }
      
      doRequest(action, get_msg("Retrieving comments..."), function(response) {
        if (checkResponse(response, get_msg("Error loading comments!", "error"))) {
          var page = response.Responses[0].CommentPage, comments = page.Comments;
          
          console.info("page.NumberOfComments = {0}, comments.length = {1}".fmt(page.NumberOfComments, comments.length));
          
          // Clear any current comments, and use comments in reponse to
          // generate new ones on the page. 
          var $out = jQuery("#live-comments").empty();
      
          for (var i = 0; i < comments.length; i++) {
            var cur = page.Comments[i],
              elemHtml = _widget.buildCommentElem(cur);
            
            // Add report post links to comments
            jQuery(elemHtml)
              .appendTo($out)
              .find("div.poster")
                .children("p")
                  .wrapAll("<div style='float: left'></div>")
                  .end()
                .append(
                  jQuery("<a href='#' class='report-post-link'>Report this post</a>")
                    .bind("click.sbmj", function(ev) {
                      var key = getCommentKey(ev.target);
                      if (typeof key !== "string") {
                        console.warn("No comment key found for event target %o", ev.target);
                      }
                      else {
                        _widget.reportAbuse(ev, key);
                      }
                      ev.stopPropagation();
                      //return false;
                    })
                )
                .append("<div class='clear'></div>");
          }
  
          // Update # of comments on the article for pagination.
          _widget.updateNumberOfComments(numArchivedComments + parseInt(page.NumberOfComments));
           clearUserMessage();
          _widget.enableForm();
          console.info("loadComments: finished");
        }
        /*else {
          setUserMessage("Error loading comments!", "error");
        }*/
      });
    },
    
    reportAbuse: function(event, commentKey) {
      jQuery("#ReportAbuse_Select").val("");
      jQuery("#ReportAbuse_Report").bind("click.sbmj", function() {
        jQuery(this).unbind("click.sbmj");
        var reason = jQuery("#ReportAbuse_Reason").val();
        var description = jQuery("#ReportAbuse_CommentText").val();
        jQuery("#ReportAbuse_Menu").hide();

        var action = new ReportAbuseAction(new CommentKey(commentKey), reason, description);

        doRequest(action, "Sending report...", function(response) {
          if (checkResponse(response, "Error submitting report!")) {
            setUserMessage({ text: "Post has been reported", style: "submit-success" });
          }
        });
      })
      jQuery("#ReportAbuse_Menu")
        .css({ left: event.pageX, top: event.pageY, width: 200 })
        .show();
      jQuery("#ReportAbuse_CommentText").val("").focus();
    },

    /**
     * Makes a request to Sitelife to post a new comment to the article,
     * and updates the page on success/failure.
     * @param {String} body  body text of new comment
     */
    postNewComment: function(bodyText, occupation, affiliation, ci) {
      bodyText += "<div class='poster'>" +
        "<p><strong><a href='{0}' class='doc2doc-profile-link' target='_blank'>{1}</a></strong></p>".fmt(_userInfo.profileurl, _userInfo.screenname) +
        "<p>{0} / {1}</p>".fmt(affiliation, occupation);
      if (ci) bodyText += "<p class='competing-interests'>" + ci + "</p>"
      bodyText += "</div>"
      console.info("postNewComment:\n" + bodyText + "\n");
      
      var url = document.location.href.replace(/#.*/, "");

      var action = new CommentAction(new ArticleKey(this.articleKey),
        url, document.title, bodyText);

      doRequest(action, "Posting comment...", function(response) {
        _widget.enableForm();
        
        var editor = nicEditors.findEditor("comment-edit");
              
        if (checkResponse(response, "There was an error submitting your comment.<br/><br/>" +
          "Please ensure all fields are filled in correctly.")) {
          // Clear edit box, display posted message for user, jump to page 1 so user can see their comment.
          editor.setContent("");
          setUserMessage({ text: "Thank you for submitting a comment.", style: "submit-success" });
          _widget.setCurrentPage(1, true);
        }
        else {
          // Display error message, return kb focus to edit box.
          //_widget.setUserMessage({ 
          //   text: "There was an error submitting your comment. Please ensure all fields are filled in correctly.",
          //   style: "error"
          // });
          editor.elm.focus();
        }
      });
    },
  
    /**
    * Function to turn a Sitelife Comment object into HTML for display. 
    * @param {Comment} comment Sitelife comment object
    * @return {jQuery} jQuery wrapper around comment DOM
    */
    buildCommentElem: function(comment) {
      function pad(n) {
        return n < 10 ? "0" + n : "" + n;
      }
      var d = new Date(comment.PostedAtTime_UTC);
      comment.PostedAtTime = "{0}:{1} {2} {3} {4}".fmt(pad(d.getHours()), pad(d.getMinutes()), d.getDate(), d.getMonthName(), d.getFullYear())
      return COMMENT_TEMPLATE.fmt(comment);
    },

    updateNumberOfComments: function(numComments) {  
      console.info("updateNumberOfComments({0})".fmt(numComments));
      
      this.totalNumberOfComments = numComments;
      this.numberOfCommentPages = Math.max(Math.ceil(numComments / this.commentPageSize), 1);
      
      var _widget = this, onLastPage = this.currentPage == this.numberOfCommentPages;
      
      console.log("total comments = {totalNumberOfComments}, num pages = {numberOfCommentPages}".fmt(this));
      
      // Only show archived comments if we're on the last page of comments.
      jQuery("#archived-responses").toggle(onLastPage);
      
      // Set alternating odd/even classes on all visible comments.
      jQuery("#responses .article-comment:visible")
        .filter(":even")
          .addClass("response-even").removeClass("response-odd")
          .end()
        .filter(":odd")
          .addClass("response-odd").removeClass("response-even")
          .end();
            
      // Update link in article header with the new number of comments.
      if (numComments > 0)
        jQuery("#resp-link-top").text("Responses to this article (" + numComments + ")");
        
      var pag = jQuery("#pagination").empty().append("Go to page: ");
      
      for (var i = 1; i <= this.numberOfCommentPages; i++) {
        if (i == this.currentPage) {
          jQuery("<span class='cur-page'>{0}</span>".fmt(i)).appendTo(pag);
        }
        else {
          var p = i;
          jQuery("<a href='#'>{0}</a>".fmt(i))
            .click(function(ev) {
              _widget.setCurrentPage(p);
            })
            .appendTo(pag)
            .wrap("<span></span>");
        }
      }
    },
  
    /**
     * Set current comment page - will trigger comment loading if it's a
     * different page or the forceRefresh parameter is true.
     * @param {Number} newPageNum        Number of comment page to display
     * @param {Boolean} [forceRefresh]   If true, loads comments even when newPageNum is the current page
     */
    setCurrentPage: function(newPageNum, forceRefresh) {
      console.info("setCurrentPage({0}, force = {1})".fmt(newPageNum, forceRefresh));
      
      newPageNum = Math.max(1, Math.min(newPageNum, this.numberOfCommentPages));
  
      if (forceRefresh || newPageNum != this.currentPage) {
        console.info("Requesting comments from Sitelife for new page");
        
        this.currentPage = newPageNum;
        this.loadComments();
      }
    }
  }

  var RECENT_COMMENT_TEMPLATE = "\n" +
    "<h3><a class='article' href='{ArticleUrl}'>{ArticleTitle}</a></h3>\n" +
    "<div class='article-comment clearfix response-odd'><span class='pointer'></span><div class='response-b'><div class='response-m'>\n" +
      "<p>{CommentBody}</p>" +
      "<a href='{ArticleUrl}'>Read the article …</a>" +
      "<div class='clear'></div>" +
    "</div></div></div>\n\n";
  
  RecentComments = function() {
    function processComments(comments) {
      //console.info("here! comments = %o", comments);
      comments.sort(function(a, b) {
        return new Date(b.PostedAtTime_UTC).getTime() - new Date(a.PostedAtTime_UTC).getTime();
      });
      for (var i = 0; i < comments.length; i++) {
        var html = RECENT_COMMENT_TEMPLATE.fmt(comments[i]);
        jQuery(html)
          .appendTo("#latest-responses")
          .find("div.poster")
            .remove();
      }
      clearUserMessage();
      //console.dir(comments);
    }
    
    jQuery("<div id='user-message'></div>").insertBefore("#latest-responses");

    var discoveryAction = new DiscoverArticlesAction([new Section("All")], [new Category("All")],
      [new UserTier("All")], new Activity("Recent"), 7, 10);
    doRequest(discoveryAction, "Loading comments...", function(responseBatch) {
      if (checkResponse(responseBatch, "Error loading recent coments!")) {
        disc = responseBatch.Responses[0].DiscoverArticlesAction;
        
        var numArticles = disc.DiscoveredArticles.length, numRequests = 0, comments = [];
        for (var i = 0; i < numArticles; i++) {
          var curArticle = disc.DiscoveredArticles[i];
          var commentPage = new CommentPage(new ArticleKey(curArticle.ArticleKey.Key),
            10, 1, "TimeStampDescending");

          doRequest(commentPage, null, function(responseBatch) {
            var cp = responseBatch.Responses[0].CommentPage;
            for (var j = 0; j < cp.Comments.length; j++) {
              var cur = cp.Comments[j];
              cur.ArticleTitle = curArticle.PageTitle;
              cur.ArticleUrl = curArticle.PageUrl.replace(/#.*/, "");
              cur.PostedAtTime = getDateString(cur.PostedAtTime_UTC);
              comments.push(cur);
            }
            
            if (++numRequests == numArticles) {
              processComments(comments);
            }
          });
        }
      }
    });
  }

  window.CommentWidget = CommentWidget;
  window.parseATCookie = parseATCookie;
  //w  = new CommentWidget("sbmj.b2588")

})(); // end closure
