/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * 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 com.android.internal.telephony;

import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.uicc.IccUtils;

import junit.framework.TestCase;

import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;

public class GsmAlphabetTest extends TestCase {

    private static final String sGsmExtendedChars = "{|}\\[~]\f\u20ac";

    @SmallTest
    public void test7bitWithHeader() throws Exception {
        SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
        concatRef.refNumber = 1;
        concatRef.seqNumber = 2;
        concatRef.msgCount = 2;
        concatRef.isEightBits = true;
        SmsHeader header = new SmsHeader();
        header.concatRef = concatRef;

        String message = "aaaaaaaaaabbbbbbbbbbcccccccccc";
        byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message,
                SmsHeader.toByteArray(header), 0, 0);
        int septetCount = GsmAlphabet.countGsmSeptetsUsingTables(message, true, 0, 0);
        String parsedMessage = GsmAlphabet.gsm7BitPackedToString(
                userData, SmsHeader.toByteArray(header).length+2, septetCount, 1, 0, 0);
        assertEquals(message, parsedMessage);
    }

    // TODO: This method should *really* be a series of individual test methods.
    // However, it's a SmallTest because it executes quickly.
    @SmallTest
    public void testBasic() throws Exception {
        // '@' maps to char 0
        assertEquals(0, GsmAlphabet.charToGsm('@'));

        // `a (a with grave accent) maps to last GSM character
        assertEquals(0x7f, GsmAlphabet.charToGsm('\u00e0'));

        //
        // These are the extended chars
        // They should all return GsmAlphabet.GSM_EXTENDED_ESCAPE
        //

        for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
            assertEquals(GsmAlphabet.GSM_EXTENDED_ESCAPE,
                    GsmAlphabet.charToGsm(sGsmExtendedChars.charAt(i)));

        }

        // euro symbol
        assertEquals(GsmAlphabet.GSM_EXTENDED_ESCAPE,
                GsmAlphabet.charToGsm('\u20ac'));

        // An unmappable char (the 'cent' char) maps to a space
        assertEquals(GsmAlphabet.charToGsm(' '),
                GsmAlphabet.charToGsm('\u00a2'));

        // unmappable = space = 1 septet
        assertEquals(1, GsmAlphabet.countGsmSeptets('\u00a2'));

        //
        // Test extended table
        //

        for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
            assertEquals(sGsmExtendedChars.charAt(i),
                    GsmAlphabet.gsmExtendedToChar(
                            GsmAlphabet.charToGsmExtended(sGsmExtendedChars.charAt(i))));

        }

        // Unmappable extended char
        assertEquals(GsmAlphabet.charToGsm(' '),
                GsmAlphabet.charToGsmExtended('@'));

        //
        // gsmToChar()
        //

        assertEquals('@', GsmAlphabet.gsmToChar(0));

        // `a (a with grave accent) maps to last GSM character
        assertEquals('\u00e0', GsmAlphabet.gsmToChar(0x7f));

        assertEquals('\uffff',
                GsmAlphabet.gsmToChar(GsmAlphabet.GSM_EXTENDED_ESCAPE));

        // Out-of-range/unmappable value
        assertEquals(' ', GsmAlphabet.gsmToChar(0x80));

        //
        // gsmExtendedToChar()
        //

        assertEquals('{', GsmAlphabet.gsmExtendedToChar(0x28));

        // No double-escapes
        assertEquals(' ', GsmAlphabet.gsmExtendedToChar(
                GsmAlphabet.GSM_EXTENDED_ESCAPE));

        // Reserved for extension to extension table (mapped to space)
        assertEquals(' ', GsmAlphabet.gsmExtendedToChar(GsmAlphabet.GSM_EXTENDED_ESCAPE));

        // Unmappable (mapped to character in default or national locking shift table)
        assertEquals('@', GsmAlphabet.gsmExtendedToChar(0));
        assertEquals('\u00e0', GsmAlphabet.gsmExtendedToChar(0x7f));

        //
        // stringTo7BitPacked, gsm7BitPackedToString
        //

        byte[] packed;
        StringBuilder testString = new StringBuilder(300);

        // Check all alignment cases
        for (int i = 0; i < 9; i++, testString.append('@')) {
            packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
            assertEquals(testString.toString(),
                    GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));
        }

        // Check full non-extended alphabet
        for (int i = 0; i < 0x80; i++) {
            char c;

            if (i == GsmAlphabet.GSM_EXTENDED_ESCAPE) {
                continue;
            }

            c = GsmAlphabet.gsmToChar(i);
            testString.append(c);

            // These are all non-extended chars, so it should be
            // one septet per char
            assertEquals(1, GsmAlphabet.countGsmSeptets(c));
        }

        packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
        assertEquals(testString.toString(),
                GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));

        // Test extended chars too

        testString.append(sGsmExtendedChars);

        for (int i = 0, s = sGsmExtendedChars.length(); i < s; i++) {
            // These are all extended chars, so it should be
            // two septets per char
            assertEquals(2, GsmAlphabet.countGsmSeptets(sGsmExtendedChars.charAt(i)));

        }

        packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
        assertEquals(testString.toString(),
                GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));

        // stringTo7BitPacked handles up to 255 septets

        testString.setLength(0);
        for (int i = 0; i < 255; i++) {
            testString.append('@');
        }

        packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
        assertEquals(testString.toString(),
                GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));

        // > 255 septets throws runtime exception
        testString.append('@');

        try {
            GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
            fail("expected exception");
        } catch (EncodeException ex) {
            // exception expected
        }

        // Try 254 septets with 127 extended chars

        testString.setLength(0);
        for (int i = 0; i < (255 / 2); i++) {
            testString.append('{');
        }

        packed = GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
        assertEquals(testString.toString(),
                GsmAlphabet.gsm7BitPackedToString(packed, 1, 0xff & packed[0]));

        // > 255 septets throws runtime exception
        testString.append('{');

        try {
            GsmAlphabet.stringToGsm7BitPacked(testString.toString(), 0, 0);
            fail("expected exception");
        } catch (EncodeException ex) {
            // exception expected
        }

        // Reserved for extension to extension table (mapped to space)
        packed = new byte[]{(byte)(0x1b | 0x80), 0x1b >> 1};
        assertEquals(" ", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));

        // Unmappable (mapped to character in default alphabet table)
        packed[0] = 0x1b;
        packed[1] = 0x00;
        assertEquals("@", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));
        packed[0] = (byte)(0x1b | 0x80);
        packed[1] = (byte)(0x7f >> 1);
        assertEquals("\u00e0", GsmAlphabet.gsm7BitPackedToString(packed, 0, 2));

        //
        // 8 bit unpacked format
        //
        // Note: we compare hex strings here
        // because Assert doesn't have array comparisons

        byte unpacked[];

        unpacked = IccUtils.hexStringToBytes("566F696365204D61696C");
        assertEquals("Voice Mail",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        assertEquals(IccUtils.bytesToHexString(unpacked),
                IccUtils.bytesToHexString(
                        GsmAlphabet.stringToGsm8BitPacked("Voice Mail")));

        unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
        // two bytes for every extended char
        assertEquals(2 * sGsmExtendedChars.length(), unpacked.length);
        assertEquals(sGsmExtendedChars,
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        // should be two bytes per extended char
        assertEquals(2 * sGsmExtendedChars.length(), unpacked.length);

        // Test truncation of unaligned extended chars
        unpacked = new byte[3];
        GsmAlphabet.stringToGsm8BitUnpackedField(sGsmExtendedChars, unpacked,
                0, unpacked.length);

        // Should be one extended char and an 0xff at the end

        assertEquals(0xff, 0xff & unpacked[2]);
        assertEquals(sGsmExtendedChars.substring(0, 1),
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        // Test truncation of normal chars
        unpacked = new byte[3];
        GsmAlphabet.stringToGsm8BitUnpackedField("abcd", unpacked,
                0, unpacked.length);

        assertEquals("abc",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        // Test truncation of mixed normal and extended chars
        unpacked = new byte[3];
        GsmAlphabet.stringToGsm8BitUnpackedField("a{cd", unpacked,
                0, unpacked.length);

        assertEquals("a{",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        // Test padding after normal char
        unpacked = new byte[3];
        GsmAlphabet.stringToGsm8BitUnpackedField("a", unpacked,
                0, unpacked.length);

        assertEquals("a",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        assertEquals(0xff, 0xff & unpacked[1]);
        assertEquals(0xff, 0xff & unpacked[2]);

        // Test malformed input -- escape char followed by end of field
        unpacked[0] = 0;
        unpacked[1] = 0;
        unpacked[2] = GsmAlphabet.GSM_EXTENDED_ESCAPE;

        assertEquals("@@",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length));

        // non-zero offset
        assertEquals("@",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));

        // test non-zero offset
        unpacked[0] = 0;
        GsmAlphabet.stringToGsm8BitUnpackedField("abcd", unpacked,
                1, unpacked.length - 1);


        assertEquals(0, unpacked[0]);

        assertEquals("ab",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));

        // test non-zero offset with truncated extended char
        unpacked[0] = 0;

        GsmAlphabet.stringToGsm8BitUnpackedField("a{", unpacked,
                1, unpacked.length - 1);

        assertEquals(0, unpacked[0]);

        assertEquals("a",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 1, unpacked.length - 1));

        // Reserved for extension to extension table (mapped to space)
        unpacked[0] = 0x1b;
        unpacked[1] = 0x1b;
        assertEquals(" ", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));

        // Unmappable (mapped to character in default or national locking shift table)
        unpacked[1] = 0x00;
        assertEquals("@", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
        unpacked[1] = 0x7f;
        assertEquals("\u00e0", GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, 2));
    }

    @SmallTest
    public void testGsm8BitUpackedWithEuckr() throws Exception {
        // Some feature phones in Korea store contacts as euc-kr.
        // Test this situations.
        byte unpacked[];

        // Test general alphabet strings.
        unpacked = IccUtils.hexStringToBytes("61626320646566FF");
        assertEquals("abc def",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));

        // Test korean strings.
        unpacked = IccUtils.hexStringToBytes("C5D7BDBAC6AEFF");
        assertEquals("\uD14C\uC2A4\uD2B8",
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));

        // Test gsm Extented Characters.
        unpacked = GsmAlphabet.stringToGsm8BitPacked(sGsmExtendedChars);
        assertEquals(sGsmExtendedChars,
                GsmAlphabet.gsm8BitUnpackedToString(unpacked, 0, unpacked.length, "euc-kr"));
    }
}
