/*
 * 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.dubbo.common.serialize.fst;

import org.apache.dubbo.common.serialize.model.AnimalEnum;
import org.apache.dubbo.common.serialize.model.person.FullAddress;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;

public class FstObjectOutputTest {
    private FstObjectOutput fstObjectOutput;
    private FstObjectInput fstObjectInput;
    private ByteArrayOutputStream byteArrayOutputStream;
    private ByteArrayInputStream byteArrayInputStream;

    @BeforeEach
    public void setUp() {
        this.byteArrayOutputStream = new ByteArrayOutputStream();
        this.fstObjectOutput = new FstObjectOutput(byteArrayOutputStream);
    }

    @AfterEach
    public void tearDown() throws IOException {
        new FstObjectInput(new ByteArrayInputStream(new byte[]{0}));
    }


    @Test
    public void testWriteBool() throws IOException {
        this.fstObjectOutput.writeBool(false);
        this.flushToInput();

        boolean result = this.fstObjectInput.readBool();
        assertThat(result, is(false));
    }


    @Test
    public void testWriteUTF() throws IOException {
        this.fstObjectOutput.writeUTF("I don’t know 知りません Не знаю");
        this.flushToInput();

        String result = this.fstObjectInput.readUTF();
        assertThat(result, is("I don’t know 知りません Не знаю"));
    }

    @Test
    public void testWriteShort() throws IOException {
        this.fstObjectOutput.writeShort((short) 1);
        this.flushToInput();

        Short result = this.fstObjectInput.readShort();
        assertThat(result, is((short) 1));
    }

    @Test
    public void testWriteLong() throws IOException {
        this.fstObjectOutput.writeLong(12345678L);
        this.flushToInput();

        Long result = this.fstObjectInput.readLong();
        assertThat(result, is(12345678L));
    }

    @Test
    public void testWriteDouble() throws IOException {
        this.fstObjectOutput.writeDouble(-1.66d);
        this.flushToInput();

        Double result = this.fstObjectInput.readDouble();
        assertThat(result, is(-1.66d));
    }


    @Test
    public void testWriteInt() throws IOException {
        this.fstObjectOutput.writeInt(1);
        this.flushToInput();

        Integer result = this.fstObjectInput.readInt();
        assertThat(result, is(1));
    }

    @Test
    public void testWriteByte() throws IOException {
        this.fstObjectOutput.writeByte((byte) 222);
        this.flushToInput();

        Byte result = this.fstObjectInput.readByte();
        assertThat(result, is(((byte) 222)));
    }

    @Test
    public void testWriteBytesWithSubLength() throws IOException {
        this.fstObjectOutput.writeBytes("who are you".getBytes(), 4, 3);
        this.flushToInput();

        byte[] result = this.fstObjectInput.readBytes();
        assertThat(result, is("are".getBytes()));
    }

    @Test
    public void testWriteBytes() throws IOException {
        this.fstObjectOutput.writeBytes("who are you".getBytes());
        this.flushToInput();

        byte[] result = this.fstObjectInput.readBytes();
        assertThat(result, is("who are you".getBytes()));
    }

    @Test
    public void testWriteFloat() throws IOException {
        this.fstObjectOutput.writeFloat(-666.66f);
        this.flushToInput();

        Float result = this.fstObjectInput.readFloat();
        assertThat(result, is(-666.66f));
    }

    @Test
    public void testWriteNullBytesWithSubLength() throws IOException {
        this.fstObjectOutput.writeBytes(null, 4, 3);
        this.flushToInput();

        byte[] result = this.fstObjectInput.readBytes();
        assertThat(result, is(nullValue()));
    }

    @Test
    public void testWriteNullBytes() throws IOException {
        this.fstObjectOutput.writeBytes(null);
        this.flushToInput();

        byte[] result = this.fstObjectInput.readBytes();
        assertThat(result, is(nullValue()));
    }


    @Test
    public void testWriteObject() throws IOException, ClassNotFoundException {
        FullAddress fullAddress = new FullAddress("cId", "pN", "cityId", "Nan Long Street", "51000");
        this.fstObjectOutput.writeObject(fullAddress);
        this.flushToInput();

        FullAddress result = this.fstObjectInput.readObject(FullAddress.class);
        assertThat(result, is(fullAddress));
    }

    @Test
    public void testWriteEnum() throws IOException, ClassNotFoundException {
        this.fstObjectOutput.writeObject(AnimalEnum.cat);
        this.flushToInput();

        AnimalEnum animalEnum = (AnimalEnum) this.fstObjectInput.readObject();
        assertThat(animalEnum, is(AnimalEnum.cat));
    }

    private void flushToInput() throws IOException {
        this.fstObjectOutput.flushBuffer();
        this.byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        this.fstObjectInput = new FstObjectInput(byteArrayInputStream);
    }
}