/*******************************************************************************
 * Javascript utility functions to be used by multiple scripts.
 *******************************************************************************/


// Returns true if ch is a string consisting of a single digit, false
// otherwise.
function IsDigit(ch)
{
  return ch.match(/^\d$/) != null;
}

function IsNumeric(str)
{
  return str.match(/^\d+$/) != null;
}

// Find toFind in ar.
function ArrayIndexOf(ar, toFind)
{
  len = ar.length;
  for (i = 0; i < len; i++) {
    if (ar[i] == toFind) return i;
  }
  return -1;
}

/////
// Remove el from array, if it exists. Returns true if it was there.
function ArrayRemove(array, el)
{
  var gotIt = false;

  while((index = ArrayIndexOf(array, el)) != -1) {
    gotIt = true;
    array.splice(index, 1);
  }
  return gotIt;
}

/////
// Adds el to array if it doesn't already exist.
function ArrayAddUnique(array, el)
{
  if (ArrayIndexOf(array, el) == -1) {
    array.push(el);
    return true;
  }
  return false;
}

// Returns a random element of an array.
function RandomChoice(choices)
{
  var i = Math.floor(choices.length * Math.random());
  return choices[i];
}

// Returns a random integer between lower and upper-1, inclusive.
function RandomInt(lower, upper)
{
  return Math.floor(Math.random() * (upper - lower)) + lower;
}

function sendHttpRequest(url, callback, params, isPost)
{
  var xmlhttp = false;
  /*@cc_on @*/
  /*@if (@_jscript_version >= 5)
  // JScript gives us Conditional compilation, we can cope with old IE versions.
  // and security blocked creation of the objects.
  try {
  xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
  try {
  xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (E) {
  xmlhttp = false;
  }
  }
  @end @*/
  if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
    try {
      xmlhttp = new XMLHttpRequest();
    } catch (e) {
      xmlhttp=false;
    }
  }
  if (!xmlhttp && window.createRequest) {
    try {
      xmlhttp = window.createRequest();
    } catch (e) {
      xmlhttp=false;
    }
  }

  if (isPost) {
    xmlhttp.open("POST", url, true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.setRequestHeader("Content-length", params.length);
    xmlhttp.setRequestHeader("Connection", "close");
  } else {
    if (params) {
      url += "?" + params;
    }
    params = null;
    xmlhttp.open("GET", url, true);
  }
  xmlhttp.onreadystatechange = callback;
  xmlhttp.send(params);
  return xmlhttp;
}

function doHttpRequest(url, params, isPost,
		       success, failure)
{
  var request = null;
  function callback()
  {
    if (request && request.readyState == 4) {
      var resp = request.responseText;
      if (request.status == 200) {
	if (success) success(resp);
      } else {
	if (failure) failure(request.status, resp);
      }
      request = null;
    }
  }
//  console.log('doHttpRequest, params: ', params, ' typeof(params): ', typeof(params));
  if (typeof(params) != 'string') params = makeParams(params);
//  console.log('doHttpRequest, now params: ', params, ' typeof(params): ', typeof(params));
  request = sendHttpRequest(url, callback, params, isPost);
}

function makeParams(obj)
{
  var params = '';
  var sep = '';
  for (var el in obj) {
    params += sep + encodeURIComponent(el) + '=' + encodeURIComponent(obj[el]);
    sep = '&';
  }
//  console.log('makeParams: obj: ', obj, ', params: ', params);
  return params;
}

function setElementsDisplay(els, display)
{
  for (var i = 0; i < els.length; i++) {
    var el = document.getElementById(els[i]);
    el.style.display = display;
  }
}

function showElements(els)
{
  setElementsDisplay(els, '');
}

function hideElements(els)
{
  setElementsDisplay(els, 'none');
}

function showElements(els)
{
  for (var i = 0; i < els.length; i++) {
    document.getElementById(els[i]).style.display = '';
  }
}

function hideElements(els)
{
  for (var i = 0; i < els.length; i++) {
    document.getElementById(els[i]).style.display = 'none';
  }
}


// Array of strings for converting notes from strings to numbers.
var noteArray = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];
// Array of strings for converting notes from strings to numbers, using flats.
var flatNoteArray = ["A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab"];

// Convert a note name (A-G) with a sharp or flat sign into a note number (0-11).
function NoteNameToNum(noteName, sharpFlat)
{
  if (sharpFlat != null && noteName.length == 1) {
    noteName += sharpFlat;
  }

  var num = ArrayIndexOf(noteArray, noteName.toUpperCase());
  return num;
}

function NoteNumToName(noteNum, accidentalIn, includeOctave)
{
  if (!accidentalIn) accidentalIn = accidental;
  if (accidentalIn == '#') {
    ret =  noteArray[noteNum % 12];
  } else {
    ret = flatNoteArray[noteNum % 12];
  }
  if (includeOctave) {
    ret += Math.floor(noteNum / 12);
  }
  return ret;
}
var m2 = 1;
var maj2 = 2;
var m3 = 3;
var maj3 = 4;
var p4 = 5;
var aug4 = 6;
var p5 = 7;
var m6 = 8;
var maj6 = 9;
var m7 = 10;
var maj7 = 11;
var octave = 12;
var dim7 = maj6;
var dim5 = aug4;
var aug5 = m6;

var IntervalNames = ['b2',
		     '2',
		     'b3',
		     '3',
		     '4',
		     'b5',
		     '5',
		     '#5',
		     '6',
		     'b7',
		     '7'];


function IntervalToName(interval) {
  ret = IntervalNames[interval-1];
  return ret;
}


var LongIntervalNames = ['minor second',
			 'major second',
			 'minor third',
			 'major third',
			 'perfect fourth',
			 'diminished fifth',
			 'perfect fifth',
			 'minor sixth',
			 'major sixth',
			 'diminished seventh',
			 'major seventh',
			 'octave'];

function IntervalToLongName(interval)
{
  return LongIntervalNames[(interval - 1) % 12];
}

var chordTypes = [
		  ['', [maj3, p5], 'major'],
		  ['m', [m3, p5], 'minor'],
		  ['dim', [m3, dim5], 'diminished'],
		  ['aug', [maj3, aug5], 'augmented'],
		  ['7th', [maj3, p5, m7], 'seventh'],
		  ['m7th', [m3, p5, m7], 'minor seventh'],
		  ['maj7th', [maj3, p5, maj7], 'major seventh']
		  ];


function getEl(id)
{
  return document.getElementById(id);
}

/******************************************************************************
 * Cookie code from:
 * http://www.quirksmode.org/js/cookies.html
 ******************************************************************************/

function createCookie(name,value,days) {
    if (days) {
	var date = new Date();
	date.setTime(date.getTime()+(days*24*60*60*1000));
	var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
	var c = ca[i];
	while (c.charAt(0)==' ') c = c.substring(1,c.length);
	if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name,"",-1);
}

function makeEvent(el, eventType, fn)
{
  if (el.addEventListener){
    el.removeEventListener(eventType, fn, false);
    el.addEventListener(eventType, fn, false);
  } else if (el.attachEvent){
    clearEvent(el, eventType, fn);
    var eProp = eventType + fn;
    el["e"+eProp] = fn;
    el[eProp] = function() { el["e"+eProp]( window.event ); };
    el.attachEvent("on"+eventType, el[eProp] );
  } else {
    el['on'+eventType] = fn;
  }
}

function addEvent(el, eventName, fn)
{
  return makeEvent(el, eventName, fn);
}

function clearEvent(el, eventName, fn ) {
  if (el.removeEventListener) {
    el.removeEventListener(eventName, fn, false);
  }  else if (el.detachEvent) {
    var eProp = eventName + fn;
    if (el[eProp]) {
      el.detachEvent("on"+eventName, el[eProp]);
      el['e'+eProp] = null;
      el[eProp] = null;
    } else {
      el.detachEvent('on'+eventName, fn);
    }
  } else {
    el['on'+eventName] = null;
  }
}


// Scheme-like lazy evaluation (see Scheme function "delay")
function MakePromise(fn)
{
  var gotResult = false;
  var result = null;
  return function() {
    if (!gotResult) {
      result = fn();
      gotResult = true;
    }
    return result;
  };
}

function ForEach(ar, fn)
{
  for(var i = 0; i < ar.length; i++) {
    var ret = fn(ar[i], i);
    if (ret) return ret;
  }
  return null;
}

/////
// Calls fn n times (with the index) or until fn returns a true value.
function Repeat(n, fn)
{
  for (var i = 0; i < n; i++) {
    var ret = fn(i);
    if (ret) return ret;
  }
  return false;
};

function Pairwise(ar, fn)
{
  for(var i = 0; i < ar.length - 1; i++) {
    var ret = fn(ar[i], ar[i + 1]);
    if (ret) return ret;
  }
  return null;
}

function makeArray(param)
{
  var ret;
  if (typeof(param) == 'number') {
    ret = [];
    ret.length = param;
    return ret;
  } else {
    return [param];
  }
}

function Map(ar, fn)
{
  var len = ar.length;
  var ret = makeArray(len);
  for(var i = 0; i < len; i++) {
    ret[i] = fn(ar[i]);
  }
  return ret;
}

function Reduce(ar, init, fn)
{
  var total = init;
  ForEach(ar, function(item) {
    total = fn(total, item);
  });
  return total;
}

function ReduceOr(ar, fn)
{
  var arLen = ar.length;
  for(var i = 0; i < arLen; i++) {
    var val = fn(ar[i]);
    if (val) return val;
  }
  return false;
}

function ReduceAnd(ar, fn)
{
  var arLen = ar.length;
  var val;
  for(var i = 0; i < arLen; i++) {
    val = fn(ar[i]);
    if (!val) return false;
  }
  return val;
}

function Filter(ar, fn)
{
  var ret = [];
  ForEach(ar, function(item) {
    if (fn(item)) {
      ret.push(item);
    }
  });
  return ret;
}

function Complement(ar1, ar2)
{
  return Filter(ar1, function(item) {
    return (ArrayIndexOf(ar2, item) == -1);
  });
}

function Sum(ar)
{
  return Reduce(ar, 0, function(total, item) { return total + item; });
}

function ArrayFind(ar, fn)
{
  for (i = 0; i < ar.length; i++) {
    if (fn(ar[i])) return ar[i];
  }
  return null;
}

function ArrayFindAndRemove(ar, fn)
{
  for (i = 0; i < ar.length; i++) {
    if (fn(ar[i])) {
      var ret = ar[i];
      ar = ar.splice(i, 1);
      return ret;
    }
  }
  return null;
}


/******************************************************************************
 * Turn a div into a link
 ******************************************************************************/
function MakeDivLink(elId, url, bgColor, fgColor)
{
    var el = document.getElementById(elId);
    if (url) {
	var aEl = document.createElement('a');
	aEl.href = url;
	var imgEl = document.createElement('img');
	// ES: Don't hard-link to chorderator.
	imgEl.src = 'http://www.chorderator.com/images/transparent.gif';
	aEl.appendChild(imgEl);
	el.style.position = 'relative';
	el.appendChild(aEl);
	imgEl.style.zIndex = '10';
	imgEl.style.width = '100%';
	imgEl.style.height = '100%';
	imgEl.style.position = 'absolute';
	imgEl.style.float = 'left';
	imgEl.style.left = '0';
	imgEl.style.top = '0';
	imgEl.style.border = 'none';
    }
    if (bgColor) {
	addEvent(el, 'mouseover',
		 function() {
		     el.style.backgroundColor = bgColor;
		     el.style.color = fgColor;
		 });
	addEvent(el, 'mouseout',
		 function() {
		     el.style.backgroundColor = '';
		     el.style.color = '';
		 });
    }
}


function Format(fmt, params)
{
  for (var p in params) {
    fmt = fmt.replace('{' + p + '}', params[p]);
  }
  return fmt;
}

