package com.github.davidmoten.rtree;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.charset.Charset;

import com.esotericsoftware.kryo.Kryo;
import com.github.davidmoten.rtree.fbs.SerializerFlatBuffers;
import com.github.davidmoten.rtree.geometry.Geometry;
import com.github.davidmoten.rtree.kryo.SerializerKryo;
import com.github.davidmoten.rx.Functions;

import rx.functions.Func0;
import rx.functions.Func1;

public final class Serializers {

    private Serializers() {
        // prevent instantiation
    }

    public static class SerializerBuilder {

        private Method method = Method.FLATBUFFERS;

        private SerializerBuilder() {

        }

        public <T> SerializerTypedBuilder<T> serializer(Func1<? super T, byte[]> serializer) {
            return new SerializerTypedBuilder<T>(serializer, null, method);
        }

        public <T> SerializerTypedBuilder<T> deserializer(Func1<byte[], ? extends T> deserializer) {
            return new SerializerTypedBuilder<T>(null, deserializer, method);
        }

        public <S extends Geometry> Serializer<String, S> string(Charset charset) {
            Func1<String, byte[]> serializer = createStringSerializer(charset);
            Func1<byte[], String> deserializer = createStringDeserializer(charset);
            return new SerializerTypedBuilder<String>(serializer, deserializer, method).create();
        }

        @SuppressWarnings("unchecked")
        public <T extends Serializable, S extends Geometry> Serializer<T, S> javaIo() {
            Func1<T, byte[]> serializer = (Func1<T, byte[]>) javaIoSerializer();
            Func1<byte[], T> deserializer = (Func1<byte[], T>) javaIoDeserializer();
            return new SerializerTypedBuilder<T>(serializer, deserializer, method).create();
        }

        public <S extends Geometry> Serializer<String, S> utf8() {
            return string(Charset.forName("UTF-8"));
        }

        public <S extends Geometry> Serializer<byte[], S> bytes() {
            Func1<byte[], byte[]> serializer = Functions.identity();
            Func1<byte[], byte[]> deserializer = Functions.identity();
            return new SerializerTypedBuilder<byte[]>(serializer, deserializer, method).create();
        }

        public SerializerBuilder method(Method method) {
            this.method = method;
            return this;
        }

    }

    public static final class SerializerTypedBuilder<T> {

        private Func1<? super T, byte[]> serializer;
        private Func1<byte[], ? extends T> deserializer;
        private Method method;
        private Func0<Kryo> kryoFactory = new Func0<Kryo>() {
            @Override
            public Kryo call() {
                return new Kryo();
            }
        };

        private SerializerTypedBuilder(Func1<? super T, byte[]> serializer,
                Func1<byte[], ? extends T> deserializer, Method method) {
            this.serializer = serializer;
            this.deserializer = deserializer;
            this.method = method;
        }

        public SerializerTypedBuilder<T> serializer(Func1<? super T, byte[]> serializer) {
            this.serializer = serializer;
            return this;
        }

        public SerializerTypedBuilder<T> deserializer(Func1<byte[], ? extends T> deserializer) {
            this.deserializer = deserializer;
            return this;
        }

        public SerializerTypedBuilder<T> method(Method method) {
            this.method = method;
            return this;
        }

        public SerializerTypedBuilder<T> kryo(Func0<Kryo> kryoFactory) {
            this.method = Method.KRYO;
            this.kryoFactory = kryoFactory;
            return this;
        }

        @SuppressWarnings("unchecked")
        public <S extends Geometry> Serializer<T, S> create() {
            if (method == Method.FLATBUFFERS) {
                if (serializer == null) {
                    serializer = (Func1<T, byte[]>) javaIoSerializer();
                }
                if (deserializer == null) {
                    deserializer = (Func1<byte[], T>) javaIoDeserializer();
                }
                return SerializerFlatBuffers.create(serializer, deserializer);
            } else {
                return SerializerKryo.create(serializer, deserializer, kryoFactory);
            }
        }

    }

    public static <T, S extends Geometry> SerializerBuilder flatBuffers() {
        return new SerializerBuilder().method(Method.FLATBUFFERS);
    }

    public enum Method {
        FLATBUFFERS, KRYO;
    }

    private static Func1<String, byte[]> createStringSerializer(final Charset charset) {
        return new Func1<String, byte[]>() {
            @Override
            public byte[] call(String s) {
                return s.getBytes(charset);
            }
        };
    }

    private static <T> Func1<byte[], String> createStringDeserializer(final Charset charset) {
        return new Func1<byte[], String>() {
            @Override
            public String call(byte[] bytes) {
                return new String(bytes, charset);
            }
        };
    }

    private static Func1<Serializable, byte[]> javaIoSerializer() {
        return new Func1<Serializable, byte[]>() {
            @Override
            public byte[] call(Serializable o) {
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                ObjectOutputStream oos = null;
                try {
                    oos = new ObjectOutputStream(bytes);
                    oos.writeObject(o);
                    oos.close();
                    return bytes.toByteArray();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } finally {
                    try {
                        if (oos != null)
                            oos.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
        };
    }

    private static Func1<byte[], Serializable> javaIoDeserializer() {
        return new Func1<byte[], Serializable>() {
            @Override
            public Serializable call(byte[] bytes) {
                ByteArrayInputStream is = new ByteArrayInputStream(bytes);
                ObjectInputStream ois = null;
                try {
                    ois = new ObjectInputStream(is);
                    return (Serializable) ois.readObject();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                } finally {
                    if (ois != null)
                        try {
                            ois.close();
                        } catch (IOException e) {
                            // ignore
                        }
                }
            }
        };
    }

}

@@ -301,24 +301,78 @@
 .kryo.Kryo;%0A
+import com.github.davidmoten.guavamini.Preconditions;%0A
 import com.g
@@ -3654,32 +3654,212 @@
 ethod method) %7B%0A
+            // TODO remove this check when kryo ready%0A            Preconditions.checkArgument(method != Method.KRYO,%0A                    %22kryo serialization not implemented yet%22);%0A
             this
@@ -3912,38 +3912,73 @@
      %7D%0A%0A        
-public
+// TODO enable when ready%0A        private
  SerializerTyped

disable kryo serialization till ready