/**
 * Phoebe DOM Implementation.
 *
 * Base64-enabled input and output streams
 *
 * This class allows easy encoding and decoding
 * of Base64 data with a stream interface, hiding
 * the implementation from the user.
 *
 * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
 *
 * Authors:
 *   Bob Jamison
 *
 * Copyright (C) 2006 Bob Jamison
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */



#include "base64stream.h"


namespace org
{
namespace w3c
{
namespace dom
{
namespace io
{


//#########################################################################
//# B A S E 6 4    I N P U T    S T R E A M
//#########################################################################

static int base64decode[] =
{
/*00*/    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
/*08*/    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
/*10*/    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
/*18*/    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
/*20*/    -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
/*28*/    -1,   -1,   -1,   62,   -1,   -1,   -1,   63,
/*30*/    52,   53,   54,   55,   56,   57,   58,   59,
/*38*/    60,   61,   -1,   -1,   -1,   -1,   -1,   -1,
/*40*/    -1,    0,    1,    2,    3,    4,    5,    6,
/*48*/     7,    8,    9,   10,   11,   12,   13,   14,
/*50*/    15,   16,   17,   18,   19,   20,   21,   22,
/*58*/    23,   24,   25,   -1,   -1,   -1,   -1,   -1,
/*60*/    -1,   26,   27,   28,   29,   30,   31,   32,
/*68*/    33,   34,   35,   36,   37,   38,   39,   40,
/*70*/    41,   42,   43,   44,   45,   46,   47,   48,
/*78*/    49,   50,   51,   -1,   -1,   -1,   -1,   -1
};


/**
 *
 */
Base64InputStream::Base64InputStream(InputStream &sourceStream)
                    : BasicInputStream(sourceStream)
{
    outCount = 0;
    padCount = 0;
    closed   = false;
    done     = false;
}

/**
 *
 */
Base64InputStream::~Base64InputStream()
{
    close();
}

/**
 * Returns the number of bytes that can be read (or skipped over) from
 * this input stream without blocking by the next caller of a method for
 * this input stream.
 */
int Base64InputStream::available()
{
    if (closed )
        return 0;
    int len = source.available() * 2 / 3;
    return len;
}


/**
 *  Closes this input stream and releases any system resources
 *  associated with the stream.
 */
void Base64InputStream::close()
{
    if (closed)
        return;
    source.close();
    closed = true;
}

/**
 * Reads the next byte of data from the input stream.  -1 if EOF
 */
int Base64InputStream::get()
{
    if (closed)
        return -1;

    if (outCount - padCount > 0)
        {
        return outBytes[3-(outCount--)];
        }

    if (done)
        return -1;

    int inBytes[4];
    int inCount = 0;
    while (inCount < 4)
        {
        int ch = source.get();
        if (ch < 0)
            {
            while (inCount < 4)  //pad if needed
                {
                inBytes[inCount++] = 0;
                padCount++;
                }
            done = true;
            break;
            }
        if (isspace(ch)) //ascii whitespace
            {
            //nothing
            }
        else if (ch == '=') //padding
            {
            inBytes[inCount++] = 0;
            padCount++;
            }
        else
            {
            int byteVal = base64decode[ch & 0x7f];
            //printf("char:%c %d\n", ch, byteVal);
            if (byteVal < 0)
                {
                //Bad lookup value
                }
            inBytes[inCount++] = byteVal;
            }
        }

    outBytes[0] = ((inBytes[0]<<2) & 0xfc) | ((inBytes[1]>>4) & 0x03);
    outBytes[1] = ((inBytes[1]<<4) & 0xf0) | ((inBytes[2]>>2) & 0x0f);
    outBytes[2] = ((inBytes[2]<<6) & 0xc0) | ((inBytes[3]   ) & 0x3f);

    outCount = 3;

    //try again
    if (outCount - padCount > 0)
        {
        return outBytes[3-(outCount--)];
        }

    return -1;

}


//#########################################################################
//# B A S E 6 4    O U T P U T    S T R E A M
//#########################################################################

static char const *base64encode =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

/**
 *
 */
Base64OutputStream::Base64OutputStream(OutputStream &destinationStream)
                     : BasicOutputStream(destinationStream)
{
    column      = 0;
    columnWidth = 72;
    outBuf      = 0L;
    bitCount    = 0;
}

/**
 *
 */
Base64OutputStream::~Base64OutputStream()
{
    close();
}

/**
 * Closes this output stream and releases any system resources
 * associated with this stream.
 */
void Base64OutputStream::close()
{
    if (closed)
        return;

    //get any last bytes (1 or 2) out of the buffer
    if (bitCount == 16)
        {
        outBuf <<= 2;  //pad to make 18 bits

        int indx  = (int)((outBuf & 0x0003f000L) >> 12);
        int obyte = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x00000fc0L) >>  6);
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x0000003fL)      );
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        putCh('=');
        }
    else if (bitCount == 8)
        {
        outBuf <<= 4; //pad to make 12 bits

        int indx  = (int)((outBuf & 0x00000fc0L) >>  6);
        int obyte = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x0000003fL)      );
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        putCh('=');
        putCh('=');
        }

    if (columnWidth > 0) //if <=0, no newlines
        destination.put('\n');

    destination.close();
    closed = true;
}

/**
 *  Flushes this output stream and forces any buffered output
 *  bytes to be written out.
 */
void Base64OutputStream::flush()
{
    if (closed)
        return;
    //dont flush here.  do it on close()
    destination.flush();
}

/**
 * Private. Put a char to the output stream, checking for line length
 */
void Base64OutputStream::putCh(int ch)
{
    destination.put(ch);
    column++;
    if (columnWidth > 0 && column >= columnWidth)
        {
        destination.put('\n');
        column = 0;
        }
}


/**
 * Writes the specified byte to this output stream.
 */
int Base64OutputStream::put(XMLCh ch)
{
    if (closed)
        {
        //probably throw an exception here
        return -1;
        }

    outBuf   <<=  8;
    outBuf   |=  (ch & 0xff);
    bitCount +=  8;
    if (bitCount >= 24)
        {
        int indx  = (int)((outBuf & 0x00fc0000L) >> 18);
        int obyte = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x0003f000L) >> 12);
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x00000fc0L) >>  6);
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        indx      = (int)((outBuf & 0x0000003fL)      );
        obyte     = (int)base64encode[indx & 63];
        putCh(obyte);

        bitCount = 0;
        outBuf   = 0L;
        }

    return 1;
}



}  //namespace io
}  //namespace dom
}  //namespace w3c
}  //namespace org


//#########################################################################
//# E N D    O F    F I L E
//#########################################################################
