/*
 * Copyright 2016 higherfrequencytrading.com
 *
 * 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 net.openhft.chronicle.wire;

import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.bytes.BytesUtil;
import net.openhft.chronicle.core.io.IORuntimeException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.After;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static junit.framework.TestCase.assertNull;
import static org.junit.Assert.assertEquals;

/*
 * Created by peter.lawrey on 06/02/2016.
 */
public class JSONWireTest {
    @NotNull
    private JSONWire getWire() {
        return new JSONWire(Bytes.elasticByteBuffer());
    }

    @NotNull
    private JSONWire getWire(@NotNull String json) {
        return new JSONWire(Bytes.from(json));
    }

    @Test
    public void testListFormatting() {
        @NotNull Wire wire = getWire();

        @NotNull List<Item> items = new ArrayList<>();
        items.add(new Item("item1", 1235666L, 1.1231231));
        items.add(new Item("item2", 2235666L, 1.0987987));
        items.add(new Item("item3", 3235666L, 1.12312));
        items.add(new Item("item4", 4235666L, 1.51231));

        @NotNull WireOut out = wire.writeEventName(() -> "myEvent").list(items, Item.class);

        assertEquals("\"myEvent\":[{\"name\":\"item1\",\"number1\":1235666,\"number2\":1.1231231},\n" +
                "{\"name\":\"item2\",\"number1\":2235666,\"number2\":1.0987987},\n" +
                "{\"name\":\"item3\",\"number1\":3235666,\"number2\":1.12312},\n" +
                "{\"name\":\"item4\",\"number1\":4235666,\"number2\":1.51231}]", out.toString());

        wire.bytes().release();
    }

    @Test
    public void testOpenBracket() {
        @NotNull StringBuilder sb = new StringBuilder();

        @NotNull Wire wire1 = getWire("\"echo\":\"Hello\"\n\"echo2\":\"Hello2\"\n");
        @Nullable String text1 = wire1.readEventName(sb).text();
        assertEquals("echo", sb.toString());
        assertEquals("Hello", text1);
        @Nullable String text2 = wire1.readEventName(sb).text();
        assertEquals("echo2", sb.toString());
        assertEquals("Hello2", text2);

        @NotNull Wire wire2 = getWire("{ \"echoB\":\"HelloB\" }\n{ \"echo2B\":\"Hello2B\" }\n");
        @Nullable String textB = wire2.readEventName(sb).text();
        assertEquals("echoB", sb.toString());
        assertEquals("HelloB", textB);
        @Nullable String textB2 = wire2.readEventName(sb).text();
        assertEquals("echo2B", sb.toString());
        assertEquals("Hello2B", textB2);
    }

    @Test
    public void testNoSpaces() {
        @NotNull Wire wire = getWire("\"echo\":\"\"");
        @NotNull VanillaWireParser parser = new VanillaWireParser(soutWireParselet(), VanillaWireParser.SKIP_READABLE_BYTES);
        parser.parseOne(wire);
        assertEquals("", wire.bytes().toString());
    }

    @NotNull
    private WireParselet soutWireParselet() {
        return (s, v) -> System.out.println(s + " - " + v.text());
    }

    @Test
    public void testMarshallableWithTwoLists() {
        @NotNull Wire wire = getWire();

        @NotNull TwoLists lists1 = new TwoLists(null, 5, 5);
        wire.writeEventName("two_lists").marshallable(lists1);

        @NotNull TwoLists lists2 = new TwoLists();

        @NotNull final StringBuilder sb = new StringBuilder();
        @NotNull ValueIn valueIn = wire.readEventName(sb);

        valueIn.marshallable(lists2);

        // fails due to a trailing space if we don't call toString.
        // assertEquals(lists1, lists2);
//        assertEquals(lists1.toString(), lists2.toString());
        try {
            assertEquals("!net.openhft.chronicle.wire.JSONWireTest$TwoLists {\n" +
                    "  name: !!null \"\",\n" +
                    "  list1: [\n" +
                    "    { name: !!null \"\", number1: 0, number2: 0.0 },\n" +
                    "    { name: !!null \"\", number1: 1, number2: 10.0 },\n" +
                    "    { name: !!null \"\", number1: 2, number2: 20.0 },\n" +
                    "    { name: !!null \"\", number1: 3, number2: 30.0 },\n" +
                    "    { name: !!null \"\", number1: 4, number2: 40.0 }\n" +
                    "  ],\n" +
                    "  list2: [\n" +
                    "    { name: !!null \"\", number1: 0, number2: 0.0 },\n" +
                    "    { name: !!null \"\", number1: 1, number2: 10.0 },\n" +
                    "    { name: !!null \"\", number1: 2, number2: 20.0 },\n" +
                    "    { name: !!null \"\", number1: 3, number2: 30.0 },\n" +
                    "    { name: !!null \"\", number1: 4, number2: 40.0 }\n" +
                    "  ]\n" +
                    "}\n", lists1.toString());
        } finally {
            wire.bytes().release();
        }
    }

    @Test
    public void testNullString() {
        @NotNull Wire w = getWire();

        @NotNull Item item1 = new Item(null, 1, 2);
        w.write("item").marshallable(item1);

        @NotNull Item item2 = new Item();
        w.read(() -> "item").marshallable(item2);

        assertNull(item2.name);
        assertEquals(item1, item2);
        assertEquals(item1.toString(), item2.toString());

        w.bytes().release();
    }

    @Test
    public void testBytes() {
        @NotNull Wire w = getWire();

        Bytes<byte[]> bs = Bytes.from("blablabla");
        w.write("a").int64(123);
        w.write("somebytes").text(bs);

        Wire w2 = WireType.JSON.apply(w.bytes());
        assertEquals("\"a\":123,\"somebytes\":\"blablabla\"", w2.toString());

        Bytes<java.nio.ByteBuffer> bb = Bytes.elasticByteBuffer();
        assertEquals(123, w2.read("a").int64());

        w2.read("somebytes").text(bb);
        assertEquals(bs, bb);

        w2.bytes().release();
        bb.release();
    }

    @After
    public void checkRegisteredBytes() {
        BytesUtil.checkRegisteredBytes();
    }

    private static class Item extends AbstractMarshallable {
        String name;
        long number1;
        double number2;

        Item() {
        }

        Item(String name, long number1, double number2) {
            this.name = name;
            this.number1 = number1;
            this.number2 = number2;
        }
    }

    private static class TwoLists implements Marshallable {
        @Nullable
        String name;
        List<Item> list1;
        List<Item> list2;

        TwoLists() {
        }

        TwoLists(String name, long number1, double number2) {
            this.name = name;
            this.list1 = new ArrayList<>();
            for (int i = 0; i < number1; i++) {
                list1.add(new Item(name, i, i * 10));
            }
            this.list2 = new ArrayList<>();
            for (int i = 0; i < number2; i++) {
                list2.add(new Item(name, i, i * 10));
            }
        }

        @Override
        public String toString() {
            return Marshallable.$toString(this);
        }

        @Override
        public void readMarshallable(@NotNull WireIn wire) throws IORuntimeException {
            name = wire.read(() -> "name").text();
            list1 = wire.read(() -> "list1").list(Item.class);
            list2 = wire.read(() -> "list2").list(Item.class);
        }

        @Override
        public void writeMarshallable(@NotNull WireOut wire) {
            wire.write(() -> "name").text(name);
            wire.write(() -> "list1").list(list1, Item.class);
            wire.write(() -> "list2").list(list2, Item.class);
        }
    }
}
