/* uricalc_controller.js:  miscellaneous routines for managing the user view in uricalc

   Copyright (c) 2008 World Wide Web Consortium, 
   (Massachusetts Institute of Technology, European Research 
   Consortium for Informatics and Mathematics, Keio University). 
   All Rights Reserved. This work is distributed under the 
   W3C(TM) Software License [1] in the hope that it will be 
   useful, but WITHOUT ANY WARRANTY; without even the implied 
   warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   [1] http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231

   Some material here based on work by Black Mesa Technologies LLC;
   used by permission.

*/

/* Revisions:
   2009-03-15 : CMSMcQ : made first version of these routines
*/

// 0 Global configuration values.  (Constants all).
// Min and max lengths for randomly generated strings.
// N.B. lenMax must not exceed the @maxlength on the input elements in stringcalc.html
var lenMin = 0;
var lenMax = 100;
var lenMaxTNT = 10; // TNT units are already strings, 100 is too large.
var cMaxTries = 1000; // maximum number of attempts to satisfy a text (e.g. daylight)
var fHistoriaIntacta = true; // History (papertape) has never been touched.

// 1 Command dispatcher 

// uricalc_run(kw):  run the function indicated by kw (a string,
// but in practice usually a name token)
// and update both sBuf and xBuf
function uricalc_run(kw) {
  var sT;

  switch (kw) {
  case 'random_uri':
    sT = s_randomexample();
    resynch('both','s',sT,'Random sample URI reference');
    break;

  case 'random_ascii':
    var nLen = random_minmax(lenMin,lenMax);
    sT = s_make_random(nLen,'ascii');
    resynch('both','s',sT,'Random printable-ASCII string');
    break;

  case 'random_tnt':
    var nLen = random_minmax(lenMin,lenMaxTNT );
    sT = s_make_randomurifromtnt(nLen);
    resynch('both','s',sT,'Random string from bis of URIs');
    break;

  case 'random_ucs':
    var nLen = random_minmax(lenMin,lenMax);
    sT = s_make_random(nLen,'ucs');
    resynch('both','s',sT,'Random UCS string');
    break;

  case 'random_octets':
    var nLen = random_minmax(lenMin,lenMax);
    sT = s_make_random(nLen,'octets');
    resynch('both','s',sT,'Random sequence of octets');
    break;

  /*
  case 'parse_test':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'test-re'));
    break;
    */

  case 'parse_uri_a':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'URI'));
    break;

  case 'parse_uriref_a':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'URI-reference'));
    break;

  case 'parse_uriref_b':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'URI-reference-AppB'));
    break;

  case 'parse_uriref_h':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'URI-reference-H5'));
    break;

  case 'parse_uriref_w':
    synch_components(obj_analyse_URIref(xsl_id('sBuf').value,'URI-reference-WAH5'));
    break;

  case 'random_daylight':
    var arrRule = arr_GetChecklist_V(arr_Rulesets);
    var arrGens = arr_GetChecklist_V(arr_Generators);
    if (arrRule.length < 2) {
      alert("At least two of the boxes must be checked, for 'Find different behavior' to work");
    } else if (arrGens.length < 1) {
      alert("At least one generator box must be checked, for 'Find different behavior' to work");
    } else {
      var d = discrepancy_Find_aRules_aGens_Min_MaxS_MaxNT_MaxTries(arrRule,arrGens,
								    lenMin,lenMax,lenMaxTNT,cMaxTries);
      clearComponents();
      ReportDiscrepancy(d);
    };
    break;

  case 'uppercase':
    sT = xsl_id('sBuf').value.toUpperCase();
    resynch('both','s',sT,'Uppercased');
    break;

  case 'lowercase':
    sT = xsl_id('sBuf').value.toLowerCase();
    resynch('both','s',sT,'Lowercased');
    break;

  case 'reverse':
    sT = s_reverse_s(xsl_id('sBuf').value);
    resynch('both','s',sT,'Reversed');
    break;

  case 'dup':
    sT = s_duplicate_s(xsl_id('sBuf').value);
    resynch('both','s',sT,'Duplicated');
    break;

  case 'length':
    resynch('both','s',xsl_id('sBuf').value.length.toString(),'Got length');
    break;

  case 'c2x':
    var enc = xsl_id('char_encoding').value;
    sT = c2x(xsl_id('sBuf').value,enc);
    resynch('both','s',sT,'Translated to hex');
    break;

  case 'x2c':
    var enc = xsl_id('char_encoding').value;
    sT = x2c(xsl_id('sBuf').value,enc);
    resynch('both','s',sT,'Translated from hex to characters');
    break;

  case 'center':
    sT = s_center_s_n(xsl_id('sBuf').value,80);
    resynch('both','s',sT,'Centered in field of 80');
    break;

  case 'right':
    sT = s_right_s_n(s_strip_s(xsl_id('sBuf').value),80);
    resynch('both','s',sT,'Right-aligned in field of 80');
    break;

  case 'left':
    sT = s_left_s_n(s_strip_s(xsl_id('sBuf').value),80);
    resynch('both','s',sT,'Left-aligned in field of 80');
    break;

  case 'normalize-space':
    sT = s_normalizespace_s(xsl_id('sBuf').value);
    resynch('both','s',sT,'Normalized white space');
    break;

  case 'strip':
    sT = s_strip_s(xsl_id('sBuf').value);
    resynch('both','s',sT,'Stripped leading and trailing white space');
    break;

  case 'strip-left':
    sT = s_strip_s(xsl_id('sBuf').value,'left');
    resynch('both','s',sT,'Stripped leading white space');
    break;

  case 'strip-right':
    sT = s_strip_s(xsl_id('sBuf').value,'right');
    resynch('both','s',sT,'Stripped trailing white space');
    break;


  default:
    alert('Function ' + kw + ' not yet implemented.  Sorry.');
    break;
  }
}


// resynch(kwBuf,kwType,s):  re-synchronize the display buffers.
// The first argument (kwBuf) indicates which buffers to update.
// The second argument (kwType) indicates what kind of string is being passed in.
// The third argument (s) provides the input string to use.
function resynch(kwBuffername, kwType, s, sDesc, elemDetails) {
  var sT;
  var sX;
  var enc = xsl_id('char_encoding').value;

  // calculate the two buffer values: sT for string buffer, sX for hex
  switch (kwType) {
  case 's':
    sT = s;
    sX = c2x(s,enc);
    break;
  case 'x':
    sX = 's';
    sT = x2c(s,enc);
    break;
  }
  
  // Now update the appropriate buffers
  switch (kwBuffername) {
  case 'sBuf':
    xsl_id('sBuf').value = sT;
    break;
  case 'xBuf':
    xsl_id('xBuf').value = sX;
    break;
  default:
    xsl_id('sBuf').value = sT;
    xsl_id('xBuf').value = sX;
    break;
  }
  record_event(sDesc,sT,sX,elemDetails);
}


var compnames = ['scheme','authority','path','query','fragment'];

// synch_components(C):  use associative array / object C
// to refresh the component fields.
function synch_components( Comps ) {
  var validity;

  if (Comps['valid']) {
    xsl_id('string_validity').innerHTML = "yes";
    for (var i = 0; i < compnames.length; i++) {
      xsl_id(compnames[i]).value = Comps[compnames[i]];
    }
    record_event('Analysed valid ' + Comps['NT'] + ' into components', xsl_id('sBuf').value, xsl_id('xBuf').value);
  } else {
    xsl_id('string_validity').innerHTML = "no";
    clearComponents();
    record_event('String not valid as ' + Comps['NT'], xsl_id('sBuf').value, xsl_id('xBuf').value);
  };
};

function clearComponents() {
  for (var i = 0; i < compnames.length; i++) {
    xsl_id(compnames[i]).value = '';
  }
}

// arr_GetChecklist_V(Vocab)
// make an array containing just those items from Vocab for which 
// the checkbox is checked.
function arr_GetChecklist_V(arr_V) {
  var arr = new Array();

  for (var i in arr_V) {
    var nmT = arr_V[i];
    if (xsl_id(nmT).checked) {
      arr = arr.concat(nmT);
    } else {
    }
  }
  return arr;
}

// History maintenance
// Clear the history display
function clear_history() {
  xsl_id('papertape').innerHTML = '';
}

// Record an event with description, string buffer contents and (if requested) hex 
// buffer contents.
function record_event(sDesc, sBuf, xBuf, elemDetails) {
  var elemItem = document.createElement('li');
  var elemDesc = document.createElement('em');
  var elemSBuf = document.createElement('code');
  var tn1 = document.createTextNode(': ');
  elemDesc.textContent = sDesc;
  elemSBuf.textContent = sBuf;

  elemItem.appendChild(elemDesc);
  elemItem.appendChild(tn1);
  elemItem.appendChild(elemSBuf);

  // alert("fShowHex.checked has the value " + xsl_id('fShowHex').checked);
  if (xsl_id('fShowHex').checked == true) {
    var elemXBuf = document.createElement('code');
    elemXBuf.textContent = xBuf;
    var tn2 = document.createTextNode(" (");
    var tn3 = document.createTextNode(")");
    elemItem.appendChild(tn2);
    elemItem.appendChild(elemXBuf);
    elemItem.appendChild(tn3);
  }

  // If we got an elemDetails argument, append it.
  if (typeof elemDetails != "undefined") {
    elemItem.appendChild(elemDetails);
  }

  var target = xsl_id('papertape');
  if (fHistoriaIntacta) {
    target.innerHTML = '';
  }
  target.appendChild(elemItem);
  fHistoriaIntacta = false;
}



function ReportDiscrepancy(d) {
  // When a discrepancy is found, say something.
  // var elemDisc = dl_from_object(d);
  var elemDisc = document.createElement('div');
  var elemDesc = document.createElement('p');
  elemDesc.textContent = d['description'];
  elemDisc.appendChild(elemDesc);
  if (typeof (d['analyses']) != "undefined") {
    var elemAnalyses = ul_from_array(d['analyses']);
    elemDisc.appendChild(elemAnalyses);
  }

  resynch('both','s',d['string'],'Found a disputed string',elemDisc);
};

// ul_from_array: dump an array into an ul
function ul_from_array(a) {
  var ul = document.createElement('ul');
  for (var i in a) {
    var li = document.createElement('li');
    switch (typeof a[i]) {
    case "string":
      li.textContent = a[i];
      break;
    case "number":
      li.textContent = a[i].toString();
      break;
    case "boolean":
      li.textContent = a[i].toString();
      break;
    case "undefined":
      li.textContent = "[undefined]";
      break;
    case "object":
      var e = div_from_object(a[i]);
      li.appendChild(e);
      break;
    }
    ul.appendChild(li);
  }
  return ul;
}

// p_from_object(a):  dump an object into 
// an HTML div (was dl)
function div_from_object(ana) {
  var e = document.createElement('div');
  e.setAttribute('class','object_display');

  for (var p in ana) {
    var propholder = document.createElement('div');
    var label = document.createElement('b');
    var colon = document.createTextNode(': ');
    label.textContent = p.toString();
    /*
    if (typeof ana[p] == "string") {
      desc.textContent = ana[p];
    } else if (typeof ana[p] == "object") {
      desc.appendChild(dl_from_object(ana[p]));
    }
    */
    switch (typeof ana[p]) {
    case "string":
    case "number":
    case "boolean":
      var desc = document.createElement('code');
      desc.textContent = ana[p].toString();
      break;
    case "undefined":
      var desc = document.createElement('em');
      desc.textContent = "[undefined]";
      break;
    case "object":
      var desc = div_from_object(ana[p]);
      break;
    }

    // quick hack:  suppress properties names "string",
    // they are uninteresting.
    if (p == "string") {
      ; // nop
    } else {
      // display normally
      propholder.appendChild(label);
      propholder.appendChild(colon);
      propholder.appendChild(desc);
      
      e.appendChild(propholder);
    }
  }
  return e;
}
