package com.github.davidmoten.rtree.fbs;

import static org.junit.Assert.assertEquals;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.junit.Test;

import com.github.davidmoten.rtree.Entry;
import com.github.davidmoten.rtree.GreekEarthquakes;
import com.github.davidmoten.rtree.InternalStructure;
import com.github.davidmoten.rtree.Precision;
import com.github.davidmoten.rtree.RTree;
import com.github.davidmoten.rtree.Serializer;
import com.github.davidmoten.rtree.geometry.Geometries;
import com.github.davidmoten.rtree.geometry.Point;

import rx.Observable;
import rx.functions.Func1;

public class SerializerFlatBuffersTest {

    private static final byte[] EMPTY = new byte[] {};

    @Test
    public void testSerializeRoundTripToFlatBuffersSingleArray() throws Exception {
        roundTrip(InternalStructure.SINGLE_ARRAY, false);
    }

    @Test
    public void testSerializeRoundTripToDefaultStructure() throws Exception {
        roundTrip(InternalStructure.DEFAULT, false);
    }

    @Test
    public void testSerializeRoundTripToFlatBuffersSingleArrayBackpressure() throws Exception {
        roundTrip(InternalStructure.SINGLE_ARRAY, true);
    }

    @Test
    public void testSerializeRoundTripToDefaultStructureBackpressure() throws Exception {
        roundTrip(InternalStructure.DEFAULT, true);
    }

    private void roundTrip(InternalStructure structure, boolean backpressure) throws Exception {
        RTree<Object, Point> tree = RTree.star().maxChildren(10).create();
        tree = tree.add(GreekEarthquakes.entries(Precision.SINGLE)).last().toBlocking().single();
        long t = System.currentTimeMillis();
        File file = new File("target/file");
        FileOutputStream os = new FileOutputStream(file);
        Serializer<Object, Point> fbSerializer = createSerializer();

        serialize(tree, t, file, os, fbSerializer);

        deserialize(structure, file, fbSerializer, backpressure);
    }

    private static Serializer<Object, Point> createSerializer() {
        Func1<Object, byte[]> serializer = new Func1<Object, byte[]>() {
            @Override
            public byte[] call(Object o) {
                return EMPTY;
            }
        };
        Func1<byte[], Object> deserializer = new Func1<byte[], Object>() {
            @Override
            public Object call(byte[] bytes) {
                return null;
            }
        };
        Serializer<Object, Point> fbSerializer = SerializerFlatBuffers.create(serializer,
                deserializer);
        return fbSerializer;
    }

    private static void serialize(RTree<Object, Point> tree, long t, File file, FileOutputStream os,
            Serializer<Object, Point> fbSerializer) throws IOException {
        fbSerializer.write(tree, os);
        os.close();
        System.out.println("written in " + (System.currentTimeMillis() - t) + "ms, " + "file size="
                + file.length() / 1000000.0 + "MB");
        System.out.println("bytes per entry=" + file.length() / tree.size());
    }

    private static void deserialize(InternalStructure structure, File file,
            Serializer<Object, Point> fbSerializer, boolean backpressure) throws Exception {
        long t = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        t = System.currentTimeMillis();
        RTree<Object, Point> tr = fbSerializer.read(is, file.length(), structure);
        System.out.println(tr.root().get());

        System.out.println("read in " + (System.currentTimeMillis() - t) + "ms");
        Observable<Entry<Object, Point>> o = tr.search(Geometries.rectangle(40, 27.0, 40.5, 27.5));
        if (backpressure)
            o = o.take(10000);
        int found = o.count().toBlocking().single();
        System.out.println("found=" + found);
        assertEquals(22, found);
        System.out.println(tr.size());
    }

    @Test(expected = RuntimeException.class)
    public void testInputStreamNotAsLongAsExpected() throws IOException {
        SerializerFlatBuffers.readFully(new ByteArrayInputStream(new byte[10]), 12);
    }

    @Test
    public void testInputStreamReturnsArrayInSmallChunks() throws IOException {
        InputStream is = new InputStream() {

            int i = 0;

            @Override
            public int read() throws IOException {
                i += 1;
                if (i == 1) {
                    return 1;
                } else if (i == 2) {
                    throw new IOException();
                } else {
                    return 1;
                }
            }

        };
        byte[] b = SerializerFlatBuffers.readFully(is, 2);
        assertEquals(2, b.length);
        assertEquals(1, b[0]);
        assertEquals(1, b[0]);
    }

    public static void main(String[] args) throws Exception {
        // use this with jvisualvm and heap dump, find biggest objects to check
        // memory usage of rtree
        // deserialize(InternalStructure.FLATBUFFERS_SINGLE_ARRAY, new
        // File("target/file"),
        // createSerializer());
    }

}
