// OutputBuffer.java

/**
 *      Copyright (C) 2008 10gen Inc.
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package org.bson.io;

import java.io.*;
import java.security.*;

public abstract class OutputBuffer extends OutputStream {

    public abstract void write(byte[] b);
    public abstract void write(byte[] b, int off, int len);
    public abstract void write(int b);
    
    public abstract int getPosition();
    public abstract void setPosition( int position );
    
    public abstract void seekEnd();
    public abstract void seekStart();
    
    /**
     * @return size of data so far
     */
    public abstract int size();

    /**
     * @return bytes written
     */
    public abstract int pipe( OutputStream out )
        throws IOException;

    /**
     * mostly for testing
     */
    public byte [] toByteArray(){
        try {
            final ByteArrayOutputStream bout = new ByteArrayOutputStream( size() );
            pipe( bout );
            return bout.toByteArray();
        }
        catch ( IOException ioe ){
            throw new RuntimeException( "should be impossible" , ioe );
        }
    }

    public String asString(){
        return new String( toByteArray() );
    }

    public String asString( String encoding )
        throws UnsupportedEncodingException {
        return new String( toByteArray() , encoding );
    }


    public String hex(){
        final StringBuilder buf = new StringBuilder();
        try {
            pipe( new OutputStream(){
                    public void write( int b ){
                        String s = Integer.toHexString(0xff & b);
                        
                        if (s.length() < 2) 
                            buf.append("0");
                        buf.append(s);
                    }
                } 
                );
        }
        catch ( IOException ioe ){
            throw new RuntimeException( "impossible" );
        }
        return buf.toString();
    }

    public String md5(){
        final MessageDigest md5 ;
        try {
            md5 = MessageDigest.getInstance("MD5");
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Error - this implementation of Java doesn't support MD5.");
        }        
        md5.reset();

        try {
            pipe( new OutputStream(){
                    public void write( byte[] b , int off , int len ){
                        md5.update( b , off , len );
                    }

                    public void write( int b ){
                        md5.update( (byte)(b&0xFF) );
                    }
                } 
                );
        }
        catch ( IOException ioe ){
            throw new RuntimeException( "impossible" );
        }
        
        return com.mongodb.util.Util.toHex( md5.digest() );
    }
    
    public void writeInt( int x ){
        write( x >> 0 );
        write( x >> 8 );
        write( x >> 16 );
        write( x >> 24 );
    }

    public void writeIntBE( int x ){
        write( x >> 24 );
        write( x >> 16 );
        write( x >> 8 );
        write( x );
    }

    public void writeInt( int pos , int x ){
        final int save = getPosition();
        setPosition( pos );
        writeInt( x );
        setPosition( save );
    }

    public void writeLong( long x ){
        write( (byte)(0xFFL & ( x >> 0 ) ) );
        write( (byte)(0xFFL & ( x >> 8 ) ) );
        write( (byte)(0xFFL & ( x >> 16 ) ) );
        write( (byte)(0xFFL & ( x >> 24 ) ) );
        write( (byte)(0xFFL & ( x >> 32 ) ) );
        write( (byte)(0xFFL & ( x >> 40 ) ) );
        write( (byte)(0xFFL & ( x >> 48 ) ) );
        write( (byte)(0xFFL & ( x >> 56 ) ) );
    }

    public void writeDouble( double x ){
        writeLong( Double.doubleToRawLongBits( x ) );
    }

    public String toString(){
        return getClass().getName() + " size: " + size() + " pos: " + getPosition() ;
    }
}
