/*
  Name:     englishNum.js
  Purpose:  Convert integer values to English written form.
  Author:   M. J. Fromberger <http://www.dartmouth.edu/~sting/>
  
  Copyright (C) 2004 M. J. Fromberger, All Rights Reserved.

  Permission is hereby granted to use, copy, merge, publish, and
  distribute this software without restriction, provided this
  copyright notice and associated terms are included in all copies or
  substantial excerpts of the software.

 */

// The first entry in the enUnits array is special; it holds the tag
// for the 10^2s place, usu. "hundred" for English
enUnitsUS = ["hundred", "thousand", "million", "billion", "trillion",
	     "quadrillion", "quintillion", "sextillion"]; 

enUnitsUK = ["hundred", "thousand", "million", "milliard", "billion",
	     "thousand billion", "quadrillion", "thousand quadrillion"];

enDigits  = ["zero", "one", "two", "three", "four", "five", "six", 
	     "seven", "eight", "nine", "ten", "eleven", "twelve", 
	     "thirteen", "fourteen", "fifteen", "sixteen", "seventeen",
	     "eighteen", "nineteen"];

/* The entries in the currency arrays are:
   0  the singular form of the primary currency name
   1  the zero or plural form of the primary currency name
   2  the singular form of the currency subunit
   3  the zero or plural form of the currency subunit
   4  the number of subunits per primary currency unit

   Note that this format assumes a reasonably decimalized currency
   system like that of the U.S. and Europe, with only two interesting
   divisions.  It will not work for old-style British currency.
 */
enCurrUS  = ["dollar", "dollars", "cent", "cents", 100];
enCurrUK  = ["pound", "pounds", "penny", "pence", 100];
enCurrEU  = ["euro", "euros", "eurocent", "eurocents", 100];

// The first two entries in the enDecads array are placeholders to
// make the index math work out more easily; their contents are not
// interpreted.
enDecads  = ["n/a", "n/a", "twenty", "thirty", "forty", "fifty", "sixty", 
	     "seventy", "eighty", "ninety"];

enUnits   = enUnitsUS; // Adjust as desired
enCurr    = enCurrUS;  // Adjust as desired

/* natToEnglish(value)

   Convert a nonnegative integer into its English written equivalent.
   If no_and is true, the end of the value will not have an "and"
   inserted (the default is to do so).

   Precondition: value >= 0
 */
function natToEnglish(value, no_and)
{
    var last_sep = no_and ? " " : " and ";

    // Catenate two strings with the given separator, but the
    // separator is omitted if either string is empty.
    function paste(s1, s2, sep) {
	if (s1.length == 0)
	    return s2;
	else if (s2.length == 0)
	    return s1;
	else
	    return s1 + sep + s2;
    }

    function conv100(val) {  // Precondition: 0 < val < 100
	if (val < 20)
	    return enDigits[val];
	else {
	    var base = enDecads[Math.floor(val / 10)];
	    if (val % 10 != 0)
		return paste(base, enDigits[val % 10], "-");
	    else
		return base;
	}
    }

    function conv1000(val, last) { // Precondition: 0 < val < 1000
	if (val < 100)
	    return conv100(val);
	
	base = enDigits[Math.floor(val / 100)] + "-" + enUnits[0];
	return paste(base, conv100(val % 100), last);
    }
    
    function convert(val, depth) { // Precondition: 0 <= val, 0 <= depth
	var cur  = val % 1000;
	var rest = Math.floor(val / 1000);
	var base, last;
	
	last = (depth == 0 && cur >= 100) ? last_sep : " ";
	base = (cur == 0) ? "" : conv1000(cur, last);

	if (depth > 0 && cur != 0) {
	    last = (cur < 20) ? "-" : " ";
	    base = paste(base, enUnits[depth], last);
	}

	if (rest != 0) {
	    last = (depth == 0 && cur < 100) ? last_sep : " ";
	    base = paste(convert(rest, depth + 1), base, last);
	}

	return base;
    }

    if (value == 0)
	return enDigits[value];
    else
	return convert(value, 0);
}

/* intToEnglish(value)

   Convert a signed integer into its English written representation.
   If no_and is true, the end of the number will not have an "and"
   inserted; the default is to do so.

   See also: natToEnglish(...)
 */
function intToEnglish(value) 
{
    if (value < 0)
	return "negative " + natToEnglish(-value);
    else
	return natToEnglish(value);
}

/* currencyToEnglish(amount, unit, div, subunit)

   Convert a currency value into an English written representation.
   Fractional subunits are rounded.

   amount  -- the total value to be converted (may be fractional)
   info    -- array of currency information (see enCurrUS above)

   Preconditions:  amount >= 0.
 */
function currencyToEnglish(amount, info)
{
    if (info == undefined)
	info = enCurr;

    var unit_s = info[0], unit_p = info[1];
    var usub_s = info[2], usub_p = info[3];
    var div    = info[4];

    u_val = Math.floor(amount);
    s_val = Math.round((amount - u_val) * div);

    base = natToEnglish(u_val, true) + " ";
    base += (u_val == 1) ? unit_s : unit_p;
    if (s_val == 0)
	base += " exactly";
    else {
	base += " and ";
	base += natToEnglish(s_val, true) + " ";
	base += (s_val == 1) ? usub_s : usub_p;
    }

    return base;
}

/* Here there be dragons */
