/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 libcore.java.nio.charset;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.util.Arrays;

import junit.framework.TestCase;

import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;

/**
 * Super class for concrete charset test suites.
 */
public abstract class OldCharset_AbstractTest extends TestCase {

    static String charsetName;
    static private Charset charset;
    static CharsetDecoder decoder;
    static CharsetEncoder encoder;

    static final int[] codes = Charset_TestGenerator.codes;

    static final char[] chars = new char[codes.length]; // Is filled with
                                                        // contents of codes.

    static char[] testChars;
    static byte[] testBytes;

    static char[] theseChars (int... codes) {
        char[] chars = new char[codes.length];
        for (int i = 0; i < codes.length; i++) chars[i] = (char) codes[i];
        return chars;
    }

    static byte[] theseBytes (int... codes) {
        byte[] bytes = new byte[codes.length];
        for (int i = 0; i < codes.length; i++) bytes[i] = (byte) codes[i];
        return bytes;
    }


    @Override
    protected void setUp() throws Exception {
        charset = charset.forName(charsetName);
        decoder = charset.newDecoder();
        encoder = charset.newEncoder();
        super.setUp();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void test_nameMatch () {
        assertEquals("Name of charset must match!", charsetName, charset.name());
    }

    public void test_dumpEncodableChars () {
        if (testChars == null) return;
        if (testChars.length > 0) return;
        System.out.format("\ntest_dumpEncodableChars() for name %s => %s (class = %s)\n",
                charsetName, charset.name(), getClass().getName());
        Charset_TestGenerator.Dumper out = new Charset_TestGenerator.Dumper1(16);
        int code = 0;
        while (code < 256) {
            while (!encoder.canEncode((char) code)) code ++;
            if (code < 65536) {
                out.consume(code);
                code += 1;
            }
        }
        while (code < 65536) {
            while (!encoder.canEncode((char) code)) code ++;
            if (code < 65536) {
                out.consume(code);
                code += 20;
            }
        }
        System.out.println();
        System.out.println("Encodable Chars dumped for Test Class " + getClass().getName());
        fail("Encodable Chars dumped for Test Class " + getClass().getName());
    }

    public void test_dumpEncoded () throws CharacterCodingException {
        if (testChars == null) return;
        if (testChars.length == 0) return;
        if (testBytes != null) return;
        System.out.format("\ntest_dumpEncoded() for name %s => %s (class = %s)\n",
                charsetName, charset.name(), getClass().getName());
        Charset_TestGenerator.Dumper out = new Charset_TestGenerator.Dumper1();
        CharBuffer inputCB = CharBuffer.wrap(testChars);
        ByteBuffer outputBB;
        encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
        outputBB = encoder.encode(inputCB);
        outputBB.rewind();
        while (outputBB.hasRemaining()) {
            out.consume(outputBB.get() & 0xff);
        }
        System.out.println();
        System.out.println("Encoded Bytes dumped for Test Class " + getClass().getName());
        fail("Encoded Bytes dumped for Test Class " + getClass().getName());
    }


    static void decode (byte[] input, char[] expectedOutput) throws CharacterCodingException {
        ByteBuffer inputBB = ByteBuffer.wrap(input);
        decoder.onMalformedInput(CodingErrorAction.REPORT);
        CharBuffer outputCB = decoder.decode(inputBB);
        outputCB.rewind();
        assertEqualChars(expectedOutput, outputCB);
    }

    public void test_Decode () throws CharacterCodingException {
        decode(testBytes, testChars);
    }

    public void test_Encode () throws CharacterCodingException {
        CharBuffer inputCB = CharBuffer.wrap(testChars);
        ByteBuffer outputBB;
        encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
        outputBB = encoder.encode(inputCB);
        outputBB.rewind();
//        assertTrue("Encoded bytes must match!",
//                Arrays.equals(testBytes, outputBB.array()));
        assertEqualBytes("Encoded bytes must match!", testBytes, outputBB);
    }


    public void NNtest_CodecDynamicIndividuals () throws CharacterCodingException {
        encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
        decoder.onMalformedInput(CodingErrorAction.REPORT);

        for (int code = 32; code <= 65533; code ++) {
            if (encoder.canEncode((char) code)) {
//                inputCB.rewind();
                CharBuffer inputCB = CharBuffer.allocate(1);
                inputCB.put((char) code);
                inputCB.rewind();
                ByteBuffer intermediateBB = encoder.encode(inputCB);
                inputCB.rewind();
                intermediateBB.rewind();
                try {
                    CharBuffer outputCB = decoder.decode(intermediateBB);
                    outputCB.rewind();
                    assertEqualCBs("decode(encode(A)) must be identical with A = " + code,
                            inputCB, outputCB);
                    if (code == 165) {
                        outputCB.rewind();
                        System.out.println("WOW:" + outputCB.get());
                    }
                } catch (CharacterCodingException e) {
                    fail("failed to decode(encode(" + code + "))");
                }
            }
        }
    }

    public void test_CodecDynamic () throws CharacterCodingException {
        CharBuffer inputCB = CharBuffer.allocate(65536);
        // Only test most of the Unicode BMP.
        for (int code = 0x20; code <= 0xfffd; code++) {
            // Ignore the private use area.
            if (code == 0xe000) {
                code = 0xf8ff;
                continue;
            }
            char[] codeChars = Character.toChars(code);
            if (encoder.canEncode(new String(codeChars))) {
                // Skip ignorable characters that don't map to anything.
                if (isDefaultIgnorableCode(code) && encodesToNothing(encoder, codeChars)) {
                    continue;
                }

                inputCB.put(codeChars);
            }
        }
        inputCB.rewind();

        encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
        ByteBuffer intermediateBB = encoder.encode(inputCB);
        inputCB.rewind();
        intermediateBB.rewind();

        decoder.onMalformedInput(CodingErrorAction.REPORT);
        CharBuffer outputCB = decoder.decode(intermediateBB);
        outputCB.rewind();
        assertEqualCBs("decode(encode(A)) must be identical with A!",
                inputCB, outputCB);
    }

    private static boolean encodesToNothing(CharsetEncoder encoder, char[] codeChars)
            throws CharacterCodingException {
        boolean encodesToNothing = encoder.encode(CharBuffer.wrap(codeChars)).limit() == 0;
        encoder.reset();
        return encodesToNothing;
    }

    private static boolean isDefaultIgnorableCode(int code) {
        return UCharacter.hasBinaryProperty(code, UProperty.DEFAULT_IGNORABLE_CODE_POINT);
    }

    static void assertEqualCBs (String msg, CharBuffer expectedCB, CharBuffer actualCB) {
        boolean match = true;
        boolean lenMatch = true;
        char expected, actual;
        int len = actualCB.length();
        if (expectedCB.length() != len) {
            lenMatch = false;
            if (expectedCB.length() < len) len = expectedCB.length();
        }
        for (int i = 0; i < len; i++) {
            expected = expectedCB.get();
            actual = actualCB.get();
            if (actual != expected) {
                String detail = String.format(
                        "Mismatch at index %d: U+%04X instead of expected U+%04X.\n",
                        i, (int) actual, (int) expected);
                match = false;
                fail(msg + ": " + detail);
            }
//            else {
//                System.out.format("Match index %d: %d = %d\n",
//                        i, (int) actual[i], (int) expected[i]);
//            }
        }
        assertTrue(msg, match);
        assertTrue(msg + "(IN LENGTH ALSO!)", lenMatch);
//        assertTrue(msg, Arrays.equals(actual, expected));
    }

    static void assertEqualChars(char[] expected, CharBuffer actualCB) {
        assertEquals(expected.length, actualCB.length());
        for (int i = 0; i < actualCB.length(); ++i) {
            char actual = actualCB.get();
            if (actual != expected[i]) {
                String detail = String.format(
                        "Mismatch at index %d: U+%04X instead of expected U+%04X.\n",
                        i, (int) actual, (int) expected[i]);
                fail(detail);
            }
        }
    }

    static void assertEqualBytes (String msg, byte[] expected, ByteBuffer actualBB) {
        boolean match = true;
        boolean lenMatch = true;
        byte actual;
        int len = actualBB.remaining();
        if (expected.length != len) {
            lenMatch = false;
            if (expected.length < len) len = expected.length;
        }
        for (int i = 0; i < len; i++) {
            actual = actualBB.get();
            if (actual != expected[i]) {
                String detail = String.format(
                        "Mismatch at index %d: %02X instead of expected %02X.\n",
                        i, actual & 0xff, expected[i] & 0xff);
                match = false;
                fail(msg + ": " + detail);
            }
        }
        assertTrue(msg, match);
        assertTrue(msg + "(IN LENGTH ALSO!)", lenMatch);
    }


    static abstract class CodesGenerator {
        int row = 0, col = 0;

        abstract void consume(int code);

        boolean isAccepted(int code) {
            return Character.isLetterOrDigit(code);
        }
    }

}
