var JULIAN_DAY_OFFSET = 584283;

var LONG_COUNT_COEFFS = [ 144000, 7200, 360, 20, 1 ];

var LONG_COUNT_UNITS = [ "baktun", "katun", "tun", "winal", "kin" ];

var GREGORIAN_UNITS = [ "year", "month", "day" ];

var HAAB_MONTHS = [
    "Pop", "Wo", "Sip", "Zotz'", "Sek", 
    "Xul", "Yaxk'in", "Mol", "Ch'en", "Yax",
    "Sak", "Kej", "Mak", "K'ank'in", "Muwan",
    "Pax", "K'ayab'", "Kumk'u", "Wayeb'"
];

var TZOLKIN_DAYS = [
    "Imix", "Ik'", "Akb'al", "K'an", "Chikchan",
    "Kimi", "Manik'", "Lamat", "Muluk", "Ok'",
    "Chuwen", "Eb'", "B'en", "Ix", "Men",
    "Kib'", "Kab'an", "Etz'nab'", "Kawak", "Ajaw"
];

function fixInts(date,units)
{
    for(var i = 0; i < units.length; i++) {
        date[units[i]] = parseInt(date[units[i]]);
    }
}

function LongCountDate(baktun, katun, tun, winal, kin)
{
    this.baktun = parseInt(baktun);
    this.katun = parseInt(katun);
    this.tun = parseInt(tun);
    this.winal = parseInt(winal);
    this.kin = parseInt(kin);
    this.toString = function() {
        return this.baktun + "." + this.katun + "." + this.tun + "." + this.winal + "." + this.kin;
    }
}

LongCountDate.parse = function(str) 
{
    var lc = new LongCountDate();
    var n = str.split(".");
    for(var unit = 0; unit < LONG_COUNT_UNITS.length; unit++) {
        lc[LONG_COUNT_UNITS[unit]] = n[unit];
    }
    return lc;
}

function GregorianDate(year, month, day)
{
    this.year = year;
    this.month = month;
    this.day = day;
    this.toString = function() {
        return this.year + "/" + this.month + "/" + this.day;
    }
}

function Tzolkin(trecena, veintena)
{
    this.trecena = trecena;
    this.veintena = veintena;
}

function Haab(day, month)
{
    this.day = day;
    this.month = month;
}

function iadd()
{
    var sum = 0;
    for(var i = 0; i < arguments.length; i++) {
        sum += parseInt(arguments[i]);
    }
    return parseInt(sum);
}

function isub()
{
    var sum = 0;
    var mul = 1;
    for(var i = 0; i < arguments.length; i++) {
        sum += mul * parseInt(arguments[i]);
        mul = -1;
    }
    return parseInt(sum);
}

function imul()
{
    var prod = 1;
    for(var i = 0; i < arguments.length; i++) {
        prod *= parseInt(arguments[i]);
    }
    return parseInt(prod);
}

function idiv(a,b)
{
    return parseInt(parseInt(a) / parseInt(b));
}

function julianDayToGregorianDate(jd)
{
    var l, n, i, j, k;
    var gd = new GregorianDate();
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = iadd(jd, 68569);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    n = idiv(imul(4, l), 146097);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = isub(l, idiv(iadd(imul(146097, n), 3), 4));
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    i = idiv(imul(4000, iadd(l, 1)), 1461001);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = iadd(isub(l, idiv(imul(1461, i), 4)), 31);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    j = idiv(imul(80, l), 2447);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    k = isub(l, idiv(imul(2447, j), 80));
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = idiv(j, 11);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    j = isub(iadd(j, 2), imul(12, l));
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    i = iadd(imul(100, isub(n, 49)), i, l);
    //alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    gd.day = parseInt(k);
    gd.month = parseInt(j);
    gd.year = parseInt(i);
    return gd;
}

function julianDayToGregorianDate_2(jd)
{
    var l, n, i, j, k;
    var gd = new GregorianDate();
    l = parseInt(jd) + 68569;
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    n = parseInt(4 * l / 146097);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = parseInt(l - (146097 * n + 3) / 4);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    i = parseInt(4000 * (l + 1) / 1461001);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = parseInt(l - 1461 * i / 4 + 31);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    j = parseInt(80 * l / 2447);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    k = parseInt(l - 2447 * j / 80);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    l = parseInt(j / 11);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    j = parseInt(j + 2 - 12 * l);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    i = parseInt(100 * (n - 49) + i + l);
    alert("l=" + l + " n=" + n + " i=" + i + " j=" + j + " k=" + k);
    gd.day = parseInt(k);
    gd.month = parseInt(j);
    gd.year = parseInt(i);
    return gd;
}

function gregorianDateToJulianDay(gd)
{
    fixInts(gd,GREGORIAN_UNITS);
    var y = parseInt(gd.year);
    var m = parseInt(gd.month);
    var d = parseInt(gd.day);
    var jd = d - 32075 + parseInt(1461 * (y + 4800 + parseInt((m - 14) / 12)) / 4) + 367 * (m - 2 - parseInt((m - 14) / 12) * 12) / 12 - 3 * parseInt(parseInt((y + 4900 + parseInt((m - 14) / 12)) / 100) / 4);
    return parseInt(jd); 
}

function longCountDateToGregorianDate(lc)
{
    var jd = JULIAN_DAY_OFFSET;
    for(var unit = 0; unit < 5; unit++) {
        jd += lc[LONG_COUNT_UNITS[unit]] * LONG_COUNT_COEFFS[unit];
    }
    return julianDayToGregorianDate(jd);
}

function gregorianDateToLongCountDate(gd)
{
    var jd = gregorianDateToJulianDay(gd);
    var lc = new LongCountDate();
    var n = jd - JULIAN_DAY_OFFSET;
    for(var unit = 0; unit < 5; unit++) {
        lc[LONG_COUNT_UNITS[unit]] = parseInt(n / LONG_COUNT_COEFFS[unit]);
        n = n % LONG_COUNT_COEFFS[unit];
    }
    return lc;
}

function longCountToTzolkin(lc)
{
    return new Tzolkin(getTrecena(lc), getVeintena(lc));
}

function longCountToHaab(lc)
{
    var dn = getHaab(lc);
    var month = parseInt(dn / 20);
    var day = dn % 20;
    return new Haab(day, month);
}

function getTrecena(lc)
{
    var t = iadd(4, lc.kin, (-6 * lc.winal), (-4 * lc.tun), (-2 * lc.katun), (-1 * lc.baktun)) % 13;
    if (t < 0) { t += 13; }
    return t;
}

function getVeintena(lc)
{
    var v = (lc.kin - 1) % 20;
    if (v < 0) { v += 20; }
    return v;
}

function getHaab(lc)
{
    var haab = (-17 + ((lc.kin * 1) + (lc.winal * 20) + (lc.tun * -5) + (lc.katun * -100) + (lc.baktun * -175))) % 365;
    if (haab < 0) { haab += 365; }
    return haab;
}

