// Mostly stolen from:
//     http://www0.arch.cuhk.edu.hk/~hall/ag/sw/SpinCalc/SpinCalc.htm

// Initialize the calculator exactly once.
function init( f )
{
    var i, opts;
    var c, G, pi, hbar, k, sig;
    
    if( typeof( bhdata ) != "object" )
    {
        bhdata = new Object();
        
        // Some browsers don't support this function
        if( typeof( Number.prototype.toPrecision ) == "function" )
            bhdata.prec = function( num ) { return num.toPrecision( 7 ) };
        else
            bhdata.prec = function( num ) { return num };
        
        // The various constants we need to perform the calculations
        c    = 299792458.0;
        G    = 6.67259e-11;
        pi   = Math.PI;
        hbar = 1.05457e-34;
        k    = 1.38066e-23;
        sig  = pi * pi * Math.pow( k, 4 ) / (60 * Math.pow( hbar, 3 ) * c * c);
        
        with( bhdata )
        {
            // Conversion factors between mass and dependent quantities.
            // SI units.
            bhdata.rad_c = 2 * G / (c * c);
            bhdata.srf_c = 4 * pi * Math.pow( rad_c, 2 );
            bhdata.grv_c =      G / Math.pow( rad_c, 2 );
            bhdata.tid_c =  2 * G / Math.pow( rad_c, 3 );
            bhdata.ent_c = srf_c * Math.pow( c, 3 ) / (4 * Math.LN10 * G * hbar);
            bhdata.tmp_c = hbar * c / (4 * k * pi * rad_c );
            bhdata.lum_c = srf_c * sig * Math.pow( tmp_c, 4 );
            bhdata.tim_c = c * c / (3 * lum_c);
        }
        
        // Create properties
        bhdata.mass = 0.0;
        bhdata.rad  = 0.0;
        bhdata.srf  = 0.0;
        bhdata.grv  = 0.0;
        bhdata.tid  = 0.0;
        bhdata.ent  = 0.0;
        bhdata.tmp  = 0.0;
        bhdata.lum  = 0.0;
        bhdata.tim  = 0.0;
        
        // Bind properties to the document <form>.
        bhdata.mass_text = f.mass_text;
        bhdata.mass_unit = f.mass_unit;
        bhdata.mass_fact = new Array();
        
        bhdata.rad_text = f.rad_text;
        bhdata.rad_unit = f.rad_unit;
        bhdata.rad_fact = new Array();
        
        bhdata.srf_text = f.srf_text;
        bhdata.srf_unit = f.srf_unit;
        bhdata.srf_fact = new Array();
        
        bhdata.grv_text = f.grv_text;
        bhdata.grv_unit = f.grv_unit;
        bhdata.grv_fact = new Array();
        
        bhdata.tid_text = f.tid_text;
        bhdata.tid_unit = f.tid_unit;
        bhdata.tid_fact = new Array();
        
        bhdata.ent_text = f.ent_text;
        
        bhdata.tmp_text = f.tmp_text;
        bhdata.tmp_unit = f.tmp_unit;
        bhdata.tmp_fact = new Array();
        bhdata.tmp_absz = new Array();
        
        bhdata.lum_text = f.lum_text;
        bhdata.lum_unit = f.lum_unit;
        bhdata.lum_fact = new Array();
        
        bhdata.tim_text = f.tim_text;
        bhdata.tim_unit = f.tim_unit;
        bhdata.tim_fact = new Array();
    }
    
    with( bhdata )
    {
        opts = mass_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "plm" )
                mass_fact[i] = 5.45604e-8;
            else if( opts[i].value == "ug" )
                mass_fact[i] = 1e-9;
            else if( opts[i].value == "g" )
                mass_fact[i] = 1e-3;
            else if( opts[i].value == "kg" )
                mass_fact[i] = 1.0;
            else if( opts[i].value == "mton" )
                mass_fact[i] = 1000.0;
            else if( opts[i].value == "neuble" )
                mass_fact[i] = 1e12;
            else if( opts[i].value == "mearth" )
                mass_fact[i] = 5.97e24;
            else if( opts[i].value == "msol" )
                mass_fact[i] = 1.99e30;
            else
            {
                badunit_error( "mass", opts[i].value );
                opts[i].value = "kg";
                mass_fact[i] = 1.0;
            }
        } // end for
        
        opts = rad_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "pll" )
                rad_fact[i] = 4.05096e-35;
            else if( opts[i].value == "fm" )
                rad_fact[i] = 1e-15;
            else if( opts[i].value == "nm" )
                rad_fact[i] = 1e-9;
            else if( opts[i].value == "cm" )
                rad_fact[i] = 1e-2;
            else if( opts[i].value == "in" )
                rad_fact[i] = 0.0254;
            else if( opts[i].value == "ft" )
                rad_fact[i] = 0.3048;
            else if( opts[i].value == "m" )
                rad_fact[i] = 1.0;
            else if( opts[i].value == "km" )
                rad_fact[i] = 1000.0;
            else if( opts[i].value == "mi" )
                rad_fact[i] = 1609.344;
            else if( opts[i].value == "ls" )
                rad_fact[i] = 2.99792458e09;
            else if( opts[i].value == "au" )
                rad_fact[i] = 1.495979e11;
            else if( opts[i].value == "ly" )
                rad_fact[i] = 9.460528e15;
            else
            {
                badunit_error( "length", opts[i].value );
                opts[i].value = "m";
                rad_fact[i] = 1.0;
            }
        } // end for
        
        opts = srf_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "pll2" )
                srf_fact[i] = 1.64102e-69;
            else if( opts[i].value == "fm2" )
                srf_fact[i] = 1e-30;
            else if( opts[i].value == "barn" )
                srf_fact[i] = 1e-28;
            else if( opts[i].value == "cm2" )
                srf_fact[i] = 1e-4;
            else if( opts[i].value == "in2" )
                srf_fact[i] = 6.4516e-4;
            else if( opts[i].value == "ft2" )
                srf_fact[i] = 9.290304e-2;
            else if( opts[i].value == "m2" )
                srf_fact[i] = 1.0;
            else if( opts[i].value == "km2" )
                srf_fact[i] = 1e6;
            else if( opts[i].value == "mi2" )
                srf_fact[i] = 2.589988e6;
            else
            {
                badunit_error( "area", opts[i].value );
                opts[i].value = "m2";
                srf_fact[i] = 1.0;
            }
        } // end for
        
        opts = grv_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "gal" )
                grv_fact[i] = 0.01;
            else if( opts[i].value == "fps2" )
                grv_fact[i] = 0.3048;
            else if( opts[i].value == "mps2" )
                grv_fact[i] = 1.0;
            else if( opts[i].value == "g" )
                grv_fact[i] = 9.80665;
            else
            {
                badunit_error( "acceleration", opts[i].value );
                opts[i].value = "mps2";
                grv_fact[i] = 1.0;
            }
        } // end for
        
        opts = tid_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "is2" )
                tid_fact[i] = 1.0;
            else if( opts[i].value == "gpm" )
                tid_fact[i] = 9.80665;
            else if( opts[i].value == "gpf" )
                tid_fact[i] = 32.174049;
            else
            {
                badunit_error( "tide", opts[i].value );
                opts[i].value = "is2";
                tid_fact[i] = 1.0;
            }
        } // end for
        
        opts = tmp_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "K" )
            {
                tmp_fact[i] = 1.0;
                tmp_absz[i] = 0.0;
            }
            else if( opts[i].value == "degC" )
            {
                tmp_fact[i] = 1.0;
                tmp_absz[i] = -273.15;
            }
            else if( opts[i].value == "degF" )
            {
                tmp_fact[i] = 5/9;
                tmp_absz[i] = -459.67;
            }
            else
            {
                badunit_error( "temperature", opts[i].value );
                opts[i].value = "K";
                tmp_fact[i] = 1.0;
                tmp_absz[i] = 0.0;
            }
        } // end for
        
        opts = lum_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "W" )
                lum_fact[i] = 1.0;
            else if( opts[i].value == "cps" )
                lum_fact[i] = 4.1868;
            else if( opts[i].value == "bps" )
                lum_fact[i] = 1.055056e3;
            else if( opts[i].value == "MW" )
                lum_fact[i] = 1e6;
            else if( opts[i].value == "mts" )
                lum_fact[i] = 4.1868e15;
            else if( opts[i].value == "lsol" )
                lum_fact[i] = 3.9e26;
            else
            {
                badunit_error( "power", opts[i].value );
                opts[i].value = "m2";
                lum_fact[i] = 1.0;
            }
        } // end for
        
        opts = tim_unit.options;
        for( i = 0; i < opts.length; ++i )
        {
            if     ( opts[i].value == "plt" )
                tim_fact[i] = 1.35125e-43;
            else if( opts[i].value == "sh" )
                tim_fact[i] = 1e-8;
            else if( opts[i].value == "s" )
                tim_fact[i] = 1.0;
            else if( opts[i].value == "m" )
                tim_fact[i] = 60.0;
            else if( opts[i].value == "h" )
                tim_fact[i] = 3600.0;
            else if( opts[i].value == "d" )
                tim_fact[i] = 86400.0;
            else if( opts[i].value == "yr" )
                tim_fact[i] = 3.155693e7;
            else if( opts[i].value == "Gyr" )
                tim_fact[i] = 3.155693e16;
            else
            {
                badunit_error( "time", opts[i].value );
                opts[i].value = "m2";
                tim_fact[i] = 1.0;
            }
        } // end for
    }
    
    reset();
}


function reset()
{
    with( bhdata )
    {
        // Initialize the mass to one standard neuble and calculate the rest.
        mass = 1e12;
        calc( "mass" );
    }
}


// Recalculate values based on whatever just changed.
function calc( what )
{
    with( bhdata )
    {
        if( what != "rad" )  rad = rad_c * mass;
        if( what != "srf" )  srf = srf_c * mass * mass;
        if( what != "grv" )  grv = grv_c / mass;
        if( what != "tid" )  tid = tid_c / (mass * mass);
        if( what != "ent" )  ent = ent_c * mass * mass;
        if( what != "tmp" )  tmp = tmp_c / mass;
        if( what != "lum" )  lum = lum_c / (mass * mass);
        if( what != "tim" )  tim = tim_c * Math.pow( mass, 3 );
    }
    
    // Update text fields with new values
    get_mass();
    get_rad();
    get_srf();
    get_grv();
    get_tid();
    get_ent();
    get_tmp();
    get_lum();
    get_tim();
}


////////////////////////////////////////////////////////////////////////////////
// Error handlers

function badunit_error( type, what )
{
    alert( "Unsupported " + type + " unit: \"" + what + "\""  );
}


function nonpos_error( type )
{
    alert( "Error: Got nonpositive value for " + type + "!\n" +
           "Enter a value greater than " +
           (type == "temperature" ? "absolute " : "") + "zero." );
}


////////////////////////////////////////////////////////////////////////////////
// Each set_*()/get_*() pair updates the text fields whenever a numeric quantity
// or a unit of measurement is changed, respectively.

// mass ------------------------------------------------------------------------
function set_mass()
{
    var i, val;
    with( bhdata )
    {
        i = mass_unit.selectedIndex;
        val = mass_text.value * mass_fact[i];
        
        if( val > 0 )
        {
            mass = val;
            calc( "mass" );
        }
        else
        {
            nonpos_error( "mass" );
            get_mass();
        }
    }
}

function get_mass()
{
    var i;
    with( bhdata )
    {
        i = mass_unit.selectedIndex;
        mass_text.value = prec( mass / mass_fact[i] );
    }
}


// radius ----------------------------------------------------------------------
function set_rad()
{
    var i, val;
    with( bhdata )
    {
        i = rad_unit.selectedIndex;
        val = rad_text.value * rad_fact[i];
        
        if( val > 0 )
        {
            rad = val;
            mass = rad / rad_c;
            calc( "rad" );
        }
        else
        {
            nonpos_error( "radius" );
            get_rad();
        }
    }
}

function get_rad()
{
    var i;
    with( bhdata )
    {
        i = rad_unit.selectedIndex;
        rad_text.value = prec( rad / rad_fact[i] );
    }
}


// surface area ----------------------------------------------------------------
function set_srf()
{
    var i, val;
    with( bhdata )
    {
        i = srf_unit.selectedIndex;
        val = srf_text.value * srf_fact[i];
        
        if( val > 0 )
        {
            srf = val;
            mass = Math.sqrt( srf / srf_c );
            calc( "srf" );
        }
        else
        {
            nonpos_error( "surface area" );
            get_srf();
        }
    }
}

function get_srf()
{
    var i;
    with( bhdata )
    {
        i = srf_unit.selectedIndex;
        srf_text.value = prec( srf / srf_fact[i] );
    }
}


// surface gravity -------------------------------------------------------------
function set_grv()
{
    var i, val;
    with( bhdata )
    {
        i = grv_unit.selectedIndex;
        val = grv_text.value * grv_fact[i];
        
        if( val > 0 )
        {
            grv = val;
            mass = grv_c / grv;
            calc( "grv" );
        }
        else
        {
            nonpos_error( "surface gravity" );
            get_grv();
        }
    }
}

function get_grv()
{
    var i;
    with( bhdata )
    {
        i = grv_unit.selectedIndex;
        grv_text.value = prec( grv / grv_fact[i] );
    }
}


// surface tides ---------------------------------------------------------------
function set_tid()
{
    var i, val;
    with( bhdata )
    {
        i = tid_unit.selectedIndex;
        val = tid_text.value * tid_fact[i];
        
        if( val > 0 )
        {
            tid = val;
            mass = Math.sqrt( tid_c / tid );
            calc( "tid" );
        }
        else
        {
            nonpos_error( "surface tides" );
            get_tid();
        }
    }
}

function get_tid()
{
    var i;
    with( bhdata )
    {
        i = tid_unit.selectedIndex;
        tid_text.value = prec( tid / tid_fact[i] );
    }
}


// entropy ---------------------------------------------------------------------
function set_ent()
{
    var val;
    with( bhdata )
    {
        val = ent_text.value;
        
        if( val > 0 )
        {
            ent = val;
            mass = Math.sqrt( ent / ent_c );
            calc( "ent" );
        }
        else
        {
            nonpos_error( "entropy" );
            get_ent();
        }
    }
}

function get_ent()
{
    with( bhdata )
        ent_text.value = prec( ent );
}


// temperature -----------------------------------------------------------------
function set_tmp()
{
    var i, val;
    with( bhdata )
    {
        i = tmp_unit.selectedIndex;
        val = (tmp_text.value - tmp_absz[i]) * tmp_fact[i];
        
        if( val > 0 )
        {
            tmp = val;
            mass = tmp_c / tmp;
            calc( "tmp" );
        }
        else
        {
            nonpos_error( "temperature" );
            get_tmp();
        }
    }
}

function get_tmp()
{
    var i;
    with( bhdata )
    {
        i = tmp_unit.selectedIndex;
        tmp_text.value = prec( tmp / tmp_fact[i] + tmp_absz[i] );
    }
}


// luminosity ------------------------------------------------------------------
function set_lum()
{
    var i, val;
    with( bhdata )
    {
        i = lum_unit.selectedIndex;
        val = lum_text.value * lum_fact[i];
        
        if( val > 0 )
        {
            lum = val;
            mass = Math.sqrt( lum_c / lum );
            calc( "lum" );
        }
        else
        {
            nonpos_error( "luminosity" );
            get_lum();
        }
    }
}

function get_lum()
{
    var i;
    with( bhdata )
    {
        i = lum_unit.selectedIndex;
        lum_text.value = prec( lum / lum_fact[i] );
    }
}


// lifetime --------------------------------------------------------------------
function set_tim()
{
    var i, val;
    with( bhdata )
    {
        i = tim_unit.selectedIndex;
        val = tim_text.value * tim_fact[i];
        
        if( val > 0 )
        {
            tim = val;
            mass = Math.pow( tim / tim_c, 1/3 );
            calc( "tim" );
        }
        else
        {
            nonpos_error( "lifetime" );
            get_tim();
        }
    }
}

function get_tim()
{
    var i;
    with( bhdata )
    {
        i = tim_unit.selectedIndex;
        tim_text.value = prec( tim / tim_fact[i] );
    }
}
