/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.cdc.i18n.j2me;

import java.io.*;
import com.sun.cdc.i18n.*;
/**
 * Generic interface for stream conversion reading of 
 * specific character encoded input streams.
 */
public class Gen_Reader extends StreamReader {
    /** Saved encoding string from construction. */
    private String enc;
    /** Native handle for conversion routines. */
    private int id;
    /** Local buffer to read converted characters. */
    private byte[] buf;
    /** Maximum length of characters in local buffer. */
    private int maxByteLen;

    /**
     * Constructor for generic reader.
     * @param inp_enc character encoding to use for byte to character
     * conversion.
     * @exception ClassNotFoundException is thrown if the conversion
     * class is not available
     */
    Gen_Reader(String inp_enc) throws ClassNotFoundException {
        id = Conv.getHandler(inp_enc);
        if (id == -1) {
            // this lets Helper throw UnsupportedEncodingException
            throw new ClassNotFoundException();
        }
        enc = inp_enc;
        maxByteLen = Conv.getMaxByteLength(id);
        pos = maxByteLen;
        tmp = new byte[pos];
        buf = new byte[pos];
    }

    /**
     * Generic routine to open an InputStream with a specific
     * character encoding.
     * 
     * @param in the input stream to process
     * @param open_enc the character encoding of the input stream
     * @return Reader instance for converted characters
     * @throws UnsupportedEncodingException if encoding is not supported
     */
    public Reader open(InputStream in, String open_enc) 
        throws UnsupportedEncodingException {
        if (!open_enc.equals(enc)) {
            throw new UnsupportedEncodingException();
        }
        init();
        return super.open(in, open_enc);
    }

    /**
     * Read a single converted character.
     *
     * @return a single converted character
     * @exception IOException is thrown if the input stream 
     * could not be read for the raw unconverted character
     */
    synchronized public int read() throws IOException {
        int c = get();
        if (c == -1) {
            return -1;
        }

        char[] cb = {(char)0xFFFD};

        int bufLen = 0;
        buf[bufLen++] = (byte) c;

        boolean eof = false;

        for (int i = 1; i < maxByteLen; i++) {
            c = get();
            if (c == -1) {
                eof = true;
                break;
            }
            buf[bufLen++] = (byte) c;
        }

        int bytelen = Conv.getByteLength(id, buf, 0, bufLen);

        /* the byte indicates that there are tailing bytes */
        if (bytelen == -1) {
            /* if the stream is in the end, throw IOException */
            if (eof) {
                throw new IOException(/* "incomplete byte" */);
            }

            /* put the read ahead bytes back to the stream */
            for (int i = bufLen; i > 1; i--) {
                put(buf[--bufLen]);
            }
            /* return the leading byte as an unknown character */
            return cb[0];
        }

        /* the byte is invalid */
        if (bytelen == 0) {
            for (int i = bufLen; i > 1; i--) {
                put(buf[--bufLen]);
            }
            return cb[0];
        }

        /* put the read ahead bytes back to the stream */
        if (bytelen < bufLen) {
            for (int i = bufLen; i > bytelen; i--) {
                put(buf[--bufLen]);
            }
        }

        int convLen = Conv.byteToChar(id, buf, 0, bufLen, cb, 0, 1);

        if (convLen != 1) {
            throw new IOException(/* "Converter error" */);
        }

        return cb[0];
    }

    /**
     * Read a block of converted characters.
     *
     * @param cbuf output buffer for converted characters read
     * @param off initial offset into the provided buffer
     * @param len length of characters in the buffer
     * @return the number of converted characters, or -1 
     * if an error occurred in the input arguments
     * @exception IOException is thrown if the input stream 
     * could not be read for the raw unconverted character
     */
    synchronized public int read(char cbuf[], int off, int len) 
        throws IOException {
        /* first, check that the stream has been reached to eof */
        int c = get();
        if (c == -1) {
            return -1;
        }
        put(c); /* put it back */

        int maxlen = len * maxByteLen;
        if (buf.length < maxlen) {
            buf = new byte[maxlen];
        }

        int bufLen = readNumOfChars(buf, len);

        int ret = 0;
        if (bufLen > 0) {
            ret = Conv.byteToChar(id, buf, 0, bufLen, cbuf, off, len);
        }

        if (buf.length > maxByteLen) {
            buf = new byte[maxByteLen];
        }

        return ret;
    }

    /**
     * Skip over a number of bytes in the input stream.
     * 
     * @param n number of bytes to bypass from the input stream.
     * @return the number of characters skipped
     * @throws IOException if an I/O error occurs
     */
    public long skip(long n) throws IOException {
        if (n < 0L) {
            throw new IllegalArgumentException("skip value is negative");
        }

        /* see the stream has been reached to eof */
        int c = get();
        if (c == -1) {
            return 0;
        }
        put(c);

        return readNumOfChars(null, (int) n);
    }

    /** 
     * Get the size of the converted bytes as a Unicode 
     * byte array.
     *
     * @param c array of bytes to compute size
     * @param offset offset in the provided buffer
     * @param length length of bytes to process
     * @return length of converted characters.
     */
    public int sizeOf(byte[] c, int offset, int length) {
        return Conv.sizeOfByteInUnicode(id, c, offset, length);
    }

    /**
     * Reset the stream.
     *
     * @exception  IOException  If an I/O error occurs
     */
    public void reset() throws IOException {
        init();
    }

    /**
     * Read a specific number of characters from the 
     * input stream.
     * 
     * @param b array of bytes to read
     * @param num count of bytes to read
     * @return number of characters read
     * @exception IOException is thrown, if an error occurred 
     * reading the raw input stream
     */
    private int readNumOfChars(byte[] b, int num) throws IOException {
        int charsRead = 0;
        int bbLen = 0;
        byte bb[];

        if (b == null) {
            bb = new byte[num * maxByteLen];
        } else {
            bb = b;
        }

        boolean eof = false;

        while (charsRead < num) {

            int c = get();
            if (c == -1) {
                break;
            }

            int offset = bbLen;

            bb[bbLen++] = (byte) c;

            for (int i = 1; i < maxByteLen; i++) {
                c = get();
                if (c == -1) {
                    eof = true;
                    break;
                }
                bb[bbLen++] = (byte) c;
            }

            int readNum = bbLen - offset;
            int bytelen = Conv.getByteLength(id, bb, offset, readNum);

            /* the byte indicates that there are tailing bytes */
            if (bytelen == -1) {
                /* save the leading byte for the next read */
                if (eof) {
                    for (int i = readNum; i > 0; i--) {
                        put(bb[--bbLen]);
                    }
                    break;
                }
                /* put the rest of bytes back into the stream */
                for (int i = readNum; i > 1; i--) {
                    put(bb[--bbLen]);
                }
            /* the byte is invalid */
            } else if (bytelen == 0) {
                for (int i = readNum; i > 1; i--) {
                    put(bb[--bbLen]);
                }
            } else if (bytelen < readNum) {
                for (int i = readNum; i > bytelen; i--) {
                    put(bb[--bbLen]);
                }
            }

            charsRead++;
        }

        if (b != null) {
            return bbLen;
        } else {
            return charsRead;
        }
    }
    /** Local buffer for conversion routines. */
    private byte[] tmp;
    /** Current position in the temporary buffer. */
    private int pos;

    /**
     * Get a byte from the temporary buffer or from the 
     * input stream.
     * @return the next byte from the input stream or local buffer
     * @exception IOException is thrown, if an error occurred 
     * reading the raw input stream
     */
    private int get() throws IOException {
	if (pos < tmp.length) {
	    return tmp[pos++] & 0xff;
	}
	return in.read();
    }

    /**
     * Put a byte back into the temporary buffer.
     * @param b the character to put back in the temporary buffer
     * @exception IOException is thrown, if putting a character back 
     * past the beginning of the local temporary buffer
     */
    private void put(int b) throws IOException {
	if (pos == 0) {
	    throw new IOException();
	}
	tmp[--pos] = (byte) b;
    }

    /** reset the state */
    private void init() {
        pos = maxByteLen;
    }
}
