//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   15 Dec 08  Andy Frank  Creation
//   20 May 09  Andy Frank  Refactor to new OO model
//

/**
 * Int
 */
fan.sys.Int = fan.sys.Obj.$extend(fan.sys.Num);

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.prototype.$ctor = function() {}
fan.sys.Int.prototype.$typeof = function() { return fan.sys.Int.$type; }

fan.sys.Int.make = function(val) { return val; }

//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.fromStr = function(s, radix, checked)
{
  if (radix === undefined) radix = 10;
  if (checked === undefined) checked = true;
  try
  {
    if (radix === 10) { var n = fan.sys.Int.parseDecimal(s); return n; }
    if (radix === 16) { var n = fan.sys.Int.parseHex(s); return n; }
    throw new Error("Unsupported radix " + radix);
  }
  catch (err) {}
  if (checked) throw fan.sys.ParseErr.makeStr("Int", s);
  return null;
}

fan.sys.Int.parseDecimal = function(s)
{
  var n = 0;
  if (s.charCodeAt(0) === 45) n++;
  for (var i=n; i<s.length; i++)
  {
    ch = s.charCodeAt(i);
    if (ch >= 48 && ch <= 57) continue;
    throw new Error("Illegal decimal char " + s.charAt(i));
  }
  var x = parseInt(s, 10);
  if (isNaN(x)) throw new Error("Invalid number");
  return x;
}

fan.sys.Int.parseHex = function(s)
{
  for (var i=0; i<s.length; i++)
  {
    ch = s.charCodeAt(i);
    if (ch >= 48 && ch <= 57) continue;
    if (ch >= 65 && ch <= 70) continue;
    if (ch >= 97 && ch <= 102) continue;
    throw new Error("Illegal hex char " + s.charAt(i));
  }
  var x = parseInt(s, 16);
  if (isNaN(x)) throw new Error("Invalid number");
  return x;
}

fan.sys.Int.toStr = function(self)
{
  return self.toString();
}

fan.sys.Int.equals = function(self, obj)
{
  return self === obj;
}

fan.sys.Int.hash = function(self) { return self; }

fan.sys.Int.abs = function(self)      { return self < 0 ? -self : self; }
fan.sys.Int.min = function(self, val) { return self < val ? self : val; }
fan.sys.Int.max = function(self, val) { return self > val ? self : val; }

fan.sys.Int.isEven  = function(self) { return self % 2 == 0; }
fan.sys.Int.isOdd   = function(self) { return self % 2 != 0; }
fan.sys.Int.isSpace = function(self) { return self == 32 || self == 9 || self == 10 || self == 13; }

fan.sys.Int.isDigit = function(self, radix)
{
  if (radix == null || radix == 10) return self >= 48 && self <= 57;
  if (radix == 16)
  {
    if (self >= 48 && self <= 57) return true;
    if (self >= 65 && self <= 70) return true;
    if (self >= 97 && self <= 102) return true;
    return false;
  }
  if (radix <= 10) return 48 <= self && self <= (48+radix);
  var x = self-10;
  if (97 <= self && self <= 97+x) return true;
  if (65 <= self && self <= 65+x) return true;
  return false;
}

fan.sys.Int.toDigit = function(self, radix)
{
  if (radix == null || radix == 10) return 0 <= self && self <= 9 ? 48+self : null;
  if (self < 0 || self >= radix) return null;
  if (self < 10) return 48+self;
  return self-10+97;
}

fan.sys.Int.fromDigit = function(self, radix)
{
  if (self < 0 || self >= 128) return null;
  var ten = radix < 10 ? radix : 10;
  if (48 <= self && self < 48+ten) return self-48;
  if (radix > 10)
  {
    var alpha = radix-10;
    if (97 <= self && self < 97+alpha) return self+10-97;
    if (65 <= self && self < 65+alpha) return self+10-65;
  }
  return null;
}

fan.sys.Int.random = function(r)
{
  if (r === undefined) return Math.floor(Math.random() * Math.pow(2, 64));
  else
  {
    var start = r.start();
    var end   = r.end();
    if (r.inclusive()) ++end;
    if (end <= start) throw fan.sys.ArgErr.make("Range end < start: " + r);
    r = end-start;
    if (r < 0) r = -r;
    return Math.floor(Math.random()*r) + start;
  }
}

fan.sys.Int.isUpper    = function(self) { return self >= 65 && self <= 90; }
fan.sys.Int.isLower    = function(self) { return self >= 97 && self <= 122; }
fan.sys.Int.upper      = function(self) { return fan.sys.Int.isLower(self) ? self-32 : self; }
fan.sys.Int.lower      = function(self) { return fan.sys.Int.isUpper(self) ? self+32 : self; }
fan.sys.Int.isAlpha    = function(self) { return fan.sys.Int.isUpper(self) || fan.sys.Int.isLower(self); }
fan.sys.Int.isAlphaNum = function(self) { return fan.sys.Int.isAlpha(self) || fan.sys.Int.isDigit(self); }
fan.sys.Int.equalsIgnoreCase = function(self, ch) { return (self|0x20) == (ch|0x20); }


//////////////////////////////////////////////////////////////////////////
// Iterators
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.times = function(self, f)
{
  for (var i=0; i<self; i++)
    f.call(i);
}

//////////////////////////////////////////////////////////////////////////
// Arithmetic
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.negate    = function(self) { return -self; }
fan.sys.Int.increment = function(self) { return self+1; }
fan.sys.Int.decrement = function(self) { return self-1; }

fan.sys.Int.plus        = function(a, b) { return a + b; }
fan.sys.Int.plusFloat   = function(a, b) { return fan.sys.Float.make(a + b); }
fan.sys.Int.plusDecimal = function(a, b) { return fan.sys.Decimal.make(a + b); }

fan.sys.Int.minus        = function(a, b) { return a - b; }
fan.sys.Int.minusFloat   = function(a, b) { return fan.sys.Float.make(a - b); }
fan.sys.Int.minusDecimal = function(a, b) { return fan.sys.Decimal.make(a - b); }

fan.sys.Int.mult         = function(a, b) { return a * b; }
fan.sys.Int.multFloat    = function(a, b) { return fan.sys.Float.make(a * b); }
fan.sys.Int.multDecimal  = function(a, b) { return fan.sys.Decimal.make(a * b); }

fan.sys.Int.div = function(a, b)
{
  var r = a / b;
  if (r < 0) return Math.ceil(r);
  return Math.floor(r);
}
fan.sys.Int.divFloat   = function(a, b) { return fan.sys.Float.make(a / b); }
fan.sys.Int.divDecimal = function(a, b) { return fan.sys.Decimal.make(fan.sys.Int.div(a, b)); }

fan.sys.Int.mod        = function(a, b) { return a % b; }
fan.sys.Int.modFloat   = function(a, b) { return fan.sys.Float.make(a % b); }
fan.sys.Int.modDecimal = function(a, b) { return fan.sys.Decimal.make(a % b); }

fan.sys.Int.pow = function(self, pow)
{
  if (pow < 0) throw fan.sys.ArgErr.make("pow < 0");
  return Math.pow(self, pow);
}

//////////////////////////////////////////////////////////////////////////
// Bitwise operators
//////////////////////////////////////////////////////////////////////////

// NOTE: these methods only operate on the lowest 32 bits of the integer

fan.sys.Int.not = function(a)    { return ~a; }
fan.sys.Int.and = function(a, b) { var x = a & b;  if (x<0) x += 0xffffffff+1; return x; }
fan.sys.Int.or  = function(a, b) { var x = a | b;  if (x<0) x += 0xffffffff+1; return x; }
fan.sys.Int.xor = function(a, b) { var x = a ^ b;  if (x<0) x += 0xffffffff+1; return x; }
fan.sys.Int.shiftl = function(a, b) { var x = a << b; if (x<0) x += 0xffffffff+1; return x; }
fan.sys.Int.shiftr = function(a, b) { var x = a >> b; if (x<0) x += 0xffffffff+1; return x; }

//////////////////////////////////////////////////////////////////////////
// Conversion
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.toInt = function(val) { return val; }
fan.sys.Int.toFloat = function(val) { return fan.sys.Float.make(val); }
fan.sys.Int.toDecimal = function(val) { return fan.sys.Decimal.make(val); }

fan.sys.Int.toChar = function(self)
{
  if (self < 0 || self > 0xFFFF) throw fan.sys.Err.make("Invalid unicode char: " + self);
  return String.fromCharCode(self);
}

fan.sys.Int.toHex = function(self, width)
{
  if (width === undefined) width = null;

  // convert to hex string
  var val = self;
  var s = "";
  while (true)
  {
    // write chars backwards
    s = "0123456789abcdef".charAt(val % 16) + s;
    val = Math.floor(val / 16);
    if (val === 0) break
  }

  // pad width
  if (width != null && s.length < width)
  {
    var zeros = width - s.length;
    for (var i=0; i<zeros; ++i) s = '0' + s;
  }

  return s;
}
fan.sys.Int.$zeros = null;

fan.sys.Int.toCode = function(self, base)
{
  if (base === undefined) base = 10;
  if (base == 10) return self.toString();
  if (base == 16) return "0x" + fan.sys.Int.toHex(self);
  throw fan.sys.ArgErr.make("Invalid base " + base);
}

fan.sys.Int.toDuration = function(self)
{
  return fan.sys.Duration.make(self);
}

fan.sys.Int.toDateTime = function(self, tz)
{
  return (tz === undefined)
    ? fan.sys.DateTime.makeTicks(self)
    : fan.sys.DateTime.makeTicks(self, tz);
}

//////////////////////////////////////////////////////////////////////////
// CharMap
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.charMap = [];
fan.sys.Int.SPACE    = 0x01;
fan.sys.Int.UPPER    = 0x02;
fan.sys.Int.LOWER    = 0x04;
fan.sys.Int.DIGIT    = 0x08;
fan.sys.Int.HEX      = 0x10;
fan.sys.Int.ALPHA    = fan.sys.Int.UPPER | fan.sys.Int.LOWER;
fan.sys.Int.ALPHANUM = fan.sys.Int.UPPER | fan.sys.Int.LOWER | fan.sys.Int.DIGIT;

fan.sys.Int.charMap[32] |= fan.sys.Int.SPACE;
fan.sys.Int.charMap[10] |= fan.sys.Int.SPACE;
fan.sys.Int.charMap[13] |= fan.sys.Int.SPACE;
fan.sys.Int.charMap[9]  |= fan.sys.Int.SPACE;
fan.sys.Int.charMap[12] |= fan.sys.Int.SPACE;

// alpha characters
for (var i=97; i<=122; ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.LOWER;
for (var i=65; i<=90;  ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.UPPER;

// digit characters
for (var i=48; i<=57; ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.DIGIT;

// hex characters
for (var i=48; i<=57;  ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.HEX;
for (var i=97; i<=102; ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.HEX;
for (var i=65; i<=70;  ++i) fan.sys.Int.charMap[i] |= fan.sys.Int.HEX;

//////////////////////////////////////////////////////////////////////////
// Locale
//////////////////////////////////////////////////////////////////////////

fan.sys.Int.toLocale = function(self, pattern)
{
  if (pattern === undefined) pattern = null;

  // if pattern is "B" format as bytes
  if (pattern != null && pattern.length == 1 && pattern.charAt(0) == 'B')
    return fan.sys.Int.toLocaleBytes(self);

  // get current locale
// TODO FIXIT
//  Locale locale = Locale.cur();
//  java.text.DecimalFormatSymbols df = locale.decimal();
   var df = null;

  // get default pattern if necessary
  if (pattern == null)
// TODO FIXIT
//    pattern = Env.cur().locale(Sys.sysPod, "int", "#,###");
    pattern = "#,###";

  // parse pattern and get digits
  var p = fan.sys.NumPattern.parse(pattern);
  var d = fan.sys.NumDigits.makeLong(self);

  // route to common FanNum method
  return fan.sys.Num.toLocale(p, d, df);
}

fan.sys.Int.toLocaleBytes = function(b)
{
  var KB = fan.sys.Int.m_KB;
  var MB = fan.sys.Int.m_MB;
  var GB = fan.sys.Int.m_GB;
  if (b < KB)    return b + "B";
  if (b < 10*KB) return fan.sys.Float.toLocale(b/KB, "#.#") + "KB";
  if (b < MB)    return Math.round(b/KB) + "KB";
  if (b < 10*MB) return fan.sys.Float.toLocale(b/MB, "#.#") + "MB";
  if (b < GB)    return Math.round(b/MB) + "MB";
  if (b < 10*GB) return fan.sys.Float.toLocale(b/GB, "#.#") + "GB";
  return Math.round(b/fan.sys.Int.m_GB) + "GB";
}
fan.sys.Int.m_KB = 1024;
fan.sys.Int.m_MB = 1024*1024;
fan.sys.Int.m_GB = 1024*1024*1024;


