Prototype.Browser.Mac = navigator.userAgent.toLowerCase().indexOf('mac') != -1;

var Widget = Class.create({
  initialize: function(args) {
    this.id = args.id;
    this.locationId = args.locationId;
    this.isEmpty = args.isEmpty;
    this.sendInvitationMail = args.sendInvitationMail;
    if (Prototype.Browser.Mac) {
      if (this._closeButton()) {
        this._closeButton().addClassName('mac_widget_close_button');
      }
    }
    if (this.isEmpty) {
      this.containingElement().hide();
    };
  },
  containingElement: function() {
    return $('widget_' + this.id);
  },
  show: function() {
    this.containingElement().blindDown({duration: 0.2});
  },
  hide: function(afterFinishFunction) {
    this.containingElement().blindUp({duration: 0.2, afterFinish: afterFinishFunction});
  },
  destroy: function() {
    this.containingElement().remove();
  },
  _closeButton: function() {
    return this.containingElement().down('.widget_close_button');
  },
  _editButton: function() {
    return this.containingElement().down('.widget_edit_button');
  },
  showControls: function() {
    if (this.isEmpty) {
      this.containingElement().show();
    };
    this._closeButton().appear({duration: 0.3});
    if (this._editButton()) {
      this._editButton().appear({duration: 0.3});
    };
    this.containingElement().addClassName('widget_edit_mode');
  },
  hideControls: function() {
    this._closeButton().fade({duration: 0.2});
    if (this._editButton()) {
      this._editButton().fade({duration: 0.3});
    };
    this.containingElement().removeClassName('widget_edit_mode');
    if (this.isEmpty) {
      this.containingElement().hide();
    };
  }
});

Object.extend(Widget, {
  asyncInsertion: function(responseJSON) {
    var fixedWidget = $('widget_fixed');
    responseJSON.each(function(json_widget) {
      if (json_widget.location < 0 && fixedWidget) {
        fixedWidget.insert({before: json_widget.inner_html})
      } else {
        $('sidebar').insert({bottom: json_widget.inner_html})
      }
    });

    Widget.redraw();
    Widget.fireLoadEvents.delay(0.1);
  },
  
  redraw: function() {
    var addWidgetSelectWidget = $('add_widget_select_widget');
    if (Widget.hasVisibleWidgets() || (addWidgetSelectWidget && addWidgetSelectWidget.visible())) {
      $('container').removeClassName('full_width');
    } else {
      $('container').addClassName('full_width');
    };
  },
  
  hasVisibleWidgets: function() {
    return $$('#sidebar .draggable').any(function(w) {return w.visible();});
  },
  
  pendingWidgets: new Array(),
  
  registeredWidgets: new Array(),
  
  register: function(widgetClass, options) {
    Widget.pendingWidgets.push([widgetClass, options || {}]);
  },
  
  activateWidgets: function() {
    var classAndOptions;
    while (classAndOptions = Widget.pendingWidgets.shift()) {
      Widget.registeredWidgets.push(ObjectFactory.create(classAndOptions[0], classAndOptions[1]));
    }
  }
});



Widget.ScriptLoadingQueue = Class.create({
  initialize: function($super) {
    this.QUEUED = 10;
    this.LOADING = 20;
    this.LOADED = 30;
    this.queue = new Array();
    this.running = false;
  },
  
  setExternalScriptState: function(src, state) {
    var entry = this.queue.find(function(e) {return e.src == src});
    if (entry.state < state) {
      switch(state) {
        case this.LOADING:
          entry.loadingStartTime = new Date().getTime();
          break;
        case this.LOADED:
          entry.loadingEndTime = new Date().getTime();
          break;
      }
      entry.state = state;
    };
  },
  
  loading: function() {
    return this.queue.find(function(e) {return e.state == this.LOADING}, this);
  },
  
  isDone: function() {
    return this.queue.all(function(e) {return e.state == this.LOADED}, this);
  },
  
  next: function() {
    return this.queue.find(function(e) {return e.state == this.QUEUED}, this);
  },
  
  push: function(theSrc, theOptions) {
    if (!this.include(theSrc)) {
      this.queue.push({ src: theSrc,
                        state: this.QUEUED,
                        options: theOptions || {},
                        loadingStartTime: null,
                        loadingEndTime: null});
    };
  },
  
  include: function(src) {
    return this.queue.any(function(e) {return e.src == src;});
  },
  
  loadScripts: function(callback) {
    if (this.isDone()) {
      //console.log('done loading scripts');
      this.running = false;
      if (callback) {
        callback();
      };
    } else {
      this.running = true;
      
      if (this.loading()) {
        //console.log('delay loading next script')
      } else {
        var nextScript = this.next();
        this.setExternalScriptState(nextScript.src, this.LOADING);
        //console.log('loading ' + nextScript.src);
        
        var attribs = $H({type: 'text/javascript', charset: 'utf-8'}).merge(nextScript.options);
        var script = new Element('script');
        script.src = nextScript.src;
        attribs.each(function(pair) {
          script.setAttribute(pair.key, pair.value);
        });
        var head = document.getElementsByTagName('head')[0];
        if (!head) {
          head = document.body.parentNode.appendChild(new Element('head'));
        }

        script.onload = this.setExternalScriptState.curry(nextScript.src, this.LOADED).bind(this);

        script.onreadystatechange = function() {
          if (this.readyState == 'loaded' || this.readyState == 'complete') {
            this.onload();
          }
        }.bind(script);

        head.appendChild(script);
      };
      this.loadScripts.bind(this).delay(0.1, callback);
    };
  }
});

Widget.scriptLoadingQueue = new Widget.ScriptLoadingQueue();

Object.extend(Widget, {
  //fancy external dependency loading queue is disabled for now.
  require: Widget.scriptLoadingQueue.push.bind(Widget.scriptLoadingQueue),
  require_old: function(src, options) {
    var attribs = $H({type: 'text/javascript', charset: 'utf-8'}).merge(options);
    var script = new Element('script');
    script.src = src;
    attribs.each(function(pair) {
      script.setAttribute(pair.key, pair.value);
    });
    var head = document.getElementsByTagName('head')[0];
    if (!head) {
      head = document.body.parentNode.appendChild(new Element('head'));
    }
    head.appendChild(script);
  },
  
  fireLoadEvents: function() {
    Widget.activateWidgets();
    Widget.scriptLoadingQueue.loadScripts(document.fire.curry('widgets:load').bind(document));
    //document.fire('widgets:load');
  }
});

var ReportWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.output  = args.output;
    this.graphs  = args.graphs;
    this.pointer = 0;
  },
  displayGraph: function() {
     if($(this.output) == null) {
       return;
     }
     this.pointer = this.pointer % this.graphs.length;
     $(this.output).src = this.graphs[this.pointer];
  },
  next: function() {
    this.pointer = this.pointer + 1;
    this.displayGraph();
  },
  start: function() {
    this.displayGraph();
  }
});

var TextWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.output  = args.output;
    this.texts   = args.texts;
    this.delay   = args.delay;
    this.pointer = 0;
    this.callout = null;
  },
  displayText: function(position, target) {
     if($(target) == null) {
       return;
     }

     if(position < 0) {
       position = this.texts.length - 1;
     }
     else {
       position = position % this.texts.length;
     }

     this.pointer = position;

     $(target).hide();
     $(target).update(this.texts[position]);
     $(target).appear({ duration: 1.5 });

     if(this.texts.length > 1) {
       this.callout = this.displayText.bind(this).delay(this.delay, position+1, target);
     }
  },
  next: function() {
    window.clearTimeout(this.callout);
    this.displayText(this.pointer+1, this.output);
  },
  prev: function() {
    window.clearTimeout(this.callout);
    this.displayText(this.pointer-1, this.output);
  },
  start: function() {
    if(this.texts.length > 0) {
      this.displayText(this.pointer, this.output);
    }
  }
});

var CustomWidget = Class.create(Widget, {});

var BuddhaMachineWidget = Class.create(Widget, {});

var TopPerformersWidget = Class.create(Widget, {});

var CampfireWidget = Class.create(Widget, {});

var OrganizationWidget = Class.create(Widget, {});

var EmbedWidget = Class.create(Widget, {});

var RelatedEntriesWidget = Class.create(Widget, {});

var TopForumEntriesWidget = Class.create(Widget, {});

var ViewsWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    if (typeof(ruleCounts) != 'undefined') {
      ruleCounts.updateUi();
    };
  }
});

var GetSatisfactionWidget = Class.create(Widget, {});

var ForumsWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    
    var selectedForum = document.getUrlParameter('forum_id')
    if (selectedForum) {
      set_selected_for_select($('forum_id'), selectedForum);
    };
    
    var query = document.getUrlParameter('query');
    if (query && ($('query').value == '')) {
      $('query').value = query;
    };
  }
});

var HarvestWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.harvest_domain = args.domain;
    this.use_ssl = args.use_ssl;
    this.ticket_id = args.ticket_id;
    this.ticket_nice_id = args.ticket_nice_id;
    if (this.ticket_nice_id == undefined)
      this.ticket_nice_id = this.ticket_id
    if (this.harvest_domain && this.ticket_id) {
      this.harvest_resource = new Zendesk.Resource(
        {anchor: 'widget_' + this.id,
         domain: this.harvest_domain,
         use_ssl: this.use_ssl || 'false',
         login_content: this.login.bind(this),
         application_content: this.application.bind(this),
         application_resources: [ {resource: 'daily', on_success: this.projectsSelector.bind(this)} ]
        }
      );
    } else {
      this.containingElement().down('div#content').update(args.not_available || 'Only available when viewing tickets');
    }
  },

  login: function() {
    return  '<form onsubmit="ObjectFactory.get(' + this.id + ').harvest_resource.submit_credentials(this); return false;" class="form">' +
            '<label>Username</label><input type="text" id="username"/>' +
            '<label>Password</label><input type="password" id="password"/>' +
            '<br/><input type="submit" value="Login" id="submit">' +
            '</form>'
  },

  application: function() {
    return '<form onsubmit="if (parseFloat(this[\'request[hours]\'].value)==NaN) {alert(\'Please enter a valid value for hours\'); return false;}; this[\'request[notes]\'].value +=\' (ticket #' + this.ticket_nice_id + ')\';  ObjectFactory.get(' + this.id + ').harvest_resource.submit_data(this); return false;" class="form">' +
           /* Form fields to POST to Harvest */
           '<label>Select project</label><select name="request[project_id]" id="harvest-form-projects" onchange="ObjectFactory.get(' + this.id + ').tasksSelector(this.options[this.selectedIndex].value)"></select>' +
           '<label>Select task</label><select name="request[task_id]" id="harvest-form-tasks"></select>' +
           '<label>Notes</label><input type="text" name="request[notes]"/>' +
           '<label><b>Hours</b></label><input type="text" name="request[hours]" size="5" maxlength="5"/><br/>' +
           '<input type="hidden" name="request[spent_at]" value="' + Date('dd/mm/yyyy') + '">' +
           /* Request parameter to pluck from form fields for POST*/
           '<input type="hidden" name="pluck_param" value="request">' +
           /* REST resource to POST to */
           '<input type="hidden" name="resource" value="daily/add">' +
           /* Format for REST resource POST (json or xml) */
           '<input type="hidden" name="media_type" value="application/xml">' +
           /* Title on ticket event */
           '<input type="hidden" name="event_reference" value="Harvest time tracking">' +
           /* Return values from POST to log on ticket event */
           '<input type="hidden" name="event_log" value="Project,Task,Notes,Hours,location,ID">' +
           '<input type="submit" value="Submit" id="submit">' +
           '<span class="link" style="font-weight:normal;margin-left:20px;" onclick="ObjectFactory.get(' + this.id + ').harvest_resource.logout()">(logout)</span>' +
           '</form>'
  },

  // Harvest client selector
  projectsSelector: function(response) {
    // load options into the project dropdown
    var currentClient = ''
    var projectInput = $('harvest-form-projects');
    var optGroup;
    projectInput.update();
    projects = $A(response.projects);
    if (projects.length == 0) return;
    projects.sortBy(function(p){return p.client + p.name}).each(function(p){
      // if this is a new client, create the option group for it
      if (currentClient != p.client)  {
        // add the last option group to the input selector if one has been created
        if (currentClient != '')
          projectInput.appendChild(optGroup);
          optGroup = new Element("optgroup");
          optGroup.setAttribute("label", p.client);
          currentClient = p.client;
        }

        // keep adding this to the option group
        var option = new Element("option");
        option.value = p.id;
        option.update(p.name);
        optGroup.appendChild(option);
    });

    // tack on the final optgroup
    projectInput.appendChild(optGroup);

    // display the default
    projectInput.selectedIndex = 0;

    // load default task list
    if (projects.length>0) this.tasksSelector(projects[0].id);
  },

  // Harvest task selector - tasks are dependant on project selected
  tasksSelector: function(project_id) {
    // we always start with the billable group
    var billable = false;
    var currentGroup = '';
    $('harvest-form-tasks').update();

    projects.each(function(p){
      // if we have a match, print out the tasks
      if (p.id == project_id) {
        $A(p.tasks).sortBy(function(t){return !t.billable + t.name}).each(function(t){
          var option = new Element("option");
          option.value = t.id;
          option.update(t.name);
          // if the current group matches this task's billable status, add it in
          currentTask = t.billable ? "Billable" : "Non-billable";
          if (currentGroup != currentTask) {
            // wrap up the first option group
            if (currentGroup != '') $('harvest-form-tasks').insert(optGroup);
            // start the next one
            optGroup = new Element("optgroup");
            optGroup.setAttribute("label", currentTask);
            currentGroup = currentTask;
          }
          optGroup.appendChild(option);
        });
        // add last group to option group
        $('harvest-form-tasks').insert(optGroup);
      }
    });
  }
});

var HighriseWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.highrise_domain = args.domain;
    this.use_ssl = args.use_ssl;
    this.initial_name = args.name;
    this.ticket_id = args.ticket_id;
    this.ticket_nice_id = args.ticket_nice_id;
    if (this.ticket_nice_id == undefined)
      this.ticket_nice_id = this.ticket_id
    this.highrise_resource = new Zendesk.Resource(
      {anchor: 'widget_' + this.id,
       domain: this.highrise_domain,
       use_ssl: this.use_ssl || 'false',
       user: args.username,
       pass: 'X',
       login_content: this.login.bind(this),
       application_content: this.application.bind(this),
       application_resources: [ {resource: null, on_success: this.application.bind(this)} ]
      }
    );

  },

  login: function() {
    return  '<form onsubmit="ObjectFactory.get(' + this.id + ').highrise_resource.submit_credentials(this); return false;" class="form">' +
            '<label>Enter your highrise token</label><input type="text" class="text" id="username"/>' +
            '<input type="hidden" id="password" value="X"/>' +
            '<input type="submit" value="Login" id="submit" style="float:left">' +
            '</form>'
  },

  application: function() {
    return '<div id="highrise-lookup-updating"><form id="lookup-form" onsubmit="disable_submit($(\'lookup-form\')); ObjectFactory.get(' + this.id + ').lookup(); return false;">' +
              '<input type="text" class="text" style="margin-bottom:5px;" id="h_lookup" value="' + (this.initial_name || '') + '"/>' +
              '<input type="submit" id="submit" value="Lookup user"/>' +
            '</form></div>' +
           '<div id="highrise-lookup-result" style="display:none;"></div>'
  },

  lookup: function() {
    hName = $F('h_lookup').split(' ');
    if (hName.length > 1) {
      name = hName.first() + ' ' + hName.last();
    } else {
      name = hName.first();
    }
    if (name.blank()) {
      alert("Please enter a name");
      enable_submit($('lookup-form'));
    }
    resource = 'people/search.js?term=' + escape(name);
    this.highrise_resource.request({resource: resource, on_success: this.render_highrise_lookup_result.bind(this)});
  },

  lookup_id: function(id) {
    person = this.people.detect(function(p) {return p.id == id;})
    result = this.format_person_data(person)
    $('highrise-lookup-result').update(result);
  },

  people: null,

  render_highrise_lookup_result: function(response) {
    this.people = response.people;
    if (typeof(this.people) != 'undefined') {
      if (this.people.length > 1) {
        result = '<h4>' + this.people.length + " matching people found in Highrise. Click on a person for more information</h4>";
        result += this.people.collect(function(p) {return ('<span class="link" onclick="ObjectFactory.get(' + this.id + ').lookup_id(' + p.id + ')">' + (p.first_name == null ? "" : p.first_name) + ' ' + (p.last_name == null ? "" : p.last_name) + '</span>')}.bind(this)).join('<br/>');
        result += '<br/><br/><a href="javascript:ObjectFactory.get(' + this.id + ').reset_form();">New search</a>';
      } else {
        result = this.format_person_data(this.people.first());
      }
      toggle_visibility('highrise-lookup-updating', 'highrise-lookup-result');
      $('highrise-lookup-result').update(result);
    } else {
      alert("No match found in Highrise");
      enable_submit($('lookup-form'));
    }
  },

  format_person_data: function(person) {
      host  = 'http' + (this.use_ssl ? 's' : '') + '://' + this.highrise_domain;
      result = '<h4 style="font-size:16px"><a href="' + host + '/people/' + person.id + '" target="_new">' + (person.first_name == null ? "" : person.first_name) + ' ' + (person.last_name == null ? "" : person.last_name) + '</a></h4>';
      if (person.title) result += '<p class="minimum">' + person.title + '</p>';
      if (person.background) result += '<h4>Background</h4><p class="minimum">' + person.background + '</p>';

      // Phones
      if (person.contact_data.phone_numbers) {
        items = person.contact_data.phone_numbers.collect(function(s) {return (s.number + ' <span class="sub">' + s.location.toLowerCase() + '</span>')}).join('<br/>');
        if (items) result += '<h4>Phone</h4><p class="minimum">' + items + '</p>';
      }

      // Emails
      if (person.contact_data.email_addresses) {
        items = person.contact_data.email_addresses.collect(function(s) {return '<a href="mailto:' + s.address + '">' + s.address + '</a> <span class="sub">' + s.location.toLowerCase() + '</span>'}).join('<br/>');
        if (items) result += '<h4>Email</h4><p class="minimum">' + items + '</p>' ;
      }

      // Web adresses
      if (person.contact_data.web_addresses) {
        items = person.contact_data.web_addresses.collect(function(s) {return '<a href="' + s.url + '" target="_new">' + s.url + '</a> <span class="sub">' + s.location.toLowerCase() + '</span>'}).join('<br/>');
        if (items) result += '<h4>Web</h4><p class="minimum">' + items + '</p>';
      }

      // Instant messengers
      if (person.contact_data.instant_messengers) {
        items = person.contact_data.instant_messengers.collect(function(s) {return s.address + ' <span class="sub">' + s.protocol + ', ' + s.location.toLowerCase() + '</span>'}).join('<br/>');
        if (items) result += '<h4>IM</h4><p class="minimum">' + items + '</p>';
      }

      // Addresses?
      //items = person.contact_data.addresses.collect(function(s) {return s.street + '<br/>' + s.city + ' '  + s.zip + '<br/>' + s.state + '<br/>'  + s.country + ' <span class="sub">' + s.location + '</span>'}).join('<br/><br/>');
      //if (items) result += '<h4>Addresses(s)</h4><p class="minimum">' + items + '</p>';

      result += '<h4>Add a note to this person in Highrise</h4>';
      result += '<form onsubmit="if (this[\'note[body]\'].value==\'\') {alert(\'Please enter a note\'); return false;}; ObjectFactory.get(' + this.id + ').add_ticket_to_note(this); ObjectFactory.get(' + this.id + ').highrise_resource.submit_data(this); return false;" class="form">' +
           /* Form fields to POST to Highrise */
           '<textarea name="note[body]"></textarea>' +
           '<input type="hidden" name="note[subject-id]" value="' + person.id + '"/>' +
           '<input type="hidden" name="note[subject-type]" value="Person"/>' +
           /* Request parameter to pluck from form fields for POST*/
           '<input type="hidden" name="pluck_param" value="note">' +
           /* REST resource to POST to */
           '<input type="hidden" name="resource" value="notes">' +
           /* Format for REST resource POST (json or xml) */
           '<input type="hidden" name="media_type" value="application/xml">' +
           /* Title on ticket event */
           '<input type="hidden" name="event_reference" value="Highrise requester note">' +
           /* Return values from POST to log on ticket event */
           '<input type="hidden" name="event_log" value="Body">' +
           '<input type="submit" value="Submit" id="submit">' +
           ' <a href="javascript:ObjectFactory.get(' + this.id + ').reset_form();">Lookup user</a>' +
           '</form>';
      return result;
  },

  reset_form: function() {
    enable_submit($('lookup-form'));
    toggle_visibility('highrise-lookup-result', 'highrise-lookup-updating');
  }, 

  add_ticket_to_note: function(form) {
    if(ticket_id) {
      form['note[body]'].value += ' (Zendesk ticket #' + this.ticket_nice_id + ')';
    }
  }
});

/*
  The SalesforceHelper provides a series of static templates for evaluation and a selection
  of convenience methods to call from the widget implementation.
*/
var SalesforceHelper = Class.create();

SalesforceHelper.SOAP_LOGIN = new Template(
  '<?xml version="1.0" encoding="UTF-8"?>' +
  '<SOAP-ENV:Envelope ' +
  '    xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' +
  '    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
  '    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" ' +
  '    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" ' +
  '    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' +
  '      <SOAP-ENV:Body>' +
  '        <login xmlns="urn:enterprise.soap.sforce.com">' +
  '          <username xsi:type="xsd:string">#{username}</username>' +
  '          <password xsi:type="xsd:string">#{password}</password>' +
  '        </login>' +
  '      </SOAP-ENV:Body>' +
  '</SOAP-ENV:Envelope>'
);

SalesforceHelper.SOAP_QUERY = new Template(
  '<?xml version="1.0" encoding="utf-8"?>' +
  '   <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
  '    xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
  '  <soap:Header>' +
  '    <SessionHeader xmlns="urn:enterprise.soap.sforce.com">' +
  '      <sessionId>#{session}</sessionId>' +
  '    </SessionHeader>' +
  '  </soap:Header>' +
  '  <soap:Body>' +
  '    <query xmlns="urn:enterprise.soap.sforce.com">' +
  '      <queryString>#{query}</queryString>' +
  '    </query>' +
  '  </soap:Body>' +
  '</soap:Envelope>'
);

SalesforceHelper.SELECT = new Template(
  "SELECT department, email, fax, firstname, homephone, lastname, mailingcity, " +
  "       mailingcountry, mailingpostalcode, mailingstreet, mobilephone, name, " +
  "       othercity, othercountry, otherphone, otherpostalcode, otherstate, " +
  "       otherstreet, phone, salutation, title " +
  "FROM contact WHERE email LIKE '%#{search}%' OR name LIKE '%#{search}%'"
);

SalesforceHelper.LOGIN_URL     = '/proxy/direct.xml?url=https://www.salesforce.com/services/Soap/c/13.0&SOAPAction=login&body=';
SalesforceHelper.QUERY_URL     = new Template('/proxy/direct.xml?url=#{serverUrl}&SOAPAction=query&body=#{xml}');
SalesforceHelper.SESSION_MATCH = new RegExp("/.*<sessionId>(.*)</sessionId>.*/");
SalesforceHelper.SERVER_MATCH  = new RegExp("/.*<serverUrl>(.*)</serverUrl>.*/");

SalesforceHelper.authorizationUrl = function(username, password) {
  return this.LOGIN_URL + escape(this.SOAP_LOGIN.evaluate({ username: username, password: password }));
}

SalesforceHelper.getNodeValue = function(document, nodeName) {
  if(Prototype.Browser.WebKit) {
    nodeName = nodeName.replace("sf:","");
  }

  var node = document.getElementsByTagName(nodeName);
  if(node == null || node.length == 0) {
    return null;
  }
  return node[0].childNodes[0].nodeValue;
}

SalesforceHelper.getNodeString = function(document, nodeName) {
  var value = this.getNodeValue(document, nodeName);
  if(value == null) {
    return '';
  }
  else {
    return value;
  }
}

SalesforceHelper.getErrorCode = function(document) {
   return this.getNodeValue(document, 'faultcode');
}

SalesforceHelper.getRecords = function(document) {
  var results = document.getElementsByTagName('records')
  if(results == null) {
    return new Array();
  }
  return results;
}

SalesforceHelper.renderLine = function(record, elements, template) {
  var output = '';
  for(var i=0; i<elements.length; i++) {
    output += this.getNodeString(record, elements[i]);
    if(output != '') {
      output += ' ';
    }
  }
  if(output != '') {
    return template.evaluate({ output: output });
  }
  else {
    return '';
  }
}

SalesforceHelper.renderRecord = function(record) {
  var result = '';

  result += this.renderLine(record, ['sf:Salutation', 'sf:FirstName', 'sf:LastName'], new Template("<h4 style='font-size: 16px;'>#{output}</h4>"))
  result += this.renderLine(record, ['sf:Title'], new Template("<p class='minimum'>#{output}</p>"));
  result += this.renderLine(record, ['sf:Department'], new Template("<p class='minimum'>#{output}</p>"));

  var phones = '';

  phones += this.renderLine(record, [ 'sf:Phone' ], new Template("<p class='minimum'>#{output}</p>"));
  phones += this.renderLine(record, [ 'sf:MobilePhone' ], new Template("<p class='minimum'>#{output}<span class='sub'>mobile</span></p>"));

  if(phones != '') {
    result += '<h4>Phone</h4>';
    result += phones;
  }

  result += this.renderLine(record, [ 'sf:Email' ], new Template("<p class='minimum'><a href='mailto:#{output}'>#{output}</a></p>"));

  var address = this.renderLine(record, [ 'sf:MailingStreet' ], new Template("#{output}")).replace(',', '<br />');
  if(address != '') {
    subaddress = this.renderLine(record, [ 'sf:MailingPostalCode', 'sf:MailingCity'], new Template("#{output} "));
    if(subaddress != '') {
      address += '<br /> ' + subaddress;
    }
    address += this.renderLine(record, ['sf:MailingCountry'], new Template("<br />#{output}"));
  }

  result += address;
  return result;
}

SalesforceHelper.buildQueryUrl = function(sessionId, serverUrl, input) {
  var query = this.SELECT.evaluate({ search: input });

  var xml = this.SOAP_QUERY.evaluate({ query: query, session: sessionId });
  var url = this.QUERY_URL.evaluate({ serverUrl: serverUrl, xml: escape(xml) });

  return url;
}

/*
  The salesforce widget is a mechanism for looking up person data on salesforce. The widget must be
  configured with valid user login and password, after which it can be deployed and used on pages.
*/
var SalesforceWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.username     = args.username;
    this.password     = args.password;
    this.destination  = args.destination;
    this.formInstance = null;
  },
  disableForm: function() {
    this.formInstance.button.value    = 'Looking...';
    this.formInstance.button.disabled = true;
  },
  enableForm: function() {
    this.formInstance.button.value    = 'Lookup user';
    this.formInstance.button.disabled = false;
  },
  lookup: function(submittedForm) {
    this.formInstance = submittedForm;
    this.disableForm();

    var url = SalesforceHelper.authorizationUrl(this.username, this.password);

    new Ajax.Request(url, {
      method:'post',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        this.executeAuthorizedQuery(transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Salesforce login failed. Try again in a short moment.");
        this.enableForm();
      }.bind(this)
    });
  },
  executeAuthorizedQuery: function(responseXML) {
    var sessionId = SalesforceHelper.getNodeValue(responseXML, 'sessionId');
    var serverUrl = SalesforceHelper.getNodeValue(responseXML, 'serverUrl');

    if (sessionId != null && serverUrl != null) {
      var url = SalesforceHelper.buildQueryUrl(sessionId, serverUrl, this.formInstance.email.value);
      new Ajax.Request(url, {
        method:'post',
        requestHeaders: { Accept: 'application/xml' },
        onSuccess: function(transport) {
          this.displayResult(transport.responseXML);
        }.bind(this),
        onFailure: function() {
          alert("Salesforce query failed.");
          this.enableForm();
        }.bind(this)
      });
    }
    else {
      var errorCode = SalesforceHelper.getNodeValue(responseXML, 'faultcode');

      if (errorCode == 'sf:LOGIN_MUST_USE_SECURITY_TOKEN') {
        alert("Salesforce login failed - Salesforce network access needs configuration.");
      }
      else if (errorCode == 'sf:API_DISABLED_FOR_ORG') {
        alert("Salesforce login failed - Your Salesforce edition does not support API access.");
      }
      else {
        alert("Salesforce login failed. Failed to find server or session. Code: "+errorCode);
      }
      this.enableForm();
    }
  },
  displayResult: function(responseXML)  {

    var records = SalesforceHelper.getRecords(responseXML);

    if(records.length == 0) {
      alert("No match found in Salesforce.");
      this.enableForm();
      return;
    }

    var output = '';

    for(var i=0; i<records.length; i++) {
      output += SalesforceHelper.renderRecord(records[i]);
    }

    output += '<br />';
    $(this.destination).update(output);
    this.enableForm();
  }
});



/*
 * JIRA
 *
 *
 */

var JiraHelper = {

  GET_SESSION: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' + 
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="#{url}rpc/soap/jirasoapservice-v2" xmlns:types="#{url}rpc/soap/jirasoapservice-v2/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
    '    <q1:login xmlns:q1="http://soap.rpc.jira.atlassian.com">' +
    '      <in0 xsi:type="xsd:string">#{username}</in0>' +
    '      <in1 xsi:type="xsd:string"><![CDATA[#{password}]]></in1>' +
    '    </q1:login>' + 
    '  </soap:Body>' +
    '</soap:Envelope>'
  ),

  GET_PROJECTS: new Template(
    '<?xml version="1.0" encoding="UTF-8"?>' + 
    '<SOAP-ENV:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">' +
    '  <SOAP-ENV:Body>' + 
    '    <m:getProjectsNoSchemes xmlns:m="#{url}rpc/soap/jirasoapservice-v2">' +
    '      <in0 xsi:type="xsd:string">#{sessionId}</in0>' +
    '    </m:getProjectsNoSchemes>' +
    '  </SOAP-ENV:Body>' + 
    '</SOAP-ENV:Envelope>'
  ),

  GET_ASSIGNEES: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="#{url}rpc/soap/agilossoapservice-v1" xmlns:types="#{url}rpc/soap/agilossoapservice-v1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
    '    <q1:getAssignableUsers xmlns:q1="#{url}">' +
    '      <in0 xsi:type="xsd:string">#{sessionId}</in0>' +
    '      <in1 xsi:type="xsd:string">#{projectKey}</in1>' +
    '    </q1:getAssignableUsers>' +
    '  </soap:Body>' +
    '</soap:Envelope>'
  ),

  GET_ISSUE_TYPES2: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="#{url}rpc/soap/jirasoapservice-v2" xmlns:types="#{url}rpc/soap/jirasoapservice-v2/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
    '  <q1:getIssueTypesForProject xmlns:q1="http://soap.rpc.jira.atlassian.com">' +
    '   <in0 xsi:type="xsd:string">#{sessionId}</in0>' +
    '   <in1 xsi:type="xsd:string">#{projectId}</in1>' +
    '  </q1:getIssueTypesForProject>' +
    '</soap:Body>' +
    '</soap:Envelope>'
  ),
  
  GET_ISSUE_TYPES: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="#{url}rpc/soap/jirasoapservice-v2" xmlns:types="#{url}rpc/soap/jirasoapservice-v2/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
    '<q1:getIssueTypesForProject xmlns:q1="http://soap.rpc.jira.atlassian.com">' +
    '<in0 xsi:type="xsd:string">#{sessionId}</in0>' +
    '<in1 xsi:type="xsd:string">#{projectId}</in1>' +
    '</q1:getIssueTypesForProject>' +
    '</soap:Body>' +
    '</soap:Envelope>'
  ),
  
  SUBMIT_ISSUE: new Template(
    '<?xml version="1.0" encoding="ISO-8859-1"?>' +
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="#{url}rpc/soap/jirasoapservice-v2" xmlns:types="#{url}rpc/soap/jirasoapservice-v2/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
    '    <q1:createIssue xmlns:q1="http://soap.rpc.jira.atlassian.com">' +
    '      <in0 xsi:type="xsd:string">#{sessionId}</in0>' +
    '      <in1 href="#id1" />' +
    '    </q1:createIssue>' +
    '    <q2:RemoteIssue id="id1" xsi:type="q2:RemoteIssue" xmlns:q2="http://beans.soap.rpc.jira.atlassian.com">' +
    '      <id xsi:nil="true" />' +
    '      <affectsVersions xsi:nil="true" />' +
    '      <assignee xsi:type="xsd:string">#{assignee}</assignee>' +
    '      <attachmentNames xsi:nil="true" />' +
    '      <components xsi:nil="true" />' +
    '      <created xsi:nil="true" />' +
    '      <customFieldValues xsi:nil="true" />' +
    '      <description>#{description}</description>' +
    '      <duedate xsi:nil="true" />' +
    '      <environment xsi:nil="true" />' +
    '      <fixVersions xsi:nil="true" />' +
    '      <key xsi:nil="true" />' +
    '      <priority xsi:nil="true" />' +
    '      <project xsi:type="xsd:string">#{projectKey}</project>' +
    '      <reporter xsi:nil="true" />' +
    '      <resolution xsi:nil="true" />' +
    '      <status xsi:nil="true" />' +
    '      <summary xsi:type="xsd:string">#{summary}</summary>' +
    '      <type xsi:type="xsd:string">#{type}</type>' +
    '      <updated xsi:nil="true" />' +
    '      <votes xsi:nil="true" />' +
    '      <customFieldValues href="#id2" />' +
    '    </q2:RemoteIssue>' +
    '    <soapenc:Array id="id2" xmlns:q3="http://beans.soap.rpc.jira.atlassian.com" soapenc:arrayType="q3:RemoteCustomFieldValue[1]">' +
    '      <Item href="#id3" />' +
    '    </soapenc:Array>' +
    '    <q4:RemoteCustomFieldValue id="id3" xsi:type="q4:RemoteCustomFieldValue" xmlns:q4="http://beans.soap.rpc.jira.atlassian.com">' +
    '      <customfieldId xsi:type="xsd:string">customfield_#{customFieldId}</customfieldId>' +
    '      <key xsi:nil="true" />' +
    '      <values href="#id4" />' +
    '    </q4:RemoteCustomFieldValue>' +
    '    <soapenc:Array id="id4" soapenc:arrayType="xsd:string[1]">' +
    '      <Item>#{ticketId}</Item>' +
    '    </soapenc:Array>' +
    '  </soap:Body>' +
    '</soap:Envelope>'
  ),
  
  SUBMIT_FORM: new Template(
    '<form class="form" id="jira-form-#{widgetId}">' +
    ' Submit this ticket as a new issue' + 
    ' <label>Project</label><select name="projectId" id="jiraProjectSelector" class="jiraProjectSelector" onchange="ObjectFactory.get(#{widgetId}).projectsSelector(this.options[this.selectedIndex].value)"></select>' + 
    ' <label>Type</label><select name="typeId" id="jiraTypeSelector" class="jiraTypeSelector"></select>' +
    ' <label>Assignee ID</label><select name="jiraAssigneeSelector" class="jiraAssigneeSelector" id="jiraAssigneeSelector"></select>' + 
    ' <label></label><input type="button" value="Submit" id="submit" class="jiraSubmit" onclick="ObjectFactory.get(#{widgetId}).submitIssue($(\'jiraForm-#{widgetId}\'));return false;">' +              
    '</form>'
  ),

  // EXTERNAL_ID: new Template(
  //     '<ticket><external-id>#{externalId}</external-id><additional-tags>jira</additional-tags></ticket>'
  //   ),
  
  JIRA_TAG: new Template(
    '<ticket><additional-tags>jira</additional-tags></ticket>'
  ),
  
  JIRA_ISSUE_EXTERNAL_LINK: new Template(
    '<external-link><type>JiraIssue</type><issue-id>#{issueId}</issue-id></external-link>'
  ),

  NOTICE: new Template(
    'This ticket is related to the following issue in JIRA:<br /><br /><b><a href="#{url}/browse/#{issueId}" target=_blank>#{issueId}</a></b>' + 
    '<br />&nbsp;'
  ),

  MISSING_TICKET: new Template(
    'Only available when viewing tickets.' +
    '<br />&nbsp;'
  ),

  getSessionRequest: function(username, password, url) {
    return escape(this.GET_SESSION.evaluate({ username: username, password: password, url: url }));
  },

  getProjectsRequest: function(sessionId, url) {
    return escape(this.GET_PROJECTS.evaluate({ sessionId: sessionId, url: url }));
  },
  
  getProjectsAssigneeRequest: function(sessionId, url, projectKey) {
    return escape(this.GET_ASSIGNEES.evaluate({ sessionId: sessionId, url: url, projectKey: projectKey}));
  },

  getProjectsIssueTypesRequest: function(sessionId, url, projectId) {
    return escape(this.GET_ISSUE_TYPES.evaluate({ sessionId: sessionId, url: url, projectId: projectId}));
  },

  getSubmitRequest: function(sessionId, url, assignee, type, projectKey, summary, description, ticketId, customFieldId) {
    // Already encoded, no need to encode again for UTF-8 XML
    return escape(this.SUBMIT_ISSUE.evaluate({ sessionId: sessionId, url: url, assignee: assignee, projectKey: projectKey, type: type, summary: XmlHelper.encodeString(summary), description: XmlHelper.encodeString(description), ticketId: ticketId, customFieldId: customFieldId }));
  },

  getSubmitForm: function(widgetId) {
    return (this.SUBMIT_FORM.evaluate({widgetId: widgetId}));
  },

  getInJiraNotice: function(url, issueId) {
    return (this.NOTICE.evaluate({url: url, issueId: issueId}));
  },
  
  getMissingTicketMessage: function () {
    return (this.MISSING_TICKET.evaluate({}));
  },

  setIssueIdRequest: function (issueId) {
    return this.JIRA_ISSUE_EXTERNAL_LINK.evaluate({ issueId: issueId });
  },
  
  setJiraTagRequest: function () {
    return this.JIRA_TAG.evaluate();
  },

  PROXY_URL1: new Template(
    '/proxy/direct?log=1&url=#{url}rpc/soap/jirasoapservice-v2&json=1&SOAPAction=login'
  ),

  PROXY_URL2: new Template(
    '/proxy/direct?log=1&url=#{url}rpc/soap/agilossoapservice-v1?&SOAPAction='
  ),

  getProxyUrl: function(url, body) {
    return escape(this.PROXY_URL1.evaluate({ url: url, body: body }));
  },

  getProxyUrl2: function(url, body) {
    return escape(this.PROXY_URL2.evaluate({ url: url, body: body }));
  },

  getSessionUrl: function(username, password, url) {
    doc = JiraHelper.getSessionRequest(username, password, url);
    url = JiraHelper.getProxyUrl(url, doc);
    return url;
  },

  getNodeValue: function(document, nodeName) {
    if(Prototype.Browser.WebKit) {
      nodeName = nodeName.replace("sf:","");
    }
  
    node = document.getElementsByTagName(nodeName);
    if(node == null || node.length == 0) {
      return null;
    }
    return node[0].childNodes[0].nodeValue;
  },

  getNodeString: function(document, nodeName) {
    value = this.getNodeValue(document, nodeName);

    if(value == null) {
      return '';
    }
    else {
      return value;
    }
  },

  getRecords: function(document, ns, tagName) {
    results = document.getElementsByTagName(tagName);
    if (results.length == 0)
      results = document.getElementsByTagName(ns + ':' + tagName);
      
    if(results == null) {
      return new Array();
    }
    return results;
  },
  
  populateProjects: function(contentRoot, responseXML) {
    var keys = [];
    var projects = [];

    if ($('jiraProjectSelector').options.length == 0) {
      var multiRefs = $A(XmlHelper.getRecords(responseXML, "", "multiRef"));

      multiRefs.each(function(multiRef) {
        projects.push(new JiraProject(multiRef));
      });

      projects.sort(function(a, b) {
        return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
      });

      projectSelector = contentRoot.down("select.jiraProjectSelector");
      projectSelector.appendChild(new Element("option"));

      projects.each(function(project) {
        var option = new Element("option");
        option.value = project.key;
        option.innerHTML = project.name;
        projectSelector.appendChild(option);

        keys[project.key] = project.id;
      });
    }

    return keys;
  },

  populateProjectIssueTypes: function(contentRoot, responseXML) {
    var types = [];
    var multiRefs = $A(XmlHelper.getRecords(responseXML, "", "multiRef"));

    multiRefs.each(function(multiRef) {
      types.push(new JiraType(multiRef));
    });

    types.sort(function(a, b) {
      return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
    });

    typeSelector = contentRoot.down("select.jiraTypeSelector");
    typeSelector.update("");
    typeSelector.appendChild(new Element("option"));

    types.each(function(type) {
      var option = new Element("option");
      option.value = type.id;
      option.innerHTML = type.name;
      typeSelector.appendChild(option);
    });
  },

  populateAssignees: function(contentRoot, responseXML) {
    var asignees = [];
    var multiRefs = $A(XmlHelper.getRecords(responseXML, "", "multiRef"));

    multiRefs.each(function(multiRef) {
      asignees.push(new JiraAsignee(multiRef));
    });

    asignees.sort(function(a, b) {
      return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
    });

    asigneeSelector = contentRoot.down("select.jiraAssigneeSelector");
    asigneeSelector.update("");
    asigneeSelector.appendChild(new Element("option"));

    asignees.each(function(asignee) {
      var option = new Element("option");
      option.value = asignee.name;
      option.innerHTML = asignee.fullName;
      asigneeSelector.appendChild(option);
    });
  }

}

var XmlHelper = {
  getTextContentByKey: function(document, key) {
    if (Prototype.Browser.IE)
      return document.getElementsByTagName(key)[0].childNodes[0].nodeValue;

    // all other browsers
    return document.getElementsByTagName(key)[0].textContent;
  },

  encodeString: function(st) {
    var result = st;
    
    result = result.replace(/&/g, '&amp;');
    result = result.replace(/</g, '&lt;');
    result = result.replace(/>/g, '&gt;');
    result = result.replace(/'/g, '&apos;');
    result = result.replace(/"/g, '&quot;');

    return result;
  },
  
  getRecords: function(document, ns, tagName) {
    results = document.getElementsByTagName(tagName);
    if (results.length == 0)
      results = document.getElementsByTagName(ns + ':' + tagName);
      
    if(results == null) {
      return new Array();
    }
    return results;
  }

}



var JiraProject = Class.create({
  initialize: function(xml) {
    this.key  = XmlHelper.getTextContentByKey(xml, "key");
    this.id   = XmlHelper.getTextContentByKey(xml, "id");
    this.name = XmlHelper.encodeString(XmlHelper.getTextContentByKey(xml, "name"));
  }
});

var JiraType = Class.create({
  initialize: function(xml) {
    this.id   = XmlHelper.getTextContentByKey(xml, "id");
    this.name = XmlHelper.encodeString(XmlHelper.getTextContentByKey(xml, "name"));
  }
});

var JiraAsignee = Class.create({
  initialize: function(xml) {
    this.name     = XmlHelper.encodeString(XmlHelper.getTextContentByKey(xml, "name"));
    this.fullName = XmlHelper.encodeString(XmlHelper.getTextContentByKey(xml, "fullname"));
  }
});



var JiraWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.url = args.url;
    this.username = args.username;
    this.password = args.password;
    this.formInstance = null;
    this.ticketId = args.ticketId;
    this.issueId = args.issueId || args.externalId;
    this.externalId = args.externalId;
    this.customFieldId = args.customFieldId;
    this.widgetId = args.id;
    this.subject = args.subject;
    this.description = args.description;
    this.projectIds = null;

    this.contentRoot = this.containingElement().down("div.jiraContent" + this.widgetId);

    if(!this.url.endsWith('/')) {
      this.url += '/';      
    }
    
    this.start();
  },
  
  start: function() {
    if (this.ticketId) {
      if (this.issueId) {
        this.contentRoot.update(JiraHelper.getInJiraNotice(this.url, this.issueId));
      } else {
        this.contentRoot.update(JiraHelper.getSubmitForm(this.id));

        this.proxyUrl = JiraHelper.PROXY_URL1.evaluate({ url: this.url });
        this.proxyUrl2 = JiraHelper.PROXY_URL2.evaluate({ url: this.url });

        this.getSession();
      }
    } else {
      this.contentRoot.update(JiraHelper.getMissingTicketMessage());
    }
  },
  
  getSession: function() {
    var doc = "body=" + JiraHelper.getSessionRequest(this.username, this.password, this.url);

    new Ajax.Request(this.proxyUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        this.extractSessionID(transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }.bind(this)
     });
  },
  
  extractSessionID: function(responseXML) {
    var arr = JiraHelper.getRecords(responseXML, "ns1", "loginResponse");

    if (arr.length == 1) {
      this.sessionId = JiraHelper.getNodeString(arr[0], "loginReturn");
      this.getProjects();
    } else {
      alert("Login failed");
    }
  },
  
  getProjects: function() {
    var doc = "body=" + JiraHelper.getProjectsRequest(this.sessionId, this.url);

    new Ajax.Request(this.proxyUrl, {
      method:'post',
      postBody: doc, 
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        this.projectIds = JiraHelper.populateProjects(this.contentRoot, transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed to get projects");
      }.bind(this)
     });
  },
  
  projectsSelector: function(projectkey) {
    var doc = "body=" + JiraHelper.getProjectsAssigneeRequest(this.sessionId, this.url, projectkey);

    new Ajax.Request(this.proxyUrl2 , {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        JiraHelper.populateAssignees(this.contentRoot, transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed to get projects");
      }.bind(this)
    });

    doc = "body=" + JiraHelper.getProjectsIssueTypesRequest(this.sessionId, this.url, this.projectIds[projectkey] );

    new Ajax.Request(this.proxyUrl , {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        JiraHelper.populateProjectIssueTypes(this.contentRoot, transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed to get projects");
      }.bind(this)
    });
  },
  
  submitIssue: function(form) {
		var url = "/tickets/" + this.ticketId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
				var subject = XmlHelper.getTextContentByKey(transport.responseXML, "subject");
				this.submitIssue2(subject, this.description)
      }.bind(this),
      onFailure: function() {
        alert("Failed to submit issue.");
      }.bind(this)
    });
    return false;
  },

  submitIssue2: function(subject, description) {
    var summary = subject;

    if (summary == '')
      summary = 'Ticket #' + this.ticketId;

    var doc = "body=" + JiraHelper.getSubmitRequest(this.sessionId, this.url,
      this.contentRoot.down('select.jiraAssigneeSelector').getValue(),
      this.contentRoot.down('select.jiraTypeSelector').getValue(),
      this.contentRoot.down("select.jiraProjectSelector").getValue(),
      summary, description, this.ticketId, this.customFieldId);

    new Ajax.Request(this.proxyUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) { 
        this.setIssueId(transport.responseXML);
        this.setJiraTag();
        this.addTag("jira");
      }.bind(this),
      onFailure: function() {
        alert("Failed to submit issue.");
      }.bind(this)
    });
    return false;
  },
  
  addTag: function(tag) {
	  if(typeof(ticketTagField) != 'undefined') {
	    ticketTagField.addEntry("jira", "jira");
	  };
  },
  
  setIssueId: function(responseXML) {
    var issues = JiraHelper.getRecords(responseXML, "", "multiRef");

    if (issues.length > 0) {
      var key = XmlHelper.getTextContentByKey(issues[0],'key');
      var doc = JiraHelper.setIssueIdRequest(key);
      var url = '/tickets/' + this.ticketId + '/external_links.xml?';

      new Ajax.Request(url, {
        contentType: 'application/xml',
        method:'post',
        encoding: '',
        postBody: doc,
        onSuccess: function(transport) { this.contentRoot.update(JiraHelper.getInJiraNotice(this.url, key)); }.bind(this),
        onFailure: function() { alert('Failed to set JIRA issue ID.'); }
      });
    } else {
      var faultString = XmlHelper.getTextContentByKey(responseXML, 'faultstring');
      alert("Failed to retrieve JIRA issue key.\n\n(" + faultString + ")");
    }
  },

  setJiraTag: function(responseXML) {
    var doc = JiraHelper.setJiraTagRequest();
    var url = '/tickets/' + this.ticketId + '.xml?_method=put';

    new Ajax.Request(url, {
      contentType: 'application/xml',
      method:'put',
      encoding: '',
      postBody: doc,
      //onSuccess: function(transport) { }.bind(this),
      onFailure: function() { alert('Failed to set JIRA tag.'); }
    });
  }
});




/*
 *
 * TactileCRM
 *
 */
var TactileHelper = {
  PROXY_URL: new Template(
    '/proxy/direct?log=1&url=#{url}'
  ),

  QUERYSTRING_PEOPLE: new Template(
    'people/?api_token=#{apiToken}&email=#{email}'
  ),

  QUERYSTRING_CONTACTMETHODS: new Template(
    'people/contact_methods/?api_token=#{apiToken}&id=#{id}'
  ),

  QUERYSTRING_PERSON: new Template(
    'people/view/?api_token=#{apiToken}&id=#{id}'
  ),

  getQueryURL: function(url) {
    return TactileHelper.PROXY_URL.evaluate({url: url});
  },

  getPeopleQueryString: function(apiToken, email) {
    return TactileHelper.QUERYSTRING_PEOPLE.evaluate({ apiToken: apiToken, email: email });
  },

  getContactMethodsQueryString: function(apiToken, id) {
    return TactileHelper.QUERYSTRING_CONTACTMETHODS.evaluate({ apiToken: apiToken, id: id });
  },
  
  getPersonQueryString: function(apiToken, id) {
    return TactileHelper.QUERYSTRING_PERSON.evaluate({ apiToken: apiToken, id: id });
  },
  
  renderLine: function(values, template) {
    var output = '';

    for(var i=0; i<values.length; i++) {
      if (values[i] > "") {
        if (output != '')
          output += ' ';
        output += values[i];
      }
    }

    return output > '' ? template.evaluate({ output: output }) : '';
  },

  renderRecord: function(record, contact) {
    var output = '';
    var companyInfo = '';
    var address = '';
    var template = new Template("#{output}<br />");

    output += this.renderLine([record.title, record.name], new Template("<h4 style='font-size: 16px;'>#{output}</h4>")); 

    companyInfo += this.renderLine([record.jobtitle], template);
    companyInfo += this.renderLine([record.organisation], template);
    output += this.renderLine([companyInfo], new Template("<p class=\"minimum\">#{output}</p>"));

    address += this.renderLine([record.street1], template);
    address += this.renderLine([record.street2], template);
    address += this.renderLine([record.street3], template);
    address += this.renderLine([record.town], template);
    address += this.renderLine([record.county], template);
    address += this.renderLine([record.postcode], template);
    address += this.renderLine([record.country], template);
    output += this.renderLine([address], new Template("<p class=\"minimum\">#{output}</p>"));

    output += this.renderContactMethods(contact, "T", "Telephone");
    output += this.renderContactMethods(contact, "M", "Mobile");
    output += this.renderContactMethods(contact, "E", "Email");
    
    output += this.renderLine([record.description], new Template("<h4>Description</h4>#{output}<br />"));

    return output + "<br />";
  },

  renderContactMethods: function(contact, type, label) {
    var output = '';
    var template = new Template("#{value} <span class=\"sub\">#{name}</span><br />");
    
    for (var i=0; i<contact.contact_methods.length; i++) {
      var method = contact.contact_methods[i];
      if (method.type == type) {
        output += template.evaluate({ value: method.contact, name: method.name});
      }
    }
    
    return output > "" ? "<h4>" + label + "</h4>" + output : "";
  },
  
  renderList: function(people, id) {
    var result = '';

    result = '<h4>' + people.length + " matching people found in TactileCRM. Click on a person for more information</h4>";
    result += people.collect(function(p) {return ('<span class="link" onclick="ObjectFactory.get(' + id + ').lookupPerson(' + p.id + ')">' + p.name + '</span>')}.bind(this)).join('<br/>')+"<br />&nbsp;";

    return result;
  }
};

var TactileWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.widgetId     = args.id;
    this.url          = args.url;
    this.apiToken     = args.apiToken;
    this.formInstance = null;
    this.contentRoot  = this.containingElement().down("div.tactileContent" + this.widgetId);

    if(!this.url.endsWith('/')) {
      this.url += '/';      
    }
  },

  lookup: function(submittedForm) {
    var email = submittedForm.down('input.email').value;
    var url = TactileHelper.getQueryURL(this.url) + escape(TactileHelper.getPeopleQueryString(this.apiToken, email));

    new Ajax.Request(url, {
      method:'get',
      encoding: '',
      onSuccess: function(transport) {
        this.showList(transport.responseText.evalJSON());
      }.bind(this),
      onFailure: function() {
        alert("Failed to lookup.");
      }.bind(this)
    });
    return false;
  },
  
  showList: function(result) {
    if (result.status == "error") {
      alert("Error: Unable to login to TactileCRM");
      return false;
    }     
    if (result.people.length == 0) {
      alert("No match found in TactileCRM");
    } else {
      if (result.people.length > 1) {
        this.contentRoot.update(TactileHelper.renderList(result.people, this.id));
      } else {
        this.lookupPerson(result.people[0].id)
      }
    }
  },
  
  lookupPerson: function(id) {
    var url = TactileHelper.getQueryURL(this.url) + escape(TactileHelper.getPersonQueryString(this.apiToken, id));

    new Ajax.Request(url, {
      method:'get',
      encoding: '',
      onSuccess: function(transport) {
        this.result1 = transport.responseText.evalJSON();
        this.lookupContactMethods(id);
      }.bind(this),
      onFailure: function() {
        alert("Failed to lookup.");
      }.bind(this)
    });
  },
  
  lookupContactMethods: function(id) {
    var url = TactileHelper.getQueryURL(this.url) + escape(TactileHelper.getContactMethodsQueryString(this.apiToken, id));
    new Ajax.Request(url, {
      method:'get',
      encoding: '',
      onSuccess: function(transport) {
        this.result2 = transport.responseText.evalJSON();
        this.show();
      }.bind(this),
      onFailure: function() {
        alert("Failed to lookup.");
      }.bind(this)
    });
  },
  
  show: function() {
    var output = TactileHelper.renderRecord(this.result1.person, this.result2);
    this.contentRoot.update(output);
  }
});




/*
 * Paglo widget
 *
 *
 */
var PagloHelper = {
	SEARCH_FORM: new Template(
		'<div class="asset"></div>' +
		'<div class="software"></div>' +
    '<form class="form" id="paglo-form-#{widgetId}">' +
    ' <label>Search</label><input type="text" name="token" id="token" value="#{name}"/	>' +
    ' <br/><input type="submit" id="submit" value="Search" onclick="ObjectFactory.get(#{widgetId}).doSearch($(\'paglo-form-#{widgetId}\').token.value);return false;">' +
    '</form>'
	),

	getSearchForm: function(widgetId, name) {
		return PagloHelper.SEARCH_FORM.evaluate({ widgetId: widgetId, name: name });
	},

	QUERY: new Template(
		'select nvl(system/dns_name, first(interface/inet/ip_address)) as name, ' +
		'first(interface/inet/ip_address) as ip_address from /network/device ' +
		'where system/dns_name like \'%#{token}%\' or interface/inet/ip_address = \'#{token}%\' ' +
		'or wmi/win32_computersystem/username = \'#{token}\' or wmi/win32_computersystem/username like \'%#{token2}\' ' +
		'order by 1 limit 10'
	),

	getQuery: function(token) {
		return PagloHelper.QUERY.evaluate({ token: token, token2: '\\' + token });
	},

	SOFTWARE_QUERY: new Template(
		'select Name, Version from /network/device[system/dns_name = \'#{name}\']/wmi/win32_product order by 1,2'
	),

	getSoftwareQuery: function(name) {
		return PagloHelper.SOFTWARE_QUERY.evaluate({ name: name });
	},

	URL: new Template(
		'/proxy/direct?url=https://api.gotomanage.com/api&contenttype=application/x-www-form-urlencoded&body=#{body}'
	),

	getURL: function(body) {
		return PagloHelper.URL.evaluate({ body: escape(body) });
	},

	doc: new Template(
			'method=paglo.query&v=1.0&api_key=#{apiKey}&query=#{query}'
	),

	getDoc: function(apiKey, query) {
		return PagloHelper.doc.evaluate({ apiKey: apiKey, query: query });
	},

	ITEM: new Template(
		'<span class="link" onClick="ObjectFactory.get(#{widgetId}).#{functionName}(\'#{name}\')">#{name}</span><br />'
	),

	renderItem: function(name, functionName, widgetId) {
		return PagloHelper.ITEM.evaluate({ name: name, functionName: functionName, widgetId: widgetId });
	},

	LIST_TEMPLATE: new Template(
		'<b>#{title}</b><br /><br />#{content}'
	),

	getList: function(title, content) {
		return PagloHelper.LIST_TEMPLATE.evaluate({ title: title, content: content });
	},

	SOFTWARE_SELECTION: new Template(
		'<b>Select software package:</b><br /><select id="paglo_software" class="software" onChange="ObjectFactory.get(#{widgetId}).selectSoftware(this.options[this.selectedIndex].value)"></select>'
	),

	getSoftwareSelection: function(widgetId) {
		return PagloHelper.SOFTWARE_SELECTION.evaluate({ widgetId: widgetId });
	},

	ASSET: new Template(
		'select system/dns_name, interface/inet/ip_address, nvl(system_profile/spsoftwaredatatype/os_version, os/version, ' +
		'wmi/win32_operatingsystem/caption || \' \' || wmi/win32_operatingsystem/csdversion) as os, ' +
		'nvl(system/class, system/computed_class) as class, nvl(system/vendor, ' +
		'wmi/win32_computersystemproduct/vendor, system/computed_vendor) as vendor, ' +
		'nvl(system/model, wmi/win32_computersystemproduct/name, system/computed_model) as model, ' +
		'nvl(wmi/win32_computersystem/username, system_profile/spsoftwaredatatype/user_name) as username ' +
		'from /network/device where system/(dns_name = \'#{name}\')'
	),

	getAssetQuery: function(name) {
		return PagloHelper.ASSET.evaluate({ name: name });
	},

	ASSET_INFO_SECTION: new Template(
		'<b>#{title}:</b><br /> #{value}<br /><br />'
	),

	getAssetInfoSection: function(title, value) {
		return (value != '' ? PagloHelper.ASSET_INFO_SECTION.evaluate({ title: title, value: value }) : ''); 
	},

	ASSET_HEADER: new Template(
			'<a href="https://app.paglo.com/app/inventory/lookup?key=system/dns_name&value=#{name}" target="_new"><img src="/images/widgets/paglo/asset.png" />&nbsp;&nbsp;<b>#{name}</b></a> <br /><br />'
	),

	getAssetHeader: function(name) {
		return PagloHelper.ASSET_HEADER.evaluate({ name: name});
	},

	getAssetInfo: function(records, name) {
		var output = '';
		var reg = new RegExp("\\S");

		for (var i=0; i<records.length; i++)
			if (records[i].attributes.length > 0 && records[i].childNodes.length > 0)
 				if (records[i].getAttribute('name') == name && records[i].firstChild.nodeValue.match(reg)) 
					output += (output != '' ? '<br />' : '') + records[i].firstChild.nodeValue;

		return output;
	},

	renderAssetInfo: function(xml) {
		var output = '';
		var records = XmlHelper.getRecords(xml, "", "value");

		output += PagloHelper.getAssetHeader(PagloHelper.getAssetInfo(records, "system/dns_name"));
		output += PagloHelper.getAssetInfoSection("Class", PagloHelper.getAssetInfo(records, "class"));
		output += PagloHelper.getAssetInfoSection("Vendor", PagloHelper.getAssetInfo(records, "vendor"));
		output += PagloHelper.getAssetInfoSection("Model", PagloHelper.getAssetInfo(records, "model"));
		output += PagloHelper.getAssetInfoSection("Operating system", PagloHelper.getAssetInfo(records, "os"));
		output += PagloHelper.getAssetInfoSection("Username", PagloHelper.getAssetInfo(records, "username"));
		output += PagloHelper.getAssetInfoSection("Network", PagloHelper.getAssetInfo(records, "interface/inet/ip_address"));

		return output;
	}
}


var PagloWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.apiKey = args.apikey;
    this.formInstance = null;
    this.widgetId = args.id;
		this.isEmpty = args.isEmpty;
		this.hardwareFieldId = args.hardwareFieldId;
		this.softwareFieldId = args.softwareFieldId;
		this.externalId = args.externalId;
		this.email = args.email;
    this.contentRoot = this.containingElement().down("div.paglo-content");

		this.startup();
  },

	startup: function() {
		// check if the custom fields are in place
		if (!($('ticket_fields_' + this.hardwareFieldId) && $('ticket_fields_' + this.softwareFieldId))) {
			this.contentRoot.update('Not a ticket, or missing custom fields.');
			return;
		}
		this.contentRoot.update( PagloHelper.getSearchForm(this.widgetId, ''));
		
		// if the hardware ID is present in the custom field, retrieve hardware info
		var name = $('ticket_fields_' + this.hardwareFieldId).value;
		if (name) {
			this.getAsset(name);	
		} else {
			// if not hardware name found, use externalID
			name = (name ? name : this.externalId);

			// if no externalId found, use mailbox name from email
			if (!name && this.email) {
				var reg = /(\S*)@\S*/;
				var arr = reg.exec(this.email);
				if (arr.length > 1)
					name = arr[1];
			} 
			if (name)
				this.doSearch(name);	
		}

		this.contentRoot.down('form').token.value = name;
	},

	doSearch: function(token) {
		var query = PagloHelper.getQuery(token);
		var doc = PagloHelper.getDoc(this.apiKey, query);
		var url = PagloHelper.getURL(doc);

		this.contentRoot.down("div.asset").update('Searching...');
		this.contentRoot.down("div.software").update('');

    new Ajax.Request(url, {
      method:'post',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
				this.showResults(transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }
    });
	},

	showResults: function(xml) {
		var output = '';

		// loop records
		var records = XmlHelper.getRecords(xml, '', 'tree');
		if (records.length > 0) {
			for (var i=0; i<records.length; i++) {

				// loop values
				var arr = new Array();
				var values = XmlHelper.getRecords(records[i], '','value');
				for (var u=0; u<values.length; u++) {
					arr[values[u].getAttribute('name')] = values[u].firstChild.nodeValue;
				}
				if (arr["name"])
					output += PagloHelper.renderItem(arr["name"], "selectAsset", this.widgetId);
			}
			this.contentRoot.down("div.asset").update(PagloHelper.getList('Select hardware asset:', output));
			this.contentRoot.down("div.software").update('');
		} else {
			this.contentRoot.down("div.asset").update('Nothing found');
		}
	},

	selectAsset: function(name) {
		if (name != '') {
			$('ticket_fields_' + this.hardwareFieldId).value = name;
			$('ticket_fields_' + this.softwareFieldId).value = '';
			this.getAsset(name);
			this.getSoftware(name);
		}
	},

	getSoftware: function(name) {
		var query = PagloHelper.getSoftwareQuery(name);
		var doc = PagloHelper.getDoc(this.apiKey, query);
		var url = PagloHelper.getURL(doc);

    new Ajax.Request(url, {
      method:'post',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
				this.showSoftware(transport.responseXML);
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }
    });
	},

	showSoftware: function(xml) {
		var output = '';
		var ddlSoftware;

		var records = XmlHelper.getRecords(xml, "", "tree");

		if (records.length > 0) {
			this.contentRoot.down("div.software").update(PagloHelper.getSoftwareSelection(this.widgetId));
			ddlSoftware = this.contentRoot.down("select.software");
			ddlSoftware.appendChild(new Element("option"));

			for (var i=0; i<records.length; i++) {
				var values = XmlHelper.getRecords(records[i], "", "value");
				for (var u=0; u<values.length; u++) {
					if (values[u].getAttribute('name') == 'Name') {
						var option = new Element("option");
						option.value=values[u].firstChild.nodeValue;
						option.innerHTML = option.value;
						ddlSoftware.appendChild(option);
					}
				}
			}
		} else {
			this.contentRoot.down("div.software").update('');
		}
	},

	selectSoftware: function(name) {
		$('ticket_fields_' + this.softwareFieldId).value = name;
	},

	getAsset: function(name) {
		var query = PagloHelper.getAssetQuery(name);
		var doc = PagloHelper.getDoc(this.apiKey, query);
		var url = PagloHelper.getURL(doc);

		this.contentRoot.down("div.asset").update('Retrieving info...');

    new Ajax.Request(url, {
      method:'post',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
				this.contentRoot.down("div.asset").update(PagloHelper.renderAssetInfo(transport.responseXML));
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }
    });
	}

});



/*
*
* Contactology Widget
*
*/

var ContactologyHelper = {
  GET_ACTIVE_LISTS: new Template(
		'<?xml version="1.0" encoding="utf-8"?>' + 
		'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'    <tns:GetActiveLists>' + 
		'      <sortDir xsi:type="xsd:string">U</sortDir>' + 
		'      <sortID xsi:type="xsd:string">Name</sortID>' + 
		'      <startRecord xsi:type="xsd:int">0</startRecord>' +
		'      <pageSize xsi:type="xsd:int">0</pageSize>' +
		'    </tns:GetActiveLists>' +
		'  </soap:Body>' +
		'</soap:Envelope>'
  ),
/*
	GET_ADDRESS_SUBSCRIPTIONS: new Template(
		'<?xml version="1.0" encoding="utf-8"?>' +
		'  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'    <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'      <tns:GetAddressSubscriptions>' +
		'        <email xsi:type="xsd:string">#{email}</email>' + 
		'      </tns:GetAddressSubscriptions>' +
		'    </soap:Body>' +
		'</soap:Envelope>'
	),
*/
	GET_CONTACT_SUBSCRIPTIONS: new Template(
		'<?xml version="1.0" encoding="utf-8"?>' +
		'  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'    <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'      <tns:GetContactSubscriptions>' +
		'        <email xsi:type="xsd:string">#{email}</email>' + 
		'      </tns:GetContactSubscriptions>' +
		'    </soap:Body>' +
		'</soap:Envelope>'
	),
	
	SUBSCRIBE_TO_LIST: new Template(
		'<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'    <tns:SubscribeToList>' +
		'      <email xsi:type="xsd:string">#{email}</email>' +
		'      <listId xsi:type="xsd:int">#{id}</listId>' +
		'    </tns:SubscribeToList>' +
		'  </soap:Body>' +
		'</soap:Envelope>'
	),
	
	UNSUBSCRIBE_FROM_LIST: new Template(
		'<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'    <tns:UnsubscribeFromList>' +
		'      <email xsi:type="xsd:string">#{email}</email>' +
		'      <listId xsi:type="xsd:int">#{id}</listId>' +
		'    </tns:UnsubscribeFromList>' +
		'  </soap:Body>' +
		'</soap:Envelope>'
	),
	
	GET_CONTACT: new Template(
		'<?xml version="1.0" encoding="utf-8"?>' +
		'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'    <tns:GetContactByEmail>' +
		'      <emailAddress xsi:type="xsd:string">#{email}</emailAddress>' +
		'    </tns:GetContactByEmail>' +
		'  </soap:Body>' +
		'</soap:Envelope>'
	),
	
	REACTIVATE: new Template(
		'<?xml version="1.0" encoding="utf-8"?>' +
		'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="http://soapserver.emailcampaigns.net/1.1/" xmlns:types="http://soapserver.emailcampaigns.net/1.1/encodedTypes" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
		'  <soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
		'    <tns:UpdateContact>' +
		'      <addressId xsi:type="xsd:int">#{id}</addressId>' +
		'      <emailAddress xsi:type="xsd:string" />' +
		'      <status xsi:type="xsd:int">0</status>' +
		'    </tns:UpdateContact>' +
		'  </soap:Body>' +
		'</soap:Envelope>'
	),

  PROXY_URL: new Template(
    '/proxy/direct?basic_user=#{username}&basic_pass=#{password}&log=1&url=http://soapserver.emailcampaigns.net/1.1/index.php&SOAPAction=http://soapserver.emailcampaigns.net/1.1/index.php/#{action}'
  ),

  getActiveListsRequest: function() {
    return ContactologyHelper.GET_ACTIVE_LISTS.evaluate({ })
  },
/*
	getAddressSubscriptionsRequest: function(email) {
		return ContactologyHelper.GET_ADDRESS_SUBSCRIPTIONS.evaluate({ email: email});
	},
*/
	getContactSubscriptionsRequest: function(email) {
		return ContactologyHelper.GET_CONTACT_SUBSCRIPTIONS.evaluate({ email: email});
	},
	
	getSubscribeToListRequest: function(email, id) {
		return ContactologyHelper.SUBSCRIBE_TO_LIST.evaluate({ email: email, id: id });
	},

	getGetContactRequest: function(email) {
		return ContactologyHelper.GET_CONTACT.evaluate({ email: email });
	},
	
	getReactivateRequest: function(id) {
		return ContactologyHelper.REACTIVATE.evaluate({ id: id });
	},

	getUnsubscribeFromListRequest: function(email, id) {
		return ContactologyHelper.UNSUBSCRIBE_FROM_LIST.evaluate({ email: email, id: id });
	},

	getProxyURL: function(username, password, action) {
    return this.PROXY_URL.evaluate({ username: username, password: password, action: action })
	},

	STATUS_SECTION: new Template(
		'<b>Status:</b><div class="contact_status">#{status}</div><br />'
	),
		
	LIST_SECTION: new Template(
		'<b>#{header}</b><br />#{items}<br />'
	),

	LIST_ELEMENT: new Template (
		'<input type="checkbox" style="vertical-align: top;" value="#{id}" #{checked} #{disabled} onClick="ObjectFactory.get(#{widgetId}).changeSubscription(this);"> #{name} <span class="sub">#{status}</span><br />'
	),
	
	REACTIVATE_LINK: new Template (
		'<a href="javascript:ObjectFactory.get(#{widgetId}).reactivate();">Click to reactivate</a><br/><br/>'
	),
	
	renderView: function(responseXML, newSubscriber, widgetId) {
		var output = '';
		var active = true;
		
		var addressSubscriptions = XmlHelper.getRecords(responseXML, "", "item");

		if (newSubscriber)
			status_code = "NEW";
			
		else {
			var status_code = ContactologyHelper.getStatus(responseXML);
			active = (status_code == 'GLOBALY_UNSUBSCRIBED' || status_code == 'GLOBALLY_UNSUBSCRIBED' || status_code == 'BOUNCED' || status_code == 'DELETED') ? false : true;
		}
		
		output += ContactologyHelper.renderStatus(status_code, newSubscriber);
		
		if (status_code == 'BOUNCED')
			output += ContactologyHelper.REACTIVATE_LINK.evaluate({ widgetId: widgetId });
			
		output += ContactologyHelper.renderListsByType(addressSubscriptions, "INTERNAL", "Internal", newSubscriber, widgetId, active);
		output += ContactologyHelper.renderListsByType(addressSubscriptions, "PUBLIC", "Public", newSubscriber, widgetId, active);
		output += ContactologyHelper.renderListsByType(addressSubscriptions, "PRIVATE", "Private", newSubscriber, widgetId, active);
		return output;
	},
	
	renderListsByType: function(addressSubscriptions, typeName, typeLabel, newSubscriber, widgetId, enabled) {
		var output = '';

		for (var i=0; i<addressSubscriptions.length; i++) {	
			if (XmlHelper.getTextContentByKey(addressSubscriptions[i], newSubscriber? "type_label" : "type").toUpperCase() == typeName) {
				var name = XmlHelper.getTextContentByKey(addressSubscriptions[i], newSubscriber? "list_name" : "name");
				var id = XmlHelper.getTextContentByKey(addressSubscriptions[i], newSubscriber? "list_id" : "id");
				var status = newSubscriber ? "NONE" : XmlHelper.getTextContentByKey(addressSubscriptions[i], "subscription_status");
				var checked = '';
				var substatus = '';
				
				if (status == "SUBSCRIBED" || status == "AWAITING_CONFIRMATION")
					checked = "checked";
					
				if (status == "AWAITING_CONFIRMATION")
					substatus = "pending";

				output += ContactologyHelper.LIST_ELEMENT.evaluate({ name: name, id: id, checked: checked, widgetId: widgetId, status: substatus, disabled: (enabled? '' : 'DISABLED') });
			}
		}

		if (output > "")
			return ContactologyHelper.LIST_SECTION.evaluate({ header: typeLabel, items: output });
		else
			return "";
	},
	
	renderStatus: function(status, newSubscriber) {
		if (newSubscriber)
			return ContactologyHelper.STATUS_SECTION.evaluate({ status: 'NEW' });		
			
		return ContactologyHelper.STATUS_SECTION.evaluate({ status: status });
	},
	
	getStatus: function(responseXML) {
		var element = responseXML.getElementsByTagName('return')[0];
		var status = XmlHelper.getTextContentByKey(element, "contact_status");
		return status;
	},
	
	errorFree: function(xml) {
		var element = xml.getElementsByTagName('faultstring');
		return(element.length > 0 ? false : true);
	}
};

var ContactologyWidget = Class.create(Widget, {

  initialize: function($super, args) {
    $super(args);
    this.widgetId 		= args.id;
    this.username	    = args.username;
    this.password     = args.password;
		this.email				= args.email;
    this.formInstance = null;
    this.contentRoot 	= this.containingElement().down("div.contactology-content" + this.widgetId);
		this.lastChange		= null;
		this.contactId		= null;
		this.startup();
  },

	startup: function() {
		if (this.email == null)
			this.contentRoot.update('No requester found');
		else
			this.getSubscriptions();
	},

	getSubscriptions: function(change) {
		var doc = "body=" + ContactologyHelper.getContactSubscriptionsRequest(this.email);
		var url = ContactologyHelper.getProxyURL( this.username, this.password, 'GetContactSubscriptions' );

    new Ajax.Request(url, {
      method:'post',
      encoding: '',
			postBody: doc, 
      onSuccess: function(transport) {	
				if (XmlHelper.getRecords(transport.responseXML, "SOAP-ENV", "Fault").length > 0)
					this.getActiveLists();
				else if (change == this.lastChange)
					this.contentRoot.update(ContactologyHelper.renderView(transport.responseXML, false, this.widgetId));
      }.bind(this),
      onFailure: function() {
        alert("Failed to lookup.");
      }.bind(this)
    });
	},
	
	getActiveLists: function() {
		var doc = "body=" + ContactologyHelper.getActiveListsRequest();
		var url = ContactologyHelper.getProxyURL(this.username, this.password, 'GetActiveLists');
		
    new Ajax.Request(url, {
      method:'post',
      encoding: '',
			postBody: doc, 
      onSuccess: function(transport) {
				this.contentRoot.update(ContactologyHelper.renderView(transport.responseXML, true, this.widgetId));
      }.bind(this),
      onFailure: function() {
        alert("Failed to lookup.");
      }.bind(this)
    });
	},
		
	changeSubscription: function(item) {
		var doc = '';
		var action = '';
		var checked = item.checked;
		
		item.disabled = true;
		this.lastChange = item.value;
		
		if (checked) {
			doc = "body=" + ContactologyHelper.SUBSCRIBE_TO_LIST.evaluate({ email: this.email, id: item.value });
			action = 'SubscribeToList';
		} else {
			doc = "body=" + ContactologyHelper.UNSUBSCRIBE_FROM_LIST.evaluate({ email: this.email, id: item.value });
			action = 'UnsubscribeFromList';
		}
		
		var url = ContactologyHelper.getProxyURL(this.username, this.password, action);

		this.showStatus("UPDATING...");
    new Ajax.Request(url, {
      method:'post',
      encoding: '',
			postBody: doc, 
      onSuccess: function(transport) {
				if (ContactologyHelper.errorFree(transport.responseXML)) {
					if (XmlHelper.getTextContentByKey(transport.responseXML, "return") != 1) {
					}
					if (this.lastChange == item.value)
						this.getSubscriptions(item.value);
				} else {
					this.changeFailed(item, checked);
					return;
					
				}
      }.bind(this),
      onFailure: function() {
				alert("Failure!");
				this.changeFailed(item, checked);
      }.bind(this)
    });
	},
	
	reactivate: function() {
		var doc = "body=" + ContactologyHelper.getGetContactRequest(this.email);
		this.showStatus("UPDATING...");
		
    new Ajax.Request(ContactologyHelper.getProxyURL(this.username, this.password, "GetContactByEmail"), {
      method:'post',
      encoding: '',
			postBody: doc, 
      onSuccess: function(transport) {
				var element = transport.responseXML.getElementsByTagName('return')[0];
				var id = XmlHelper.getTextContentByKey(element, "ID");
				var doc2 = "body=" + ContactologyHelper.getReactivateRequest(id);
		    new Ajax.Request(ContactologyHelper.getProxyURL(this.username, this.password, "UpdateContact"), {
		      method:'post',
		      encoding: '',
					postBody: doc2, 
		      onSuccess: function(transport) {
						this.lastChange = null;
						this.getSubscriptions(null);
		      }.bind(this),
		      onFailure: function() {
						this.refreshStatus();
						alert("Failed to reactivate");
		      }.bind(this)
		    });
      }.bind(this),
      onFailure: function() {
				this.showStatus("UNKNOWN");
				alert("Failed to reactivate");
      }.bind(this)
    });
	},	
	
	showStatus: function(status) {
		this.containingElement().down("div.contact_status").update(status);
	},
	
	refreshStatus: function() {
		var doc = "body=" + ContactologyHelper.getGetContactRequest(this.email);
    new Ajax.Request(ContactologyHelper.getProxyURL(this.username, this.password, "GetContactByEmail"), {
      method:'post',
      encoding: '',
			postBody: doc, 
      onSuccess: function(transport) {
				var element = transport.responseXML.getElementsByTagName('return')[0];
				var status = XmlHelper.getTextContentByKey(element, "StatusLabel");
				this.showStatus(status.toUpperCase());
      }.bind(this),
      onFailure: function() {
				this.showStatus("ERROR");
      }.bind(this)
    });
	},
	
	changeFailed: function(item, checked) {
    alert("Failed to change subscription.");
		item.checked = !checked;
		item.disabled = false;
		this.refreshStatus();
	}

});


/*
 *
 * Blue Mango ScreenSteps Live Widget
 *
 */
var ScreenStepsLiveWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.widgetId     = args.id;
    this.anchor       = args.anchor;
    this.domainname   = args.domainname;
    this.username     = args.username;
    this.password     = args.password;
    this.use_ssl      = args.use_ssl;
    this.space_id     = args.space_id;
    this.ticket_id    = args.ticket_id;
    this.include_login_in_url = args.include_login_in_url;
    this.screenstepslive_resource = new Zendesk.Resource(
      {		
     	  anchor: this.anchor,
       	domain: this.domainname || '',
       	user: this.username || '',
       	pass: this.password || '',
       	use_ssl: this.use_ssl || 'false',
       	space_id: this.space_id || null,
       	include_login_in_url : this.include_login_in_url || false,
       	enable_resource_cache: 'false',
       	show_logout: false,
       	login_content: this.application.bind(this),
       	application_content: this.application.bind(this), 
       	application_resources: [ {resource: 'spaces', media_type: "application/xml", on_success: this.spaces_selector.bind(this) } ]
     	}
    );
  },
   
  application: function() {
 	  text = '<form id="lookup-form" onsubmit="disable_submit($(\'lookup-form\')); ObjectFactory.get(' + this.id + ').lookup(); return false;">'
 	  if (this.space_id == "" || this.space_id == null) {
 		  text += '<label style="width:50px;display:block;">Space: </label><select style="margin-bottom:5px;width:175px;" name="request[space_id]" id="screenstepslive-form-spaces"></select><br />'
 	  } else {
 		  text += '<input type="hidden" name="request[space_id]" id="screenstepslive-form-spaces" value="' + this.space_id + '">'
 	  }

 	  text +=  '<label style="width:50px;display:block;">Search: </label><input type="text" style="margin:5px 5px 5px 0;width:175px;" name="request[text]" id="sslive_search_text"/>' +		 				 
 	           '<br /><input type="submit" value="Submit" id="submit">'

 	  text += '</form>' + '<div id="screensteps_live_results"></div>'
 	  return text				
  },

  lookup: function() {
   	text = $F('sslive_search_text');
   	space_id = $F('screenstepslive-form-spaces');
 	  space_selected_index = $('screenstepslive-form-spaces').selectedIndex;
   	resource = 'spaces/'+ $F('screenstepslive-form-spaces') + '/searches.xml?text=' + escape(text);
   	this.screenstepslive_resource.request({resource: resource, on_success: this.search_results.bind(this)});
 	  enable_submit($('lookup-form'));
 	  if (this.space_id == null) { 
 	    $('screenstepslive-form-spaces').selectedIndex = space_selected_index; 
 	  };
 	  $('sslive_search_text').value = text;
  },

  format_lesson_url: function(url) {
 	  if (this.include_login_in_url == true) {
   		url += "?login=" + this.screenstepslive_resource.options.user + "&password=" + this.screenstepslive_resource.options.pass
   		return url;
   	} else {
   		return url;
   	}
  },
  
  register_events: function() {
    var ul = $$('#sslive_lesson ul.drop-list').each(function(ul) {
      // Close the menu if user clicks away
  		document.observe('click', function(event) {
  			if (!event.findElement().descendantOf(ul)) {
  			  ul.select('#title').each(function(title) {
  			    title.style.zIndex = '0';
  			  });
  			  
  				ul.select('#action-menu').each(function(menu) {
  				  menu.style.display = 'none';
  				  menu.style.zIndex = '0';
  				});
  			}
  		});
  		
  		// Open the menu if user clicks
  		ul.observe('click', function(event) {
  		  title_container = ul.select('#title-container')[0];
  		  title = ul.select('#title')[0];
  		  menu = ul.select('#action-menu')[0];
  		  title.style.zIndex = '26';
  		  menu.style.display = 'block';
  		  menu.style.zIndex = '25';
  		}); 		
  	});  
  },
  
  render_lesson_results: function(lessons) {
    result = '<h4>' + lessons.length + " matching lessons found. Click on a lesson to view it</h4>";
 	  result +='<ul>';
 	  if (this.ticket_id != null) {
 	    result += lessons.collect(function(l) {
 	      return '<li><b>' + l.asset.title + ':</b><br />' +
               '<div id="sslive_lesson"><ul class="drop-list" style="position: relative">' +
               '<li>' +
               '<div id="title-container"><span class="drop link" id="title" style="cursor:default; z-index:20">' + l.title + '</span></div>' +
               '<ul id="action-menu" class="rounded-4" style="width: 100%; margin-left:-160px;">' +
               '<li id="ticket_link" class=" link" onclick="$$(\'#comment_value\')[0].value += \'' + this.format_lesson_url(l.url) + '\'; this.up().style.display=\'none\'; Event.stop(event);">Copy link to ticket</li>' +
               '<li id="window_link" class=" link" onclick="window.open(\'' + this.format_lesson_url(l.url) + '\', \'new\');  this.up().style.display=\'none\'; Event.stop(event);">Open in new window</li>' +
               '</ul>' +
               '</li>' +
               '</ul></div>'}.bind(this)).join('</li>');
    } else {
      result += lessons.collect(function(l) { 
        return '<li class="link"><b>' + l.asset.title + ':</b><br /><a href="' + this.format_lesson_url(l.url) + '" target="_blank">' + l.title + '</a></li>';
      }.bind(this)).join('</li>');
    }
    result += "</ul>";
 		return result;
  },

  search_results: function(response) {
   	result_field = $('screensteps_live_results');
 	  result_field.innerHTML = '';
 	  lessons = $A(response.lessons);
 	  if (lessons.length > 0) {
   		result = this.render_lesson_results(lessons)
   	} else {
   		result = "No results"
   	}
   	result_field.innerHTML = result;
   	this.register_events();
  },



  spaces_selector: function(response) {
 	  if (this.space_id == "" || this.space_id == null) {
 		  // load options into the spaces dropdown
   		var spaceInput = $('screenstepslive-form-spaces');
   		spaceInput.innerHTML = '';
   		spaces = $A(response.spaces);
   		spaces.each(function(s){
   			var option = document.createElement("option");
   			option.value = s.id;
   			option.innerHTML = s.title;
   			spaceInput.appendChild(option);
   		});
 		}
  }
  
});


/*
 *
 * Blue Mango ScreenSteps Live Tasks Widget
 *
 */
var ScreenStepsLiveTasksWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.widgetId     = args.id;
    this.anchor       = args.anchor;
    this.domainname   = args.domainname;
    this.username     = args.username;
    this.password     = args.password;
    this.use_ssl      = args.use_ssl;
    this.space_id     = args.space_id;
    this.ticket_id    = args.ticket_id;
    this.screenstepslive_tasks_resource = new Zendesk.Resource(
      {		
        anchor: this.anchor,
      	domain: this.domainname || '',
      	user: this.username || '',
      	pass: this.password || '',
      	use_ssl: this.use_ssl || 'false',
      	space_id: this.space_id || null,
      	enable_resource_cache: 'false',
      	show_logout: false,
      	login_content: this.application.bind(this),
      	application_content: this.application.bind(this), 
      	application_resources: [ {resource: 'spaces/api', media_type: "application/xml", on_success: this.spaces_selector.bind(this) } ]
    	}
    );
  },

  application: function() {
    /* Form header */
    text = '<form id="screensteps-live-task-form" onsubmit="if (ObjectFactory.get(' + this.id + ').validate_sslive_form() == true) {alert(\'Please enter a valid value for title\'); return false;}; ObjectFactory.get(' + this.id + ').setup_task_form(); ObjectFactory.get(' + this.id + ').screenstepslive_tasks_resource.submit_data(this); return false;" class="form">';
    /* Field Values */
    
    if (this.space_id == "" || this.space_id == null) {
 		  text += '<label style="width:50px;display:block;">Space: </label><select style="margin-bottom:5px;width:175px;" name="task[space_id]" id="screenstepslive-task-form-spaces"></select><br />';
 	  } else {
 		  text += '<input type="hidden" name="task[space_id]" id="screenstepslive-task-form-spaces" value="' + this.space_id + '">';
 	  }
    
    text += '<label>Title</label><input type="text" id="task_title" style="width:175px;" name="task[title]"/>' +
    '<label>Description</label><textarea rows="3" id="task_description" style="width:175px;" name="task[description]"></textarea>' +
     /* Request parameter to pluck from form fields for POST*/
    '<input type="hidden" name="pluck_param" value="task">' +
  	/* REST resource to POST to */
  	'<input type="hidden" id="task_resource" name="resource" value="">' +
  	/* Format for REST resource POST (json or xml) */
  	'<input type="hidden" name="media_type" value="application/xml">' +
  	/* Title on ticket event */
  	'<input type="hidden" name="event_reference" value="ScreenSteps Live Task Created">' +
  	/* Return values from POST to log on ticket event */					 			 		 
    '<input type="hidden" name="event_log" value="Title,Description,ID">' +
    '<input type="submit" value="Submit" id="submit">' +
    '</form>';
    return text;
  },

  validate_sslive_form: function() {
    var task_title = document.getElementById('task_title').value;
    if (task_title == '' || task_title == null) {
      return true
    } else {
      return false
    }
  },

  setup_task_form: function() {
    if (this.ticket_id != null) {
      $('task_description').value += ('\n\n' + location.href);    
    }
    $('task_resource').value = 'spaces/' + $F('screenstepslive-task-form-spaces') + '/tasks';
    return false;
  },

  spaces_selector: function(response) {    
    if (this.space_id == "" || this.space_id == null) {
      // load options into the spaces dropdown
      var spaceInput = $('screenstepslive-task-form-spaces');
      spaceInput.innerHTML = '';
      spaces = $A(response.spaces);
      spaces.each(function(s){
        var option = document.createElement("option");
        option.value = s.id;
        option.innerHTML = s.title;
        spaceInput.appendChild(option);
      }); 
    }
  }
});



var WidgetManagerWidget = Class.create({
  initialize: function() {
    widgetManager = this;
    Widget.redraw();
  },
  widgets: function() {
    var foundWidgets = $$('#sidebar div.widget');
    foundWidgets = foundWidgets.without($$('#preview-output .widget')[0]);
    return foundWidgets.map(function(elm) {
      return ObjectFactory.get(parseInt(elm.id.substring(7)));
    });
  },
  show: function() {
    $('add_widget_select_widget').show();
    $('add_widget_button').hide();
    this.widgets().each(function(widget) {
      widget.showControls();
      widget.containingElement().title = 'You can drag and drop widgets to rearrange them';
    });
    if ($('widget_fixed')) {
      $('widget_fixed').addClassName('widget_edit_mode');
    };
    Sortable.create('sidebar', {tag: 'div',
                                only: 'draggable',
                                scroll: window,
                                onUpdate: this.updateOrder});
    Widget.redraw();
  },
  hide: function() {
    $('add_widget_button').show();
    $('add_widget_select_widget').hide();
    this.widgets().each(function(widget) {
      widget.hideControls();
      widget.containingElement().title = '';
    });
    if ($('widget_fixed')) {
      $('widget_fixed').removeClassName('widget_edit_mode');
    };
    Sortable.destroy('sidebar');
    Widget.redraw();
  },
  updateOrder: function(container) {
    var locations = Sortable.sequence(container).map(function(widgetId) {
      if (widgetId == 'fixed') {
        return 'fixed';
      } else {
        return ObjectFactory.get(widgetId).locationId;
      }
    });
    new Ajax.Request('/widgets/reorder_locations', {
      method: 'put',
      parameters: {'locations[]': locations}
    });
  },
  addWidgetToPage: function(controllerName, actionName, widgetId) {
    new Ajax.Request('/widgets/' + widgetId + '/add_to_page', {
      method: 'post',
      parameters: {controller_name: controllerName,
                   action_name: actionName}
    });
  },
  removeWidgetFromPage: function(controllerName, actionName, widgetId) {
    new Ajax.Request('/widgets/' + widgetId + '/remove_from_page', {
      method: 'delete',
      parameters: {controller_name: controllerName,
                   action_name: actionName}
    });
  },
  removeWidget: function(widget_id) {
    var widget = ObjectFactory.get(widget_id);
    widget.hide(widget.destroy.bind(widget));
    ObjectFactory.remove(widget_id);
    $('add_widget_' + widget_id).show();
  },
  displayNewWidget: function() {
    $('widget_manager_widget').insert({before: 'hello'});
  }
});

var widgetManager = null;

/*
 * Batchbook widget
 *
 * Enables lookup of contact details in batchbook
 */

/*
  The BatchBookHelper provides a selection of convenience methods to call from the widget implementation.
*/
var BatchBookHelper = Class.create();

BatchBookHelper.getNodeValue = function(document, nodeName) {
  var node = document.getElementsByTagName(nodeName);
  if(node == null || node.length == 0 || node[0].childNodes.length == 0) {
    return null;
  }
  return node[0].childNodes[0].nodeValue;
}

BatchBookHelper.getNodeString = function(document, nodeName) {
  var value = this.getNodeValue(document, nodeName);
  if(value == null) {
    return '';
  }
  else {
    return value;
  }
}

BatchBookHelper.getArrayByTagName = function(tag_name, document) {
  var results = document.getElementsByTagName(tag_name)
  if(results == null) {
    return new Array();
  }
  return results;
}

BatchBookHelper.getRecords = BatchBookHelper.getArrayByTagName.curry('person')
BatchBookHelper.getLocations = BatchBookHelper.getArrayByTagName.curry('location')



/*
  The BatchBookQuery provides a template and function for building BatchBook API query URLs.
*/

var BatchBookQuery = Class.create();

BatchBookQuery.SERVER_URL = new Template('https://#{account_name}.batchbook.com/service/people.xml?email=#{email}');
BatchBookQuery.PROXY_URL  = new Template('/proxy/direct.xml?basic_user=#{token}&basic_pass=x&url=#{server_url}');
BatchBookQuery.CONTACT_URL = new Template('https://#{account_name}.batchbook.com/contacts/show/#{record_id}');

BatchBookQuery.buildUrl = function(account_name, token, email) {
  var serverUrl = this.SERVER_URL.evaluate({ account_name: account_name, email: email });
  var proxyUrl = this.PROXY_URL.evaluate({ server_url: escape(serverUrl), token: token });
  return proxyUrl;
}




/*
  The BatchBookWidgetView provides functions for turning fetched API results into markup.
*/

var BatchBookWidgetView = Class.create();

BatchBookWidgetView.HEADLINE_TEMPLATE = new Template('<h4 style="font-size: 16px"><a href="#{contact_url}" target="_new">#{first_name} #{last_name}</a></h4>');
BatchBookWidgetView.LOCATION_TEMPLATE = new Template('<h4>#{label}</h4>#{items}');
BatchBookWidgetView.LOCATION_ADDRESS_TEMPLATE = new Template("<span class=\"sub\">#{item_label}</span><br />#{item_value}<br />");
BatchBookWidgetView.LOCATION_ITEM_TEMPLATE = new Template("#{item_value} <span class=\"sub\">#{item_label}</span><br />");
BatchBookWidgetView.ADDRESS_TEMPLATE = new Template('#{street_1}</h4><p class="minimal">#{items}</p>');

BatchBookWidgetView.renderLine = function(record, elements, template) {
  var output = '';
  for(var i=0; i<elements.length; i++) {
    output += BatchBookHelper.getNodeString(record, elements[i]);
    if(output != '') {
      output += ' ';
    }
  }
  if(output != '') {
    return template.evaluate({ output: output });
  }
  else {
    return '';
  }
}

BatchBookWidgetView.renderAddress = function(l) {
  var s1 = BatchBookHelper.getNodeString(l, 'street_1');
  var s2 = BatchBookHelper.getNodeString(l, 'street_2');
  var ct = BatchBookHelper.getNodeString(l, 'city');
  var st = BatchBookHelper.getNodeString(l, 'state');
  var pc = BatchBookHelper.getNodeString(l, 'postal_code');
  var cn = BatchBookHelper.getNodeString(l, 'country');

  var a = '';

  if (s1 != '') { a += s1 + '<br />'; }
  if (s2 != '') { a += s2 + '<br />'; }

  if (ct != '') {
  a += ct;
    if (st != '' || pc != '') { a += ', '; }
  }

  if (st != '' || pc != '') {
    a += st;
    if (pc != '') { a += ' '; }
  }

  if (ct != '' || st != '' || pc != '') {
    a += pc + '<br />'
  }

  if (cn != '') { a += cn; }

  return a;
}


BatchBookWidgetView.renderRecord = function(record, accountName) {
  var result = '';

  var record_id = BatchBookHelper.getNodeString(record, 'id');
  var first_name = BatchBookHelper.getNodeString(record, 'first_name');
  var last_name = BatchBookHelper.getNodeString(record, 'last_name');

  var contact_url = BatchBookWidgetView.contact_url(record_id, accountName);

  result += BatchBookWidgetView.HEADLINE_TEMPLATE.evaluate({ contact_url: contact_url,
                                                           first_name: first_name,
                                                           last_name: last_name});

  result += this.renderLine(record, ['title'], new Template("<p class='minimum'>#{output}</p>"));
  result += this.renderLine(record, ['company'], new Template("<p class='minimum'>#{output}</p>"));

  var locations = BatchBookHelper.getLocations(record);
  var location_string = {'address': '', 'email': '', 'website': '', 'phone': '', 'cell': '', 'fax': ''};
  var location_keys = ['address', 'email', 'website', 'phone', 'cell', 'fax']

  for (var i=0; i<locations.length; i++) {
    var l = locations[i];
    var loc_label = $j(l).find('label').text();

    for (var j=0; j<location_keys.length; j++) {
      var lk = location_keys[j];
      var val = '';

      if (lk == 'address') {
        val = this.renderAddress(l);
      } else {
        val = BatchBookHelper.getNodeString(l, lk);
      }

      if (val != '') {
        if (lk == 'address') {
          if (location_string[lk])
            location_string[lk] += "<br />";
          location_string[lk] += BatchBookWidgetView.LOCATION_ADDRESS_TEMPLATE.evaluate({ item_label: loc_label, item_value: val });
        } else {
          location_string[lk] += BatchBookWidgetView.LOCATION_ITEM_TEMPLATE.evaluate({ item_label: loc_label, item_value: val });
        }
      }
    }
  }

  for (var j=0; j<location_keys.length; j++) {
    var lk = location_keys[j];
    if (location_string[lk] != '') {
      result += BatchBookWidgetView.LOCATION_TEMPLATE.evaluate({ label: lk, items: location_string[lk] });
    }
  }

  return result + "<br />";
}

BatchBookWidgetView.contact_url = function(record_id, accountName) {
  return BatchBookQuery.CONTACT_URL.evaluate({ record_id: record_id, account_name: accountName });
}



/*
  The BatchBook widget is a mechanism for looking up person data on BatchBook. The widget must be
  configured with valid account_name and token, after which it can be deployed and used on pages.
*/

var BatchBookWidget = Class.create(Widget, {

  lookup: function(form) {
    $j('#batchbooks-widget-spinner').show();

    if (this.accountName != null && this.token != null) {
      var url = BatchBookQuery.buildUrl(this.accountName, this.token, form.email.value);

      new Ajax.Request(url, {
        method:'get',
        onSuccess: function(transport) {
          this.displayResult(transport.responseXML);
          $j('#batchbooks-widget-spinner').hide();
        }.bind(this),
        onFailure: function() {
          $(this.contentRoot).update("<h4>Unable to search your BatchBook account at this time.  Please try again later.</h4>");
        }.bind(this)
      });
    }
    else {
      $(this.contentRoot).update("<h4>BatchBook login failed - BatchBook account needs configuration.</h4>");
    }
  },

  displayResult: function(responseXML) {
    var records = BatchBookHelper.getRecords(responseXML);

    if(records.length == 0) {
      $(this.contentRoot).update("<h4>No match found in your BatchBook account.</h4>");
      return;
    } else {
      // only render first result in sidebar.  can link to BB to search for more hits.
      var m = BatchBookWidgetView.renderRecord(records[0], this.accountName);
      $(this.contentRoot).update(m);
    }
  },

  initialize: function($super, args) {
    $super(args);

    this.token = args.token;
    this.formInstance = null;
    this.widgetId = args.id;
    this.isEmpty = args.isEmpty;
    this.accountName = args.accountName;
    this.requesterEmail = args.requester_email;
    this.contentRoot = this.containingElement().down("div.batchbook-content-" + this.widgetId);
  }

} );

/*
*
* Clarizen Widget
*
*/



/*
 * Tools
 */

var CustomCalendarDateSelect = Class.create(CalendarDateSelect, {
  updateValue: function($super) {
    $super();
    var d = this.selected_date;
    var day = d.getDate() < 10 ? ("0" + d.getDate()) : d.getDate();
    var month = (d.getMonth()+1) < 10 ? ("0" + parseInt(d.getMonth() + 1)) : parseInt(d.getMonth() + 1);

    var str = month + "/" + day + "/" + d.getFullYear();
    this.target_element.value = str;
  },
  parseDate: function($super)
  {
    var value = $F(this.target_element).strip();
    $super();
    if (this.selection_made) {
        this.selected_date.setFullYear(value.split('/')[2],parseInt(value.split('/')[0]) - 1,parseInt(value.split('/')[1]));
        this.date = this.selected_date;
    }
  }
});

var clarizenWidgetUnicodeConverter = {
    convertChar2CP: function ( textString ) {
        var haut = 0;
        var n = 0;
        CPstring = '';
        for (var i = 0; i < textString.length; i++) {
            var b = textString.charCodeAt(i);
            if (b < 0 || b > 0xFFFF) {
                CPstring += 'Error ' + this.dec2hex(b) + '!';
                }
            if (haut != 0) {
                if (0xDC00 <= b && b <= 0xDFFF) {
                    CPstring += this.dec2hex(0x10000 + ((haut - 0xD800) << 10) + (b - 0xDC00)) + ' ';
                    haut = 0;
                    continue;
                    }
                else {
                    CPstring += '!erreur ' + this.dec2hex(haut) + '!';
                    haut = 0;
                    }
                }
            if (0xD800 <= b && b <= 0xDBFF) {
                haut = b;
                }
            else {
                CPstring += this.dec2hex(b) + ' ';
                }
            }
        CPstring = CPstring.substring(0, CPstring.length-1);

        return this.convertCP2HexNCR( CPstring );

    },
    convertCP2HexNCR: function ( textString ) {
          var outputString = "";
          textString = textString.replace(/^\s+/, '');
          if (textString.length == 0) { return ""; }
          textString = textString.replace(/\s+/g, ' ');
          var listArray = textString.split(' ');
          for ( var i = 0; i < listArray.length; i++ ) {
            var n = parseInt(listArray[i], 16);
            outputString += '&#x' + this.dec2hex(n) + ';';
          }
          return( outputString );
    },
    dec2hex: function( textString ) {
         return (textString+0).toString(16).toUpperCase();
    }
}


/*
 * Helper
 */

var clarizenWidgetHelper = {

  PARTNER_ID:   'baa861be-8460-4de8-b678-56f915b6be43',
  SERVICE_URL:  'https://api.clarizen.com/v1.0/Clarizen.svc',
  LOGIN_URL:    'http://clarizen.com/api/IClarizen/Login',
  QUERY_URL:    'http://clarizen.com/api/IClarizen/Query',
  EXECUTE_URL:  'http://clarizen.com/api/IClarizen/Execute',
  WIDGET_ID:    'clarizenWidget',
  IMAGE_PATH:   '/images/widgets/clarizen/',

  GET_CLARIZEN_USER_PROFILE: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '   <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '   <soap:Header>' +
      '       <Session xmlns="http://clarizen.com/api">' +
      '         <ID>#{sessionId}</ID>' +
      '       </Session>' +
      '   </soap:Header>' +
      '  <soap:Body>' +
      '  <Query xmlns="http://clarizen.com/api">' +
      '  <queryExpression xmlns:q1="http://clarizen.com/api/queries" xsi:type="q1:EntityQuery">' +
      '     <q1:Paging xsi:nil="true" />' +
      '     <q1:Fields />' +
      '     <q1:Orders xsi:nil="true" />' +
      '     <q1:TypeName>User</q1:TypeName>' +
      '     <q1:Where xsi:type="q1:And">' +
      '       <q1:Conditions>' +
      '          <Condition xsi:type="q1:Compare">' +
      '            <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '               <q1:FieldName>Email</q1:FieldName>' +
      '            </q1:LeftExpression>' +
      '            <q1:Operator>Equal</q1:Operator>' +
      '            <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '               <q1:Value xsi:type="xsd:string">#{email}</q1:Value>' +
      '            </q1:RightExpression>' +
      '          </Condition>' +
      '          <Condition xsi:type="q1:Compare">' +
      '            <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '               <q1:FieldName>FirstName</q1:FieldName>' +
      '            </q1:LeftExpression>' +
      '            <q1:Operator>Equal</q1:Operator>' +
      '            <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '               <q1:Value xsi:type="xsd:string">#{firstName}</q1:Value>' +
      '            </q1:RightExpression>' +
      '          </Condition>' +
      '          <Condition xsi:type="q1:Compare">' +
      '            <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '               <q1:FieldName>LastName</q1:FieldName>' +
      '            </q1:LeftExpression>' +
      '            <q1:Operator>Equal</q1:Operator>' +
      '            <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '               <q1:Value xsi:type="xsd:string">#{lastName}</q1:Value>' +
      '            </q1:RightExpression>' +
      '          </Condition>' +
      '       </q1:Conditions>' +
      '     </q1:Where>' +
      '  </queryExpression>' +
      '  </Query>' +
      '  </soap:Body>' +
      '</soap:Envelope>'
  ),
  GET_SYSTEM_SETTINGS: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '   <soap:Header>' +
      '       <Session xmlns="http://clarizen.com/api">' +
      '         <ID>#{sessionId}</ID>' +
      '       </Session>' +
      '   </soap:Header>' +
      '  <soap:Body>' +
      '      <Execute xmlns="http://clarizen.com/api">' +
      '         <request>' +
      '            <BaseMessage xmlns:q1="http://clarizen.com/api/metadata" xsi:type="q1:GetSystemSettingsValuesMessage">' +
      '              <q1:Settings>' +
      '                  <string>Enable issue management</string>' +
      '                  <string>Enable bug tracking</string>' +
      '                  <string>Enable requests for enhancements</string>' +
      '                  <string>Enable risk management</string>' +
      '              </q1:Settings>' +
      '            </BaseMessage>' +
      '         </request>' +
      '      </Execute>' +
      '  </soap:Body>' +
      '</soap:Envelope>'
  ),
  UNLINK_ISSUE: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '   <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '   <soap:Header>' +
      '       <Session xmlns="http://clarizen.com/api">' +
      '         <ID>#{sessionId}</ID>' +
      '       </Session>' +
      '   </soap:Header>' +
      '   <soap:Body>' +
      '      <Execute xmlns="http://clarizen.com/api">' +
      '         <request>' +
      '             <BaseMessage xsi:type="UpdateMessage">' +
      '               <Entity xsi:type="GenericEntity">' +
      '                  <Id>' +
      '                     <TypeName>Issue</TypeName>' +
      '                     <Value>#{IssueId}</Value>' +
      '                  </Id>' +
      '                  <Values>' +
      '                     <FieldValue>' +
      '                        <FieldName>ZdExternalId</FieldName>' +
      '                        <Value xsi:type="xsd:string"></Value>' +
      '                     </FieldValue>' +
      '                  </Values>' +
      '               </Entity>' +
      '            </BaseMessage>' +
      '         </request>' +
      '     </Execute>' +
      '  </soap:Body>' +
      ' </soap:Envelope>'
  ),
  GET_EXTERNAL_ID_BY_TICKET_ID: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '  <soap:Header>' +
      '    <Session xmlns="http://clarizen.com/api">' +
      '      <ID>#{sessionId}</ID>' +
      '    </Session>' +
      '  </soap:Header>' +
      '  <soap:Body>' +
      '<Execute xmlns="http://clarizen.com/api">' +
      ' <request>' +
      '   <BaseMessage xmlns:q1="http://clarizen.com/api/queries" xsi:type="q1:EntityQuery">' +
      '     <q1:Paging xsi:nil="true" />' +
      '     <q1:Fields />' +
      '     <q1:Orders xsi:nil="true" />' +
      '     <q1:TypeName>Issue</q1:TypeName>' +
      '     <q1:Where xsi:type="q1:Compare">' +
      '       <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '          <q1:FieldName>ZdExternalId</q1:FieldName>' +
      '       </q1:LeftExpression>' +
      '       <q1:Operator>Equal</q1:Operator>' +
      '       <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '          <q1:Value xsi:type="xsd:string">#{ticketId}</q1:Value>' +
      '       </q1:RightExpression>' +
      '     </q1:Where>' +
      '   </BaseMessage>' +
      '   <BaseMessage xmlns:q1="http://clarizen.com/api/queries" xsi:type="q1:EntityQuery">' +
      '     <q1:Paging xsi:nil="true" />' +
      '     <q1:Fields />' +
      '     <q1:Orders xsi:nil="true" />' +
      '     <q1:TypeName>Bug</q1:TypeName>' +
      '     <q1:Where xsi:type="q1:Compare">' +
      '       <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '          <q1:FieldName>ZdExternalId</q1:FieldName>' +
      '       </q1:LeftExpression>' +
      '       <q1:Operator>Equal</q1:Operator>' +
      '       <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '          <q1:Value xsi:type="xsd:string">#{ticketId}</q1:Value>' +
      '       </q1:RightExpression>' +
      '     </q1:Where>' +
      '   </BaseMessage>' +
      ' </request>' +
      '</Execute>' +
      '  </soap:Body>' +
      '</soap:Envelope>'
  ),
  UPDATE_ISSUE: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '   <soap:Header>' +
      '       <Session xmlns="http://clarizen.com/api">' +
      '         <ID>#{sessionId}</ID>' +
      '       </Session>' +
      '   </soap:Header>' +
      '   <soap:Body>' +
      '     <Execute xmlns="http://clarizen.com/api">' +
      '          <request>' +
      '              <BaseMessage xsi:type="LifecycleMessage">' +
      '                  <Ids>' +
      '                      <EntityId>' +
      '                          <TypeName>#{entity}</TypeName>' +
      '                          <Value>#{issueId}</Value>' +
      '                      </EntityId>' +
      '                  </Ids>' +
      '                  <Operation>#{operation}</Operation>' +
      '              </BaseMessage>' +
      '              <BaseMessage xsi:type="CreateMessage">' +
      '                  <Entity xsi:type="GenericEntity">' +
      '                      <Id>' +
      '                          <TypeName>IssueTeamMembers</TypeName>' +
      '                          <Value xsi:nil="true" />' +
      '                      </Id>' +
      '                      <Values>' +
      '                          <FieldValue>' +
      '                              <FieldName>Case</FieldName>' +
      '                              <Value xsi:type="EntityId">' +
      '                                  <TypeName>#{entity}</TypeName>' +
      '                                  <Value>#{issueId}</Value>' +
      '                              </Value>' +
      '                          </FieldValue>' +
      '                          <FieldValue>' +
      '                              <FieldName>TeamMember</FieldName>' +
      '                              <Value xsi:type="EntityId">' +
      '                                  <TypeName>User</TypeName>' +
      '                                  <Value>#{creatorId}</Value>' +
      '                              </Value>' +
      '                          </FieldValue>' +
      '                      </Values>' +
      '                  </Entity>' +
      '                </BaseMessage>' +
      '          </request>' +
      '      </Execute>' +
      '   </soap:Body>' +
      '</soap:Envelope>'
  ),
  DUE_DATE: new Template(
      '<FieldValue>' +
      '  <FieldName>DueDate</FieldName>' +
      '  <Value xsi:type="xsd:dateTime">#{dueDate}</Value>' +
      '</FieldValue>'
  ),
  PROJECT_ID: new Template(
      '<FieldValue>' +
      '  <FieldName>PlannedFor</FieldName>' +
      '  <Value xsi:type="EntityId">' +
      '    <TypeName>Project</TypeName>' +
      '    <Value>#{projectId}</Value>' +
      '  </Value>' +
      '</FieldValue>'
  ),
  SUBMIT_ISSUE: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '   <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '   <soap:Header>' +
      '       <Session xmlns="http://clarizen.com/api">' +
      '         <ID>#{sessionId}</ID>' +
      '       </Session>' +
      '   </soap:Header>' +
      '   <soap:Body>' +
      '      <Execute xmlns="http://clarizen.com/api">' +
      '         <request>' +
      '             <BaseMessage xsi:type="CreateMessage">' +
      '               <Entity xsi:type="GenericEntity">' +
      '                  <Id>' +
      '                     <TypeName>#{entity}</TypeName>' +
      '                  </Id>' +
      '                  <Values>' +
      '                     <FieldValue>' +
      '                        <FieldName>Title</FieldName>' +
      '                        <Value xsi:type="xsd:string">#{title}</Value>' +
      '                     </FieldValue>' +
      '                     <FieldValue>' +
      '                        <FieldName>Description</FieldName>' +
      '                        <Value xsi:type="xsd:string">#{description}</Value>' +
      '                     </FieldValue>' +
      '                     #{dueDateTemplate}' +
      '                     <FieldValue>' +
      '                        <FieldName>Severity</FieldName>' +
      '                        <Value xsi:type="EntityId">' +
      '                           <TypeName>Severity</TypeName>' +
      '                           <Value>#{severity}</Value>' +
      '                        </Value>' +
      '                     </FieldValue>' +
      '                     <FieldValue>' +
      '                        <FieldName>AssignedTo</FieldName>' +
      '                        <Value xsi:type="EntityId">' +
      '                           <TypeName>User</TypeName>' +
      '                           <Value>#{userId}</Value>' +
      '                        </Value>' +
      '                     </FieldValue>' +
      '                     <FieldValue>' +
      '                        <FieldName>Owner</FieldName>' +
      '                        <Value xsi:type="EntityId">' +
      '                           <TypeName>User</TypeName>' +
      '                           <Value>#{ownerId}</Value>' +
      '                        </Value>' +
      '                     </FieldValue>' +
      '                     #{projectTemplate}' +
      '                     <FieldValue>' +
      '                        <FieldName>ZdExternalId</FieldName>' +
      '                        <Value xsi:type="xsd:string">#{ticketId}</Value>' +
      '                     </FieldValue>' +
      '                  </Values>' +
      '               </Entity>' +
      '            </BaseMessage>' +
      '         </request>' +
      '     </Execute>' +
      '  </soap:Body>' +
      ' </soap:Envelope>'
  ),
  GET_PROJECTS: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '  <soap:Header>' +
      '    <Session xmlns="http://clarizen.com/api">' +
      '      <ID>#{sessionId}</ID>' +
      '    </Session>' +
      '  </soap:Header>' +
      '  <soap:Body>' +
      '  <Query xmlns="http://clarizen.com/api">' +
      '  <queryExpression xmlns:q1="http://clarizen.com/api/queries" xsi:type="q1:EntityQuery">' +
      '     <q1:Paging xsi:nil="true" />' +
      '     <q1:Fields>' +
      '       <string>Name</string>' +
      '     </q1:Fields>' +
      '     <q1:Orders xsi:nil="true" />' +
      '     <q1:TypeName>Project</q1:TypeName>' +
      '     <q1:Where xsi:nil="true" />' +
      '  </queryExpression>' +
      '  </Query>' +
      '  </soap:Body>' +
      '</soap:Envelope>'
  ),
  GET_ASSIGNEES: new Template(
      '<?xml version="1.0" encoding="utf-8"?>' +
      '  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
      '  <soap:Header>' +
      '      <Session xmlns="http://clarizen.com/api">' +
      '        <ID>#{sessionId}</ID>' +
      '      </Session>' +
      '  </soap:Header>' +
      '  <soap:Body>' +
      '  <Query xmlns="http://clarizen.com/api">' +
      '      <queryExpression xmlns:q1="http://clarizen.com/api/queries" xsi:type="q1:EntityQuery">' +
      '         <q1:Paging xsi:nil="true" />' +
      '            <q1:Fields>' +
      '                 <string>FirstName</string>' +
      '                 <string>LastName</string>' +
      '                 <string>Email</string>' +
      '             </q1:Fields>' +
      '             <q1:Orders>' +
      '               <q1:OrderBy>' +
      '                 <q1:FieldName>DisplayName</q1:FieldName>' +
      '               </q1:OrderBy>' +
      '             </q1:Orders>' +
      '             <q1:TypeName>User</q1:TypeName>' +
      '             <q1:Where xsi:type="q1:Compare">' +
      '                      <q1:LeftExpression xsi:type="q1:FieldExpression">' +
      '                          <q1:FieldName>State</q1:FieldName>' +
      '                      </q1:LeftExpression>' +
      '                      <q1:Operator>NotEqual</q1:Operator>' +
      '                      <q1:RightExpression xsi:type="q1:ConstantExpression">' +
      '                          <q1:Value xsi:type="EntityId">' +
      '                              <TypeName>State</TypeName>' +
      '                              <Value>Deleted</Value>' +
      '                          </q1:Value>' +
      '                      </q1:RightExpression>' +
      '               </q1:Where>' +
      '     </queryExpression>' +
      '  </Query>' +
      '  </soap:Body>' +
      '</soap:Envelope>'
  ),
  GET_SESSION: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '  <soap:Body>' +
    '    <Login xmlns="http://clarizen.com/api">' +
    '      <userName>#{username}</userName>' +
    '      <password>#{password}</password>' +
    '      <options xsi:type="ExtendedLoginOptions">' +
    '         <ApplicationId>ClarizenClient</ApplicationId>' +
    '         <PartnerId>#{partnerId}</PartnerId>' +
    '         <ClientVersion>1.0</ClientVersion>' +
    '      </options>' +
    '    </Login>' +
    '  </soap:Body>' +
    '</soap:Envelope>'
  ),
  ISSUE_INLINE_UPDATES: new Template(
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Issue number:</strong></div><div style="float: left;">#{sysId}</div><div style="clear:both;"></div>' +
    '<div><strong>Title:</strong></div><div style="padding-bottom:5px;"><span title="#{fullticketTitle}">#{ticketTitle}</span></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>State:</strong></div><div style="float: left;">#{state}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Severity:</strong></div><div style="float: left;">#{severity}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Due Date:</strong></div><div style="float: left;">#{date}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Assigned To:</strong></div><div style="float: left;">#{assignedTo}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Owner:</strong></div><div style="float: left;">#{owner}</div><div style="clear:both;"></div>' +
    '#{projectTemplate}'
  ),
  PROJECT_ISSUE_TEMPLATE: new Template(
    '<div><strong>To be Resolved In:</strong></div><span title="#{fullProjectName}">#{projectName}</span>'
  ),
  SEND_MAIL_TEMPLATE: new Template(
    '<a href="mailto:#{mailAsigneeEmail}?subject=%20[zendesk]%20Message%20regarding%20ticket%20\'#{mailTitle}\'%20(#{mailTicketId})&cc=#{mailOwnerEmail}">Send email</a>'
  ),
  DISPLAY_ISSUE_FORM: new Template(
    '<div id="issue-details" style="padding:3px;width:100%;overflow:hidden;">' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Issue number:</strong></div><div><div style="float: left;">#{sysId}</div><div style="clear:both;"></div>' +
    '<div><strong>Title:</strong></div><div style="padding-bottom:5px;"><span title="#{fullticketTitle}">#{ticketTitle}</span></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>State:</strong></div><div style="float: left;">#{state}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Severity:</strong></div><div style="float: left;">#{severity}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Due Date:</strong></div><div><div style="float: left;">#{date}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Assigned To:</strong></div><div style="float: left;">#{assignedTo}</div><div style="clear:both;"></div>' +
    '<div style="padding-bottom:5px; width:85px; float:left;"><strong>Owner:</strong></div><div style="float: left;">#{owner}</div><div style="clear:both;"></div>' +
    '#{projectTemplate}' +
    '</div>' +
    '<div style="text-align:center;padding-top:5px;"><!--<a onclick="clarizenWidget.unLinkIssue(false);" href="javascript:void(0);">unlink</a> |--> <a onclick="ObjectFactory.get(#{widgetId}).getIssueFromClarizen(false);" href="#">Refresh</a> | #{mailTemplate}</div>' +
    '</div>'
  ),
  SUBMIT_FORM: new Template(
    '<div id="clarizen-modal-popup" class="frame" style="display:none;margin:20px;width:400px;padding:20px;position:absolute;top:10%;left:20%;z-index:5000;border-width:10px;">' +
    '<div id="clarizen-form-close" style="float:right;padding-right:10px;">' +
    '<a href="javascript:void(0);" onclick="$(\'lv_overlay\').hide();$(\'clarizen-modal-popup\').hide();">Close</a></div>' +
    '<form class="form" id="clarizen-form" style="width:98%;">' +
    '<div style="float:left;padding-right:10px;">#{logoImage}</div><div style="font-size:16px;padding-top:10px;">Create issue in Clarizen</div><div style="clear:both;"></div>' +
    '<div class="select" style="padding-top:5px"><label style="display:inline;padding-right:70px;">Subject:</label>' +
    '<input type="text" value="" size="30" id="clarizenTitle" style="width:75%;" class="clarizenTitle"/>' +
    '</div>' +

    '<div class="select" style="padding-bottom:5px;padding-top:5px;"><label style="float:left;padding-right:50px;">Description:</label>' +
    '<textarea rows="4" id="clarizenDescription" class="clarizenDescription" style="width:75%;" ></textarea>' +
    '<div style="clear:both;"></div></div>' +

    '<div style="padding-bottom: 5px">' +
    '<div style="width:120px; float: left;"><label style="margin-top: 0px;">Clarizen priority:</label></div><div style="float:left;">' +
    '<select id="clarizenSeveritySelector" class="clarizenSeveritySelector" style="width:215px;padding-left:10px;max-width:215px;">' +
    '   <option value="Critical">Urgent</option>' +
    '   <option value="High">High</option>' +
    '   <option value="Low">Low</option>' +
    '   <option selected="selected" value="Medium">Normal</option>' +
    '</select></div>' +
    '<div style="clear:both"></div>' +
    '</div>' +

    '<div style="padding-bottom: 5px">' +
    '<div style="width:120px; float: left;"><label style="margin-top: 0px;">To be resolved In:</label></div><div style="float:left;">' +
    '<select id="clarizenProjectSelector" disabled class="clarizenProjectSelector" style="width:215px;padding-left:10px;max-width:215px;"></select>' +
    '</div>' +
    '<div style="clear:both"></div>' +
    '</div>' +

    '<div style="padding-bottom: 5px">' +
    '<div style="width:120px; float: left;"><label style="margin-top: 0px;">Assigned To:</label></div><div style="float:left;">' +
    '<select id="clarizenAssigneeSelecter" disabled class="clarizenAssigneeSelecter" style="width:215px;padding-left:10px;max-width:215px;"></select>' +
    '</div>' +
    '<div style="clear:both"></div>' +
    '</div>' +

    '<div style="width:120px; float: left;"><label style="margin-top: 0px;">Due Date:</label></div><div style="float:left;">' +
    '<input type="text" value="" size="10" id="dueDate" class="dueDate" />' +
    '<img alt="Calendar" class="calendar_date_select_popup_icon" onclick="new CustomCalendarDateSelect( $(this).previous(), {buttons:false, popup:\'force\', time:false, year_range:10} ); $$(\'.calendar_date_select\')[0].setStyle({\'zIndex\':\'5010\'}); " src="http://asset0.zendesk.com/images/calendar_date_select/calendar.gif?1264325148" style="border:0px; cursor:pointer;" />' +

    '</div>' +

    '<div style="float:right;">' +
//    '<input type="button" value="Submit" onclick="clarizenWidget.submitIssueForm($(\'clarizen-form\'));" class="buttonsubmit" />' +
    '<input type="button" value="Submit" onclick="ObjectFactory.get(#{widgetId}).submitIssueForm($(\'clarizen-form\'));" class="buttonsubmit" />' +
    '<input type="button" value="Cancel" onclick="$(\'lv_overlay\').hide();$(\'clarizen-modal-popup\').hide();" class="buttonsubmit" />' +
    '</div>' +

    '</form></div>'
  ),
  SHOW_AVAILABILITY_LINK: new Template(
    '<div id="availabilityLnk" style="text-align:center;padding-top:10px;padding-bottom:10px;">' +
    '  <a onclick="ObjectFactory.get(#{widgetId}).checkAvailability();" href="#">Show Availability</a>' +
    '</div>'
  ),
  TICKET_RESOLUTION: new Template(
     '<div class="clarizen_message">You can create a request for resolution of the ticket in Clarizen' +
        '<div style="text-align:center;padding-top:10px;padding-bottom:10px;">' +
        '  <a onclick="ObjectFactory.get(#{widgetId}).showForm();" href="#">Request ticket resolution</a>' +
        '</div>' +
     '</div>'
  ),
  PROXY_URL: new Template(
    '/proxy/direct?url=#{url}&SOAPAction=#{soapAction}&log=1'
  ),
  CREATE_USER: new Template(
   '<?xml version="1.0" encoding="utf-8"?>' +
   '   <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
   '   <soap:Header>' +
   '       <Session xmlns="http://clarizen.com/api">' +
   '         <ID>#{sessionId}</ID>' +
   '       </Session>' +
   '   </soap:Header>' +
   '   <soap:Body>' +
   '      <Execute xmlns="http://clarizen.com/api">' +
   '         <request>' +
   '             <BaseMessage xsi:type="CreateMessage">' +
   '               <Entity xsi:type="GenericEntity">' +
   '                  <Id>' +
   '                     <TypeName>User</TypeName>' +
   '                  </Id>' +
   '                  <Values>' +
   '                     <FieldValue>' +
   '                        <FieldName>FirstName</FieldName>' +
   '                        <Value xsi:type="xsd:string">#{firstName}</Value>' +
   '                     </FieldValue>' +
   '                     <FieldValue>' +
   '                        <FieldName>LastName</FieldName>' +
   '                        <Value xsi:type="xsd:string">#{lastName}</Value>' +
   '                     </FieldValue>' +
   '                     <FieldValue>' +
   '                        <FieldName>Email</FieldName>' +
   '                        <Value xsi:type="xsd:string">#{email}</Value>' +
   '                     </FieldValue>' +
   '                     <FieldValue>' +
   '                        <FieldName>AllowEmails</FieldName>' +
   '                        <Value i:type="b:boolean" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://www.w3.org/2001/XMLSchema">true</Value>' +
   '                     </FieldValue>' +
   '                     <FieldValue>' +
   '                        <FieldName>SendInvitationMail</FieldName>' +
   '                        <Value xsi:type="xsd:boolean">#{sendInvitationMail}</Value>' +
   '                     </FieldValue>' +
   '                  </Values>' +
   '               </Entity>' +
   '            </BaseMessage>' +
   '         </request>' +
   '     </Execute>' +
   '  </soap:Body>' +
   ' </soap:Envelope>'
 ),
 GET_ISSUE_QUERY: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '  <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
    '  <soap:Header>' +
    '      <Session xmlns="http://clarizen.com/api">' +
    '        <ID>#{sessionId}</ID>' +
    '      </Session>' +
    '  </soap:Header>' +
    '  <soap:Body>' +
    '   <Execute xmlns="http://clarizen.com/api">' +
    '      <request>' +
    '        <BaseMessage xsi:type="RetrieveMessage">' +
    '          <Fields>' +
    '            <string xmlns="http://clarizen.com/api">SYSID</string>' +
    '            <string xmlns="http://clarizen.com/api">DueDate</string>' +
    '            <string xmlns="http://clarizen.com/api">State</string>' +
    '            <string xmlns="http://clarizen.com/api">Severity</string>' +
    '            <string xmlns="http://clarizen.com/api">AssignedTo.DisplayName</string>' +
    '            <string xmlns="http://clarizen.com/api">Owner.DisplayName</string>' +
    '            <string xmlns="http://clarizen.com/api">PlannedFor.Name</string>' +
    '            <string xmlns="http://clarizen.com/api">Title</string>' +
    '            <string xmlns="http://clarizen.com/api">ZdExternalId</string>' +
    '          </Fields>' +
    '          <Id>' +
    '               <TypeName>#{entity}</TypeName>' +
    '               <Value>#{externalId}</Value>' +
    '          </Id>' +
    '        </BaseMessage>' +
    '      </request>' +
    '   </Execute>' +
    '  </soap:Body>' +
    '</soap:Envelope>'
  ),
  MISSING_TICKET: new Template(
    'Only available when viewing tickets or users.' +
    '<br />&nbsp;'
  ),
  PROFILE_PAGE_TEXT: new Template(
    '<!--#{logoImage}-->' +
    '<div>Click Show Assignment to see user usage in Clarizen.</div>'
  ),
  PROFILE_NOT_EXISTS: new Template(
    '<div>User #{name} is not yet registered in Clarizen.</div>'
  ),
  getMissingTicketMessage: function () {
    return (this.MISSING_TICKET.evaluate({}));
  },
  getProfileNotExistsMessage: function(name) {
    return (this.PROFILE_NOT_EXISTS.evaluate({name: name}));
  },
  getAvailabilityLnkForProfilePage: function(Id) {
    return (this.PROFILE_PAGE_TEXT.evaluate({ logoImage: this.getLogoImage() })) + this.getAvailabilityLnk(Id);
  },
  getAvailabilityLnk: function(widgetId) {
     return this.SHOW_AVAILABILITY_LINK.evaluate({
           widgetId: widgetId
     });
  },
  getClarizenUserProfile: function(sessionId, email, firstName, lastName  ) {
     return escape(this.GET_CLARIZEN_USER_PROFILE.evaluate({sessionId: sessionId, email: email, firstName: firstName, lastName: lastName}));
  },
  getExternalIdByTicketRequest: function(sessionId, ticketId) {
     return escape(this.GET_EXTERNAL_ID_BY_TICKET_ID.evaluate({sessionId: sessionId, ticketId: ticketId }));
  },
  setIssueInlineUpdates: function(severity, sysId, state, date, assignedTo, owner, projectName, title) {
      return this.ISSUE_INLINE_UPDATES.evaluate({
          severity: severity,
          sysId: sysId,
          ticketTitle: title,
          fullticketTitle: title,
          state: (state == "Submitted2" ? "Submitted" : state),
          stateImage: this.getStateImage(state),
          date: this.getFormattedDate(date),
          assignedTo: assignedTo,
          owner: owner,
          projectTemplate: projectName == " - " ? null : this.PROJECT_ISSUE_TEMPLATE.evaluate({fullProjectName: projectName, projectName: projectName }),
          path: this.IMAGE_PATH
      });
  },
  getDisplayIssueForm: function(widgetId, severity, sysId, state, date, assignedTo, owner, projectName, title, mailTitle, mailTicketId, mailAsigneeEmail, mailOwnerEmail) {
     return this.DISPLAY_ISSUE_FORM.evaluate({
          widgetId: widgetId,
          severity: severity,
          sysId: sysId,
          ticketTitle: title,
          fullticketTitle: title,
          state: (state == "Submitted2" ? "Submitted" : state),
          stateImage: this.getStateImage(state),
          date: this.getFormattedDate(date),
          assignedTo: assignedTo,
          owner: owner,
          projectTemplate: projectName == " - " ? null : this.PROJECT_ISSUE_TEMPLATE.evaluate({fullProjectName: projectName, projectName: projectName }),
          path: this.IMAGE_PATH,
          mailTemplate: this.SEND_MAIL_TEMPLATE.evaluate({ mailTitle: mailTitle, mailTicketId: mailTicketId, mailAsigneeEmail: mailAsigneeEmail, mailOwnerEmail: mailOwnerEmail})
     });
  },
  getSystemSettings: function(sessionId) {
     return escape(this.GET_SYSTEM_SETTINGS.evaluate({sessionId: sessionId}));
  },
  getProjects: function(sessionId) {
     return escape(this.GET_PROJECTS.evaluate({sessionId: sessionId}));
  },
  getCreateUserRequest: function(sessionId, email, firstName, lastName, sendInvitationMail) {
     return escape(this.CREATE_USER.evaluate({sessionId: sessionId, email: email, firstName: firstName, lastName: lastName, sendInvitationMail: sendInvitationMail }));
  },
  getSubmitForm: function(widgetId) {
    return (this.SUBMIT_FORM.evaluate({widgetId: widgetId, logoImage: this.getLogoImage()}));
  },
  getProxyUrl: function(url, soapAction) {
     return escape(this.PROXY_URL.evaluate({ url: url, soapAction: soapAction }));
  },
  getSubmitFormRequest: function(sessionId, title, description, dueDate, severity, userId, projectId, ownerId, ticketId, mode) {

     entity = mode ? "Bug" : "Issue"

    return escape(this.SUBMIT_ISSUE.evaluate({sessionId: sessionId,
                title: title,
                description: description,
                dueDateTemplate: dueDate == null ? null : this.DUE_DATE.evaluate({ dueDate: dueDate }),
                severity: severity,
                userId: userId,
                ownerId: ownerId,
                projectTemplate: projectId == '' || projectId == null ? null : this.PROJECT_ID.evaluate({ projectId:projectId }),
                ticketId: ticketId,
                entity: entity
     }));
  },
  getUpdateFormRequest: function(sessionId, issueId, statusId, mode, creatorId) {
     return escape(this.UPDATE_ISSUE.evaluate({sessionId: sessionId, issueId: issueId, operation: this.getStateValue(statusId), creatorId: creatorId,  entity: mode ? "Bug" : "Issue" }));
  },
  getLinkTicketResolution: function(widgetId) {
     return this.TICKET_RESOLUTION.evaluate({ widgetId: widgetId });
  },
  setUnLinkIssue: function(sessionId, IssueId) {
     return escape(this.UNLINK_ISSUE.evaluate({sessionId: sessionId, IssueId: IssueId }));
  },
  getIssueFromClarizenQuery: function(sessionId, externalId, mode) {
     return escape(this.GET_ISSUE_QUERY.evaluate({sessionId: sessionId, externalId: externalId, entity: mode ? "Bug" : "Issue" }));
  },
  getAssigneeRequest: function(sessionId) {
     return escape(this.GET_ASSIGNEES.evaluate({ sessionId: sessionId }));
  },
  getSessionRequest: function(username, password, partnerId) {
     return escape(this.GET_SESSION.evaluate({ username: username, password: password, partnerId: partnerId }));
  },
  getLogoImage: function() {
      return  '<img title="Clarizen Logo" alt="Clarizen Logo" src="'+ this.IMAGE_PATH +'logo.jpg"/>';
  },
  getLoadingImage: function() {
      return  '<img title="Indicator" alt="Indicator" src="'+ this.IMAGE_PATH +'indicator.gif"/>';
  },
  getStateValue: function(stateId) {
     switch(stateId) {
        case 1:
            return "Open";
        case 2:
            return "Reject";
        case 3:
            return "MarkResolved";
        default:
           return null;
     }
  },
  getSeverityImage: function(severity) {
     switch(severity) {
        case "High":
           return  '<img height="16" style="display:inline;" title="High" alt="High" src="'+ this.IMAGE_PATH +'High.jpg"/>';
        case "Low":
           return  '<img height="16" style="display:inline;" title="Low" alt="Low" src="'+ this.IMAGE_PATH +'Low.jpg"/>';
        case "Critical":
           return  '<img height="16" style="display:inline;" title="Critical" alt="Critical" src="'+ this.IMAGE_PATH +'Critical.jpg"/>';
        default:
           return "";
     }
  },
  getStateImage: function(state) {
     switch(state) {
        case "Submitted":
        case "Submitted2":
           return '<img height="16" style="display:inline;" title="Submitted" alt="Submitted" src="'+ this.IMAGE_PATH +'Submitted.jpg"/>';
        case "Opened":
           return '<img height="16" style="display:inline;" title="Opened" alt="Opened" src="'+ this.IMAGE_PATH +'Opened.jpg"/>';
        case "In Work":
           return '<img height="16" style="display:inline;" title="In Work" alt="In Work" src="'+ this.IMAGE_PATH +'Inwork.jpg"/>';
        case "Resolved":
           return '<img height="16" style="display:inline;" title="Resolved" alt="Resolved" src="'+ this.IMAGE_PATH +'Resolved.jpg"/>';
        case "Duplicated":
           return '<img height="16" style="display:inline;" title="Duplicated" alt="Duplicated" src="'+ this.IMAGE_PATH +'Duplicated.jpg"/>';
        case "Not Reproducible":
           return '<img height="16" style="display:inline;" title="Not Reproducible" alt="Not Reproducible" src="'+ this.IMAGE_PATH +'Notreproducible.jpg"/>';
        case "Rejected":
           return '<img height="16" style="display:inline;" title="Rejected" alt="Rejected" src="'+ this.IMAGE_PATH +'Rejected.jpg"/>';
        case "Deferred":
           return '<img height="16" style="display:inline;" title="Deferred" alt="Deferred" src="'+ this.IMAGE_PATH +'Deferred.jpg"/>';
        case "Closed":
           return '<img height="16" style="display:inline;" title="Closed" alt="Closed" src="'+ this.IMAGE_PATH +'Closed.jpg"/>';
        default:
           return "";
     }
  },
  convertDateToXSD: function(date) {
     var currentTime = new Date();
     var d = date.split('/');
     var hours = currentTime.getHours() < 10 ? ("0" + currentTime.getHours()) : currentTime.getHours();
     var seconds = currentTime.getSeconds() < 10 ? ("0" + currentTime.getSeconds()) : currentTime.getSeconds();
     var minutes = currentTime.getMinutes() < 10 ? ("0" + currentTime.getMinutes()) : currentTime.getMinutes();
     var gmtHours = -currentTime.getTimezoneOffset() / 60;
     gmtHours = (gmtHours < 0 ? "&#45;" : "&#43;") + (Math.abs(gmtHours) < 10 ? "0" : "") + Math.abs(gmtHours);
     //return d[2] + "-" + d[0] + "-" + d[1] + "T12:00:00Z";
//    alert(gmtHours);
     return d[2] + "-" + d[0] + "-" + d[1] + "T12:00:00" + gmtHours +":00";

  },
  getFormattedDate: function(date) {
     if (date.split('T').length == 1)
        return date;

     var d = date.split('T')[0].split('-');
     var t = date.split('T')[1].split(':');

     var currentTime = new Date();
     var gmtHours = -currentTime.getTimezoneOffset() / 60;

     var parseDate = new Date(d[0],d[1]-1,d[2], parseInt(t[0]), 0, 0);

     //fix a timezone
     parseDate.setHours(parseDate.getHours() + gmtHours + 7 /*Clarizen server location GMT -7 */);

     var day = parseDate.getDate() < 10 ? ("0" + parseDate.getDate()) : parseDate.getDate();
     var month = (parseDate.getMonth() +1) < 10 ? ("0" + parseInt(parseDate.getMonth() + 1)) : parseInt(parseDate.getMonth() + 1);

     return month + "/" + day + "/" + parseDate.getFullYear();
  },
  fixTitle: function(title) {
     return title.length > 20 ? title.substr(0, 20) + '...' : title;
  },
  fixProjectName: function(name) {
     return name.length > 14 ? name.substr(0, 14) + '...' : name;
  },
  getValue: function(document, sValue, rValue) {
      rValue = rValue || null;
      nodes = document.getElementsByTagName("FieldName");
      for (var i = 0; i< nodes.length; i++ ) {
          if (nodes[i].childNodes.length != 0 && sValue == nodes[i].childNodes[0].nodeValue
                        && nodes[i].parentNode != null && this.getTextContentByKey(nodes[i].parentNode,"Value") != '' ) {

             var elem = nodes[i].parentNode.childNodes[1];
             if (elem.attributes && elem.attributes.length ) {
                 for ( var j=0; j< elem.attributes.length; j++ ) {
                    if (elem.attributes[j].nodeName == "i:type" && elem.attributes[j].nodeValue == "GenericEntity") {
                        if (rValue) {
                           return this.getValue(elem, rValue);
                        }
                        else {
                           var v1 = this.getTextContentByKey(elem.childNodes[0],"Value");
                           return v1 == null ? " - " : v1;
                        }
                    }
                 }
             }
             var v = this.getTextContentByKey(nodes[i].parentNode,"Value")
             return v == null ? " - " : v;
          }
      }
      return " - ";
  },
  getTextContentByKey: function(document, key) {
    node = document.getElementsByTagName(key);
    if(typeof node == 'undefined' || node == null || node.length == 0) {
      return null;
    }

    if (Prototype.Browser.IE) {
       if (document.getElementsByTagName(key)[0].childNodes.length ==0)
          return null;

       return document.getElementsByTagName(key)[0].childNodes[0].nodeValue;
    }
    // all other browsers
    return document.getElementsByTagName(key)[0].textContent;
  },
  getNodeValue: function(node) {
    if(typeof node == 'undefined' || node == null) {
      return null;
    }
    if (Prototype.Browser.IE) {
        return node.childNodes[0].nodeValue;
    }
    return node.textContent;
  }
}


/*
 * Clarizen widget
 *
 * Business logic
 */

var ClarizenWidget = Class.create(Widget, {

  initialize: function($super, args) {
    $super(args);
    this.widgetId               = args.id;
    this.username               = args.username;
    this.password               = args.password;
    this.partnerID              = clarizenWidgetHelper.PARTNER_ID;

    this.ticketId               = typeof ticket_id == 'undefined' ? null : ticket_id;
    this.externalId             = this.clarizenUserIdForPopup = this.sessionId = null;
    this.settings               = { enableIssues: false, enableBugs:false };
    this.sendInvitationMail     = args.sendInvitationMail;

    this.formInstance           = null;
    this.contentRoot            = this.containingElement().down("div.clarizen-content" + this.widgetId);

    this.doSelectAssignee       = false;
    this.assigneeXml            = null;
    this.formLoaded             = false;

    this.start();
  },

  /*
   *  initialize - see if it's a ticket or a user
   */
  start: function() {
    this.proxyClarizenLoginUrl = clarizenWidgetHelper.PROXY_URL.evaluate({
      url: clarizenWidgetHelper.SERVICE_URL,
      soapAction: clarizenWidgetHelper.LOGIN_URL
    });
    this.proxyClarizenQueryUrl = clarizenWidgetHelper.PROXY_URL.evaluate({
      url: clarizenWidgetHelper.SERVICE_URL,
      soapAction: clarizenWidgetHelper.QUERY_URL
    });
    this.proxyClarizenExecuteUrl = clarizenWidgetHelper.PROXY_URL.evaluate({
      url: clarizenWidgetHelper.SERVICE_URL,
      soapAction: clarizenWidgetHelper.EXECUTE_URL
    });

    if (this.ticketId) {
        this.showLoading();
      this.getSession(this.getExternalIdByTicketId);
    } else if (this.isUserProfilePage()) {
      this.contentRoot.insert(clarizenWidgetHelper.getLoadingImage());
      this.getSession(
        function() { this.getZendeskUserForProfilePage(document.location.toString().split('/')[4]) }
      );
    }
    else {
      this.contentRoot.update(clarizenWidgetHelper.getMissingTicketMessage());
    }
  },

  isBugMode: function() {
    return this.settings.enableBugs && !this.settings.enableIssues;
  },

  isUserProfilePage: function() {
    return /users/.test(document.location);
  },

  showLoading: function() {
    this.contentRoot.insert(clarizenWidgetHelper.getLoadingImage());
  },

  /*
   * Shows the default widget, allowing the user to create an issue in Clarizen etc.
   *
   * The modal form is included, but invisible
   */
  showInitial: function() {
    this.contentRoot.update(
      clarizenWidgetHelper.getSubmitForm(this.widgetId) +
      clarizenWidgetHelper.getLinkTicketResolution(this.widgetId)
    );
  },

  failLoading: function() {
    $('resolutionLnk').update(clarizenWidgetHelper.getLinkTicketResolution(this.widgetId));
  },

  /*
   * Shows the modal form
   */
  showForm: function() {
    $('lv_overlay').show();
    $('clarizen-modal-popup').show();
    if (!this.formLoaded) {
      this.getCurrentTicket();
      this.retrieveProjects();
      this.retrieveAssignees();
    }
  },

  /*
   * Starts a new Clarizen API session, optionally calls callFunction on success
   */
  getSession: function(callFunction) {
    var callFunction = callFunction || null;
    var self = this;

    var doc = "body=" + clarizenWidgetHelper.getSessionRequest(this.username, this.password, this.partnerID);
    new Ajax.Request(this.proxyClarizenLoginUrl, {
      method: 'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        this.extractSessionID(transport.responseXML, callFunction);
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert('Login has Failed');
      }
    });
  },

  extractSessionID: function(responseXML, callFunction) {
    var arr = XmlHelper.getRecords(responseXML, '', "LoginResult");
    if (arr.length == 1) {
      this.sessionId = arr[0].childNodes[4].firstChild.nodeValue;
      this.organizationId = arr[0].childNodes[2].firstChild.nodeValue;
      this.userId = arr[0].childNodes[5].firstChild.nodeValue;

      if (callFunction) {
        callFunction.apply(this);
      }
    } else {
      var faultString = clarizenWidgetHelper.getTextContentByKey(responseXML, 'faultstring');
      this.failLoading();
      if (faultString != null && faultString != '')
        alert(faultString);
      else
        alert('Login has Failed');
    }
  },

  checkSessionTimeOut: function(responseXML) {
    return clarizenWidgetHelper.getTextContentByKey(responseXML, 'faultcode') == 's:SessionTimeout';
  },

  /*
   * Search for bugs and issues containing the Zendesk ticket ID
   */
  getExternalIdByTicketId: function() {
    var doc = "body=" + clarizenWidgetHelper.getExternalIdByTicketRequest(this.sessionId, this.ticketId);

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
            this.getSession(this.getExternalIdByTicketId);
            return false;
        }
        var typeName = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "TypeName");
        if (typeName == "Bug")
            this.settings.enableBugs = true;
        this.externalId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Value");

        if (!this.externalId) {
          this.showInitial();
        } else {
          this.getIssueFromClarizen(true);
        }
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to get Projects List");
      }.bind(this)
    });
  },

  /*
   * Retieve a list of projects from Clarizen. Called when the modal form opens
   */
  retrieveProjects: function() {
    var doc = "body=" + clarizenWidgetHelper.getProjects(this.sessionId);

    new Ajax.Request(this.proxyClarizenQueryUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(this.retrieveProjects);
          return false;
        }
        this.populateProjects(this.contentRoot, transport.responseXML);
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to get Projects List");
      }.bind(this)
    });
  },

  populateProjects:  function(contentRoot, responseXML) {
    var hasProjects = false;
    ddlProjects = contentRoot.down("select.clarizenProjectSelector");
    ddlProjects.disabled = false;
    ddlProjects.update("");

    try {
      this.ProjectsList = XmlHelper.getRecords(responseXML, "", "BaseEntity");
      if (this.ProjectsList.length > 0)
        hasProjects = true;
    } catch (e) {
    }

    if (!hasProjects) {
      var defaultOption = new Element("option");
      defaultOption.value = "";
      defaultOption.innerHTML = "";
      defaultOption.selected = true;
      ddlProjects.appendChild(defaultOption);
      return;
    }

    var defaultOption = new Element("option");
    defaultOption.value = "";
    defaultOption.innerHTML = "—select the project—";
    defaultOption.selected = true;
    ddlProjects.appendChild(defaultOption);

    for (var i=0; i<this.ProjectsList.length; i++)
    {
      var option = new Element("option");
      option.value = XmlHelper.encodeString(clarizenWidgetHelper.getTextContentByKey(this.ProjectsList[i].childNodes[0], 'Value'));
      option.innerHTML = XmlHelper.encodeString(clarizenWidgetHelper.getTextContentByKey(this.ProjectsList[i].childNodes[1], 'Value'));
      ddlProjects.appendChild(option);
    }
  },

  /*
   * Retrieve a list of assignees from Clarizen. Called when the model form opens
   */
  retrieveAssignees: function() {
    var doc = "body=" + clarizenWidgetHelper.getAssigneeRequest(this.sessionId);

    new Ajax.Request(this.proxyClarizenQueryUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(this.retrieveAssignees);
          return false;
        }
        this.populateAssignees(this.contentRoot, transport.responseXML);
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to get Assignee List");
      }.bind(this)
    });
  },

  populateAssignees: function(contentRoot, responseXML) {
    ddlAssignees = contentRoot.down("select.clarizenAssigneeSelecter");
    ddlAssignees.update("");

    var defaultOption = new Element("option");
    defaultOption.value = "-1";
    defaultOption.innerHTML = "—select an assignee—";
    defaultOption.selected = true;

    ddlAssignees.appendChild(defaultOption);

    this.assigneeXml = responseXML;
    this.AssigneesList = XmlHelper.getRecords(responseXML, "", "BaseEntity");

    for (var i=0; i<this.AssigneesList.length; i++)
    {
      var value = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[0], 'Value');
      if (value) {
        var option = new Element("option");
        option.value = value;
        option.innerHTML = XmlHelper.encodeString(clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[1], 'Value'));
        ddlAssignees.appendChild(option);
      }
    }
    ddlAssignees.disabled = false;
    if (this.doSelectAssignee)
      this.selectAssignee();
    else
      this.doSelectAssignee = true;
  },

  selectSeverity: function(priorityId) {
    var select  = this.contentRoot.down('select.clarizenSeveritySelector');

    switch(parseInt(priorityId)) {
      case 4:
        select.options[0].selected = true;//Critical
        break;
      case 3:
        select.options[1].selected = true;//High
        break;
      case 1:
        select.options[2].selected = true;//Low
        break;
      default:
        select.options[3].selected = true;//Medium
        break;
    }
  },

  /*
   * Selects an assignee from the list of assignees, based on either email or name (prioritized)
   */
  selectAssignee: function() {
    var nFirstMatchByName = null;
    var nFirstMatchByEmail = null;

    if (this.ZendeskTicketAssigneeEmail) {
      this.AssigneesList = XmlHelper.getRecords(this.assigneeXml, "", "BaseEntity");

      for (var i=0; i<this.AssigneesList.length; i++)
      {
        var userId = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[0], 'Value');
        var firstName = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[1].childNodes[0], 'Value');
        var lastName = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[1].childNodes[1], 'Value');
        var email = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[1].childNodes[2], 'Value');

        if (!nFirstMatchByName && firstName && lastName && this.ZendeskTicketAssigneeFullname == firstName + ' ' + lastName)
          nFirstMatchByName = i+1;

        if (!nFirstMatchByEmail && email && this.ZendeskTicketAssigneeEmail == email)
          nFirstMatchByEmail = i+1;
      }

      var assignees = this.contentRoot.down("select.clarizenAssigneeSelecter");
      if (nFirstMatchByEmail)
        assignees.selectedIndex = nFirstMatchByEmail;
      else if (nFirstMatchByName)
        assignees.selectedIndex = nFirstMatchByName;
    }
  },

  /*
   * Retrieve the current Zendesk ticket. Used for the email link and check availability link
   */
  getCurrentTicket: function() {
    var url = "/tickets/" + this.ticketId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(this.getCurrentTicket);
          return false;
        }

        this.contentRoot.down('input.clarizenTitle').setValue(clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "subject"));
        this.contentRoot.down('textarea.clarizenDescription').setValue(clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "description"));
        this.selectSeverity(clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "priority-id"));

        this.ticketRequesterId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "requester-id");
        this.ticketAssigneeId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "assignee-id");
        this.ticketStatusId = parseInt(clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "status-id"));

        this.getTicketAssignee(this.ticketAssigneeId);
        this.getTicketOwner(this.ticketRequesterId);
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to retrieve a ticket from Zendesk.");
      }.bind(this)
    });
  },

  /*
   * Get user details for the user assigned to the ticket.
   */
  getTicketAssignee: function(userId) {
    // the ticket might not be assigned
    if (!userId)
      return;

    var url = "/users/" + userId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession( function() { this.getTicketAssignee(userId) } );
          return false;
        }
        this.ZendeskTicketAssigneeEmail = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "email");
        this.ZendeskTicketAssigneeFullname = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "name");
        if (this.doSelectAssignee)
          this.selectAssignee();
        else
          this.doSelectAssignee = true;
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to retrieve a Zendesk User. (3)");
      }.bind(this)
    });
  },

  getTicketOwner: function(userId) {
    var url = "/users/" + userId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getTicketOwner(userId) } );
          return false;
        }
        this.zendeskTicketRequesterEmail = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "email");
        this.zendeskTicketRequesterFullName = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "name");
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to retrieve a Zendesk User. (4)");
      }.bind(this)
    });
  },

  /*
   * Form validation before submit.
   */
  validateForm: function() {
    if (this.contentRoot.down('input.clarizenTitle').getValue() == '') {
       alert('Subject is required');
       return false;
    }
    if (this.contentRoot.down('textarea.clarizenDescription').getValue() == '') {
       alert('Description is required');
       return false;
    }
    if (this.contentRoot.down('select.clarizenAssigneeSelecter').getValue() == '-1') {
       alert('Please select an assignee');
       return false;
    }

    //Date format only mm/dd/yyyy
    var date = this.contentRoot.down('input.dueDate').getValue();
    if (date != '' && !/^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/.test(date)) {
       alert('Invalid date, must be in MM/DD/YYYY format');
       return false;
    }

    // Validate that the selected due date is today or later
    var selected_date = new Date()
    selected_date.setFullYear(date.split('/')[2],parseInt(date.split('/')[0]) - 1,parseInt(date.split('/')[1]));
    if (selected_date < new Date()) {
      alert('Please select a due date in the future.');
      return false;
    }

    if (this.contentRoot.down('select.clarizenAssigneeSelecter').getValue() == '-1' && !this.ticketAssigneeId) {
       alert('This ticket is currently unassigned. Please select an assignee from the list');
       return false;
    }

    $('lv_overlay').hide();
    $('clarizen-modal-popup').hide();
    return true;
  },

  submitIssueForm: function(form) {
    if (this.validateForm()) {
      this.contentRoot.down('div.clarizen_message').hide();
      this.showLoading();
      this.prepareForSubmit();
    }
  },

  /*
   * Check the system settings in Clarizen
   */
  prepareForSubmit: function() {
    var doc = "body=" + clarizenWidgetHelper.getSystemSettings(this.sessionId);

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(this.prepareForSubmit);
          return false;
        }
        if (clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Success") == "false") {
          alert("Failed to get Clarizen Settings ( " + clarizenWidgetHelper.getTextContentByKey(transport.responseXML, 'faultstring') + " )");
        }
        else {
          var settings = XmlHelper.getRecords(transport.responseXML, "", "anyType");
          this.settings.enableIssues = clarizenWidgetHelper.getNodeValue(settings[0]) == "true" ? true: false;
          this.settings.enableBugs = clarizenWidgetHelper.getNodeValue(settings[1]) == "true" ? true: false;
          if(!this.settings.enableBugs && !this.settings.enableIssues) {
              this.failLoading();
              alert("Issues and Bugs are disabled in this organization.");
          }
          else {
            this.checkRequesterInClarizen();
          }
        }
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to get Clarizen Settings.");
      }.bind(this)
    });
  },

  /*
   * Get user details for the user assigned to the ticket.
   */
  checkRequesterInClarizen: function() {
    this.requestorId = null;
    // Check if user (ticket requester) exists in Clarizen
    if (this.zendeskTicketRequesterFullName) {
      var firstNameRequester = this.zendeskTicketRequesterFullName.split(' ')[0];
      for (var i=0; i<this.AssigneesList.length; i++)   {
        var clarizenUserFirstName = XmlHelper.encodeString(clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[1], 'Value'));
        var value = clarizenWidgetHelper.getTextContentByKey(this.AssigneesList[i].childNodes[0], 'Value');

        if (clarizenUserFirstName == firstNameRequester && value && this.requestorId == null) {
            this.requestorId = value;
        }
      }
    }

    // Create new user in Clarizen (if needed)
    if (this.requestorId == null) {
      this.createUserInClarizen();
    } else {
      this.submitIssue();
    }
  },

  /*
   * Submit the issue to Clarizen
   */
  submitIssue: function() {
    var assigneeId = this.contentRoot.down("select.clarizenAssigneeSelecter").getValue();
    var dueDate = this.contentRoot.down('input.dueDate').getValue();
    var doc = "body=" + clarizenWidgetHelper.getSubmitFormRequest(
      this.sessionId,
      clarizenWidgetUnicodeConverter.convertChar2CP(this.contentRoot.down('input.clarizenTitle').getValue()),
      clarizenWidgetUnicodeConverter.convertChar2CP(this.contentRoot.down('textarea.clarizenDescription').getValue()),
      dueDate == '' ? null : clarizenWidgetHelper.convertDateToXSD(dueDate),
      this.contentRoot.down('select.clarizenSeveritySelector').getValue(),
      assigneeId,
      this.contentRoot.down("select.clarizenProjectSelector").getValue(),
      assigneeId,
      this.ticketId,
      this.isBugMode()
    );

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession( function() { this.submitIssue() } );
          return false;
        }
        if (clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Success") == "false") {
          this.failLoading();
          alert("An error occured. (" + clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Message") + ")");
        }
        else {
          this.externalId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Value");
          //Check ticket status
          if (this.ticketStatusId == 0) {
             this.getIssueFromClarizen(true);
          }
          else {
             this.changeIssueStatus();
          }
        }
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to submit issue.");
      }.bind(this)
    });
  },

  /*
   * These settings could not be set on create time, so update the issue in Clarizen
   */
  changeIssueStatus: function() {
    var doc = "body=" + clarizenWidgetHelper.getUpdateFormRequest(
      this.sessionId,
      this.externalId,
      this.ticketStatusId,
      this.isBugMode(),
      this.requestorId
    );

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(this.changeIssueStatus);
          return false;
        }
        if (clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Success") == "false") {
          this.failLoading();
          alert("An error occured. (" + clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Message") + ")");
        } else {
          this.getIssueFromClarizen(true);
        }
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to submit issue.");
      }.bind(this)
    });
  },

  createUserInClarizen: function() {
    var fullName = this.zendeskTicketRequesterFullName.split(' ');
    var doc = "body=" + clarizenWidgetHelper.getCreateUserRequest(
      this.sessionId,
      this.zendeskTicketRequesterEmail,
      fullName.length == 1 ? fullName : fullName[0],
      fullName.length == 1 ? fullName : fullName[1],
      this.sendInvitationMail
    );

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.createUserInClarizen() });
          return false;
        }
        if (clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Success") == "false") {
          this.failLoading();
          alert("Failed to create User In Clarizen.\n An error occured. (" + clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Message") + ")");
        } else {
          this.requestorId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Value");
          this.submitIssue();
        }
      }.bind(this),
      onFailure: function() {
        this.failLoading();
        alert("Failed to create User In Clarizen.");
      }.bind(this)
    });
  },

  getIssueFromClarizen: function() {
    var doc = "body=" + clarizenWidgetHelper.getIssueFromClarizenQuery(this.sessionId, this.externalId, this.isBugMode() );
    this.contentRoot.update(clarizenWidgetHelper.getLoadingImage());

    new Ajax.Request(this.proxyClarizenExecuteUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getIssueFromClarizen(insertRequired) } );
          return false;
        }
        this.parseIssueResponse(transport.responseXML);
      }.bind(this),
      onFailure: function() {
        if (!insertRequired)
          this.contentRoot.update("");
        alert("Failed to get Issue From Clarizen");
      }.bind(this)
    });
  },

  parseIssueResponse: function(responseXML) {
    var success = clarizenWidgetHelper.getTextContentByKey(responseXML, "Success");

    if (success != null && success != "false" ) {
      // issue exists in Clarizen
      this.IssueSysId = clarizenWidgetHelper.getValue(responseXML, "SYSID");
      this.IssueDueDate = clarizenWidgetHelper.getValue(responseXML, "DueDate");
      this.IssueState = clarizenWidgetHelper.getValue(responseXML, "State");
      this.IssueSeverity = clarizenWidgetHelper.getValue(responseXML, "Severity");

      this.IssuezendeskTicketId =  clarizenWidgetHelper.getValue(responseXML, "ZdExternalId");

      this.IssueAssignedTo =  clarizenWidgetHelper.getValue(responseXML, "AssignedTo", "DisplayName");
      this.IssueOwner = clarizenWidgetHelper.getValue(responseXML, "Owner", "DisplayName");
      this.IssueProjectName = clarizenWidgetHelper.getValue(responseXML, "PlannedFor", "Name");
      this.IssueTitle = clarizenWidgetHelper.getValue(responseXML, "Title");

      this.getZendeskTicket(this.IssuezendeskTicketId);
    } else {
      // issue doesn't exist in Clarizen
      var err = clarizenWidgetHelper.getTextContentByKey(responseXML, "ErrorCode");
      if (err != null && err == "EntityNotFound") {
         $('issuePlaceHolder').update("");
         this.externalId = null;
         this.showInitial();
      } else {
         // an error occured
         $('issue-details').update("");
         var faultstring = clarizenWidgetHelper.getTextContentByKey(responseXML, "faultstring");
         alert(faultstring != null ? faultstring : "Failed to parse Issue response");
      }
    }
  },

  getZendeskTicket: function(ticketId) {
    var url = "/tickets/" + ticketId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getZendeskTicket(ticketId) });
          return false;
        }
        this.ZendeskUserIdAssigneeId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "assignee-id");
        var ownerId = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "requester-id");

        this.getZendeskOwner(ownerId);
      }.bind(this),
      onFailure: function() {
        alert("Failed to retrieve a ticket from Zendesk.");
      }
    });

  },

  getZendeskOwner: function(userId) {
    var url = "/users/" + userId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getZendeskOwner(userId) } );
          return false;
        }
        this.ZendeskUserOwnerEmail = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "email");

        if (this.ZendeskUserIdAssigneeId) {
          this.getZendeskUserForProfilePage(this.ZendeskUserIdAssigneeId);
        } else {
          this.updateIssueDetails();
        }
      }.bind(this),
      onFailure: function() {
        alert("Failed to retrieve a Zendesk User. (1)");
      }
    });
  },

  updateIssueDetails: function() {
    this.contentRoot.update(clarizenWidgetHelper.getDisplayIssueForm(this.widgetId, this.IssueSeverity,
      this.IssueSysId, this.IssueState,
      this.IssueDueDate, this.IssueAssignedTo,
      this.IssueOwner, this.IssueProjectName,
      this.IssueTitle,
      clarizenWidgetUnicodeConverter.convertChar2CP(encodeURIComponent(this.IssueTitle)),
      window.ticket_id,
      this.ZendeskUserAssigneeEmail,
      this.ZendeskUserOwnerEmail
    ) + clarizenWidgetHelper.getAvailabilityLnk(this.widgetId) );
  },

  updateIssueForm: function() {
    this.contentRoot.update(clarizenWidgetHelper.getDisplayIssueForm(this.IssueSeverity,
      this.IssueSysId, this.IssueState, this.IssueDueDate,
      this.IssueAssignedTo, this.IssueOwner,
      this.IssueProjectName, this.IssueTitle
    ) + clarizenWidgetHelper.getAvailabilityLnk(this.widgetId));
  },

/*
  // currently disabled until 2nd iteration
  unLinkIssue: function(value) {
    if (confirm('Please confirm')) {
      var doc = "body=" + clarizenWidgetHelper.setUnLinkIssue(this.sessionId, this.externalId);

      this.showLoading();
      new Ajax.Request(this.proxyClarizenExecuteUrl, {
        method:'post',
        postBody: doc,
        requestHeaders: { Accept: 'application/xml' },
        onSuccess: function(transport) {
          if (this.checkSessionTimeOut(transport.responseXML)) {
            this.getSession(function() { this.unLinkIssue(value) });
            return false;
          }
          var success = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Success");
          if (success != null && success != "false" ) {
            $('issuePlaceHolder').update("");
            this.externalId = null;
            this.execute();
          }
        }.bind(this),
        onFailure: function() {
          alert("Failed to unlink Issue");
        }
      });
    }
  },
*/

  getZendeskUserForProfilePage: function(userId) {
    var url = "/users/" + userId + ".xml";

    new Ajax.Request(url, {
      method:'get',
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getZendeskUserForProfilePage(userId) } );
          return false;
        }
        this.ZendeskUserAssigneeEmail = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "email");
        var fullName = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "name").split(' ');

        if (fullName.length == 1) {
          alert("User '" + fullName + "' doesn't have a Last Name, cannot search user in Clarizen");
        } else {
          this.getClarizenUserProfile(this.ZendeskUserAssigneeEmail, fullName[0], fullName[1]);
        }
        if (!this.isUserProfilePage())
          this.updateIssueDetails();
      }.bind(this),
      onFailure: function() {
        if (this.isUserProfilePage())
          this.contentRoot.update("");
        alert("Failed to retrieve a Zendesk User. (2)");
      }.bind(this)
    });
  },

  getClarizenUserProfile: function(email, firstName, lastName) {
    var doc = "body=" + clarizenWidgetHelper.getClarizenUserProfile(this.sessionId, email, firstName, lastName);

    new Ajax.Request(this.proxyClarizenQueryUrl, {
      method:'post',
      postBody: doc,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (this.checkSessionTimeOut(transport.responseXML)) {
          this.getSession(function() { this.getClarizenUserProfile(email, firstName, lastName) } );
          return false;
        }
        this.clarizenUserIdForPopup = clarizenWidgetHelper.getTextContentByKey(transport.responseXML, "Value");

        if (this.isUserProfilePage())
          this.contentRoot.update(
          this.clarizenUserIdForPopup == null ?
          clarizenWidgetHelper.getProfileNotExistsMessage(firstName) :
          clarizenWidgetHelper.getAvailabilityLnkForProfilePage(this.widgetId)
        );
      }.bind(this),
      onFailure: function() {
        if (this.isUserProfilePage())
        this.contentRoot.update("");
        alert("Failed to get Clarizen User Profile");
      }.bind(this)
    });
  },

  checkAvailability: function() {
    if (this.clarizenUserIdForPopup)
      this.showPopup(this.clarizenUserIdForPopup);
    else {
      alert("Cannot find a user in Clarizen");
    }
  },

  showPopup: function(userId) {
    var title = !Prototype.Browser.IE ? "Clarizen.com - Resource Calendar View" : "_blank";
    window.open("https://app.clarizen.com/Clarizen/Pages/Calendar/ResourceCalendarView.aspx?u=" + userId, title ,"resizable=1,scrollbars=1,width=900,height=600");
  }

});

var FreshbooksHelper = {
  STAFF_FORM: new Template(
    '<form class="form" id="freshbooks-form-#{widget_id}">' +
    ' <label>Select user</label><select name="staff_id" id="freshbooks-form-staff-id" onchange="ObjectFactory.get(#{widget_id}).staffSelector(this.options[this.selectedIndex].value)"></select>' +
    ' <br/><input type="submit" disabled="disabled" id="freshbooks-submit" value="Sign in" onclick="ObjectFactory.get(#{widget_id}).saveStaffId($(\'freshbooks-form-#{widget_id}\'));return false;">' +
    '</form>'
  ),

  SUBMIT_FORM: new Template(
    '<form class="form" id="freshbooks-form-#{widget_id}">' +
    ' <label>Select project</label><select name="project_id" id="freshbooks-form-projects" onchange="ObjectFactory.get(#{widget_id}).projectsSelector(this.options[this.selectedIndex].value)"></select> <img src="/images/indicator2.gif" id="freshbooks-project-spinner" style="display:none;" />' +
    ' <label>Select task</label><select disabled name="task_id" id="freshbooks-form-tasks" onchange="ObjectFactory.get(#{widget_id}).tasksSelector(this.options[this.selectedIndex].value)"></select> <img src="/images/indicator2.gif" id="freshbooks-task-spinner" style="display:none;" />' +
    ' <label>Notes</label><textarea disabled name="freshbooks-form-notes" id="freshbooks-form-notes" wrap="virtual" style="width:190px; height: 50px;">#{default_note}</textarea>' +
    ' <label>Hours</label><input type="text" disabled name="freshbooks-form-hours" id="freshbooks-form-hours" style="width:50px">' +
    ' <br/><input type="submit" disabled id="freshbooks-form-submit" style="margin-top: 10px;" value="Submit" onclick="ObjectFactory.get(#{widget_id}).submitTimeSpent($(\'freshbooks-form-#{widget_id}\'));return false;">' +
    ' <span class="link" style="font-weight:normal;margin-left:20px;" onclick="ObjectFactory.get(#{widget_id}).logout()">(logout)</span>' +
    '</form>'
  ),

  GET_TASKS: new Template(
    '<?xml version="1.0" encoding="utf-8"?>'+
    ' <request method="task.list" >' +
    '   <project_id>#{project_id}</project_id>' +
    ' </request>'
  ),

  GET_STAFF: new Template(
    '<?xml version="1.0" encoding="utf-8"?>' +
    '<request method="staff.list">' +
    '</request>'
  ),

  POST_HOURS: new Template(
    '<?xml version="1.0" encoding="ISO-8859-1"?>' +
    '<request method="time_entry.create">' +
    '  <time_entry>' +
    '    <project_id>#{project_id}</project_id>' +
    '    <task_id>#{task_id}</task_id>' +
    '    <hours>#{hours}</hours>' +
    '    <notes><![CDATA[#{notes}]]></notes>' +
    '    <staff_id>#{staff_id}</staff_id>' +
    '  </time_entry>' +
    '</request>'
  ),

  ERROR_MESSAGE: new Template(
    'An error occured:' +
    '\n\n#{error}\n\n' +
    'See https://support.zendesk.com/forums/25876 for troubleshooting\n\n'+
    'or contact Freshbooks support at support@freshbooks.com'
  ),

  getTasksRequest: function(project_id) {
    return escape(this.GET_TASKS.evaluate({ project_id: project_id }));
  },

  getProjectsRequest: function(page) {
    var template = new Template(
      'body=<?xml version="1.0" encoding="utf-8"?>' +
      '<request method="project.list">' +
      '  <page>#{page}</page>' +
      '  <per_page>100</per_page>' +
      '</request>'
    );

    return template.evaluate({ page: page });
  },

  getClientsRequest: function(page) {
    var template = new Template(
      'body=<?xml version="1.0" encoding="utf-8"?>' +
      '<request method="client.list">' +
      '  <page>#{page}</page>' +
      '  <per_page>100</per_page>' +
      '</request>'
    );

    return template.evaluate({ page: page })
  },

  postHoursRequest: function(staff_id, project_id, task_id, notes, hours) {
    return escape(this.POST_HOURS.evaluate({ staff_id: staff_id, project_id: project_id, task_id: task_id, notes: notes, hours: hours }));
  },

  getStaffRequest: function() {
    return escape(this.GET_STAFF.evaluate({}));
  },

  buildClientList: function(clients) {
    var clientList = {};

    clients.each(function(client) {
      var id           = $j(client).find('client_id').text();
      var organization = $j(client).find('organization').text();

      clientList[id] = organization;
    });

    return clientList;
  },

  populateProjects: function(projects, clients) {
    var select = $j('#freshbooks-form-projects');

    select.text('');
    select.append($j('<option>'));

    projects.each(function(project) {
      var option    = $j('<option>');
      var name      = $j(project).find('name').text();
      var client_id = $j(project).find('client_id').text();
      var client    = clients[client_id];

      if (client) {
        name = name + ' - ' + client;
      }

      option.attr('value', $j(project).find('project_id').text());
      option.text(name);

      select.append(option);
    });

    $j('#freshbooks-project-spinner').hide();
  },

  populateStaff: function(records) {
    $('freshbooks-form-staff-id').update("");
    $('freshbooks-form-staff-id').appendChild(new Element("option"));

    for (i=0; i<records.length; i++) {
      var option = new Element("option");

      option.value = FreshbooksHelper.getNodeString(records[i], "staff_id");
      option.innerHTML = FreshbooksHelper.getNodeString(records[i], "first_name") + ' ' + FreshbooksHelper.getNodeString(records[i], "last_name");
      $('freshbooks-form-staff-id').appendChild(option);
    }
  },

  populateTasks: function(records) {
    $('freshbooks-form-tasks').update("");
    $('freshbooks-form-tasks').appendChild(new Element("option"));

    for (i=0; i<records.length; i++) {
      var option = new Element("option");

      option.value = FreshbooksHelper.getNodeString(records[i], "task_id");
      option.innerHTML = FreshbooksHelper.getNodeString(records[i], "name");
      $('freshbooks-form-tasks').appendChild(option);
    }

    $j('#freshbooks-task-spinner').hide();
    $('freshbooks-form-tasks').enable();
  },

  getNodeValue: function(document, nodeName) {
    var node = document.getElementsByTagName(nodeName);
    if(node == null || node.length == 0) {
      return null;
    }

    if(node[0].childNodes[0] == null) {
      return "";
    }

    return node[0].childNodes[0].nodeValue;
  },


  getNodeString: function(document, nodeName) {
    return this.getNodeValue(document, nodeName) || '';
  },

  getNodeAttribute: function(document, nodeName, attr) {
    var node = document.getElementsByTagName(nodeName);
    if (node == null || node.length == 0) {
      return null;
    }

    return node[0].getAttribute(attr) || null;
  },

  getRecords: function(document, tagname) {
    return document.getElementsByTagName(tagname) || new Array();
  },

  getCookie: function(name) {
    if (document.cookie.length > 0) {
      var c_start = document.cookie.indexOf(name + "=");
      if (c_start != -1) {
        c_start = c_start + name.length + 1;
        var c_end = document.cookie.indexOf(";", c_start);
        if (c_end == -1) {
          c_end = document.cookie.length;
        }
        return unescape(document.cookie.substring(c_start, c_end));
      }
    }
    return "";
  },

  setCookie: function(name, value, expiredays) {
    var exdate = new Date();
    exdate.setDate(exdate.getDate() + expiredays);
    document.cookie = name + "=" + escape(value)+((expiredays==null) ? "" : ";expires=" + exdate.toGMTString());
  },

  checkStatus: function(xml) {
    var records = FreshbooksHelper.getRecords(xml, "response");
    if (records.length > 0) {
      var text = FreshbooksHelper.getNodeString(records[0], "error");
      if (text != "") {
        alert(FreshbooksHelper.ERROR_MESSAGE.evaluate({error: text}));
        return false;
      }
    }
    return true;
  }
}

var FreshbooksWidget = Class.create(Widget, {

  initialize: function($super, args) {
    $super(args);
    this.url = args.url;
    this.formInstance = null;
    this.ticket_id = args.ticket_id;
    this.default_note = args.default_note;
    this.apikey = args.apikey;
    this.staff_id = '';
    this.subject = args.subject;
    this.contentRoot = this.containingElement().down('div.freshbooks-content');
    this.clients = null;
    this.apiUrl = this.buildApiUrl();

    this.setup();
  },

  setup: function() {
    this.staff_id = FreshbooksHelper.getCookie("freshbooks_staff_id");
    if (this.staff_id == "") {
      this.contentRoot.update(FreshbooksHelper.STAFF_FORM.evaluate({widget_id: this.id}));
      this.loadStaff();
    } else
      this.buildForm();
  },

  saveApiKey: function(submittedForm) {
    this.formInstance = submittedForm;
    FreshbooksHelper.setCookie("freshbooks_api_key", $('freshbooks-form-apikey').value, 14);
    this.setup();
  },

  logout: function() {
    FreshbooksHelper.setCookie("freshbooks_staff_id", "", 180);
    this.staff_id = '';
    this.setup();
  },

  buildForm: function() {
    var default_note = this.defaultNote();
    this.contentRoot.update(FreshbooksHelper.SUBMIT_FORM.evaluate({widget_id: this.id, default_note: default_note, ticket_id: this.ticket_id }));
    this.loadClients(1, []);
  },

  buildApiUrl: function() {
    var path = '/proxy/direct?basic_user=#{token}&basic_pass=x&url=#{url}'
    var template = new Template(path);

    return template.evaluate({token: this.apikey, url: this.url })
  },

  // DEPRECATED #{ticket_id} is supported for backwards compatibility only.
  // {{ticket.id}} and {{ticket.title}} are supported per the custom
  // widget placeholder documentation.
  defaultNote: function() {
    var ticket = { id: this.ticket_id, title: this.subject };
    var object = { ticket: ticket, ticket_id: this.ticket_id };

    return Zd.Util.parseTemplate(this.default_note, object);
  },

  loadStaff: function() {
    var body = "body=" + FreshbooksHelper.getStaffRequest();

    new Ajax.Request(this.apiUrl, {
      method: 'post',
      postBody: body,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (FreshbooksHelper.checkStatus(transport.responseXML)) {
          this.showStaff(transport.responseXML);
        }
      }.bind(this),
      onFailure: function() {
        alert("Failed to retrieve Freshbooks staff list.");
      }
    });
  },

  loadClients: function(page, clients) {
    var body = FreshbooksHelper.getClientsRequest(page);
    var self = this;

    $j.post(this.apiUrl, body, function(data) {
      self.handleLoadClients(clients, data);
    });
  },

  handleLoadClients: function(clients, data) {
    var page  = parseInt($j(data).find('clients').attr('page'));
    var pages = parseInt($j(data).find('clients').attr('pages'));

    $j.merge(clients, $j(data).find('client'));

    if (page < pages) {
      this.loadClients(page + 1, clients);
    } else {
      this.clients = FreshbooksHelper.buildClientList(clients);
      this.loadProjects(1, []);
    }
  },

  loadProjects: function(page, projects) {
    var body = FreshbooksHelper.getProjectsRequest(page);
    var self = this;

    $j('#freshbooks-project-spinner').show();

    $j.post(this.apiUrl, body, function(data) {
      self.handleLoadProjects(projects, data);
    });
  },

  handleLoadProjects: function(projects, data) {
    var page  = parseInt($j(data).find('projects').attr('page'));
    var pages = parseInt($j(data).find('projects').attr('pages'));

    $j.merge(projects, $j(data).find('project'));

    if (page < pages) {
      this.loadProjects(page + 1, projects);
    } else {
      FreshbooksHelper.populateProjects(projects, this.clients);
    }
  },

  projectsSelector: function(id) {
    this.loadTasks(id);
  },

  staffSelector: function(id) {
    if ($('freshbooks-form-staff-id').value > "") {
      $('freshbooks-submit').enable();
    } else {
      $('freshbooks-submit').disable();
    }
  },

  saveStaffId: function(submitted_form) {
    FreshbooksHelper.setCookie("freshbooks_staff_id", $('freshbooks-form-staff-id').value, 180);
    this.setup();
  },

  loadTasks: function(project_id) {
    var body = "body=" + FreshbooksHelper.getTasksRequest(project_id)

    $j('#freshbooks-form-tasks').text('');
    $j('#freshbooks-task-spinner').show();

    new Ajax.Request(this.apiUrl, {
      method:'post',
      postBody: body,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (FreshbooksHelper.checkStatus(transport.responseXML)) {
          this.showTasks(transport.responseXML);
        }
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }
    });
  },

  tasksSelector: function(id) {
    $('freshbooks-form-notes').enable();
    $('freshbooks-form-hours').enable();
    $('freshbooks-form-submit').enable();
  },

  showStaff: function(xml) {
    if ($('freshbooks-form-staff-id').options.length == 0) {
      var records = FreshbooksHelper.getRecords(xml, "member");
      FreshbooksHelper.populateStaff(records);
    }
  },

  showTasks: function(xml) {
    var records = FreshbooksHelper.getRecords(xml, "task");
    FreshbooksHelper.populateTasks(records);
  },

  submitTimeSpent: function(submittedForm) {
    var hours = parseFloat(submittedForm['freshbooks-form-hours'].value);
    if (isNaN(hours)) {
      alert('Please enter a valid value for hours');
      return false;
    }

    var body = "body=" + FreshbooksHelper.postHoursRequest(this.staff_id, $('freshbooks-form-projects').value, $('freshbooks-form-tasks').value, $('freshbooks-form-notes').value, $('freshbooks-form-hours').value);

    new Ajax.Request(this.apiUrl, {
      method:'post',
      postBody: body,
      requestHeaders: { Accept: 'application/xml' },
      onSuccess: function(transport) {
        if (FreshbooksHelper.checkStatus(transport.responseXML)) {
          this.resetForm();
        }
      }.bind(this),
      onFailure: function() {
        alert("Failed");
      }
    });

    return false;
  },

  resetForm: function() {
    var default_note = this.defaultNote();

    $('freshbooks-form-hours').value = '';
    $('freshbooks-form-notes').value = default_note;
    $('freshbooks-form-notes').focus();
  }
});

var LogMeInWidget = Class.create(Widget, {
  initialize: function($super, args) {
    $super(args);
    this.SSOID = args.SSOID;
    this.SSOpwd = args.SSOpwd;
    this.SSOcompid = args.SSOcompid;
    this.ticket_id = args.ticket_id ? args.ticket_id : '';
    this.ticket_token = args.token ? args.token : '';
    this.widget_id = args.widget_id;
    this.secret = args.secret;
    this.subdomain = args.subdomain;
    this.cfield_values = args.cfield_values;
    this.newTicket = args.newTicket;
    logmein = this;
    this.setExternalLink();
    this.getLoginTicket();
  },

  isError: function(error, errorArray) {
    return jQuery.inArray(error, errorArray) >= 0;
  },
  
  setExternalLink: function(){
    if (this.newTicket) {
      $j('#ticket-chat').append("<input name='ticket[external_links][][type]' id='external_link_type' type='hidden' value='LogMeInRescueSession' />");
      $j('#ticket-chat').append("<input name='ticket[external_links][][session_id]' id='external_link_session' type='hidden' value='" + this.ticket_token + "' />");
    }
  },

  getLoginTicket: function(){
    $j('#loading_area').show();
    var url = '/proxy/direct?url=' + encodeURIComponent('https://secure.logmeinrescue.com/sso/getloginticket.aspx?ssoid='+this.SSOID+'&password='+this.SSOpwd+'&companyid='+this.SSOcompid);
    $j.ajax({
      type: 'get',
      dataType: 'html',
      url: url,
      success: function(data) {
        $j('#loading_area').hide();
        if (data.split(':')[0] == 'OK') {
          var link = data.substring(3);
          $j('span#tech_console_link').html('<br/><a href="' + link + '" target="_blank"><strong>Launch Technician Console</strong></a>');
          $j('#tech_console_area').show();
          
          if (!Cookie.get('logmein_auth')) {
            $j('#login_area').show();
          } else {
            $j('#pin_area').show();
          }
        }
        else {
          errors = data.split(':');
          $j('#tech_console_area').hide();
          
          if (this.isError('INVALIDSSOID', errors))
            logmein.err("Your Rescue Single Sign-On ID must match your Zendesk primary email address.");
          else if (this.isError('INVALID', errors))
            logmein.err("You are not authorized to login.");
          else
            logmein.err("Error accessing the Technician Console single sign-on link.");

          $j('error_area').show();
        }
      }.bind(this)
    });
  },

  checkAuth: function(){
    var un, pw;
    logmein.err(false);
    $j('#ticket_place').html("");
    $j('#pin').text('');
    if (Cookie.get('logmein_auth')){
      //if the auth is already in the cookie bypass the login
      this.getPIN(Cookie.get('logmein_auth'));
      return;
    } else {
      un = document.getElementById('username').value;
      pw = document.getElementById('password').value;
      if (un=='' && pw=='') {
        
        logmein.err('Please enter the username and password');
        $j('#login_area').show();
        return;
      }
    }
    var url = '/proxy/direct?url=' + encodeURIComponent('https://secure.logmeinrescue.com/api/requestauthcode.aspx?email=' + un + '&pwd=' + pw);
    $j.ajax({
      type: 'get',
      dataType: 'html',
      url: url,
      success: function(data){
        data = data.split(':');
        if (data[0] == 'ERROR' || data[0] == 'INVALID'){
          logmein.err("The username and/or password you've entered is incorrect.")
          return;
        } else {
          var auth = data[1];
          Cookie.set('logmein_auth', auth);
          $j('#login_area').hide();
          $j('#pin_area').show();
          return;
        }
      }
    });
  },

  getPIN: function(auth) {
    $j('#pin').hide();
    $j('#pin_placeholder').hide();
    $j('#pin_loader').show();
    fields = this.widget_id + '|' + this.ticket_id + '|' + this.ticket_token + '|' + this.subdomain + '|'+ this.secret;
    var url = '/proxy/direct?url=' + encodeURIComponent('https://secure.logmeinrescue.com/api/requestpincode.aspx?notechconsole=1&authcode='+auth+'&tracking0='+fields+'&'+this.cfield_values);
    $j.ajax({
      url: url,
      type: 'get',
      dataType: 'html',
      success: function(data){
        $j('#pin_loader').hide();
        data = data.split(':');
        if (data[0].replace(/^\s*|\s*$|\n*/g,'') == 'OKPINCODE'){
          $j('#pin').text(data[1]);
          $j('#pin').show();
          $j('#login_area').hide();

          var new_msg = "https://secure.logmeinrescue.com/Customer/Code.aspx?Code=" + data[1]
          $j('#ticket_place').html("<a href='javascript:void(0)' onclick='logmein.comment_update(\"Click this link to launch a remote session: "+new_msg+"\")' ><strong>&laquo;</strong> Copy to ticket</a>")

          logmein.err(false);
          $j('#submit_type').val("");
        } else {
          switch(data[0]) {
            case 'ERROR':
              logmein.err("There was an error trying to get the PIN, please try again or contact the admin if the problem persists.");
              break;
            case 'NOTLOGGEDIN':
              logmein.err("Unable to login, please verify your username and password.");
              break;
            case 'NOTECHCONSOLERUNNING':
              logmein.err("Please run the Technician Console before requesting a PIN.");
              break;
            case 'OUTOFPINCODES':
              logmein.err("LogMeIn Rescue is out of PIN codes.");
              break;
            case 'INVALID_SECRETAUTHCODE':
              logmein.err("The authentication code you've obtained is invalid or has expired, try logging in again.");
              Cookie.set('logmein_auth',''); //the auth token may have expired, so remove it.
              $j('#pin_area').hide();
              $j('#login_area').show();
              break;
            case 'USER_IS_DELETED':
              logmein.err("This user has been deleted.");
              break;
            case 'TECHNICIAN_NOT_EXISTS':
              logmein.err("This technician does not exist.");
              break;
            case 'USER_DELETED_OR_DISABLED':
              logmein.err("This user has been deleted or disabled.");
              break;
            default:
              logmein.err("Error when requesting a PIN.");
              break;
          }
        }
      }
    });
  },

  err: function(msg) {
    $j('#error_area').show();
    msg ? $j('#error_area').text('Error: ' + msg) : $j('#error_area').text('');
  },

  comment_update: function(new_msg) {
    old_msg = $j('#comment_value').val();
    $j('#comment_value').val(old_msg + " " + new_msg);
  }
});

if (!Zd) var Zd = {};

Zd.Util = {

  parseTemplate: function(string, object) {
    var rubyResult  = this.rubyStyle(string, object);
    var finalResult = this.mustacheStyle(rubyResult, object);
    return finalResult;
  },

  // DEPRECATED This style should be removed at our earliest convenience.
  // Before removing, make sure no Freshbooks Widgets are using the ruby
  // style syntax tokens: #{token_here}
  rubyStyle: function(string, object) {
    var template = new Template(string);
    return template.evaluate(object);
  },

  mustacheStyle: function(string, object) {
    var syntax   = /(^|.|\r|\n)(\{\{\s*([\w\\.]+)\s*\}\})/;
    var template = new Template(string, syntax);
    return template.evaluate(object);
  }

};
