
function _sixval_encode(sixbitval)
{
    var cp;
    if(sixbitval < 26)
	cp = 65+sixbitval;
    else if(sixbitval < 52)
	cp = 97+(sixbitval-26);
    else if(sixbitval < 62)
	cp = 48+(sixbitval-52);
    else if(sixbitval == 62)
	return '+';
    else if(sixbitval == 63)
	return '/';
    else
	assert(false);
    return String.fromCharCode(cp);
    
}
function _sixval_decode(sixbitchar)
{
    var cp = sixbitchar.charCodeAt(0);
    if(cp >= 65 && cp <= 90)
	return cp - 65;
    else if(cp >= 97 && cp <= 122)
	return (cp-97) + 26;
    else if(cp >= 48 && cp <= 57)
	return (cp-48) + 52;
    else if(sixbitchar=='+')
	return 62;
    else if(sixbitchar=='/')
	return 63;
    else
    {
	assert(false);
    }
}

/*
  BitWriter.
  Writes bits to base-64 string.

  Follows RFC 3548, except it's bits that are encoded, not octets, so
  padding is 0-5 trailing 0-bits,

  Use: .put(value, bits) a number of times and finish off with
       .output() to get the resulting string.
*/
var BitReader_exhausted = "bitreader exhausted";
function BitWriter()
{
    this.acc = 0;
    this.bitc = 0;
    this.sixvals = [];
}

BitWriter.prototype.put = function(val, bits)
{
    this.acc |= (val << this.bitc);
    this.bitc += bits;
    while(this.bitc >= 6)
	this._flush();
}
BitWriter.prototype._flush = function()
{
    this.sixvals[this.sixvals.length] = this.acc & 0x3f;
    this.acc >>= 6;
    this.bitc -= 6;
}

BitWriter.prototype.output = function()
{
    if(this.bitc > 0)
    {
	this.put(0, 6-this.bitc);
    }

    var charvals = [];
    for(var vix in this.sixvals)
    {
	charvals[vix] = _sixval_encode(this.sixvals[vix]);
    }
    return charvals.join('');
}

/*
  BitReader: 
  Extract bits from a base-64 string as generated by BitWriter.

  Use: Pass base-64 string on construction and use
       .get(bits) to extract data.
       When the stream is exhausted, BitReader_exhausted is thrown.
*/
function BitReader(datastring)
{
    this._acc = 0;
    this._bitc = 0;
    this._datastring = datastring;
    this._datastring_ix = 0;
}
BitReader.prototype.get = function(bits)
{
    while(this._bitc < bits)
    {
	if(this._datastring_ix < this._datastring.length)
	{
	    var sixvalchar = this._datastring.charAt(this._datastring_ix);
	    ++this._datastring_ix;
	    this._acc |= _sixval_decode(sixvalchar) << this._bitc;
	    this._bitc += 6;
	}
	else
	    throw BitReader_exhausted;
    }
    
    // alert('acc = ' + this._acc + ', bits = ' + this._bitc + ', res = ' + (this._acc & ((1<<bits)-1)));
    var res = this._acc & ((1 << bits)-1);
    this._acc >>= bits;
    this._bitc -= bits;
    return res;
}


/*
  Extensions for writing a non-negative integer to the stream in a format biased
  towards small numbers.  To extract, the same maximum value must be
  known and provided.
*/
function _ordinal_split(M,N,S)
/*
  Divide something of size S in two by the ratio M:S, and return the
  length of the first part.
*/
{
    if(M == N)
	return Math.floor(S / 2);
    else
	return Math.round((M*S)/(M+N));
}
BitWriter.prototype.put_ordinal = function(value, maximum, skewed)
/*
  IN: 0 <= value < maximum
*/
{
    var M = 1;
    var N = skewed? 2 : 1;
    var x = value;
    var S = maximum;
    while(S > 2)
    {
	var splitpoint = _ordinal_split(M,N,S);
	if(x < splitpoint)
	{
	    this.put(0, 1);
	    S = splitpoint;
	}
	else
	{
	    this.put(1, 1);
	    S -= splitpoint;
	    x -= splitpoint;
	}
	++M; ++N;
    }
    if(S == 2)
    {
	this.put(x, 1);
    }
}

BitReader.prototype.get_ordinal = function(maximum, skewed)
{
    var M = 1;
    var N = skewed? 2 : 1;
    var S = maximum;
    var x = 0;
    while(S > 2)
    {
	var splitpoint = _ordinal_split(M,N,S);
	var bit = this.get(1);
	if(bit)
	{
	    x += splitpoint;
	    S -= splitpoint;
	}
	else
	{
	    S = splitpoint;
	}
	++M; ++N;
    }
    if(S == 2)
    {
	x += this.get(1);
    }
    return x;
}


function UnitTest_onload()
{
    try
    {
	UnitTest_onload_do();
    }
    catch(e)
    {
	ExplainException(e);
    }

}
function ExplainException(e)
{
    if(e.name && e.message && e.description)
	alert(' - ' + e.name + ': ' + e.message + ' (' + e.description + ')');
    else
    {
	var ks = [];
	for(var k in e)
	{
	    ks[ks.length] = k;
	}
	alert(ks.join(', '));
    }
}

function UnitTest_onload_do()
{
    var succeded_tests = 0;
    var failed_tests = 0;

    function failed(failtext)
    {
	log.debug(failtext);
	++failed_tests;
    }
    function assertEqual(exp1, exp2)
    {
	if(exp1 != exp2)
	    failed(exp1 + " does not match " + exp2);
	else
	    ++succeded_tests;
    }
    function assertRaises(fn, exc)
    {
	try
	{
	    fn();
	}
	catch(e)
	{
	    if(e === exc)
		++succeded_tests;
	    else
		failed(fn + " raised " + e + ", expected " + exc);
	    return;
	}
	failed(fn + " expected to raise " + exc + ", didn't");
    }

    for(var i=0; i<64; ++i)
    {
	assertEqual(i, _sixval_decode(_sixval_encode(i)));
    }
    assertEqual(_sixval_encode(0), 'A');
    assertEqual(_sixval_encode(25), 'Z');
    assertEqual(_sixval_encode(26), 'a');
    assertEqual(_sixval_encode(51), 'z');
    assertEqual(_sixval_encode(52), '0');
    assertEqual(_sixval_encode(61), '9');
    assertEqual(_sixval_encode(62), '+');
    assertEqual(_sixval_encode(63), '/');

    bw = new BitWriter();
    bw.put(0, 1);
    bout = bw.output();
    br = new BitReader(bout);
    assertEqual(br.get(1), 0);

    bw = new BitWriter();
    bw.put(1, 1);
    bout = bw.output();
    br = new BitReader(bout);
    assertEqual(br.get(1), 1);

    bw = new BitWriter();
    bw.put(11, 4);
    bw.put(42, 8);
    bw.put(43, 8);
    bout = bw.output();
    br = new BitReader(bout);
    assertEqual(br.get(1), 1);
    assertEqual(br.get(3), 5);
    assertEqual(br.get(8), 42);
    assertEqual(br.get(8), 43);   
    assertRaises(function() { br.get(8); },
		 BitReader_exhausted);
    posit = 4;

    for(var max=1; max<50; max+=5)
    {
	for(var x=0; x<max; ++x)
	{
	    bw = new BitWriter();
	    bw.put_ordinal(x, max, true);
	    bw.put(42, 6);
	    bw.put_ordinal(x, max, true);
	    bout = bw.output();
	    br = new BitReader(bout);
	    assertEqual(br.get_ordinal(max, true), x);
	    assertEqual(br.get(6), 42);
	    assertEqual(br.get_ordinal(max, true), x);
	}
    }
    posit = 5;

    if(failed_tests == 0)
	log.debug(succeded_tests + " tests succeded, A-OK!");
    else
    {
	var failure = failed_tests + " tests FAILED (" + succeded_tests + " succeded)";
	log.debug(failure);
	alert(failure);
    }
    alert("Unit testing slut");
}

