// JIRA Worklog Helper user script
// version 0.94
// 2005-08-31
// Copyright (c) 2005, Leonard Lin
// Released under the GPLv2 license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// JIRA has built in worklogging, which is a great feature w/ some very nice 
// reporting (I especially like the accuracy and developer reports), but sadly 
// worklogging is done in a separate screen and is rather inconvenient.
//
// This script simplifies work logging for JIRA:
// * Tracks the work time for an issue if you're use the 'Start Progress' link
// * Adds inline worklog form for 'Stop Progress', prefilled with tracked time
// * Resolve/Close display a worklog form, prefilled with any tracking info
//
// This has been tested to work with default templates on JIRA 3.3+, however
// Due to the nature of the additions (DOM replacements, workflow-based action)
// you may have to make modifications to the script if you have different 
// workflow IDs or document structure
//
// I've begun to abstract version specific testing and node searches into
// functions placed at the end of this file
//
// TODO: 
// * autoclear cookied times for completed items? (right now keeps 10 day 
//   cookie for all work items)
//
// JIRA Wishlist:
// * HTML id's for forms and inputs
// * Issue ID# for all issues
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          JIRA Worklog Helper
// @namespace     http://greasemonkey.randomfoo.net/jira/
// @description   JIRA worklog helper application
// @include       http://your.jira.url/*
// ==/UserScript==

(function() {
  // Run for proper pages
  var x;
  var url = document.location.href.split("/");
  if(x = url.pop()) {
    getVersion();
    if(x.indexOf("CommentAssignIssue!default.jspa?") == 0) {
      if( (x.indexOf("action=2") != -1)  || (x.indexOf("action=5") != -1) )
        modifyIssueWorkflowPage(url);
    } else if (x = url.pop()) {
      if(x == "browse") {
        modifyIssuePage(url);
      }
    }
  } 

  // Add Worklog form to Workflow Pages
  function modifyIssueWorkflowPage(url) {
    url.pop();
    var baseurl = url.join("/") + "/";
    var id = x.split("=").pop();
    var time = getStoredTime(id);
        
    // Override Submit to record worklog first
    document.jiraform = document.forms[1]; // Named form in DOM not supported anymore
    document.jiraform.addEventListener("submit", function(e) {
      if(t = document.getElementById("timeLogged").value) {
        s = baseurl + "secure/LogWork.jspa?id=" + id + "&timeLogged=" + t + "&newEstimate=" + document.getElementById("newEstimate").value + "&adjustEstimate=new&comment=" + escape(document.getElementById("wlcomment").value);
        updateWorklog(s);        
        clearStoredTime(id);
      }
    }, false);
      

    // Publish the Worklog Form
    var el = modifyIssueWorkflowPageElement();
    el.innerHTML = '<td align="right" style="padding-top:5px">Worklog:</td> \
                    <td bgcolor="white"> \
                      <div style="margin: 10px 0;"> \
                        Time Spent: <input id="timeLogged" value="' + time + '" size="5" /> \
                        Time Remaining: <input id="newEstimate" value="0" size="5" /> \
                        <input type="hidden" id="adjustEstimate" id="new_estimate_id" align=absmiddle value="new" ><br /> \
                        <textarea id="wlcomment" cols="30" rows="2" wrap="virtual" style="width:50%"></textarea> \
                      </div> \
                    </td>';
  }
  
  
  // Add Worklog form-mods to Issue Pages
  function modifyIssuePage(url) {
    var baseurl = url.join("/") + "/";
    var id = modifyIssuePageId();
       
    // Start Progress
    if(el = document.getElementById("action_id_4"))
      // Store Time on Start Progress
      el.addEventListener("click", function(e) {
        setStoredTime(id);
      }, false);

    
    // Stop Progress
    if(el = document.getElementById("action_id_301")) {
      // Submit Time on Stop Progress
      el.addEventListener("click", function(e) {
        // Submit Time
        if(t = document.getElementById("timeLogged").value) {
          s = baseurl + "secure/LogWork.jspa?id=" + id + "&timeLogged=" + t + "&adjustEstimate=auto&comment=" + escape(document.getElementById("wlcomment").value);
          updateWorklog(s);
          clearStoredTime(id);
        }
      }, false);
      
      // Render Form
      var time = getStoredTime(id);
      p = document.createElement("div");
      p.innerHTML = '<div style="margin: 2px 5px; font-size: 11px"> \
                     <div align="right">Time Spent: <input id="timeLogged" value="' + time + '" size="5" style="font-size:11px;text-align:right;" /> \
                     <textarea id="wlcomment" cols="20" wrap="virtual" style="width:100%; height: 3em; margin-top:4px; font-size:9px; font-family:verdana"></textarea> \
                     </div>';
      el.parentNode.parentNode.appendChild(p);
      
    }
  }

  function setStoredTime(id) {
    newwork = "WORK=";
  
    c = document.cookie.split("; ");
    
    // WORK cookie doesn't exist, lets just make it
    if(document.cookie.indexOf(newwork) == -1) {
      document.cookie = newwork + id + ":" + new Date().getTime() + "; expires=" + expDate(10) + "; path=/";
      return;
    }
    
    for(i = 0; i < c.length; i++) {
      if(c[i].substr(0, 5) == "WORK=") {
        kv = c[i].split("=");
        t = kv[1].split(":");
      
        // New value
        newwork += id + ":" + new Date().getTime() + ":";
        
        // Old stuff
        while(tid = t.shift()) {
          ttime = t.shift();
          
          if(tid != id) {
            newwork += tid + ":" + ttime + ":";
          }
        }
        // chomp last ":"
        newwork = newwork.substr(0, newwork.length-1);
        
        document.cookie = newwork + "; expires=" + expDate(10) + "; path=/";
      }
    }
  
  
  }

  // Tries to find Stored Time
  function getStoredTime(id) {
    if(document.cookie.length == 0) return '';
    
    c = document.cookie.split("; ");
    for(i = 0; i < c.length; i++) {
      if(c[i].substr(0, 5) == "WORK=") {
        kv = c[i].split("=");
        t = kv[1].split(":");
        
        while(tid = t.shift()) {
          ttime = t.shift();
          
          if(tid == id) {
            return Math.ceil((new Date().getTime() - ttime)/(1000*60)) + "m";
          }
        }
      }
    }
    return '';
  } 
    
  // Clears the Stored Time cookie
  function clearStoredTime(id) {
    newwork = "WORK=";
    if(document.cookie.length == 0) return;
    
    c = document.cookie.split("; ");
    for(i = 0; i < c.length; i++) {
      if(c[i].substr(0, 5) == "WORK=") {
        kv = c[i].split("=");
        t = kv[1].split(":");
        
      
        while(tid = t.shift()) {
          ttime = t.shift();
          
          if(tid != id) {
            newwork += tid + ":" + ttime + ":";
          }
        }
        // chomp last ":"
        newwork = newwork.substr(0, newwork.length-1);
        
        // empty
        if(newwork == "WORK") {
          document.cookie = newwork + "=; expires=" + expDate(-1) + "; path=/";
        } else {
          document.cookie = newwork + "; expires=" + expDate(10) + "; path=/";
        }
      }
    }
  } 

  // Return Cookie expiration date
  function expDate(i) {
    exp = i * 24 * 60 * 60 * 1000;
    var date = new Date();
    date.setTime(date.getTime() + (exp));
    return date.toGMTString();
  }
  
  function updateWorklog(s) {
    // Does not work in straightforward mannerdue to Greasemonkey 0.6.4 bug: 
    // http://greaseblog.blogspot.com/2005/11/greasemonkey-064_30.html
    try {
      i = new Image;
      i.src = s;
    } catch(e) {
      allImages = document.getElementsByTagName("img");
      position = allImages.length - 1;
      i = allImages[position];
      i.src = s;
    } finally {
      pause(500);
    }
  }
  
  // Horrible hack
  function pause(numberMillis) {
    var now = new Date();
    var exitTime = now.getTime() + numberMillis;
    while (true) {
      now = new Date();
      if (now.getTime() > exitTime)
        return;
    }
  }

  // JIRA Version Specific Functions
  // * UPDATE WHEN NEW VERSIONS ARE RELEASED
  // * REFACTOR AS NECESSARY
  
  function getVersion() {
    var footer = document.getElementsByTagName("div");
    footer = footer[footer.length-1];
    footer = footer.innerHTML;
    
    if(footer.indexOf("Version: 3.3")) document.jiraVersion = 3.3;
    else if(footer.indexOf("Version: 3.4")) document.jiraVersion = 3.4;
  }
  
  function modifyIssueWorkflowPageElement() {
    if(document.jiraVersion == 3.3) {
      return document.jiraform.childNodes[1].childNodes[1].childNodes[10];
    
    // v3.4+
    } else {
      return document.jiraform.childNodes[1].childNodes[1].childNodes[12];
    }    
  }
  
  function modifyIssuePageId() {
    // Doesn't work in my 1.5 anymore // var id = document.jiraform.id.value;
    if(document.jiraVersion == 3.3) {
      return document.forms[1].elements[2].value;
    
    // v3.4+
    } else {
      return document.getElementById('id').value;
    }
  }

})();
