//
//  ========================================================================
//  Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.junit.Before;
import org.junit.Test;

public class HttpWriterTest
{
    private HttpOutput _httpOut;
    private ByteBuffer _bytes;

    @Before
    public void init() throws Exception
    {
        _bytes = BufferUtil.allocate(2048);

        final ByteBufferPool bufferPool = new MappedByteBufferPool();
        HttpChannel<?> channel = new HttpChannel<ByteBuffer>(null,new HttpConfiguration(),null,null,new ByteBufferQueuedHttpInput())
        {
            @Override
            public ByteBufferPool getByteBufferPool()
            {
                return bufferPool;
            }
        };

        _httpOut = new HttpOutput(channel)
        {
            @Override
            public void write(byte[] b, int off, int len) throws IOException
            {
                BufferUtil.append(_bytes, b, off, len);
            }
        };
    }

    @Test
    public void testSimpleUTF8() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
        _writer.write("Now is the time");
        assertArrayEquals("Now is the time".getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
    }

    @Test
    public void testUTF8() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
        _writer.write("How now \uFF22rown cow");
        assertArrayEquals("How now \uFF22rown cow".getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
    }

    @Test
    public void testUTF16() throws Exception
    {
        HttpWriter _writer = new EncodingHttpWriter(_httpOut,StringUtil.__UTF16);
        _writer.write("How now \uFF22rown cow");
        assertArrayEquals("How now \uFF22rown cow".getBytes(StandardCharsets.UTF_16),BufferUtil.toArray(_bytes));
    }

    @Test
    public void testNotCESU8() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
        String data="xxx\uD801\uDC00xxx";
        _writer.write(data);
        assertEquals("787878F0909080787878",TypeUtil.toHexString(BufferUtil.toArray(_bytes)));
        assertArrayEquals(data.getBytes(StandardCharsets.UTF_8),BufferUtil.toArray(_bytes));
        assertEquals(3+4+3,_bytes.remaining());

        Utf8StringBuilder buf = new Utf8StringBuilder();
        buf.append(BufferUtil.toArray(_bytes),0,_bytes.remaining());
        assertEquals(data,buf.toString());
    }

    @Test
    public void testMultiByteOverflowUTF8() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);
        final String singleByteStr = "a";
        final String multiByteDuplicateStr = "\uFF22";
        int remainSize = 1;

        int multiByteStrByteLength = multiByteDuplicateStr.getBytes(StandardCharsets.UTF_8).length;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < HttpWriter.MAX_OUTPUT_CHARS - multiByteStrByteLength; i++) {
          sb.append(singleByteStr);
        }
        sb.append(multiByteDuplicateStr);
        for (int i = 0; i < remainSize; i++) {
          sb.append(singleByteStr);
        }
        char[] buf = new char[HttpWriter.MAX_OUTPUT_CHARS * 3];

        int length = HttpWriter.MAX_OUTPUT_CHARS - multiByteStrByteLength + remainSize + 1;
        sb.toString().getChars(0, length, buf, 0);

        _writer.write(buf, 0, length);

        assertEquals(sb.toString(),new String(BufferUtil.toArray(_bytes),StandardCharsets.UTF_8));
    }

    @Test
    public void testISO8859() throws Exception
    {
        HttpWriter _writer = new Iso88591HttpWriter(_httpOut);
        _writer.write("How now \uFF22rown cow");
        assertEquals("How now ?rown cow",new String(BufferUtil.toArray(_bytes),StandardCharsets.ISO_8859_1));
    }

    @Test
    public void testUTF16x2() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);

        String source = "\uD842\uDF9F";

        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
        _writer.write(source.toCharArray(),0,source.toCharArray().length);

        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos, StandardCharsets.UTF_8);
        osw.write(source.toCharArray(),0,source.toCharArray().length);
        osw.flush();

        myReportBytes(bytes);
        myReportBytes(baos.toByteArray());
        myReportBytes(BufferUtil.toArray(_bytes));

        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
    }

    @Test
    public void testMultiByteOverflowUTF16x2() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);

        final String singleByteStr = "a";
        int remainSize = 1;
        final String multiByteDuplicateStr = "\uD842\uDF9F";
        int adjustSize = -1;

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < HttpWriter.MAX_OUTPUT_CHARS + adjustSize; i++)
        {
            sb.append(singleByteStr);
        }
        sb.append(multiByteDuplicateStr);
        for (int i = 0; i < remainSize; i++)
        {
            sb.append(singleByteStr);
        }
        String source = sb.toString();

        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
        _writer.write(source.toCharArray(),0,source.toCharArray().length);

        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos, StandardCharsets.UTF_8);
        osw.write(source.toCharArray(),0,source.toCharArray().length);
        osw.flush();

        myReportBytes(bytes);
        myReportBytes(baos.toByteArray());
        myReportBytes(BufferUtil.toArray(_bytes));

        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
    }

    @Test
    public void testMultiByteOverflowUTF16x2_2() throws Exception
    {
        HttpWriter _writer = new Utf8HttpWriter(_httpOut);

        final String singleByteStr = "a";
        int remainSize = 1;
        final String multiByteDuplicateStr = "\uD842\uDF9F";
        int adjustSize = -2;

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < HttpWriter.MAX_OUTPUT_CHARS + adjustSize; i++)
        {
            sb.append(singleByteStr);
        }
        sb.append(multiByteDuplicateStr);
        for (int i = 0; i < remainSize; i++)
        {
            sb.append(singleByteStr);
        }
        String source = sb.toString();

        byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
        _writer.write(source.toCharArray(),0,source.toCharArray().length);

        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        java.io.OutputStreamWriter osw = new java.io.OutputStreamWriter(baos,StandardCharsets.UTF_8);
        osw.write(source.toCharArray(),0,source.toCharArray().length);
        osw.flush();

        myReportBytes(bytes);
        myReportBytes(baos.toByteArray());
        myReportBytes(BufferUtil.toArray(_bytes));

        assertArrayEquals(bytes,BufferUtil.toArray(_bytes));
        assertArrayEquals(baos.toByteArray(),BufferUtil.toArray(_bytes));
    }

    private void myReportBytes(byte[] bytes) throws Exception
    {
//        for (int i = 0; i < bytes.length; i++)
//        {
//            System.err.format("%s%x",(i == 0)?"[":(i % (HttpWriter.MAX_OUTPUT_CHARS) == 0)?"][":",",bytes[i]);
//        }
//        System.err.format("]->%s\n",new String(bytes,StringUtil.__UTF8));
    }

    private void assertArrayEquals(byte[] b1, byte[] b2)
    {
        String test=new String(b1)+"=="+new String(b2);
        assertEquals(test,b1.length,b2.length);
        for (int i=0;i<b1.length;i++)
            assertEquals(test,b1[i],b2[i]);
    }
}
