package org.apache.logging.log4j.core.layout;/*
 * 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.
 */

import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import org.junit.Test;

import static org.junit.Assert.*;

/**
 * Tests the {@code TextEncoderHelper} class.
 */
public class StringBuilderEncoderTest {

    @Test
    public void testEncodeText_TextFitCharBuff_BytesFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024);
        final StringBuilder text = createText(15);
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17);
        helper.encode(text, destination);

        assertEquals("drained", 0, destination.drainPoints.size());
        assertEquals("destination.buf.pos", text.length(), destination.buffer.position());

        for (int i = 0; i < text.length(); i++) {
            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i));
        }
    }

    @Test
    public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024);
        final StringBuilder text = createText(15);
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(14, 15);
        helper.encode(text, destination);

        assertEquals("drained", 1, destination.drainPoints.size());
        assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position);
        assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit);
        assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length());
        assertEquals("destination.buf.pos", text.length() - destination.buffer.capacity(),
                destination.buffer.position());

        for (int i = 0; i < destination.buffer.capacity(); i++) {
            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
        }
        for (int i = destination.buffer.capacity(); i < text.length(); i++) {
            final int bufIx = i - destination.buffer.capacity();
            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx));
        }
    }

    @Test
    public void testEncodeText_TextFitCharBuff_BytesDontFitByteBuff_MultiplePasses() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 16, 8 * 1024);
        final StringBuilder text = createText(15);
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(4, 20);
        helper.encode(text, destination);

        assertEquals("drained", 3, destination.drainPoints.size());
        assertEquals("drained[0].from", 0, destination.drainPoints.get(0).position);
        assertEquals("drained[0].to", destination.buffer.capacity(), destination.drainPoints.get(0).limit);
        assertEquals("drained[0].length", destination.buffer.capacity(), destination.drainPoints.get(0).length());
        assertEquals("drained[1].from", 0, destination.drainPoints.get(1).position);
        assertEquals("drained[1].to", destination.buffer.capacity(), destination.drainPoints.get(1).limit);
        assertEquals("drained[1].length", destination.buffer.capacity(), destination.drainPoints.get(1).length());
        assertEquals("drained[2].from", 0, destination.drainPoints.get(2).position);
        assertEquals("drained[2].to", destination.buffer.capacity(), destination.drainPoints.get(2).limit);
        assertEquals("drained[2].length", destination.buffer.capacity(), destination.drainPoints.get(2).length());
        assertEquals("destination.buf.pos", text.length() - 3 * destination.buffer.capacity(),
                destination.buffer.position());

        for (int i = 0; i < 3 * destination.buffer.capacity(); i++) {
            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
        }
        for (int i = 3 * destination.buffer.capacity(); i < text.length(); i++) {
            final int bufIx = i - 3 * destination.buffer.capacity();
            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(bufIx));
        }
    }

    @Test
    public void testEncodeText_TextDoesntFitCharBuff_BytesFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024);
        final StringBuilder text = createText(15);
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(17, 17);
        helper.encode(text, destination);

        assertEquals("drained", 0, destination.drainPoints.size());
        assertEquals("destination.buf.pos", text.length(), destination.buffer.position());

        for (int i = 0; i < text.length(); i++) {
            assertEquals("char at " + i, (byte) text.charAt(i), destination.buffer.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50);
        helper.encode(text, destination);

        assertEquals("drained", 0, destination.drainPoints.size());
        destination.drain(destination.getByteBuffer());

        final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
        for (int i = 0; i < utf8.length; i++) {
            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesFitByteBuff() throws Exception {
        final Charset SHIFT_JIS = Charset.forName("Shift_JIS");
        final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(50, 50);
        helper.encode(text, destination);

        assertEquals("drained", 0, destination.drainPoints.size());
        destination.drain(destination.getByteBuffer());

        final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
        for (int i = 0; i < bytes.length; i++) {
            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
        }
    }

    @Test
    public void testEncodeText_TextDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024);
        final StringBuilder text = createText(15);
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 17);
        helper.encode(text, destination);

        assertEquals("drained", 4, destination.drainPoints.size());
        assertEquals("destination.buf.pos", 3, destination.buffer.position());

        for (int i = 0; i < text.length() - 3; i++) {
            assertEquals("char at " + i, (byte) text.charAt(i), destination.drained.get(i));
        }
        for (int i = 0; i < 3; i++) {
            assertEquals("char at " + (12 + i), (byte) text.charAt(12 + i), destination.buffer.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_BytesDontFitByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 8 * 1024);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
        helper.encode(text, destination);

        assertEquals("drained", 7, destination.drainPoints.size());
        destination.drain(destination.getByteBuffer());

        final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
        for (int i = 0; i < utf8.length; i++) {
            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextUtf8DoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(StandardCharsets.UTF_8, 4, 5);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
        helper.encode(text, destination);

        assertEquals("drained", 15, destination.drainPoints.size());
        destination.drain(destination.getByteBuffer());

        final byte[] utf8 = text.toString().getBytes(StandardCharsets.UTF_8);
        for (int i = 0; i < utf8.length; i++) {
            assertEquals("byte at " + i, utf8[i], destination.drained.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_BytesDontFitByteBuff() throws Exception {
        final Charset SHIFT_JIS = Charset.forName("Shift_JIS");
        final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 8 * 1024);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
        helper.encode(text, destination);

        destination.drain(destination.getByteBuffer());

        final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
        for (int i = 0; i < bytes.length; i++) {
            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
        }
    }

    @Test
    public void testEncodeText_JapaneseTextShiftJisDoesntFitCharBuff_DoesntFitTempByteBuff_BytesDontFitDestinationByteBuff() throws Exception {
        final Charset SHIFT_JIS = Charset.forName("Shift_JIS");
        final StringBuilderEncoder helper = new StringBuilderEncoder(SHIFT_JIS, 4, 5);
        final StringBuilder text = new StringBuilder( // 日本語テスト文章日本語テスト文章
                "\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0\u65e5\u672c\u8a9e\u30c6\u30b9\u30c8\u6587\u7ae0");
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(3, 50);
        helper.encode(text, destination);

        destination.drain(destination.getByteBuffer());

        final byte[] bytes = text.toString().getBytes(SHIFT_JIS);
        for (int i = 0; i < bytes.length; i++) {
            assertEquals("byte at " + i, bytes[i], destination.drained.get(i));
        }
    }

    @Test
    public void testCopyCopiesAllDataIfSuffientRemainingSpace() throws Exception {
        final CharBuffer buff = CharBuffer.wrap(new char[16]);
        final StringBuilder text = createText(15);
        final int length = TextEncoderHelper.copy(text, 0, buff);
        assertEquals("everything fits", text.length(), length);
        for (int i = 0; i < length; i++) {
            assertEquals("char at " + i, text.charAt(i), buff.get(i));
        }
        assertEquals("position moved by length", text.length(), buff.position());
    }

    @Test
    public void testCopyUpToRemainingSpace() throws Exception {
        final CharBuffer buff = CharBuffer.wrap(new char[3]);
        final StringBuilder text = createText(15);
        final int length = TextEncoderHelper.copy(text, 0, buff);
        assertEquals("partial copy", buff.capacity(), length);
        for (int i = 0; i < length; i++) {
            assertEquals("char at " + i, text.charAt(i), buff.get(i));
        }
        assertEquals("no space remaining", 0, buff.remaining());
        assertEquals("position at end", buff.capacity(), buff.position());
    }

    @Test
    public void testCopyDoesNotWriteBeyondStringText() throws Exception {
        final CharBuffer buff = CharBuffer.wrap(new char[5]);
        assertEquals("initial buffer position", 0, buff.position());
        final StringBuilder text = createText(2);
        final int length = TextEncoderHelper.copy(text, 0, buff);
        assertEquals("full copy", text.length(), length);
        for (int i = 0; i < length; i++) {
            assertEquals("char at " + i, text.charAt(i), buff.get(i));
        }
        assertEquals("resulting buffer position", text.length(), buff.position());
        for (int i = length; i < buff.capacity(); i++) {
            assertEquals("unset char at " + i, 0, buff.get(i));
        }
    }

    @Test
    public void testCopyStartsAtBufferPosition() throws Exception {
        final CharBuffer buff = CharBuffer.wrap(new char[10]);
        final int START_POSITION = 5;
        buff.position(START_POSITION); // set start position
        final StringBuilder text = createText(15);
        final int length = TextEncoderHelper.copy(text, 0, buff);
        assertEquals("partial copy", buff.capacity() - START_POSITION, length);
        for (int i = 0; i < length; i++) {
            assertEquals("char at " + i, text.charAt(i), buff.get(START_POSITION + i));
        }
        assertEquals("buffer position at end", buff.capacity(), buff.position());
    }

    @Test
    public void testEncode_ALotWithoutErrors() throws Exception {
        final StringBuilderEncoder helper = new StringBuilderEncoder(Charset.defaultCharset());
        final StringBuilder text = new StringBuilder("2016-04-13 21:07:47,487 DEBUG [org.apache.logging.log4j.perf.jmh.FileAppenderBenchmark.log4j2ParameterizedString-jmh-worker-1] FileAppenderBenchmark  - This is a debug [2383178] message\r\n");
        final int DESTINATION_SIZE = 1024 * 1024;
        final SpyByteBufferDestination destination = new SpyByteBufferDestination(256 * 1024, DESTINATION_SIZE);

        final int max = DESTINATION_SIZE / text.length();
        for (int i = 0; i < max; i++) {
            helper.encode(text, destination);
        }
        // no error
    }

    private StringBuilder createText(final int length) {
        final StringBuilder result = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            result.append((char) (' ' + i)); // space=0x20
        }
        return result;
    }
}