package com.github.davidmoten.rtree.fbs;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.github.davidmoten.guavamini.Preconditions;
import com.github.davidmoten.rtree.Entries;
import com.github.davidmoten.rtree.Entry;
import com.github.davidmoten.rtree.fbs.generated.Box_;
import com.github.davidmoten.rtree.fbs.generated.Circle_;
import com.github.davidmoten.rtree.fbs.generated.Entry_;
import com.github.davidmoten.rtree.fbs.generated.GeometryType_;
import com.github.davidmoten.rtree.fbs.generated.Geometry_;
import com.github.davidmoten.rtree.fbs.generated.Node_;
import com.github.davidmoten.rtree.fbs.generated.Point_;
import com.github.davidmoten.rtree.geometry.Circle;
import com.github.davidmoten.rtree.geometry.Geometry;
import com.github.davidmoten.rtree.geometry.Point;
import com.github.davidmoten.rtree.geometry.Rectangle;
import com.github.davidmoten.rtree.internal.Util;
import com.google.flatbuffers.FlatBufferBuilder;

import rx.functions.Func1;

final class FlatBuffersHelper {

    private FlatBuffersHelper() {
        // prevent instantiation
    }

    static <T, S extends Geometry> int addEntries(List<Entry<T, S>> entries,
            FlatBufferBuilder builder, Func1<T, byte[]> serializer) {
        int[] entries2 = new int[entries.size()];
        for (int i = 0; i < entries.size(); i++) {
            Geometry g = entries.get(i).geometry();
            final int geom;
            final byte geomType;
            if (g instanceof Rectangle) {
                Rectangle b = (Rectangle) g;
                geom = Box_.createBox_(builder, b.x1(), b.y1(), b.x2(), b.y2());
                geomType = GeometryType_.Box;
            } else if (g instanceof Point) {
                Point p = (Point) g;
                geom = Point_.createPoint_(builder, p.x(), p.y());
                geomType = GeometryType_.Point;
            } else if (g instanceof Circle) {
                Circle c = (Circle) g;
                geom = Circle_.createCircle_(builder, c.x(), c.y(), c.radius());
                geomType = GeometryType_.Circle;
            } else
                throw new RuntimeException("unexpected");

            Geometry_.startGeometry_(builder);
            if (geomType == GeometryType_.Box) {
                Geometry_.addBox(builder, geom);
            } else if (geomType == GeometryType_.Point) {
                Geometry_.addPoint(builder, geom);
            } else if (geomType == GeometryType_.Circle) {
                Geometry_.addCircle(builder, geom);
            } else
                throw new RuntimeException("unexpected");

            Geometry_.addType(builder, geomType);
            int geo = Geometry_.endGeometry_(builder);
            int obj = Entry_.createObjectVector(builder, serializer.call(entries.get(i).value()));
            entries2[i] = Entry_.createEntry_(builder, geo, obj);
        }

        int ents = Node_.createEntriesVector(builder, entries2);
        Rectangle mbb = Util.mbr(entries);
        int b = Box_.createBox_(builder, mbb.x1(), mbb.y1(), mbb.x2(), mbb.y2());
        Node_.startNode_(builder);
        Node_.addMbb(builder, b);
        Node_.addEntries(builder, ents);
        return Node_.endNode_(builder);

    }

    static <T, S extends Geometry> List<Entry<T, S>> createEntries(Node_ node,
            Func1<byte[], T> deserializer) {
        List<Entry<T, S>> entries = new ArrayList<Entry<T, S>>();
        int numEntries = node.entriesLength();
        Preconditions.checkArgument(numEntries > 0);
        Entry_ entry = new Entry_();
        Geometry_ geom = new Geometry_();
        for (int i = 0; i < numEntries; i++) {
            Entry<T, S> ent = createEntry(node, deserializer, entry, geom, i);
            entries.add(ent);
        }
        return entries;
    }

    @SuppressWarnings("unchecked")
    private static <T, S extends Geometry> Entry<T, S> createEntry(Node_ node,
            Func1<byte[], T> deserializer, Entry_ entry, Geometry_ geom, int i) {
        node.entries(entry, i);
        entry.geometry(geom);
        final Geometry g = toGeometry(geom);
        return Entries.entry(parseObject(deserializer, entry), (S) g);
    }

    static <T, S extends Geometry> Entry<T, S> createEntry(Node_ node,
            Func1<byte[], T> deserializer, int i) {
        return createEntry(node, deserializer, new Entry_(), new Geometry_(), i);
    }

    static <T> T parseObject(Func1<byte[], T> deserializer, Entry_ entry) {
        ByteBuffer bb = entry.objectAsByteBuffer();
        byte[] bytes = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
        T t = deserializer.call(bytes);
        return t;
    }

    @SuppressWarnings("unchecked")
    static <S extends Geometry> S toGeometry(Geometry_ g) {
        final Geometry result;
        byte type = g.type();
        if (type == GeometryType_.Box) {
            result = createBox(g.box());
        } else if (type == GeometryType_.Point) {
            result = Point.create(g.point().x(), g.point().y());
        } else if (type == GeometryType_.Circle) {
            Circle_ c = g.circle();
            result = Circle.create(c.x(), c.y(), c.radius());
        } else
            throw new UnsupportedOperationException();
        return (S) result;
    }

    static Geometry createBox(Box_ b) {
        return Rectangle.create(b.minX(), b.minY(), b.maxX(), b.maxY());
    }

}
@@ -570,24 +570,80 @@
 .Geometry_;%0A
+import com.github.davidmoten.rtree.fbs.generated.Line_;%0A
 import com.g
@@ -845,24 +845,74 @@
 y.Geometry;%0A
+import com.github.davidmoten.rtree.geometry.Line;%0A
 import com.g
@@ -2227,16 +2227,225 @@
 Circle;%0A
+            %7D else if (g instanceof Line) %7B%0A                Line c = (Line) g;%0A                geom = Line_.createLine_(builder, c.x1(), c.y1(), c.x2(), c.y2());%0A                geomType = GeometryType_.Line;%0A
         
@@ -2871,32 +2871,139 @@
 builder, geom);%0A
+            %7D else if (geomType == GeometryType_.Line) %7B%0A                Geometry_.addLine(builder, geom);%0A
             %7D el
@@ -5621,32 +5621,123 @@
 ), c.radius());%0A
+        %7D else if (type == GeometryType_.Line) %7B%0A            result = createBox(g.line());%0A
         %7D else%0A 

add flatbuffers support for Line class