/*
 * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 in the LICENSE file that
 * accompanied this code).
 *
 * 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.io;

/**
 * An algorithmic conversion from ISO 2022 to Unicode
 *
 * @author Tom Zhou
 */
public abstract class ByteToCharISO2022 extends ByteToCharConverter
{
    // Value to be filled by subclass
    protected String SODesignator[];
    protected String SS2Designator[] = null;
    protected String SS3Designator[] = null;

    protected ByteToCharConverter SOConverter[];
    protected ByteToCharConverter SS2Converter[] = null;
    protected ByteToCharConverter SS3Converter[] = null;

    private static final byte ISO_ESC = 0x1b;
    private static final byte ISO_SI = 0x0f;
    private static final byte ISO_SO = 0x0e;
    private static final byte ISO_SS2_7 = 0x4e;
    private static final byte ISO_SS3_7 = 0x4f;
    private static final byte MSB = (byte)0x80;
    private static final char REPLACE_CHAR = '\uFFFD';
    private static final byte maximumDesignatorLength = 3;

    private static final byte SOFlag = 0;
    private static final byte SS2Flag = 1;
    private static final byte SS3Flag = 2;
    private static final byte G0 = 0;
    private static final byte G1 = 1;

    private ByteToCharConverter tmpConverter[];

    private int curSODes, curSS2Des, curSS3Des;
    private boolean shiftout;

    private byte remainByte[] = new byte[10];
    private int remainIndex = -1;
    private byte state, firstByte;

    public void reset()
    {
        int i = 0;

        shiftout = false;
        state = G0;
        firstByte = 0;

        curSODes = 0;
        curSS2Des = 0;
        curSS3Des = 0;

        charOff = byteOff = 0;
        remainIndex = -1;

        for(i = 0; i < remainByte.length; i++)
            remainByte[i] = 0;
    }

    public int flush(char[] output, int outStart, int outEnd)
        throws MalformedInputException
    {
        int i;
        if (state != G0) {
            badInputLength = 0;
            throw new MalformedInputException();
        }
        reset();
        return 0;
    }

    private byte[] savetyGetSrc(byte[] input, int inOff, int inEnd, int nbytes)
    {
        int i;
        byte tmp[];

        if(inOff <= (inEnd-nbytes+1))
            tmp = new byte[nbytes];
        else
            tmp = new byte[inEnd-inOff];

        for(i = 0; i < tmp.length; i++)
            tmp[i] = input[inOff+i];
        return tmp;
    }

    private char getUnicode(byte byte1, byte byte2, byte shiftFlag)
    {
        byte1 |= MSB;
        byte2 |= MSB;

        byte[] tmpByte = {byte1,byte2};
        char[] tmpChar = new char[1];
        int     i = 0,
                tmpIndex = 0;

        switch(shiftFlag) {
        case SOFlag:
            tmpIndex = curSODes;
            tmpConverter = SOConverter;
            break;
        case SS2Flag:
            tmpIndex = curSS2Des;
            tmpConverter = SS2Converter;
            break;
        case SS3Flag:
            tmpIndex = curSS3Des;
            tmpConverter = SS3Converter;
            break;
        }

        for(i = 0; i < tmpConverter.length; i++) {
            if(tmpIndex == i) {
                try {
                    tmpConverter[i].convert(tmpByte, 0, 2, tmpChar, 0, 1);
                } catch (Exception e) {}
                return tmpChar[0];
            }
        }
        return REPLACE_CHAR;
    }

    public final int convert(byte[] input, int inOff, int inEnd,
                             char[] output, int outOff, int outEnd)
                             throws ConversionBufferFullException,
                                    MalformedInputException
    {
        int i;
        int DesignatorLength = 0;
        charOff  =  outOff;
        byteOff  =  inOff;

        // Loop until we hit the end of the input
        while (byteOff < inEnd) {
            // If we don't have room for the output, throw an exception
            if (charOff >= outEnd)
                throw new ConversionBufferFullException();
            if(remainIndex < 0) {
                remainByte[0] = input[byteOff];
                remainIndex = 0;
                byteOff++;
            }
            switch (remainByte[0]) {
            case ISO_SO:
                shiftout = true;
                if(remainIndex > 0)
                    System.arraycopy(remainByte, 1, remainByte, 0, remainIndex);
                remainIndex--;
                break;
            case ISO_SI:
                shiftout = false;
                if(remainIndex > 0)
                    System.arraycopy(remainByte, 1, remainByte, 0, remainIndex);
                remainIndex--;
                break;
             case ISO_ESC:
                byte tmp[] = savetyGetSrc(input, byteOff, inEnd,
                               (maximumDesignatorLength-remainIndex));
                System.arraycopy(tmp, 0, remainByte, remainIndex+1, tmp.length);
                remainIndex += tmp.length;
                byteOff += tmp.length;
                if(tmp.length<(maximumDesignatorLength-remainIndex))
                    break;
                String tmpString = new String(remainByte, 1, remainIndex);
                for (i = 0; i < SODesignator.length; i++) {
                    if(tmpString.indexOf(SODesignator[i]) == 0) {
                        curSODes = i;
                        DesignatorLength = SODesignator[i].length();
                        break;
                    }
                }

                if (DesignatorLength == 0 ) { // Designator not recognized
                   badInputLength = tmp.length;
                   throw new MalformedInputException();
                }

                if (i == SODesignator.length) {
                    for (i = 0; i < SS2Designator.length; i++) {
                        if(tmpString.indexOf(SS2Designator[i]) == 0) {
                            curSS2Des = i;
                            DesignatorLength = SS2Designator[i].length();
                            break;
                        }
                    }
                    if(i == SS2Designator.length) {
                        for(i = 0; i < SS3Designator.length; i++) {
                            if (tmpString.indexOf(SS3Designator[i]) == 0) {
                                curSS3Des = i;
                                DesignatorLength = SS3Designator[i].length();
                                break;
                            }
                        }
                        if (i == SS3Designator.length) {
                            switch(remainByte[1]) {
                            case ISO_SS2_7:
                                output[charOff] = getUnicode(remainByte[2],
                                                          remainByte[3],
                                                          SS2Flag);
                                charOff++;
                                DesignatorLength = 3;
                                break;
                            case ISO_SS3_7:
                                output[charOff] = getUnicode(remainByte[2],
                                                          remainByte[3],
                                                          SS3Flag);
                                charOff++;
                                DesignatorLength = 3;
                                break;
                            default:
                                DesignatorLength = 0;
                            }
                        }
                    }
                }
                if (remainIndex > DesignatorLength) {
                    for(i = 0; i < remainIndex-DesignatorLength; i++)
                        remainByte[i] = remainByte[DesignatorLength+1+i];
                    remainIndex = i-1;
                } else {
                    remainIndex = -1;
                }
                break;
            default:
                if (!shiftout) {
                    output[charOff] = (char)remainByte[0];
                    charOff++;
                } else {
                    switch (state) {
                    case G0:
                        firstByte = remainByte[0];
                        state = G1;
                        break;
                    case G1:
                        output[charOff] = getUnicode(firstByte, remainByte[0],
                                                  SOFlag);
                        charOff++;
                        state = G0;
                        break;
                    }
                }
                if (remainIndex > 0)
                    System.arraycopy(remainByte, 1, remainByte, 0, remainIndex);
                remainIndex--;
            }
        }
        return charOff - outOff;
    }
}
