﻿//-----------------------------------------------------------------------------
// XNumber
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

XNumber.prototype = new XObject;
XNumber.prototype.constructor = XNumber;

//=============================================================================
// Constructor

function XNumber(
   num,
   create
)
{
   num = (num == null) ? 0 : num;
   create = (create == null) ? true : false;

   if (create)
      return new XNumber(num, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oNum;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      num
   )
   {
      num = (num == null) ? null : num;

      oNum = XNumber.getNumberFrom(num);

      return oNum;
   }

   //--------------------------------------------------------------------------
   // Initialization

   var oNum = this.setObjectValue(num);

}

XNumber.prototype.objectClass = "XNumber";

//=============================================================================
// Static Interface

XNumber.STYLE_ORDINAL          = "ordinal";
XNumber.STYLE_UPPER_ORDINAL    = "upper_ordinal";
XNumber.STYLE_LOWER_ORDINAL    = "lower_ordinal";
XNumber.STYLE_ROMAN_NUM        = "roman_num";
XNumber.STYLE_UPPER_ROMAN_NUM  = "upper_roman_num";
XNumber.STYLE_LOWER_ROMAN_NUM  = "lower_roman_num";
XNumber.STYLE_VULGAR_FRACTIONS = "vulgar_fractions";

XNumber.NUMBERS                = [
   ["zero",        0,      "add"],
   ["one",         1,      "add"],
   ["two",         2,      "add"],
   ["three",       3,      "add"],
   ["four",        4,      "add"],
   ["five",        5,      "add"],
   ["six",         6,      "add"],
   ["seven",       7,      "add"],
   ["eight",       8,      "add"],
   ["nine",        9,      "add"],
   ["ten",        10,      "add"],
   ["eleven",     11,      "add"],
   ["twelve",     12,      "add"],
   ["thirteen",   13,      "add"],
   ["fourteen",   14,      "add"],
   ["fifteen",    15,      "add"],
   ["sixteen",    16,      "add"],
   ["seventeen",  17,      "add"],
   ["eighteen",   18,      "add"],
   ["nineteen",   19,      "add"],
   ["twenty",     20,      "add"],
   ["thirty",     30,      "add"],
   ["forty",      40,      "add"],
   ["fifty",      50,      "add"],
   ["sixty",      60,      "add"],
   ["seventy",    70,      "add"],
   ["eighty",     80,      "add"],
   ["ninety",     90,      "add"],
   ["hundred",   100, "multiply"],
   ["thousand", 1000, "multiply"]
];

XNumber.ORDINALS               = [
   ["regular",     0,      "add"],
   ["first",       1,      "add"],
   ["second",      2,      "add"],
   ["third",       3,      "add"],
   ["fourth",      4,      "add"],
   ["fifth",       5,      "add"],
   ["sixth",       6,      "add"],
   ["seventh",     7,      "add"],
   ["eighth",      8,      "add"],
   ["ninth",       9,      "add"],
   ["tenth",      10,      "add"],
   ["eleventh",   11,      "add"],
   ["twelfth",    12,      "add"],
   ["thirteenth", 13,      "add"],
   ["fourteenth", 14,      "add"],
   ["fifteenth",  15,      "add"],
   ["sixteenth",  16,      "add"],
   ["seventeenth",17,      "add"],
   ["eighteenth", 18,      "add"],
   ["nineteenth", 19,      "add"],
   ["twentieth",  20,      "add"],
   ["thirtieth",  30,      "add"],
   ["fortieth",   40,      "add"],
   ["fiftieth",   50,      "add"],
   ["sixtieth",   60,      "add"],
   ["seventieth", 70,      "add"],
   ["eightieth",  80,      "add"],
   ["nintieth",   90,      "add"],
   ["hundredth", 100, "multiply"]
];

XNumber.ROMAN_NUMS             = [
   ["?",           0,      "add"],
   ["i",           1,      "add"],
   ["ii",          2,      "add"],
   ["iii",         3,      "add"],
   ["iv",          4,      "add"],
   ["v",           5,      "add"],
   ["vi",          6,      "add"],
   ["vii",         7,      "add"],
   ["viii",        8,      "add"],
   ["ix",          9,      "add"],
   ["x",          10,      "add"],
   ["xi",         11,      "add"],
   ["xii",        12,      "add"],
   ["xiii",       13,      "add"],
   ["xiv",        14,      "add"],
   ["xv",         15,      "add"],
   ["xvi",        16,      "add"],
   ["xvii",       17,      "add"],
   ["xviii",      18,      "add"],
   ["xix",        19,      "add"],
   ["xx",         20,      "add"],
   ["xxi",        21,      "add"],
   ["xxii",       22,      "add"],
   ["xxiii",      23,      "add"],
   ["xxiv",       24,      "add"],
   ["xxv",        25,      "add"],
   ["xxvi",       26,      "add"],
   ["xxvii",      27,      "add"],
   ["xxviii",     28,      "add"],
   ["xxix",       29,      "add"],
   ["xxx",        30,      "add"],
   ["xxxi",       31,      "add"],
   ["xxxii",      32,      "add"],
   ["xxxiii",     33,      "add"],
   ["xxxiv",      34,      "add"],
   ["xxxv",       35,      "add"],
   ["xxxvi",      36,      "add"],
   ["xxxvii",     37,      "add"],
   ["xxxviii",    38,      "add"],
   ["xxxvix",     39,      "add"],
   ["xl",         40,      "add"],
   ["xli",        41,      "add"],
   ["xlii",       42,      "add"],
   ["xliii",      43,      "add"],
   ["xliv",       44,      "add"],
   ["xlv",        45,      "add"],
   ["xlvi",       46,      "add"],
   ["xlvii",      47,      "add"],
   ["xlviii",     48,      "add"],
   ["xlix",       49,      "add"],
   ["l",          50,      "add"]
];

//-----------------------------------------------------------------------------

XNumber.getNumberFrom = function(
   value
)
{

   //--------------------------------------------------------------------------

   function getNumItem(
      numText
   )
   {
      numText = (numText == null) ? null : numText.toLowerCase().replace(/\.$/, "");

      if (!numText || numText.length == 0)
         return null;

      if ((/^[xvil]+$/).test(numText))
      {
         for (var i=0; i<XNumber.ROMAN_NUMS.length; i++)
         {
            if (numText == XNumber.ROMAN_NUMS[i][0])
               return XNumber.ROMAN_NUMS[i];
         }
      }
      else if ((/^[0-9\.]+$/).test(numText))
      {
         return [numText, Number(numText).valueOf(), "add"];
      }
      else
      {
         for (var i=0; i<XNumber.NUMBERS.length; i++)
         {
            if (numText == XNumber.NUMBERS[i][0])
               return XNumber.NUMBERS[i];
         }
         for (var i=0; i<XNumber.ORDINALS.length; i++)
         {
            if (numText == XNumber.ORDINALS[i][0])
               return XNumber.ORDINALS[i];
         }
      }

      return null;
   }

   //--------------------------------------------------------------------------

   if (value == null)
      return 0;

   var num = 0;
   switch (typeof(value))
   {
      case "string":
         if (value.length == 0)
         {
            num = 0;
         }
         else
         {
            value = value.replace(/\-/g, " ");
            value = value.replace(/\&/g, " ");
            value = value.replace(/\s+and\s+/gi, " ");
            value = value.replace(/\s+/g, " ");
            inputArray = value.split(" ");
            var numPart = 0;
            for (var i=0; i<inputArray.length; i++)
            {
               var inputItem = getNumItem(inputArray[i]);
               if (inputItem)
               {
                  if (inputItem[2] == "multiply")
                  {
                     num = num + numPart * inputItem[1];
                     numPart = 0;
                  }
                  else
                     numPart += inputItem[1];
               }
            }
            num = num + numPart;
         }
         break;
      case "boolean":
         num = (value) ? 1 : 0;
         break;
      case "number":
         num = value;
         break;
      case "function":
         try
         {
            num = getNum(value());
         }
         catch (error)
         {
            num = 0;
         }
         break;
      case "object":
         try
         {
            num = getNum(value.valueOf());
         }
         catch (error)
         {
            try
            {
               num = getNum(value.toString());
            }
            catch (errorA)
            {
               num = 0;
            }
         }
         break;
      default:
         break;
   }

   return num;
}

//=============================================================================
// Public Interface

XNumber.prototype.toString = function(
   style1,
   styleN
)
{
   style1 = (style1 == null) ? null : style1;
   styleN = (styleN == null) ? null : styleN;

   //--------------------------------------------------------------------------

   function getOrdinal(
      num
   )
   {
      num = (num == null) ? 0 : Number(num);

      if (num > 1999)
         return CString(num);

      var hundreds = Math.floor(num/100);
      var rem = num % 100;
      var tens = Math.floor(rem/10);
      var units =  rem % 10;

      var ordinal = "";
      if (hundreds > 0)
      {
         ordinal += XNumber.NUMBERS[hundreds][0] + " Hundred";
         if (tens == 0 && units == 0)
            ordinal += "th";
         else
            ordinal += " and ";
      }
      if (tens > 1)
      {
         if (units == 0)
            ordinal += XNumber.ORDINALS[tens + 18][0];
         else
            ordinal += XNumber.NUMBERS[tens + 18][0] + "-" + XNumber.ORDINALS[units][0];
      }
      else
         ordinal += XNumber.ORDINALS[tens*10 + units][0];

      return  ordinal;
   }

   //--------------------------------------------------------------------------

   function getRomanNum(
      num
   )
   {
      num = (num == null) ? 0 : Number(num);

      if (num >= 0 && num < XNumber.ROMAN_NUMS.length)
         return XNumber.ROMAN_NUMS[num][0];
      else
         return "?";
   }

   //--------------------------------------------------------------------------

   function getWithVulgarFractions(
      num
   )
   {

      var numText = String(num);

      numText = numText.replace(/\.250*$/,"¼");
      numText = numText.replace(/\.50*$/,"½");
      numText = numText.replace(/\.750*$/,"¾");

      return numText;
   }

   //--------------------------------------------------------------------------

   var num = this.valueOf();
   var numText = num + ""; // Force gently to be a string

   var style = (num == 1 || styleN == null) ? style1 : styleN;
   if (style == null)
      return numText;

   if (style != null && style.length > 0)
   {
      if (style == XNumber.STYLE_ORDINAL)
         numText = XString(getOrdinal(num)).toTitleCase();
      else if (style == XNumber.STYLE_UPPER_ORDINAL)
         numText = XString(getOrdinal(num)).toUpperCase();
      else if (style == XNumber.STYLE_LOWER_ORDINAL)
         numText = XString(getOrdinal(num)).toLowerCase();
      else if (style == XNumber.STYLE_ROMAN_NUM)
         numText = getRomanNum(num);
      else if (style == XNumber.STYLE_UPPER_ROMAN_NUM)
         numText = XString(getRomanNum(num)).toUpperCase();
      else if (style == XNumber.STYLE_LOWER_ROMAN_NUM)
         numText = XString(getRomanNum(num)).toLowerCase();
      else if (style == XNumber.STYLE_VULGAR_FRACTIONS)
         numText = getWithVulgarFractions(num);
      else if (XMatch(style, /^(.*)\{num\}(.*)$/))
         numText = XMatch.matches[1] + num + XMatch.matches[2];
      else if (XMatch(style, /^(.*)\{ordinal\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + XString(getOrdinal(num)).toTitleCase() + suffix;
      }
      else if (XMatch(style, /^(.*)\{upperOrdinal\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + XString(getOrdinal(num)).toUpperCase() + suffix;
      }
      else if (XMatch(style, /^(.*)\{lowerOrdinal\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + XString(getOrdinal(num)).toLowerCase() + suffix;
      }
      else if (XMatch(style, /^(.*)\{romanNum\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + getRomanNum(num) + suffix;
      }
      else if (XMatch(style, /^(.*)\{upperRomanNum\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + XString(getRomanNum(num)).toUpperCase() + suffix;
      }
      else if (XMatch(style, /^(.*)\{lowerRomanNum\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + XString(getRomanNum(num)).toLowerCase() + suffix;
      }
      else if (XMatch(style, /^(.*)\{vulgarFractions\}(.*)$/))
      {
         var prefix = XMatch.matches[1];
         var suffix = XMatch.matches[2];
         numText = prefix + getWithVulgarFractions(num) + suffix;
      }
      else
         numText = style + numText;
   }

   return numText;
}

//-----------------------------------------------------------------------------

XNumber.prototype.toNumber = function()
{

   return this.valueOf();
}

//=============================================================================


