/**
 * @projectDescription A jQuery plugin with various tools for writing BMJ code. 
 * @author             James Skinner
 * @version            0.2
 */


/**
 * Options for how the page will be displayed. 
 */
PAGE_OPTIONS = {
  clinicalWidget: 6
}


// For browsers such as IE with no console object, create
// a mock console object.
if (typeof(console) == "undefined") {
  console = {
    group:function() {},
    groupEnd:function() {},
    dir:function() {},
    log:function() {},
    info:function() {},
    warn:function() {},
    error:function() {}
  };
}
// Chrome has a console object, but it lacks some of the
// methods that Firebug has.
else if (!console.group) {
  console.group = function() {}
  console.groupEnd = function() {}
  console.dir = console.info;
}


(function() { // closure to prevent namespace pollution

  function getpadding(s, w) {
    return s.length < w ? new Array(w - s.length).join(" ") : "";
  }
  
  var FMT_REGEX = /{([^{}]*)}/g;
  
  jQuery.extend(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, repkey) {
        return (repkey in data) ? String(data[repkey]) : all;
      });
    },
  
    trimLength: function(maxLength) {
      if (this.length < maxLength) return this;
      return this.substr(0, maxLength - 4) + " ...";
    },
    
    times: function(ch, num) {
      if (!num) { num = ch; ch = " "; }
      return new Array(num + 1).join(ch);
    },
    
    ljust: function ljust(width) {
      if (this.length <= width)
        return this + getpadding(this, width);
      return String.prototype.trimLength.call(this, width);
    },
    
    rjust: function rjust(width) {
      if (this.length <= width)
        return getpadding(this, width) + this;
      return String.prototype.trimLength.call(this, width);
    },
    
    centre: function centre(width) {
      if (this.length <= width) {
        var left = (width - this.length) / 2;
        var os = " ".times(left) + this;
        return os + getpadding(os, width);
      }
      return String.prototype.trimLength.call(this, width);
    },
    
    startswith: function startswith(str) {
      return this.substring(0, str.length) == str;
    },
    
    endswith: function endswith(str) {
      return this.substring(this.length - str.length) == str;
    }
  });

  // ------------------------------------------------------------------
  // Basic helper functions which we attach to the jQuery object.

  jQuery.extend({

    isUndefined: function(obj) { return typeof obj == "undefined"; },
    isDefined: function(obj) { return typeof obj != "undefined"; },
    isObject: function(obj) { return typeof obj == "object"; },

    keys: function keys(obj, sorted) {
      if (typeof obj != "object") return [];
      var keys = [];
      for (var k in obj) { keys.push(k); }
      if (sorted) keys.sort();
      return keys;
    },
    
    safeString: function toString(obj) {
      if (typeof obj == "undefined") return "undefined";
      if (obj == null) return "null";
      return obj.toString();
    },
    
    format: function format(formatString) {
      var fs = jQuery.safeString(formatString), options = jQuery.makeArray(arguments).slice(1);
      if (options.length == 1 && typeof(options[0]) == "object")
        options = options[0];

      return fs.replace(/{([^{}]*)}/g, function(all, repkey) {
        return String(options[repkey]) || all;
      });
    },
    
    parseuri: (function() {
      var parser = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/;
      var queryKeyParser = /(?:^|&)([^&=]*)=?([^&]*)/g;
      var keys = ["source","protocol","authority","userInfo","user","password","host","port",
                  "relative","path","directory","file","query","anchor"];

      return function parseuri(url) {
        var m = parser.exec(url || location.href), urlinfo = { }, i = keys.length + 1;

        while (--i) { urlinfo[keys[i]] = m[i] || ""; }

        urlinfo.queryKey = {};
        urlinfo.query.replace(queryKeyParser, function($0, $1, $2) {
          if ($1) urlinfo.queryKey[$1] = $2;
        });
        urlinfo.base = urlinfo.protocol + "://" + urlinfo.host +
          (urlinfo.port ? ":" + urlinfo.port : "") + "/";
        
        urlinfo.segments = urlinfo.path.replace(/^\/|\/$/g, "").split(/\//g);

        return urlinfo;
      };
    })(),
    
		cookie: function(name, value, options) {
      if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
          value = '';
          options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
          var date;
          if (typeof options.expires == 'number') {
            date = new Date();
            date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
          }
					else {
            date = options.expires;
          }
          expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
      } 
      else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
          var cookies = document.cookie.split(';');
          for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
              cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
              break;
            }
          }
        }
        return cookieValue;
      }
    }
  });
  
  // ------------------------------------------------------------------
  // Extensions to jQuery collection objects.

  var simpleNumericFuncs = {
    inc: function(x) { return ++x; },
    dec: function(x) { return --x; }
  }

  jQuery.fn.extend({
    
    /**
     * For each selected element uses the value of this.text() as a number,
     * can optionally specify an operation to update the value, and returns
     * the value.
     * @param {String|Function} oper Either the string value "inc" or "dec",
     *                               which increment or decrement the current
     *                               value, or a function that takes an int
     *                               and returns an int
     * @returns {Object} jQuery object with result values
     */
    numeric: function numeric(oper) {
      console.log(typeof oper)
      console.log(oper)

      if (typeof oper == "string") {
        oper = simpleNumericFuncs[oper];
      }
      
      return this.map(function() {
        try {
          console.log("to = "+typeof oper)
          var intval = parseInt(jQuery(this).text());
          
          if (oper) {
            intval = oper(intval);
            jQuery(this).text(intval.toString());
          }
          
          return intval;
        }
        catch (ex) {
          return "Error! "+ex;
        }
      });
    }
    
  });
  

  // ------------------------------------------------------------------
  // Add bmj namespace for non-generic functions. 

  var _DEBUG_MODE = false, logIndex = 1;
  var eventHandlers = {
    // Handlers will be called once we've got a response
    // from Sitelife with the current user details if
    // logged in.
    DoneLoginCheck: []
  }
  
  var DOMHelpers = {
    styles: function(rules) {
      if (jQuery.isArray(rules)) {
        rules = rules.join("\n");
      }
      else if (jQuery.isObject(rules)) {
        var css = [];
        for (var k in rules) {
          css.push(jQuery.format("{0} { {1} }", k, rules[k]));
        }
        rules = css.join("\n");
      }
      return jQuery("<style type='text/css></style>").text(rules);
    }
  }
  
  var HtmlLogger = (function() {
    var $elem = null,
      entryCount = 0;
    
    return {
      get: function() {
        if (!$elem) {
          $elem = jQuery("#bmjlogwin");
          
          if ($elem.length) return $elem;
          
          /*jQuery("head").append(DOMHelpers.styles({
            "#bmjlogwin": "position: fixed; width: 480px; height: 80%; top: 10px; right: 10px; " +
              "padding: 5px 10px; background-color: #ffd; color: black; border: thick double black; " +
              "font: normal 11px Consolas; overflow: auto; text-align: left;",
            "#bmjlogwin hr": "margin: 10px 0px;"
          }));*/
          
          $elem = jQuery("<div id='bmjlogwin'></div>")
            .css({
              position: "fixed",
              width: 480,
              height:"80%",
              top: "10px",
              right: "10px",
              padding: "5px 10px",
              backgroundColor: "#ffd",
              color: "black",
              border: "thick double black",
              font: "normal 10px Consolas",
              overflow: "auto",
              textAlign: "left"
            })
            .bind("click.bmjlogger", function(ev) { jQuery(this).hide("slow"); })
            .appendTo("body");          
        }
        
        return $elem;
      },
      
      log: function(msg) {
        if (jQuery.bmj.DEBUG) {
          var logMsg = String(logIndex++) + ": " + jQuery.safeString(msg).replace(/\n/g, "<br/>");
          
          var $lw = this.get().show("medium");
          
          if (entryCount > 0) {
            jQuery("<hr/>").css("margin", "10px 0").prependTo($lw);
          }
          
          jQuery("<div></div>").html(logMsg).prependTo($lw);
          entryCount++;
        }
      }
    }
  })();

  /*
   * Tools relating to the BMJ and Sitelife in general.
   */
  jQuery.bmj = {
    
    debugMode: function debugMode() { return _DEBUG_MODE },
  
    setDebugMode: function setDebugMode(state) {
      if (state != _DEBUG_MODE) {
        _DEBUG_MODE = state;
        
        var $lw = jQuery("#bmjlogwin");
        
        if (_DEBUG_MODE) {
          if (!$lw.length) {
            jQuery("<style type='text/css'>" +
              "#bmjlogwin { position: fixed; width: 480px; height: 80%; top: 10px; right: 10px; " +
              "padding: 5px 10px; background-color: #ffd; color: black; border: thick double black; " +
              "font: normal 11px Consolas; overflow: auto; text-align: left; } " +
              "#bmjlogwin hr { margin: 10px 0px; }</style>")
              .appendTo("head");
            
            jQuery("<div id='bmjlogwin'></div>")
              .bind("click.bmj", function(ev) { jQuery(this).hide(); })
              .appendTo("body");
          }
          
          $lw.show("fast");
        }
        else if ($lw.length) {
          $lw.hide("fast");
        }
      }
    },
    
    logger: HtmlLogger,

    /**
     * If jQuery.bmj.DEBUG is true, then writes the given message to
     * the top of a logging window within the current page - clicking
     * on this window clears and closes it.
     * @param {String} msg Message to log
     */
    log: function(msg) {
      if (jQuery.bmj.DEBUG) {
        var logMsg = String(logIndex++) + ": " + jQuery.safeString(msg).replace(/\n/g, "<br/>");

        var $el = jQuery("#bmjlogwin").show();
        
        if ($el.length) {
          jQuery("<hr>").css("margin", 10).prependTo($el);
        }
        else {
          jQuery("<style type='text/css'>" +
            "#bmjlogwin { position: fixed; width: 480px; height: 80%; top: 10px; right: 10px; " +
            "padding: 5px 10px; background-color: #ffd; color: black; border: thick double black; " +
            "font: normal 11px Consolas; overflow: auto; text-align: left; } " +
            "#bmjlogwin hr { margin: 10px 0px; }</style>")
            .appendTo("head");
            
          $el = jQuery("<div id='bmjlogwin'></div>")
            .bind("click.bmj", function(ev) { jQuery(this).hide(); })
            .appendTo("body");
        }

        jQuery("<div></div>")
          .html(logMsg)
          .prependTo($el);  
      }
    },
  
    // Templates defined in the page and autoloaded further down.
    templates: {},
    
    /**
     * Loads templates from an XML file with the format:
     *   <templates>
     *     <template name="foo">
     *       <div>Template body...</div>
     *     </template>
     *     ...
     *   </templates>
     * and stores them for use with jQuery.bmj.runTemplate.
     * @param {String} src URL to load templates from
     * @param {Function} cb Optional callback to call after loading is complete
     */
    loadTemplates: function loadTemplates(src, cb) {
      jQuery.get(src, function(data) {
        var x = jQuery(data);
        
        jQuery("template", x).each(function() {
          var $el = jQuery(this), name = $el.attr("name"), val = $el.text();
          jQuery.bmj.storeTemplate(name, val);
        });
        
        if (cb) {
          cb();
        }
      }, "xml");
    },
    
    /**
     * Store a template under the given id for later use with jQuery.bmj.runTemplate.
     * @param {String} id Unique id for template
     * @param {String} value Text value of template.
     */
    storeTemplate: function storeTemplate(id, value) {
      jQuery.bmj.templates[id] = value;
      jQuery.bmj.log("Stored template {0}".fmt(id));
    },
    
    /**
     * Build and return a DOM element tree by running a template with the given data. 
     * @param {String} templateId Name of template stored in jQuery.bmj.templates
     * @param {Object} data Data to pass to the template
     * @return {Object} DOM element built from template 
     */
    runTemplate: function(templateId, data) {
      var ts = jQuery.bmj.templates[templateId];
      if (!ts) return "";
      
      tmpl = TrimPath.parseTemplate(ts, templateId);
      return jQuery(tmpl.process(data));
    },
    
    isClinicalGroup: function isClinicalGroup(key) {
      if (typeof key != "string") return false;
      var cg = jQuery.bmj.ClinicalGroups;
      for (var i = 0; i < cg.length; i++) {
        if (cg[i].matchKey(key)) return true;
      }
      return false;
    },
    
    
    GroupInfo: function GroupInfo(name, forumKey) {
      return {
        name: name,
        forumKey: forumKey,
        groupKey: forumKey.substr(70, 36),
        
        matchKey: function(k) {
          k = k.toLowerCase();
          return (k == this.forumKey || k == this.groupKey); 
        },
        toString: function() { return "{name}(GK: {groupKey})".fmt(this) }
      }
    },  

    
    getGroupUrl: function(data) {
      var url = data.DiscussionUrl;
      
      if (url.indexOf("slPage=") == -1) {
        // Hack a working url pointing to the discussion because we're
        // getting ForumDiscussions returned instead of using Discovery.
        var keys = [
          "slPage=showDiscussionPost",
          "slGroupKey=" + data.AssociatedCommunityGroupKey.Key,
          "slForumPostKey=" + data.ForumDiscussionKey.Key
        ];
        url = url.split("?")[0] + "?" + keys.join("&");

        /*console.group(discussions[i].DiscussionTitle);
        console.info(discussions[i].DiscussionUrl);
        console.info(discussions[i].Url);
        console.dir(discussions[i]);
        console.groupEnd();*/
      }
      
      return url;
    },

    /**
     * Returns a URL for an action on the MyAccount server.
     * @param {String} action - one of register, login or update
     * @param {String} returnUrl URL to return to after executing the command, default is /
     * @param {Object} options Optional overrides of the other default parameters
     * @return {String} The URL requested
     */
    getRegistrationLink: function getRegistrationLink(action, returnUrl, options) {
      options = jQuery.extend({
        thankyou: "register-confirmation-wf.jsp",
        sitePageCode: "webForum",
        resDef: "bmjpg:web-forum"
      }, options);
      
      var k = action + "Url", url = jQuery.bmj.globalConfig[k];
      
      if (returnUrl) options.returnUrl = returnUrl;
      options.doc2docUserType = action;
      if (action == "login") options.action = "doLookup";
      
      var i = [];
      for (var prop in options) {
        i.push(prop + "=" + encodeURIComponent(options[prop]));
      }
      
      return registrationUrl + "?" + i.join("&");
    },
		
		parseATCookie: function(cookie) {
		  var items = /AT=(.*?)(?:;|$)/.exec(cookie || document.cookie);
		  if (!items) return null;
		  var b = decodeURI(items[1]).split("&"), res = {};
		  for (var i = 0; i < b.length; i++) {
		    var kv = b[i].split("=");
		    res[kv[0]] = unescape(kv[1]);
		  }
		  return res;
		},
    
    addEventHandler: function(eventname, handler) {
      var handlerList = eventHandlers[eventname];
      if (!handlerList) {
        eventHandlers[eventname] = handlerList = [];
      }
      handlerList.push(handler);
      
      jQuery.bmj.log("New handler for event {0} ({1} handlers)".fmt(eventname, handlerList.length));
    },
    
    runEventHandlers: function(eventname) {
      var handlerList = eventHandlers[eventname] || [];
      
      jQuery.bmj.log("Running handlers for event {0} ({1} handlers)".fmt(eventname, handlerList.length));
      
      //options = options || {};
      var args = jQuery.makeArray(arguments).slice(1);
      
      var handlerList = eventHandlers[eventname] || [], results = [];
      
      for (var i = 0; i < handlerList.length; i++) {
        var hf = handlerList[i],
           res = hf.apply(hf, args);
        results.push(res);
      }
      
      jQuery.bmj.log("Running handlers for event {0} done, result is {1}".fmt(eventname, results.toString()));
      return results;
    },
    
    getUrlBuildFunc: function getUrlBuildFunc(baseUrl, defaultParams, defaultAnchor) {  
      function buildQueryString(params) {
        var qi = [];
        for (var k in params) {
          qi.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
        }
        return qi.length > 0 ? "?" + qi.join("&") : "";
      }
      function buildAnchor(anchor) {
        return typeof anchor === "string" ? "#" + anchor : "";
      }
      
      return function(params, anchor) {
        var allParams = jQuery.extend({}, defaultParams, params);
        var myAnchor = anchor || defaultAnchor;
        return baseUrl + buildQueryString(allParams) + buildAnchor(myAnchor);
      };  
    }

  }
  
  // ------------------------------------------------------------------
  // Code to run when the page has loaded.
  
  jQuery(function() {

    // Parse details of current URL and make it available globally.
    jQuery.bmj.urlinfo = jQuery.parseuri();

    // Preload templates from <textarea id="tmpl-xxxxx">...</textarea>,
    // using the xxxx part as it's key under jQuery.bmj.templates.
    jQuery("textarea[id^='tmpl-']").each(function() {
      var templateId = jQuery(this).attr("id").substr(5);
      jQuery.bmj.storeTemplate(templateId, jQuery(this).val());
    });

    jQuery.bmj.log("Finished jquery.bmj.js");
  });

})(); // end closure
