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.Context;
import com.github.davidmoten.rtree.Entries;
import com.github.davidmoten.rtree.Entry;
import com.github.davidmoten.rtree.Node;
import com.github.davidmoten.rtree.NonLeaf;
import com.github.davidmoten.rtree.fbs.generated.Box_;
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.geometry.Geometries;
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.LeafDefault;
import com.github.davidmoten.rtree.internal.NodeAndEntries;

import rx.Subscriber;
import rx.functions.Func1;

final class NonLeafFlatBuffersStatic<T, S extends Geometry> implements NonLeaf<T, S> {

    private final Node_ node;
    private final Context<T, S> context;
    private final Func1<byte[], T> deserializer;

    NonLeafFlatBuffersStatic(Node_ node, Context<T, S> context, Func1<byte[], T> deserializer) {
        Preconditions.checkArgument(node.childrenLength() > 0 || node.entriesLength() > 0);
        this.node = node;
        this.context = context;
        this.deserializer = deserializer;
    }

    @Override
    public List<Node<T, S>> add(Entry<? extends T, ? extends S> entry) {
        throw new UnsupportedOperationException();
    }

    @Override
    public NodeAndEntries<T, S> delete(Entry<? extends T, ? extends S> entry, boolean all) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void search(Func1<? super Geometry, Boolean> criterion,
            Subscriber<? super Entry<T, S>> subscriber) {
        search(node, criterion, subscriber, deserializer);
    }

    @SuppressWarnings("unchecked")
    private static <T, S extends Geometry> void search(Node_ node,
            Func1<? super Geometry, Boolean> criterion, Subscriber<? super Entry<T, S>> subscriber,
            Func1<byte[], T> deserializer) {
        {
            Box_ b = node.mbb();
            if (!criterion.call(Geometries.rectangle(b.minX(), b.minY(), b.maxX(), b.maxY())))
                return;
        }
        int numChildren = node.childrenLength();
        if (numChildren > 0) {
            for (int i = 0; i < numChildren; i++) {
                if (subscriber.isUnsubscribed())
                    return;
                search(node.children(i), criterion, subscriber, deserializer);
            }
        } else {
            int numEntries = node.entriesLength();
            // reduce allocations by reusing objects
            Entry_ entry = new Entry_();
            Geometry_ geometry = new Geometry_();
            // check all entries
            for (int i = 0; i < numEntries; i++) {
                if (subscriber.isUnsubscribed())
                    return;
                node.entries(entry, i);
                entry.geometry(geometry);
                final Geometry g = toGeometry(geometry);
                if (criterion.call(g)) {
                    ByteBuffer bb = entry.objectAsByteBuffer();
                    byte[] bytes = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
                    T t = deserializer.call(bytes);
                    subscriber.onNext(Entries.entry(t, (S) g));
                }
            }
        }

    }

    private List<Node<T, S>> createChildren() {
        List<Node<T, S>> children = new ArrayList<Node<T, S>>(node.childrenLength());
        for (int i = 0; i < node.childrenLength(); i++) {
            Node_ child = node.children(i);
            if (child.childrenLength() > 0)
                children.add(new NonLeafFlatBuffersStatic<T, S>(child, context, deserializer));
            else
                children.add(new LeafDefault<T, S>(
                        FlatBuffersHelper.<T, S> createEntries(child, deserializer), context));
        }
        return children;
    }

    private Entry<T, S> createEntry(final int index) {
        return new Entry<T, S>() {

            @Override
            public T value() {
                ByteBuffer bb = node.entries(index).objectAsByteBuffer();
                byte[] bytes = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());
                return deserializer.call(bytes);
            }

            @Override
            public S geometry() {
                Geometry_ g = node.entries(index).geometry();
                return toGeometry(g);
            }
        };
    }

    @Override
    public int count() {
        if (node.childrenLength() > 0)
            return node.childrenLength();
        else
            return node.entriesLength();
    }

    @Override
    public Context<T, S> context() {
        return context;
    }

    @Override
    public Geometry geometry() {
        return createBox(node.mbb());
    }

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

    @Override
    public List<? extends Node<T, S>> children() {
        return createChildren();
    }

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

    @Override
    public String toString() {
        return "Node [" + (node.childrenLength() > 0 ? "NonLeaf" : "Leaf") + ","
                + createBox(node.mbb()).toString() + "]";
    }

}

@@ -2565,32 +2565,116 @@
 ildrenLength();%0A
+        // reduce allocations by reusing objects%0A        Node_ child = new Node_();%0A
         if (numC
@@ -2684,24 +2684,24 @@
 dren %3E 0) %7B%0A
-
             
@@ -2829,31 +2829,24 @@
             
-search(
 node.childre
@@ -2847,18 +2847,55 @@
 hildren(
-i)
+child, i);%0A                search(child
 , criter
@@ -3305,32 +3305,61 @@
         return;%0A
+                // set entry%0A
                 
@@ -3378,24 +3378,56 @@
 (entry, i);%0A
+                // set geometry%0A
             
@@ -4465,568 +4465,8 @@
  %7D%0A%0A
-    private Entry%3CT, S%3E createEntry(final int index) %7B%0A        return new Entry%3CT, S%3E() %7B%0A%0A            @Override%0A            public T value() %7B%0A                ByteBuffer bb = node.entries(index).objectAsByteBuffer();%0A                byte%5B%5D bytes = Arrays.copyOfRange(bb.array(), bb.position(), bb.limit());%0A                return deserializer.call(bytes);%0A            %7D%0A%0A            @Override%0A            public S geometry() %7B%0A                Geometry_ g = node.entries(index).geometry();%0A                return toGeometry(g);%0A            %7D%0A        %7D;%0A    %7D%0A%0A
     
@@ -5191,28 +5191,54 @@
         
-if (g.type()
+byte type = g.type();%0A        if (type
  == Geom
@@ -5314,24 +5314,20 @@
 lse if (
-g.
 type
-()
  == Geom

improve flatbuffers search performance