var BASE64CHARS = new Array(
    'A','B','C','D','E','F','G','H',
    'I','J','K','L','M','N','O','P',
    'Q','R','S','T','U','V','W','X',
    'Y','Z','a','b','c','d','e','f',
    'g','h','i','j','k','l','m','n',
    'o','p','q','r','s','t','u','v',
    'w','x','y','z','0','1','2','3',
    '4','5','6','7','8','9','+','/'
);
var REVE64CHARS = new Array();
for (var ii=0; ii < BASE64CHARS.length; ++ii) {
    REVE64CHARS[BASE64CHARS[ii]] = ii;
}

function Base64()
{
    this.eof  = -1;

    this.data  = '';
    this.rndx = 0;

    this.encode = encode;
    this.decode = decode;

    this.read1  = read1;
    this.read2  = read2;
    this.ntos   = ntos;

    function read1()
    {
        if (!this.data) {
            return this.eof;
        }
        if (this.rndx >= this.data.length) {
            return this.eof;
        }
        return this.data.charCodeAt(this.rndx++) & 0xff;
    }

    function encode(data)
    {
        this.data = data;
        this.rndx = 0;

        var result = '';
        var buf = new Array(3);
        var cnt = 0;
        var done = false;
        while (!done && (buf[0] = this.read1()) != this.eof) {
            buf[1] = this.read1();
            buf[2] = this.read1();
            result += (BASE64CHARS[ buf[0] >> 2 ]);
            if (buf[1] != this.eof) {
                result += (BASE64CHARS[(( buf[0] << 4 ) & 0x30) | (buf[1] >> 4) ]);
                if (buf[2] != this.eof){
                    result += (BASE64CHARS [((buf[1] << 2) & 0x3c) | (buf[2] >> 6) ]);
                    result += (BASE64CHARS [buf[2] & 0x3F]);
                }
                else {
                    result += (BASE64CHARS [((buf[1] << 2) & 0x3c)]);
                    result += ('=');
                    done = true;
                }
            }
            else {
                result += (BASE64CHARS[((buf[0] << 4 ) & 0x30)]);
                result += ('=');
                result += ('=');
                done = true;
            }
            cnt += 4;
            if (cnt >= 76){
                //result += ('\n');
                cnt = 0;
            }
        }
        return result;
    }

    function read2()
    {
        if (!this.data) {
            return this.eof;
        }
        while (true){
            if (this.rndx >= this.data.length) {
                return this.eof;
            }
            var ch1 = this.data.charAt(this.rndx++);
            var ch2 = REVE64CHARS[ch1];
            if (ch2){
                return ch2;
            }
            if (ch1 == 'A') {
                return 0;
            }
        }
        return this.eof;
    }

    function ntos(n)
    {
        n = n.toString(16);
        if (n.length == 1) {
            n = "0"+n;
        }
        n = "%"+n;
        return unescape(n);
    }

    function decode(data)
    {
        this.data = data;
        this.rndx = 0;

        var result = '';
        var buf = new Array(4);
        var done = false;
        while (!done && (buf[0] = this.read2()) != this.eof && (buf[1] = this.read2()) != this.eof) {
            buf[2] = this.read2();
            buf[3] = this.read2();
            result += this.ntos((((buf[0] << 2) & 0xff)| buf[1] >> 4));
            if (buf[2] != this.eof) {
                result +=  this.ntos((((buf[1] << 4) & 0xff)| buf[2] >> 2));
                if (buf[3] != this.eof) {
                    result +=  this.ntos((((buf[2] << 6)  & 0xff) | buf[3]));
                }
                else {
                    done = true;
                }
            }
            else {
                done = true;
            }
        }
        return result;
    }
}