









































































var DebugFlag = 0;
var oDebugNode;
var formNameChopping = 'FormChopping';
var CGIRasmol = "/cgi-bin/DisplayRasmol.pl?";
var CGIChoppingFigure = "/cgi-bin/ChoppingFigure.pl?";
var CGIGetChainGCF = "/cgi-bin/GetChainGCF.pl?";
var CGIGetChopping = "/cgi-bin/GetChoppingData.pl?";
var imgChoppingFigureWaiting = "/images/waiting.gif";
var intMessageTimeout = 3000;
var boolPauseOnDebug = false;

/////
// These are copied from CathCGI.pm... not ideal
/////
var ACTION_ADD_CHOPPING_CHECK = 300;
var ACTION_ADD_CHOPPING_CONFIRM = 301;
var ACTION_CHOP_PDB_CHECK = 302;
var ACTION_CHOP_PDB_CONFIRM = 303;
var ACTION_ADD_CHOPPING_AND_REVIEW_CHECK = 310;
var ACTION_ADD_CHOPPING_AND_REVIEW_CONFIRM = 311;

var ACTION_HOMCHECK_ADD_ASSIGNMENT_CHECK = 400;
var ACTION_HOMCHECK_ADD_ASSIGNMENT_CONFIRM = 401;
var ACTION_HOMCHECK_ASSIGN_DOMAIN_CHECK = 402;
var ACTION_HOMCHECK_ASSIGN_DOMAIN_CONFIRM = 403;
var ACTION_HOMCHECK_ADD_ASSIGNMENT_AND_REVIEW_CHECK = 410;
var ACTION_HOMCHECK_ADD_ASSIGNMENT_AND_REVIEW_CONFIRM = 411;

var FFN_DOMCHOP_BATCH_CHAIN_EVENT_IDS = 'domchop_batch_chain_event_ids';
var FFN_DOMCHOP_BATCH_DOMAIN_EVENT_IDS = 'homcheck_batch_chain_event_ids';

onerror = handleErr


// something that will return a unique id (without global variables)
var uid = (
	function(){
		var id=1;
		return function(){
			return id++ ;
		};
	}
)();



String.prototype.pad = function()
{
  var charPad = ' ';
  var numChars;
  var str = this;
   
  if (arguments.length >= 1) {
    numChars = arguments[0];
  } 
  if (arguments.length == 2) {
    charPad = arguments[1];
  }

  while(str.length < numChars) {
    str = charPad + str;
  }
  
  return str;
}

/* fix for firebug console on non-firebug browsers */
if (!window.console || !console.firebug)
{
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

    window.console = {};
    for (var i = 0; i < names.length; ++i)
        window.console[names[i]] = function() {}
}

function displayNode(oNode, boolDisplay) 
{
  if (!oNode) {
    throw("Couldn't find node '"+oNode+"'");
		return false;
  }
  if (boolDisplay) {
    oNode.style.display = 'block';
  }
  else {
    oNode.style.display = 'none';
  }
}

function getRasmolLink(chainId, choppingParams)
{
  var urlRasmol = CGIRasmol + 
                "id=" + chainId + ";" + 
                "colouring=chopping;"+
                "chopping"+chainId+"="+encodeURIComponent(choppingParams);
  
  D("getRasmolLink.url: "+urlRasmol);
  
  return urlRasmol;
}

function launchRasmol(chainId, choppingParams)
{
  var urlRasmol = getRasmolLink(chainId, choppingParams);

  D("launchRasmol.url: "+urlRasmol);

  openRasmolWin(urlRasmol);
}

function displayHash(strTitle, aData)
{
  var strHash = '';
  var key;
  strHash += '<html>\n';
  strHash += '<head><title>'+strTitle+'</title>\n';
  strHash += '<link rel="stylesheet" href="/css/basic.css" type="text/css"></link>\n';
  strHash += '</head><body>\n';
  strHash += '<h1>'+strTitle+'</h1>';
  strHash += '<table class="summary" cellspacing=1 cellpadding=3>\n';
  while(aData.length > 0)
  {
    var strKey = aData.shift();
    var strValue = aData.shift();
    strHash += '<tr><th>'+strKey+'</th><td>'+strValue+'</td></tr>\n';
  }
  strHash += '</table>\n';
  strHash += '<p><a href="javascript:window.close()">Close this window</a></p>';
  strHash += '</body></html>\n';

  var strOptions = 'width=300,height=400,toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,copyhistory=no';
  oNewWin = window.open("","_blank",strOptions);
  oNewWin.document.write(strHash);  
}

function openRasmolWin(url)
{
  var strOptions = 'width=400,height=400,resizable=yes,toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,copyhistory=no';
  window.open(url,"RasmolWindow",strOptions)
}

/*

*/

function sendHomCheckSelectedEntries()
{
  var intActionId = sendHomCheckSelectedEntries.arguments[0];
  var nodeForm = sendHomCheckSelectedEntries.arguments[1];
  var strSubmitText = sendHomCheckSelectedEntries.arguments[2];
  
  if (!nodeForm) {
    alert("Error: expected form node to be set");
    return;
  }
  
  var selectedDomainEventIds = new Array();
  
  for(var i=0; i<nodeForm.elements.length; i++) {
    var nodeFormField = nodeForm.elements[i];
    D("FormField: " + nodeFormField + " checked : " + nodeFormField.checked);
    if (nodeFormField && nodeFormField.name == FFN_DOMCHOP_BATCH_DOMAIN_EVENT_IDS && nodeFormField.checked) {
      selectedDomainEventIds.push(nodeFormField.value);
    }
  }
  
  if (!selectedDomainEventIds.length) {
    alert("Sorry, you must select some domains using the check boxes on the left\n" +
          "before you perform this action.");
  }
  else {
    var strHiddenFields = "<input type='hidden' name='" + FFN_DOMCHOP_BATCH_DOMAIN_EVENT_IDS + "' value='" + selectedDomainEventIds.join(';') + "'>";
    
    var strTitle = strSubmitText + " (" + selectedDomainEventIds.length + " chains) : ";
    
    var strValidate = " onSubmit='return validateActionForm(this)'";
     
    var strAction = nodeForm.action ? " action='" + nodeForm.action + "'" : "";
 
    strTitle = strTitle + " (comment required)";
    
    var strOverlib = "<form " + strValidate + strAction + ">" + strHiddenFields +
              "<input type='hidden' name='aid' value='" + intActionId + "'>"+
              "<textarea name='comment' cols=40 rows=10></textarea>"+
              "<div align='center'>"+
              "<input class='action' type='button' value='Close' onClick='return cClick()'>"+
              "<input class='action' type='submit' value='" + strSubmitText + "'>"+
              "</div></form>";
  
    overlib(strOverlib, CAPTION, strTitle, STICKY, EXCLUSIVE, CLOSECLICK, LEFT, BELOW);
  }
}


function sendDomChopSelectedEntries()
{
  var intActionId = sendDomChopSelectedEntries.arguments[0];
  var nodeForm = sendDomChopSelectedEntries.arguments[1];
  var strSubmitText = sendDomChopSelectedEntries.arguments[2];
  
  if (!nodeForm) {
    alert("Error: expected form node to be set");
    return;
  }
  
  var selectedChainEventIds = new Array();
  
  for(var i=0; i<nodeForm.elements.length; i++) {
    var nodeFormField = nodeForm.elements[i];
    D("FormField: " + nodeFormField + " checked : " + nodeFormField.checked);
    if (nodeFormField && nodeFormField.name == FFN_DOMCHOP_BATCH_CHAIN_EVENT_IDS && nodeFormField.checked) {
      selectedChainEventIds.push(nodeFormField.value);
    }
  }
  
  if (!selectedChainEventIds.length) {
    alert("Sorry, you must select some chains using the check boxes on the left\n" +
          "before you perform this action.");
  }
  else {
    var strHiddenFields = "<input type='hidden' name='" + FFN_DOMCHOP_BATCH_CHAIN_EVENT_IDS + "' value='" + selectedChainEventIds.join(';') + "'>";
    
    var strTitle = strSubmitText + " (" + selectedChainEventIds.length + " chains) : ";
    
    var strValidate = " onSubmit='return validateActionForm(this)'";
     
    var strAction = nodeForm.action ? " action='" + nodeForm.action + "'" : "";
 
    strTitle = strTitle + " (comment required)";
    
    var strOverlib = "<form " + strValidate + strAction + ">" + strHiddenFields +
              "<input type='hidden' name='aid' value='" + intActionId + "'>"+
              "<textarea name='comment' cols=40 rows=10></textarea>"+
              "<div align='center'>"+
              "<input class='action' type='button' value='Close' onClick='return cClick()'>"+
              "<input class='action' type='submit' value='" + strSubmitText + "'>"+
              "</div></form>";
  
    overlib(strOverlib, CAPTION, strTitle, STICKY, EXCLUSIVE, CLOSECLICK, LEFT, BELOW);
  }
}

function sendActionComment()
{
  var intActionId = sendActionComment.arguments[0];
  var strId = sendActionComment.arguments[1];
  var boolRequireText = sendActionComment.arguments[2];
  var strSubmitText = sendActionComment.arguments[3];

  var strHiddenFields = '';
  for(var i=4; (i + 1) < sendActionComment.arguments.length; i+=2) {
    var strKey = sendActionComment.arguments[i];
    var strValue = sendActionComment.arguments[i+1];
    if (strKey && strValue)
    {
      strHiddenFields += "<input type='hidden' name='" + strKey + "' value='" + strValue + "'>";
    }
  }
  var strValidate = "";
  var strTitle = strSubmitText + " : " + strId;
  if (boolRequireText) {
    strValidate = "onSubmit='return validateActionForm(this)'";
    strTitle = strTitle + " (comment required)";
  }
  else {
    strTitle = strTitle + " (not required)";
  }

  var idType;
  if (strId.length == 4) {
    idType = 'pdb_code';
  }
  else if (strId.length == 5) {
    idType = 'chain_id';
  } 
  else if (strId.length == 7) {
    idType = 'domain_id';
  }
  else {
    alert("Warning: couldn't get ID type '"+strId+"'");
    return false;
  }

  var strOverlib = "<form "+strValidate+">"+strHiddenFields+
            "<input type='hidden' name='aid' value='" + intActionId + "'>"+
            "<input type='hidden' name='"+idType+"' value='" + strId + "'>"+
            "<textarea name='comment' cols=40 rows=10></textarea>"+
            "<div align='center'>"+
            "<input class='action' type='button' value='Close' onClick='return cClick()'>"+
            "<input class='action' type='submit' value='" + strSubmitText + "'>"+
            "</div></form>";

  overlib(strOverlib, CAPTION, strTitle, STICKY, EXCLUSIVE, CLOSECLICK, LEFT, BELOW);
}

function validateActionForm(oForm)
{
  strComment = oForm.elements['comment'].value;
  var acceptedChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n \"'.,:;-_%?[]{}()+-!=";

  if (strComment)
  {
    /* Check the character length */
    for (i=0; i<strComment.length; i++) {
      if (acceptedChars.indexOf(strComment.charAt(i),0) == -1) {
        alert("Sorry, the character '"+strComment.charAt(i)+"' (char: "+i+") is not a valid character.")
        return false;
      }
    }

    return true;
  }
  else {
    alert("Sorry, you must specify a comment when you sending this action.");
    return false;
  }
}



function getNodeText(nParent)
{  
  var nl = nParent.childNodes;
  var strText = '';
  
  D("getNodeText("+nParent+") childNodes: "+nl.length);
  for(var i=0; i<nl.length; i++) {
    var nChild = nl.item(i);

    if (nChild.nodeName == '#text') {
      D("getNodeText.child.return('"+nChild.nodeValue+"')");
      strText += nChild.nodeValue;
    }
  }
  D("getNodeText.strText: "+strText);
  return(strText);
}

function handleErr(msg,url,l)
{
  var txt = '';
  txt+="\n<strong>";
  txt+="!! Error: " + msg + "\n";
  txt+="!! URL:   " + url + "\n";
  txt+="!! Line:  " + l + "\n";
  txt+="</strong>\n";
  D(txt);
  return false;
}

function setDebug(debug) {
  if (debug) {
    oDebugNode.style.display = 'block';
  }
  else {
    oDebugNode.style.display = 'none';
  }
  DebugFlag = debug;
}

function toggleDebug(oFF) {
  DebugFlag = oFF.checked ? 1 : 0;
  setDebug(DebugFlag);
}

function setDebugNode(oNode)
{
  oDebugNode = oNode;
}

function warn(strWarning)
{
  D("!! WARNING !!  " + strWarning);
  if (DebugFlag) {
    //alert(strWarning);
  }
}

function D(strDebug)
{
  if (DebugFlag) {
    if (oDebugNode) {
      oDebugNode.innerHTML = oDebugNode.innerHTML + strDebug + "\n";
    }
    else {
      console.debug(strDebug);
    }
  }
}

function Chopping(oForm, oNode, chainId)
{
  var oChoppingNode;
  var oChoppingForm;
  var aResidues = new Array();
  var aDomains = new Array();
  var chainId;
  var urlChoppingFigure;
  var strComment = '';
  var timeoutMessage;
  var oInheritedXmlChopping;
  
  var imgElementId = 'ManualChoppingFigureImage';
  
  var strFFNChainId = 'chain_id';
  var strFFNComment = 'comment';
  var strFFNChoppingXml = 'chopping_xml';
  var strFFNDifficulty = 'difficulty';
  var strFFNAction = 'aid';
    
  // public boolean value to say whether we are providing the submit 
  // box with comments
  this.boolSubmitOption = true;
  
  this.initFromChainEventNum = function(intChainEventNum) {
    var xmlHttp = GetXmlHttpObject();
    
    this.setChoppingFigure(imgChoppingFigureWaiting);
    
  
    var url = CGIGetChopping + "chain_event_num=" + intChainEventNum;
    D("Chopping.initFromChainEventNum.url: " + url);
    
    var myChopping = this;
    
    D("Chopping.xmlHttp: " + xmlHttp);

		new Ajax.Request(url, {
			method: 'get',
			onLoading: function(transport) {
    		myChopping.displayMessage("Loading chopping (chain_event_num: "+intChainEventNum+") ...");
			},
			onSuccess: function(transport) {
				var oXML = transport.responseXML;
				if (!oXML) {
					myChopping.displayMessage("Couldn't get XML from call to '"+url+"'");
					return;
				}
        myChopping.initFromChoppingNode(oXML);
        myChopping.sortDomainsAndSegments();
        myChopping.refreshDomainAndSegmentCodes();
        myChopping.updateHtml();
        myChopping.updateChoppingFigure();
			},
			onFailure: function(transport) {
				myChopping.displayMessage("Couldn't initialise from ChainEventNum '"+intChainEventNum+"'");
			},
		});
  }
  
  ////
  // Initialise from the Chopping node
  ////
  this.initFromChoppingNode = function(oNode) {
    
    this.displayMessage("Parsing chopping data from XML...");
        
    aDomains = new Array();
    
    if (! oNode) {
      this.displayWarning("Error: parsing chopping - no XML data");
      throw("no XML data");
      return (false);
    }    
    
    this.setInheritedXmlChopping(oNode);
        
    var nlChainHistory = oNode.getElementsByTagName('chain_history');
    if (!nlChainHistory.length == 1) {
      throw("Expected 1 chain_history node, found " + nlChainHistory.length)
      return (false);
    }
    var oChainHistory = nlChainHistory.item(0);
    
    this.setChainId(oChainHistory.getAttribute('chain_id'));
    
    var nlDomains = oNode.getElementsByTagName('domain');
        
    D("Chopping.initFromChoppingNode.oDomainNodeList: "+nlDomains.length);
    for(i=0; i<nlDomains.length; i++)
    {
      oDomainNode = nlDomains.item(i);
      
      oDomain = new Domain();
        
      oDomain.initFromChoppingNode(oDomainNode);
        
      this.addDomain(oDomain);
    }
  }
  
  this.setInheritedXmlChopping = function (oXml) {
    oInheritedXmlChopping = oXml
  }
  this.getInheritedXmlChopping = function () {
    return oInheritedXmlChopping;
  }
  
  ////
  // Update the Domain values from the form
  ////
  this.updateFromForm = function () {
    var oDoc = this.getDocument();
    var oComment = oDoc.getElementById(this.getFFNComment());
    
    D("Chopping.updateFromForm() domains: "+aDomains.length);
    
    if (oComment) {
      D("Chopping.updateFromForm.comment: "+oComment.value);
      strComment = oComment.value;
    }
    
    for(var i=0; i<aDomains.length; i++) {
      oDomain = aDomains[i];
      D("Chopping.updateFromForm().Domain["+i+"].updateFromForm("+this.getForm()+")");
      oDomain.updateFromForm(this.getForm());
    }
  }

  this.getDomains = function() { return aDomains }
  this.getDomain = function(intDomainCode) {
    for (var i=0; i<aDomains.length; i++) {
      oDom = aDomains[i];
      if (oDom.getDomainCode() == intDomainCode) {
        return oDom;
      }
    }
    return false;
  }
  
  this.getDomainById = function(intDomainId) {
    for (var i=0; i<aDomains.length; i++) {
      oDom = aDomains[i];
      if (oDom.getDomainId() == intDomainId) {
        return oDom;
      }
    }
    return false;
  }
  
    
  ////
  // Add a Segment to a given position in the given domain
  ////
  this.addSegment = function(oSegment, intDomainCode, strSegmentIdPrevious) {
    var offset=0;
    D("Chopping.addSegment("+oSegment+", "+intDomainCode+", "+strSegmentIdPrevious+")");
    
    ////
    // Update the data structure from the form before we do anything
    ////
    this.updateFromForm();
    
    if(!oSegment) {
      oSegment = new Segment();
    }
    
    if (intDomainCode) {
      for(i=0; i<aDomains.length; i++) {
        oDom = aDomains[i];
        D("Chopping.addSegment.Domain["+i+"]: "+oDom.getDomainCode()+ " = "+intDomainCode);
        if (oDom.getDomainCode() == intDomainCode) {
          oDom.addSegment(oSegment, strSegmentIdPrevious);
          break;
        }
      }
    }
    else {
      warn("expected DomainId specified");
    }
    
    D("Chopping.addSegment.AFTER_ADD: "+oDom);
    
    this.refreshDomainAndSegmentCodes();

    D("Chopping.addSegment.AFTER_REFRESH: "+oDom);
        
    this.updateHtml();
  }
  
  ////
  // Add a Domain to a given position 
  ////
  this.addDomain = function(oDomain, strDomainIdPrevious) {
    var offset=0;
    
    D("Chopping.addDomain("+oDomain+", "+strDomainIdPrevious+")");
    
    ////
    // Update the data structure from the form before we do anything
    ////
    this.updateFromForm();
    
    if(!oDomain) {
      oDomain = new Domain();
    }
    if (strDomainIdPrevious) {
      for(i=0; i<aDomains.length; i++) {
        oDom = aDomains[i];
        if (oDom.getDomainId() == strDomainIdPrevious) {
          offset = i + 1;
        }
      }
    }
    oDomain.setChopping(this);
    
    D("Chopping.beforeSplice: length: " + aDomains.length);
    aDomains.splice(offset, 0, oDomain);
    D("Chopping.afterSplice: length: " + aDomains.length);
    
    this.refreshDomainAndSegmentCodes();
    
    this.updateHtml();
    
    this.displayMessage("Added domain to chopping");    
  }
    
  ////
  // Remove a given domain from the array
  ////
  this.removeDomain = function(strDomainId) {
    
    D("Chopping.removeDomain("+strDomainId+")");
    
    var domainCodeRemoved;
    
    // update the data structure from the form before we do anything
    this.updateFromForm();
    
    if (aDomains.length == 1) {
      alert("Sorry, you cannot remove this Domain as there must\nalways be at least one Domain in the chopping");
      return false;
    }
    
    for (i=0; i<aDomains.length; i++)
    {
      D("Chopping.removeDomain: "+ aDomains[i].getDomainId() + " = " + strDomainId);
      if (aDomains[i].getDomainId() == strDomainId) {
        
        domainCodeRemoved = aDomains[i].getDomainCode();
        
        D("Chopping.removeDomain.beforeSplice("+aDomains.join(", ")+")");
        aDomains.splice(i, 1);
        D("Chopping.removeSegment.afterSplice("+aDomains.join(", ")+")");
        break;
      }
    }
    
    this.refreshDomainAndSegmentCodes();
    
    this.updateHtml();
    
    if (domainCodeRemoved)
      this.displayMessage("Removed domain '"+domainCodeRemoved+"' from Chopping object");  
    else 
      this.displayWarning("Error: couldn't remove domain '"+domainCodeRemoved+"' from Chopping object");    
  }
  
  ////
  // Remove a given segment from the array
  ////
  this.removeSegment = function(intDomainCode, strSegmentId) {    
    D("Chopping.removeSegment("+intDomainCode+", "+strSegmentId+")");
    
    // update the data structure from the form before we do anything
    this.updateFromForm();
        
    if (intDomainCode) {
      for(i=0; i<aDomains.length; i++) {
        oDom = aDomains[i];
        if (oDom.getDomainCode() == intDomainCode) {          
          D("Chopping.oDomain["+intDomainCode+"].removeSegment("+strSegmentId+")");
          oDom.removeSegment(strSegmentId);
          break;
        }
      }
    }
    
    this.refreshDomainAndSegmentCodes();
    
    this.updateHtml();  
  }
  
  this.sortDomains = function () {
    D("sortDomains.BEFORE: "+this);
    aDomains.sort(this.sortByDomainCode);
    D("sortDomains.AFTER: "+this);
  }
  
  this.sortByDomainCode = function(a, b) {
    D("sortByDomainCode: a["+a.getDomainCode()+"] b["+b.getDomainCode()+"]");
    return (a.getDomainCode() > b.getDomainCode() ? -1 : 1);
  } 
  
  this.sortDomainsAndSegments = function () {
    
    this.sortDomains();
    
    // reset domain/segment codes
    for (var domOffset=0; domOffset<aDomains.length; domOffset++) {
      var oDom = aDomains[domOffset];
      oDom.sortSegments();      
    }
  }
  
  this.refreshDomainAndSegmentCodes = function () {
        
    // reset domain/segment codes
    for (var domOffset=0; domOffset<aDomains.length; domOffset++) {
      var oDom = aDomains[domOffset];
      
      oDom.setDomainCode(domOffset+1);
            
      aSegs = oDom.getSegments();
      
      for (var segOffset=0; segOffset<aSegs.length; segOffset++) {
        oSeg = aSegs[segOffset];
        oSeg.setSegmentCode(segOffset+1);
      }
    }
  }
       
  this.getDomainCount = function() {
    return aDomains.length;
  }
  
  this.getSelectDomainOptions = function(domainSelected) {
    var aDomainCodes = new Array();
    var maxDomainCode = 1;
    var strOptions = '';
    
    
    maxDomainCode = 1 + this.getDomainCount();
    D("getSelectDomainOptions.AfterLoop.maxDomainCode: "+maxDomainCode);
    
    for(var i=1; i<=maxDomainCode; i++) {
      selectedOption = '';
      if (domainSelected && domainSelected == i) {
        selectedOption = 'selected'
      }
      D("getSelectDomainOptions.option["+i+"] : "+selectedOption);
      strOptions += "<option value='" + i + "' "+selectedOption+">Domain " + i + "</option>\n" 
    }
  
    return strOptions;
  }

  this.getSelectResidueOptions = function(residueSelected) {
    var strOptions = '';
    
    // D("Chopping.getSelectResidueOptions("+residueSelected+")");
    
    // D("Chopping.getSelectResidueOptions.residues: "+aResidues.length);
    
    // get the highest domain code in all the segments
    for(var i=0; i<aResidues.length; i++) {
      resCode = aResidues[i];
      selectedOption = '';
      if (residueSelected && residueSelected == resCode) {
        selectedOption = 'selected'
      }
      strOptions += "<option value='" + resCode + "' seqValue='" + i + "' "+selectedOption+">" + resCode + "</option>\n";
    }
  
    return strOptions;
  }
  
  ////
  // http://www.cathdb.info/cgi-bin/cath/DisplayRasmol.pl?
  //    id=1ar1B&colouring=chopping&chopping1ar1B=1ar1%20D1-24%5BB%5D%2B106-252%5BB%5D%20D25-105%5BB%5D
  //
  // http://www.cathdb.info/cgi-bin/cath/DisplayRasmol.pl?
  //    id=1ar1B&colouring=chopping&chopping1ar1B=1ar1 D1-24[B]+106-252[B] D25-105[B]
  //
  // Domain ID Number  Start Res Name  Stop Res Name  Length
  // 1ar1B01   1       1               24             24
  // 1ar1B02   1       25              105            81
  // 1ar1B01   2       106             252            147
  //
  ////
  
  this.launchRasmol = function() {
    var urlRasmolChopping = '';
    var urlRasmol;
    
    urlRasmolChopping = this.getChoppingParam();

    if (!urlRasmolChopping) {
      warn("couldn't get Chopping parameters");
      return false;
    }
    
    urlRasmol = CGIRasmol + 
                  "id=" + this.getChainId() + "&" + 
                  "colouring=chopping&"+
                  "chopping"+this.getChainId()+"="+urlRasmolChopping;
    
    D("launchRasmol.url: "+urlRasmol);

    this.displayMessage("Launching Rasmol...");  
                
    oWin = window.open(urlRasmol, 'ChoppingRasmol', 'width=200,height=200,toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,copyhistory=no');    
  
  }
  
  this.setChoppingFigure = function (url) {
    urlChoppingFigure = url;
  }
  this.getChoppingFigure = function () {
    return urlChoppingFigure;
  }
  
  this.updateChoppingFigure = function() {
    var urlChopFigParams = '';
    var oDoc = this.getDocument();
    var oImage;
    
    urlChopFigParams = this.getChoppingParam();
    
    if (!urlChopFigParams) {
      warn("couldn't get Chopping parameters");
      return false;
    }
        
    oImage = oDoc.getElementById(imgElementId);
    if (!oImage) {
      alert("couldn't find id='"+imgElementId+"' in DOM");
      return false;
    }
    
    this.setChoppingFigure(CGIChoppingFigure + "chopping=" + urlChopFigParams);
    
    D("updateChoppingFigure.url: "+urlChoppingFigure);

    this.displayMessage("Updating Chopping figure...");
            
    oImage.src = urlChoppingFigure;
  }
  
  this.resetMessage = function() {
    this.displayMessage();
  }
  
  this.displayWarning = function(strMessage) {
    this.displayMessage(strMessage, 1);
  }
  
  this.displayMessage = function(strMessage, intLevel) {
    var oDoc = this.getDocument();
    var oNode = oDoc.getElementById('messageBox');
    
    
    if (!intLevel) {
      intLevel = 0;
    }
    
    if (oNode) {
      if (timeoutMessage) {
        clearTimeout(timeoutMessage);
      }
      timeoutMessage = setTimeout("oChopping.resetMessage()", intMessageTimeout);
      
      oNode.style.fontSize = '11px';
      oNode.style.padding = '3px';        
      oNode.style.textAlign = 'center';        
      if (strMessage) {
        if (intLevel == 0)
        {
          oNode.style.background = '#FFFFEE';
          oNode.style.border = '1px solid #CCCC99';
          oNode.style.color = '#666633';
        }
        else if (intLevel == 1)
        {
          oNode.style.background = '#FFEEEE';
          oNode.style.border = '1px solid #CC9999';
          oNode.style.color = '#663333';
        }
      }
      else {
        strMessage = "&nbsp;";
        oNode.style.background = '';
        oNode.style.border = '';
      }
      oNode.innerHTML = strMessage;
    }
  }
  
  ////
  // Gets the chopping param string
  ////
  // http://www.cathdb.info/cgi-bin/cath/ChoppingFigure.pl?chopping=1ar1%20D1-24%5BB%5D%2B106-252%5BB%5D%20D25-105%5BB%5D  
  //
  // http://www.cathdb.info/cgi-bin/cath/ChoppingFigure.pl?chopping=1ar1 D1-24[B]+106-252[B] D25-105[B]  
  //
  // Domain ID Number  Start Res Name  Stop Res Name  Length
  // 1ar1B01   1       1               24             24
  // 1ar1B02   1       25              105            81
  // 1ar1B01   2       106             252            147
  //
  ////
  // http://www.cathdb.info/cgi-bin/cath/ChoppingFigure.pl?chopping=1gav%20D2-20%5BA%5D+23-29%5BA%5D
  //
  // http://www.cathdb.info/cgi-bin/cath/ChoppingFigure.pl?chopping=1gav D2-20[A]+23-29[A]
  ////
  this.getChoppingParam = function() {
    var strChainId;
    var strChainCode;
    var strPdbCode;
    var urlChopFigParams;
    var aSegs;
    
    this.updateFromForm();
    
    if (!this.validate(false)) {
      this.validate(true);
      return false;
    }
    
    strChainId = this.getChainId();
    strPdbCode = strChainId.substr(0, 4);
    strChainCode = strChainId.substr(4, 1);
    
    D("getChoppingParam.domains: " + aDomains.length);

    urlChopFigParams = strPdbCode;
            
    for(var domOffset=0; domOffset<aDomains.length; domOffset++) {
      var oDom = aDomains[domOffset]; 
      var aSegs = oDom.getSegments();
      
      D("getChoppingParam.domain["+oDom.getDomainCode()+"]: " + oDom);
      
      if (!aSegs) {
        alert("getChoppingParam.aDomains["+domOffset+"] has no properties");
        continue;
      }
      urlChopFigParams += "%20D"; // equivalent of ' '
      for(var segOffset=0; segOffset<aSegs.length; segOffset++) 
      {
        oSeg = aSegs[segOffset];
        if (segOffset > 0) {
          urlChopFigParams += '%2B'; // equivalent of '+'
        }
        
        // need to put brackets round insert codes
        var startRes = oSeg.getStartRes();
        var stopRes = oSeg.getStopRes();

				startRes = this.convertResForUrl(oSeg.getStartRes());
				stopRes = this.convertResForUrl(oSeg.getStopRes());
				urlChopFigParams += escape(startRes + "-" + stopRes + "["+strChainCode+"]");

      }
    }
    
    return urlChopFigParams;
  }
  
	// Perl Chopping object expects to get insert codes in parentheses so we have to
	// convert them before we send them back (in XML and ChoppingFigure URLs)
	this.convertResForUrl = function(res) {
		if (res.charAt(res.length - 1) < '0' || res.charAt(res.length - 1) > '9')
		{
			res = res.substr(0, res.length - 1) + '(' + res.charAt(res.length - 1) + ')';
		}
		return res;
	}

  this.getXmlString = function() {
    
    D("Chopping.getXmlString.START");
    var strXml = "<chopping>";
    
    var intChainEventNum = '';
    
    /////
    // Get the chain history node
    /////
    var oXml = this.getInheritedXmlChopping();
    if (oXml) {
      var nlChainHistory = oXml.getElementsByTagName('chain_history');
      if (nlChainHistory.length != 1) {
        this.displayWarning("Error with inherited XML format (expected 1 chain_history node)");
      }
      var nChainHistory = nlChainHistory.item(0);
      intChainEventNum = nChainHistory.getAttribute('chain_event_num');
      D("Chopping.getXmlString.chain_event_num: "+intChainEventNum);
    }
    
    strXml += "<chain_history "+
                "related_event_num='"+intChainEventNum+"' "+
                "difficulty='"+this.getDifficulty()+"'>"+
                this.getComment()+
                "</chain_history>\n";
    
    D("Chopping.getXmlString.comment: "+this.getComment());
    
    /////
    // Get the domain/segment nodes
    /////
    for (var i=0; i<aDomains.length; i++)
    {
      oDomain = aDomains[i];
      D("Chopping.getXmlString.aDomains["+i+"] : "+oDomain);
      
      var domainCode = new String(oDomain.getDomainCode());
      
      var domainId = this.getChainId() + domainCode.pad(2, '0');
      
      strXml += "<domain domain_id='"+domainId+"'>";
      
      var aSegments = oDomain.getSegments();
      
      for (var j=0; j<aSegments.length; j++)
      {
        var oSeg = aSegments[j];
        strXml += "<segment chain_id='"+oSeg.getChainId()+"' "+
                    " domain_id='"+domainId+"' " + 
                    " start_res_name='"+this.convertResForUrl(oSeg.getStartRes())+"' " + 
                    " stop_res_name='"+this.convertResForUrl(oSeg.getStopRes())+"'></segment>";
      }
      
      strXml += "</domain>";
      
    }

    D("Chopping.getXmlString.END: "+strXml);
    
    strXml += "</chopping>";
 
    return (strXml);  
  }
  
  this.getComment = function() {
    var oDoc = this.getDocument();
    var oComment = oDoc.getElementById(this.getFFNComment());
    if (oComment) {
      D("Chopping.getComment(): "+oComment+" value: "+oComment.value);
      return oComment.value;
    }
    D("Chopping.getComment(): id: "+this.getFFNComment()+" not found");
    return "";
  }

  this.getDifficulty = function() {
    var oForm = this.getForm();
    D("Chopping.getDifficulty()");
    
    
    var oFF = oForm[this.getFFNDifficulty()];
    
    if (oFF) {
      for(var i=0; i<oFF.length; i++) {
        var oFFO = oFF[i];
        if (oFFO.checked) {
          D("Chopping.getDifficulty.oFFO: "+oFFO.value);
          return oFFO.value;
        }
      }
    }
    return false;
  }
    
  ////
  // Update the HTML
  ////
  this.updateHtml = function() {
    D("Chopping.updateHtml(): aDomains: "+aDomains.length);
    strContent = '';    
        
    /////
    // Add the domain form
    /////
    for (var i=0; i<aDomains.length; i++)
    {
      oDomain = aDomains[i];
      D("Chopping.updateHtml.aDomains["+i+"] : "+oDomain);
      strContent += oDomain.toHtml();
      D("Chopping.updateHtml.afterDomainToHtml");
    }
    
    strContent += 
				"<h2>Chain</h2>\n"+
        "<div class='choppingFigure'><img id='"+imgElementId+"' border=0 src='"+urlChoppingFigure+"' /></div>\n"+
        "<div class='largeButtonsContainer'>"+
				"<a href='#' onClick='oChopping.launchRasmol(); return false;' class='action'><img src='/images/32x32/rasmol.png' border='0' alt='Rasmol' /></a>"+
        "<a href='#' onClick='oChopping.updateChoppingFigure(); return false;' class='action'><img src='/images/32x32/reload.png' border=0 alt='Update Chain Image' /></a>"+
        "<a href='#' onClick='oChopping.validate(true); return false;' class='action'><img src='/images/32x32/ok.png' border=0 alt='Validate' /></a>"+
        "</div>\n";

    var oXml = this.getInheritedXmlChopping();
    var hChoppingInfo = $H({});
    var strChoppingInheritance = '';
    if (!oXml) {
      hChoppingInfo.merge({
					program: 					'MANUAL',
			});
    }
    else {
      var nlChainHistory = oXml.getElementsByTagName('chain_history');
      if (nlChainHistory.length != 1) {
        this.displayWarning("Error with XML format (expected 1 chain_history node)");
      }
      var nChainHistory = nlChainHistory.item(0);
      var dateEvent = new Date();
      dateEvent.setTime(nChainHistory.getAttribute('event_timestamp') * 1000);
      hChoppingInfo.merge({
					program: 					nChainHistory.getAttribute('chain_event_type'),
					login_name: 			nChainHistory.getAttribute('login_name'),
					event_timestamp: 	dateEvent.toString(),
					comment: 					getNodeText(nChainHistory),
				});
    }
    
    strChoppingInheritance = "<table class='summary' border='0' cellpadding='2' cellspacing='0'>\n";
    hChoppingInfo.keys().sort().each(function(item) {
      strChoppingInheritance += "<tr><th>"+item.toUpperCase()+"</th><td>"+ hChoppingInfo[item] +"</td></tr>\n";
		});
    strChoppingInheritance += "</table>\n";
    
    strContent += "<h2>Inheritance</h2>\n"+
        						"<P>" + strChoppingInheritance + "</p>";
    
    if (this.boolSubmitOption) {
      strContent += "\n"+
					"<h2>Miscellaneous</h2>"+
					"<table border=0 class='summary' cellpadding=2 cellspacing=0>"+
					"<tr><th>Comments</th><th>Difficulty</th></tr>\n" +
					"<tr><td><textarea cols='40' rows='10' class='Comment' name='"+this.getFFNComment()+"' id='"+this.getFFNComment()+"'>"+this.getComment()+"</textarea><br>\n"+
					"</td><td class='text'>"+
					"<input type='radio' name='"+this.getFFNDifficulty()+"' value='EASY'>EASY</input><br />\n"+
					"<input type='radio' name='"+this.getFFNDifficulty()+"' value='MEDIUM'>MEDIUM</input><br />\n"+
					"<input type='radio' name='"+this.getFFNDifficulty()+"' value='HARD'>HARD</input>\n"+
					"</td></tr></table>";

      strContent += "<div class='largeButtonsContainer'>\n"+
                    "<a class='largeButton add' href='#' onClick='oChopping.submitChopping("+ACTION_ADD_CHOPPING_CHECK+")'>Submit Chopping</a>&nbsp;\n"+
                    "<a class='largeButton ok' href='#' onClick='oChopping.submitChopping("+ACTION_ADD_CHOPPING_AND_REVIEW_CHECK+")'>Submit Chopping/Send To Review</a>\n"+
                    "</div>";
    }
    oChoppingNode.innerHTML = strContent;
    D('Chopping().getElementById('+oChoppingNode.tagName+'): ' + oChoppingNode);
  }

  this.getSegments = function() {
    D("Chopping.getSegments()");
    var aSegments = new Array();
    D("Chopping.getSegments.domains: "+aDomains.length);
    for(var domOffset=0; domOffset<aDomains.length; domOffset++) {
      var oDom = aDomains[domOffset];
      var aSegs = oDom.getSegments();
      D("Chopping.getSegments.domains["+(domOffset + 1)+"] segments: "+aSegs.length);
      aSegments = aSegments.concat(aSegs);
    }
    return aSegments;
  }
  
  ////
  // Validate the segments
  //   - make sure the start res comes before the stop res
  //   - make sure the start and stop do not overlap
  ////
  this.validate = function(boolWarningFlag) {
    var currentSegment = false;
    var currentSegmentOffset = false;
    var aErrors = new Array();
    var intAssignedResidues = 0;
    var aSegments;
    
    D("Chopping.validate()");
    
    this.displayMessage("Validating chopping...");  
    
    ////
    // update to the latest values from the form
    ////
    this.updateFromForm();

    aSegments = this.getSegments();
    
    D("Chopping.validate: "+this);

    D("validate.checkResiduesInSegments.StartBeforeStop: segments.length="+aSegments.length);
        
    // for each segment check that the stop residue comes after the start res
    for(var segOffset=0; segOffset<aSegments.length; segOffset++) 
    {
      var oSeg = aSegments[segOffset];
      var startOffset = false;
      var stopOffset = false;

      D("validate.checkResStartBeforeStop.Seg["+segOffset+"]" + oSeg);
      
      for(var resOffset=0; resOffset<aResidues.length; resOffset++) 
      {  
        // D("validate.checkResStartBeforeStop["+resOffset+"]: "+aResidues[resOffset]);
        if (oSeg.getStartRes() == aResidues[resOffset]) {
          // D("validate.checkResStartBeforeStop.foundStartRes["+resOffset+"]: "+oSeg.getStartRes());
          startOffset = resOffset;
        }
        if (oSeg.getStopRes() == aResidues[resOffset]) {
          // D("validate.checkResStartBeforeStop.foundStopRes["+resOffset+"]: "+oSeg.getStopRes());
          stopOffset = resOffset;
        }
        if (startOffset && stopOffset) {
          break;
        }
      }
      
      if (startOffset === false) {
        aErrors.push("Segment "+(segOffset+1)+": couldn't find start residue '"+oSeg.getStartRes()+"' in GCF");
      }
      if (stopOffset === false) {
        aErrors.push("Segment "+(segOffset+1)+": couldn't find stop residue '"+oSeg.getStopRes()+"' in GCF");
      }
      if (startOffset !== false && stopOffset !== false && stopOffset <= startOffset) {
        aErrors.push("Segment "+(segOffset+1)+": start residue '"+oSeg.getStartRes()+"' must come before stop residue '"+oSeg.getStopRes()+"'");
      }
    }

    D("validate.aErrors: " + aErrors.length);
    
    D("validate.checkResOverlap");
            
    // for each residue check the start and stop of each segment
    // to make sure none overlap
    for(var resOffset=0; resOffset<aResidues.length; resOffset++) {
      var strRes = aResidues[resOffset];
            
      // check the start residues of all the segments 
      // (needs to be done as two steps)
      for(var segOffset=0; segOffset<aSegments.length; segOffset++) {
        oSeg = aSegments[segOffset];
        if (oSeg.getStartRes() == strRes) {
          if (currentSegment) {
            aErrors.push("Start residue " + oSeg.getStartRes() + 
                            " (Dom " + oSeg.getDomainCode()+", Seg "+oSeg.getSegmentCode()+") " +
                        "overlaps with stop residue " + currentSegment.getStopRes() + 
                            " (Dom "+currentSegment.getDomainCode() + ", Seg " + currentSegment.getSegmentCode()+")");
          }
          else {
            currentSegment = oSeg;
            currentSegmentOffset = segOffset;
          }
        }
      }
      
      // check the stop residues of all the segments 
      // (needs to be done as two steps)
      for(var segOffset=0; segOffset<aSegments.length; segOffset++) {
        oSeg = aSegments[segOffset];
        if (currentSegment && currentSegment.getStopRes() == strRes) {
          currentSegment = false;
          currentSegmentOffset = false;
          intAssignedResidues++;
        }
      }
      
      // keep a count of residue that have an assigned domain
      if (currentSegment) {
        intAssignedResidues++;
      }
    }
      
    D("validate.aErrors: " + aErrors.length);
    
    
    if (aErrors.length > 0) {
      if (boolWarningFlag) {
        alert("The chopping is not valid:\n\n"+aErrors.join("\n")+"\nPlease correct these errors before continuing.");
      }
      this.displayWarning("Chopping not valid");  
      return false;
    }
    else {
      percentAssigned = new Number((intAssignedResidues / aResidues.length) * 100);
      if (boolWarningFlag) {
        alert("This chopping is valid:\n\n"+
              "Domains: "+this.getDomainCount()+"\n"+
              "Segments: "+aSegments.length+"\n"+
              "Residues Assigned: "+intAssignedResidues+"/"+aResidues.length+" ("+percentAssigned.toFixed(1)+"%)\n"              
              );
      }
      this.displayMessage("Chopping is valid");  
      return true;
    }
  }
  
  
  
  ////
  // Submit the chopping
  //   - make sure the start and stop do not overlap
  ////
  this.submitChopping = function(intActionID) {
    if (this.validate(true))
    {
      if (! this.getDifficulty()) {
        alert("You must specify the difficulty level for this chopping");
        return;
      }
      if (! this.getComment()) {
        alert("You must give a comment for your chopping");
        return;
      }
    
      var strXml = this.getXmlString();
      
      var strUrl = "?" + 
          strFFNChainId + "=" + encodeURIComponent(this.getChainId()) + ";" + 
          strFFNDifficulty + "=" + encodeURIComponent(this.getDifficulty()) + ";" + 
          strFFNComment + "=" + encodeURIComponent(this.getComment()) + ";" + 
          strFFNChoppingXml + "=" + encodeURIComponent(strXml) + ";" + 
          strFFNAction + "=" + encodeURIComponent(intActionID);
      
      D("this.submitChopping.URL:" + strUrl);
			
			console.debug(strXml);
			
      window.location = strUrl;
    }
  }
    
  
  ////
  // Set the residues array from the GCF file
  ////
  this.parseGCF = function(strGCF)
  {
    var aLines = strGCF.split(/\n/);
    aResidues = new Array();

    D("parseGCF.lines: " + aLines.length);
        
    ////
    // >gi|void|6insE
    // F     1    1B F
    // V     2    2B V
    ////
        
    for(var i=0; i<aLines.length; i++) 
    {
      // optional GCF header
      if (i==0) {
        var aCols = aLines[0].split('|');
        if (aCols.length == 3) {
          D("parseGCF.title: type: " + aCols[0] + ", col2: " + aCols[1] + ", pdbchain: " + aCols[2]);
          this.setChainId(aCols[2]);
          continue;
        }
      }
      
      var aCols = aLines[i].split(/\s+/);
      ////
      // If it looks like a valid line and the residue name looks valid
      // then push the residue name onto the array
      ////
      if (aCols.length == 4 && aCols[2] != '' && aCols[2] != '*') {
        aResidues.push(aCols[2]);
      }
    }
    
    return(aResidues.length);
  }
  
  this.setForm = function (oForm) { oChoppingForm = oForm } 
  this.getForm = function () { return oChoppingForm } 
  this.setNode = function (oNode) { oChoppingNode = oNode } 
  this.getNode = function () { return oChoppingNode } 
  
  this.getDocument = function () {
    var oNode = this.getNode();
    if (!oNode) {
      warn("Sorry, expected ChoppingNode to be set");
      return false;
    }
    return oNode.ownerDocument;
  } 
  
  this.setChainId = function (strChainId) { D("setChainId("+strChainId+")"); chainId = strChainId } 
  this.getChainId = function () { return chainId } 
  this.getChainCode = function () { return this.getChainId().substr(4, 1) }
  this.getPdbCode = function () { return this.getChainId().substr(0, 4) }
  
  this.getFFNComment = function () { return strFFNComment }
  this.getFFNDifficulty = function () { return strFFNDifficulty }
  
  this.toString = function() {
    var str = "\nCHOPPING\n";
    for(var i=0; i<aDomains.length;i++) {
      oDom = aDomains[i];
      str += oDom.toString();
    }
    return str;
  }
    
  this.setGCFXml = function (oXml) {
    aResidues = new Array();
    nodeList = oXml.getElementsByTagName('gcf_alignment_position');
    D('setGCFXml.length: ' + nodeList.length);
    for(var i=0; i<nodeList.length; i++) {
      var node = nodeList.item(i);
      var resName = node.getAttribute('pdb_res_name');
      D('setGCFXml.residueName['+i+'] ' + resName);
      if (resName && resName != '*') {
        D('setGCFXml.push('+resName+')');
        aResidues.push(resName);
      }
    }
    D('setGCFXml.aResidues.length: '+aResidues.length);
    return aResidues.length;
  }

  this.resetChoppingFigure = function () {
    var res = aResidues[0];
    if (!res) {
      this.setChoppingFigure(imgChoppingFigureWaiting);
    }
    else {
      this.setChoppingFigure(CGIChoppingFigure + 
              "chopping=" + 
              this.getPdbCode() + 
              "%20D" + res + "-" + res +
              escape("["+this.getChainCode()+"]")
            );
    }
  }
    
  this.setChainGCF = function (chainId) {
    var xmlHttp = GetXmlHttpObject();

    this.setChainId(chainId);
    
    var url = CGIGetChainGCF + "accessionId=" + chainId + ";xml=0";
    
    var myChopping = this;
    
    D("Chopping.setChainGCF.xmlHttp: " + xmlHttp);
    xmlHttp.onreadystatechange = function () {
      D("Chopping.setChainGCF.xmlHttp.onreadystatechange: "+xmlHttp.readyState);
      if (xmlHttp.readyState==4 || xmlHttp.readyState=="complete")
      { 
        var strGCF = xmlHttp.responseText;
        var oXml = xmlHttp.responseXML;
                
        myChopping.parseGCF(strGCF);
        //myChopping.setGCFXml(oXml);
        myChopping.resetChoppingFigure();
        myChopping.updateHtml();
      }
    }
    D("Chopping.setChainGCF.xmlHttp.open: "+url);
    xmlHttp.open("GET",url,true);
    D("Chopping.setChainGCF.xmlHttp.send");
    xmlHttp.send(null);
  }
  
  /////
  // Initialise the object
  /////
        
  D("Chopping.new(NODE:"+oNode+", FORM: "+oForm+")");
  
  // set these two variables straight off
  this.setNode(oNode);
  
  this.setForm(oForm);  
    
  this.displayMessage("Getting the GCF for chain '"+chainId+"' via webservices...");
  
  this.setChainGCF(chainId);
  
  this.resetChoppingFigure();
    
  // initialise from the chopping node
  //this.initFromChoppingNode(oNode);

  if (!this.getNode()) {
    warn("Expected valid ChoppingNode");
    return (false);
  }  
  
  D("Chopping.initialised from ChoppingNode: domains="+aDomains.length);

  // we must have at least one domain
  if (aDomains.length == 0) {
    D("Chopping.no domains - adding default entry");
    this.addDomain();
  }
  
  D("Chopping.toString: "+this.toString());
  
  
  D("Chopping.END");
}


function Domain() 
{
  if(!Domain.domains) {
    Domain.domains = 0;
  }
  
  var domainCode = ++Domain.domains;
  var oChopping;
  var aSegments = new Array();
    
  this.toHtml = function() {
    var oChop = this.getChopping();
    var strDomain;
    
    if (!oChop) {
      warn("Chopping object not set in domain: " + this);
      return false;
    }
    
    strDomainName = this.getDomainId();
    strDomain = "<div class='ChoppingDomain' id='" + strDomainName + "'>\n";

    strDomain += "<h3>Domain "+this.getDomainCode()+
						"<span class='actions'>"+
						"<a href='javascript: return false;' onClick='oChopping.removeDomain(\"" + strDomainName + "\")'>"+
							"<img src='/images/16x16/minus.png' border=0></a>"+
						"<a href='javascript: return false;' onClick='oChopping.addDomain(new Domain(), \"" + strDomainName + "\")'>"+
							"<img src='/images/16x16/add.png' border=0></a>"+
						"</span>"+
						"</h3>";
    
    strDomain += "</div>\n";

    for(var i=0; i<aSegments.length; i++) {
      oSeg = aSegments[i];
      strDomain += oSeg.toHtml();
    }
    
    return strDomain;  
  }
  
  this.sortSegments = function () {
    D("sortSegments.BEFORE: "+this);
    aSegments.sort(this.sortBySegmentStartRes);
    D("sortSegments.AFTER: "+this);
  }
  
  this.sortBySegmentStartRes = function(a, b) {
    D("sortBySegmentStartRes: a["+a+"] b["+b+"]");
    return (a.getStartRes() - b.getStartRes());
  } 
  
  this.initFromChoppingNode = function(oNode) {
    D("Domain.initFromChoppingNode: "+oNode);
    aSegments = new Array();
    
    var nlSegments = oNode.childNodes;
    
    D("Domain.initFromChoppingNode.segments: "+nlSegments.length);
    
    for(var i=0; i<nlSegments.length; i++) {
      var nSeg = nlSegments.item(i);
      D("Domain.initFromChoppingNode.segment: "+nSeg.tagName);
      if (nSeg.tagName == 'segment') {
        oSeg = new Segment();
        oSeg.initFromChoppingNode(nSeg);
        this.addSegment(oSeg);
      }
    }
  }
  
  ////
  // Add a Segment to a given position
  ////
  this.addSegment = function(oSegment, strSegmentIdPrevious) {
    var offset=0;
    var oChopping = this.getChopping();
            
    D("Domain.addSegment("+oSegment+", "+strSegmentIdPrevious+")");
    
    ////
    // Update the data structure from the form before we do anything
    ////
    this.updateFromForm();
    
    if(!oSegment) {
      oSegment = new Segment();
    }
    if (strSegmentIdPrevious) {
      for(i=0; i<aSegments.length; i++) {
        D("Domain.addSegment.checkingSegmentIdPrevious: "+aSegments[i].getSegmentId()+" = "+strSegmentIdPrevious);
        if (aSegments[i].getSegmentId() == strSegmentIdPrevious) {
          offset = i + 1;
          break;
        }
      }
    }
    
    oSegment.setDomain(this);
    
    D("Domain.addSegment("+offset+", "+oSegment+")");
    D("Domain.addSegment.beforeSplice: segments: " + aSegments.length);
    aSegments.splice(offset, 0, oSegment);
    D("Domain.addSegment.afterSplice: segments: " + aSegments.length);
    
    this.refreshSegmentCodes();
    D("Domain.addSegment.afterRefresh: segments: " + aSegments.length);
    
    this.updateHtml();
    D("Domain.addSegment.afterUpdateHtml: segments: " + aSegments.length);
    
    if (oChopping)
      oChopping.displayMessage("Adding new segment to domain");
          
    return aSegments.length;
  }
    
  ////
  // Remove a given segment from the array
  ////
  this.removeSegment = function(strSegmentId) {
    var strSegment;
    D("Domain.removeSegment("+strSegmentId+")");
    
    // update the data structure from the form before we do anything
    this.updateFromForm();
    
    if (aSegments.length == 1) {
      alert("Sorry, you cannot remove this segment as there must\nalways be at least one segment in the chopping");
      return false;
    }
    
    for (i=0; i<aSegments.length; i++)
    {
      D("Domain.removeSegment: "+ aSegments[i].getSegmentId() + " = " + strSegmentId);
      if (aSegments[i].getSegmentId() == strSegmentId) {
        strSegment = "Segment "+ aSegments[i].getSegmentCode() + " (Domain "+aSegments[i].getDomain().getDomainCode()+")";
        D("Domain.removeSegment.beforeSplice("+aSegments.join(", ")+")");
        aSegments.splice(i, 1);
        D("Domain.removeSegment.afterSplice("+aSegments.join(", ")+")");
        break;
      }
    }
    
    this.refreshSegmentCodes();
    
    this.updateHtml();
  
    if (strSegment) {
      if (oChopping) {
        oChopping.displayMessage("Removed "+strSegment+" from Chopping"); 
      }
      return true;
    }
    else {
      if (oChopping) {
        oChopping.displayWarning("Error: couldn't remove Segment '"+strSegmentId+"' from Chopping");
      }
      return false;
    }
  }
  
  this.refreshSegmentCodes = function () {
    for(var i=0; i<aSegments.length; i++) {
      var oSeg = aSegments[i];
      oSeg.setSegmentCode(i+1);
    }    
  }
  
  ////
  // Update the Segment values from the form
  ////
  this.updateFromForm = function (oForm) {
    
    
    if (!oForm) {
      var oChop = this.getChopping();
    
      if (!oChop) {
        D("Domain.updateFromForm(): chopping object not set in domain: " + this);
        return false;
      }
      oForm = oChop.getForm();
    }
    
    for(var i=0; i<aSegments.length; i++) {
      var oSegment = aSegments[i];
      oSegment.updateFromForm(oForm);
    }
    D("Domain.updateFromForm: "+this);
  }

  this.setDomainCode = function (intDomainCode) { domainCode = intDomainCode }
  
  this.setChopping = function (oChop) { oChopping = oChop }
  this.getChopping = function () { return oChopping }
  
  this.getSegments = function () { 
    D("Domain.getSegments : "+aSegments);
    return aSegments
  }
  
  this.getFFNDomainCode = function () { return this.getDomainId() + "__domain_code" }
  this.getDomainCode = function () { return domainCode }
  this.getDomainId = function () { return "domain_"+this.getDomainCode() }

  this.updateHtml = function () {
    var oChopping = this.getChopping();
    if (!oChopping) {
      D(" Domain.updateHtml: expected oChopping to be set");
      return false;
    }
    oChopping.updateHtml();
  }

  this.toString = function() {
    var str = "DOMAIN = {\n\tdomainCode => "+this.getDomainCode()+"\n";
    for(var i=0; i<aSegments.length; i++) {
      var oSeg = aSegments[i];
      str += "\tsegments => "+oSeg.toString()+"\n";
    }
    str += "}\n";
    return str;
  }
  
  // we must have at least one domain
  D("Domains.Segments : "+aSegments.length);
  if (aSegments.length == 0) {
    D("Domains.no segments - adding default entry");
    this.addSegment();
  }  
}

function Segment()
{  
  if (!Segment.segmentCount) {
    Segment.segmentCount = 0;
  }
  
  // unique value
  var segmentId = ++Segment.segmentCount;
  
  // segment code within domain (expected to be set externally when created)
  var segmentCode;
  
  var chainId = '';
  var startRes = '';
  var stopRes = '';
  var oChopping;
  var oDomain;
  var startInsertCode = '';
  var stopInsertCode = '';
  
  this.initFromChoppingNode = function(oNode) {
    D("Segment.initFromChoppingNode: "+oNode);
    var strChainId;
    var strStartRes;
    var strStopRes;
    
    strChainId = oNode.getAttribute('chain_id');
    strStartRes = oNode.getAttribute('start_res_name');
    strStopRes = oNode.getAttribute('stop_res_name');    
        
    this.setChainId(strChainId);
    this.setStartRes(strStartRes);
    this.setStopRes(strStopRes);
  }
  
      
  // print out the HTML for this segment
  this.toHtml = function() {
    D("Segment.toHtml()");
    
    oChop = this.getChopping();
    if (!oChop) {
      throw("Chopping object not set in segment: " + this);
      return false;
    }
    
    oDom = this.getDomain();
    if (!oDom) {
      throw("Domain object not set in segment: " + this);
      return false;
    }    
    
    strSegmentName = this.getSegmentId();
    
    strSegment = "<div class='ChoppingSegment' id='" + strSegmentName + "'>\n";
    
    strSegment += "<input type='hidden' name='"+this.getFFNChainId()+"' value='"+this.getChainId()+"'></input>\n";
    
    strSegment += "Segment "+this.getSegmentCode()+" from ";
                                        
    strSegment += "<select " +
                    " class='Residue' "+
                    " id='" + this.getFFNStartRes() + "' "+
                    " name='"+this.getFFNStartRes() + "'>"+
                    oChop.getSelectResidueOptions(this.getStartRes()) +       
                    "</select>\n";
            
    strSegment += " to ";
    
    strSegment += "<select " +
                    " class='Residue' "+
                    " id='" + this.getFFNStopRes() + "' "+
                    " name='"+this.getFFNStopRes() + "'>"+
                    oChop.getSelectResidueOptions(this.getStopRes()) + 
                    "</select>\n";
                    
    strSegment += "<a href='javascript:return false;' "+
						"onClick='oChopping.removeSegment(" + oDom.getDomainCode() + ', "' + strSegmentName + '"' + ")' "+
						"class='action'><img src='/images/16x16/minus.png' border=0 alt='-'></a>\n";

    strSegment += "<a href='javascript:return false;' "+
						"onClick='oChopping.addSegment(new Segment(), "+oDom.getDomainCode()+", \""+strSegmentName+"\")' "+
						"class='action'><img src='/images/16x16/add.png' border=0 alt='+' /></a>\n";
                  
    strSegment += "</div>\n";
    
    return strSegment;
  }
  
  
  // initialise from Form node
  this.updateFromForm = function(oForm) {
    var strChainId = '';
    var strStartRes = '';
    var strStopRes = '';
    var oFF;

    if (! oForm) {
      warn("Couldn't access the Form object: " + oForm);
      return false;
    }
    oFF = oForm[this.getFFNChainId()];
    if (oFF) {
      strChainId = oFF.value;
    }
    oFF = oForm[this.getFFNStartRes()];
    if (oFF && oFF.selectedIndex >= 0) {
      strStartRes = oFF.options[oFF.selectedIndex].value;
    }
    oFF = oForm[this.getFFNStopRes()];
    if (oFF && oFF.selectedIndex >= 0) {
      strStopRes = oFF.options[oFF.selectedIndex].value;
    }
    
    this.setChainId(strChainId);
    this.setStartRes(strStartRes);
    this.setStopRes(strStopRes);
    
    
    D("Segment.updateFromForm: "+ this);    
  }
  
  // This is unique for every segment
  this.getSegmentId = function () { return segmentId }
  
  this.getChopping = function () {
    var oDom = this.getDomain();
    if (!oDom) {
      warn("domain not set in segment");
      return false;
    }
    return oDom.getChopping();
  }
  this.setDomain = function (oDom) { 
    oDomain = oDom;
  }
  this.getDomain = function () { return oDomain }

  this.getDomainId = function () {
    var oDom = this.getDomain();
    if (!oDom) {
      warn("domain not set in segment");
      return false;    
    }
    return oDom.getDomainId();
  }
  
  this.getDomainCode = function () {
    var oDom = this.getDomain();
    if (!oDom) {
      warn("domain not set in segment");
      return false;
    }
    return oDom.getDomainCode();  
  }
    
  this.getId = function () { return segmentId }
  
  this.getSegmentId = function () { 
    oDom = this.getDomain();
    if (!oDom) {
      warn("Domain object not set in segment: " + this);
      return false;
    }
    return oDom.getDomainId() + "__segment_" + this.getSegmentCode();
  } 
  this.setSegmentCode = function (intSegCode) { segmentCode = intSegCode } 
  this.getSegmentCode = function () { return segmentCode } 
  
  // get the form field names
  this.getFFNChainId = function () { return this.getSegmentId() + "__chain_id" }
  this.getFFNStartRes = function () { return this.getSegmentId() + "__start_res" }
  this.getFFNStopRes = function () { return this.getSegmentId() + "__stop_res" }
  
  // get the values
  this.getChainId = function () { 
    if (!chainId) {
      var oDom = this.getDomain();
      if (oDom) {
        oChopping = oDom.getChopping();
        if (oChopping) {
          this.setChainId(oChopping.getChainId());
        }
      }
    }
    return chainId;
  }
  this.getStartRes = function () { return startRes }  
  this.getStopRes = function () { return stopRes }
  
  this.getStartInsertCode = function () { return startInsertCode }
  this.getStopInsertCode = function () { return stopInsertCode }
  
  // can make these a bit more strict in future
  this.setChainId = function (str) { 
    D("Segment.setChainId('"+str+"')"); 
    if (str.length == 5) {
      chainId = str;
    }
    else {
      D("Segment.setChainId: chain id not 5 characters, not setting '"+str+"'"); 
      return undef;
    }
  }
  this.setStartRes = function (strStartRes) { startRes = strStartRes }  
  this.setStopRes = function (strStopRes) { stopRes = strStopRes }
  
  this.toString = function() {
    var str = "SEGMENT: segcode["+this.getSegmentCode()+"] start["+this.getStartRes()+"] stop["+this.getStopRes()+"]";
    return str;
  }  
  
}

function GetXmlHttpObject(handler)
{ 
	var objXmlHttp=null

	if (navigator.userAgent.indexOf("Opera")>=0)
	{
		alert("This example doesn't work in Opera") 
		return 
	}
	if (navigator.userAgent.indexOf("MSIE")>=0)
	{ 
		var strName="Msxml2.XMLHTTP"
		if (navigator.appVersion.indexOf("MSIE 5.5")>=0)
		{
			strName="Microsoft.XMLHTTP"
		} 
		try
		{ 
			objXmlHttp=new ActiveXObject(strName)
			objXmlHttp.onreadystatechange=handler 
			return objXmlHttp
		} 
		catch(e)
		{ 
			alert("Error. Scripting for ActiveX might be disabled") 
			return 
		} 
	} 
	if (navigator.userAgent.indexOf("Mozilla")>=0)
	{
		objXmlHttp=new XMLHttpRequest()
		objXmlHttp.onload=handler
		objXmlHttp.onerror=handler 
		return objXmlHttp
	}
} 

