/**
 *  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 org.apache.kerby.asn1;

import org.apache.kerby.asn1.type.Asn1Tagging;
import org.apache.kerby.asn1.type.Asn1VisibleString;
import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

/**
 Ref. X.690-0207 8.14 Encoding of a tagged value
 EXAMPLE
 With ASN.1 type definitions (in an explicit tagging environment) of:
 Type1 ::= VisibleString
 Type2 ::= [APPLICATION 3] IMPLICIT Type1
 Type3 ::= [2] Type2
 Type4 ::= [APPLICATION 7] IMPLICIT Type3
 Type5 ::= [2] IMPLICIT Type2
 a value of:
 "Jones"
 is encoded as follows:
 For Type1:
 VisibleString Length Contents
 1A16 0516 4A6F6E657316
 For Type2:
 [Application 3] Length Contents
 4316 0516 4A6F6E657316
 For Type3:
 [2] Length Contents
 A216 0716
 [APPLICATION 3] Length Contents
 4316 0516 4A6F6E657316
 For Type4:
 [Application 7] Length Contents
 6716 0716
 [APPLICATION 3] Length Contents
 4316 0516 4A6F6E657316
 For Type5:
 [2] Length Contents
 8216 0516 4A6F6E657316
 */

public class TaggingEncodingTest {
    static final String TEST_STRING = "Jones";
    static final byte[] TYPE1_EXPECTED_BYTES = new byte[] {(byte) 0x1A, (byte) 0x05,
            (byte) 0x4A, (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x73};
    static final byte[] TYPE2_EXPECTED_BYTES = new byte[] {(byte) 0x43, (byte) 0x05,
            (byte) 0x4A, (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x73};
    static final byte[] TYPE3_EXPECTED_BYTES = new byte[] {(byte) 0xA2, (byte) 0x07,
            (byte) 0x43, (byte) 0x05, (byte) 0x4A, (byte) 0x6F, (byte) 0x6E,
            (byte) 0x65, (byte) 0x73};
    static final byte[] TYPE4_EXPECTED_BYTES = new byte[] {(byte) 0x67, (byte) 0x07,
            (byte) 0x43, (byte) 0x05, (byte) 0x4A, (byte) 0x6F, (byte) 0x6E,
            (byte) 0x65, (byte) 0x73};
    static final byte[] TYPE5_EXPECTED_BYTES = new byte[] {(byte) 0x82, (byte) 0x05,
            (byte) 0x4A, (byte) 0x6F, (byte) 0x6E, (byte) 0x65, (byte) 0x73};


    public static class Type1 extends Asn1VisibleString {
        public Type1(String value) {
            super(value);
        }
        public Type1() {
            this(null);
        }
    }

    public static class Type2 extends Asn1Tagging<Type1> {
        public Type2(Type1 value) {
            super(3, value, true, true);
        }
        public Type2() {
            this(null);
        }
    }

    public static class Type3 extends Asn1Tagging<Type2> {
        public Type3(Type2 value) {
            super(2, value, false, false);
        }
        public Type3() {
            this(null);
        }
    }

    public static class Type4 extends Asn1Tagging<Type3> {
        public Type4(Type3 value) {
            super(7, value, true, true);
        }
        public Type4() {
            this(null);
        }
    }

    public static class Type5 extends Asn1Tagging<Type2> {
        public Type5(Type2 value) {
            super(2, value, false, true);
        }
        public Type5() {
            this(null);
        }
    }

    @Test
    public void testAsn1TaggingEncoding() throws IOException {
        Type1 aType1 = new Type1(TEST_STRING);
        Type2 aType2 = new Type2(aType1);
        Type3 aType3 = new Type3(aType2);
        Type4 aType4 = new Type4(aType3);
        Type5 aType5 = new Type5(aType2);

        assertThat(aType1.encode()).isEqualTo(TYPE1_EXPECTED_BYTES);
        assertThat(aType2.encode()).isEqualTo(TYPE2_EXPECTED_BYTES);
        assertThat(aType3.encode()).isEqualTo(TYPE3_EXPECTED_BYTES);
        assertThat(aType4.encode()).isEqualTo(TYPE4_EXPECTED_BYTES);
        assertThat(aType5.encode()).isEqualTo(TYPE5_EXPECTED_BYTES);
    }

    @Test
    public void testAsn1TaggingDecoding() throws IOException {
        Type1 aType1 = new Type1();
        aType1.decode(TYPE1_EXPECTED_BYTES);
        assertThat(aType1.getValue()).isEqualTo(TEST_STRING);

        Type2 aType2 = new Type2();
        aType2.decode(TYPE2_EXPECTED_BYTES);
        assertThat(aType2.getValue().getValue()).isEqualTo(TEST_STRING);

        Type3 aType3 = new Type3();
        aType3.decode(TYPE3_EXPECTED_BYTES);
        assertThat(aType3.getValue().getValue().getValue()).isEqualTo(TEST_STRING);

        Type4 aType4 = new Type4();
        aType4.decode(TYPE4_EXPECTED_BYTES);
        assertThat(aType4.getValue().getValue().getValue().getValue()).isEqualTo(TEST_STRING);

        Type5 aType5 = new Type5();
        aType5.decode(TYPE5_EXPECTED_BYTES);
        assertThat(aType5.getValue().getValue().getValue()).isEqualTo(TEST_STRING);
    }

    @Test
    public void testTaggingEncodingOption() throws IOException {
        Type1 aType1 = new Type1(TEST_STRING);
        Type2 aType2 = new Type2(aType1);
        Type3 aType3 = new Type3(aType2);

        assertThat(aType1.encode()).isEqualTo(TYPE1_EXPECTED_BYTES);
        assertThat(TYPE2_EXPECTED_BYTES)
                .isEqualTo(aType1.taggedEncode(TaggingOption.newImplicitAppSpecific(3))); // for Type2
        assertThat(TYPE3_EXPECTED_BYTES)
                .isEqualTo(aType2.taggedEncode(TaggingOption.newExplicitContextSpecific(2))); // for Type3
        assertThat(TYPE4_EXPECTED_BYTES)
                .isEqualTo(aType3.taggedEncode(TaggingOption.newImplicitAppSpecific(7))); // for Type4
        assertThat(TYPE5_EXPECTED_BYTES)
                .isEqualTo(aType2.taggedEncode(TaggingOption.newImplicitContextSpecific(2))); // for Type5
    }

    @Test
    public void testTaggingDecodingOption() throws IOException {
        Type1 aType1 = new Type1();
        aType1.decode(TYPE1_EXPECTED_BYTES);
        assertThat(aType1.getValue()).isEqualTo(TEST_STRING);

        // for Type2
        aType1 = new Type1();
        aType1.taggedDecode(TYPE2_EXPECTED_BYTES, TaggingOption.newImplicitAppSpecific(3));
        assertThat(aType1.getValue()).isEqualTo(TEST_STRING);

        // for Type3
        Type2 aType2 = new Type2();
        aType2.taggedDecode(TYPE3_EXPECTED_BYTES, TaggingOption.newExplicitContextSpecific(2));
        assertThat(aType2.getValue().getValue()).isEqualTo(TEST_STRING);

        // for Type4
        Type3 aType3 = new Type3();
        aType3.taggedDecode(TYPE4_EXPECTED_BYTES, TaggingOption.newImplicitAppSpecific(7));
        assertThat(aType3.getValue().getValue().getValue()).isEqualTo(TEST_STRING);

        // for Type5
        aType2 = new Type2();
        aType2.taggedDecode(TYPE5_EXPECTED_BYTES, TaggingOption.newImplicitContextSpecific(2));
        assertThat(aType2.getValue().getValue()).isEqualTo(TEST_STRING);
    }
}
