package org.yogurt.protobufftools;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

public class ReflectiveEncoder implements IMessageEncoder {
    public byte[] encode(Object o) throws Exception {
        Object builder = populateBuilder(o);
        Object built = builder.getClass().getDeclaredMethod("build").invoke(builder);
        byte[] payload = (byte[]) built.getClass().getMethod("toByteArray").invoke(built);
        return new MessageWrapper().wrap(o.getClass().getCanonicalName(), payload);
    }

    private Object populateBuilder(Object o) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Object builder = getProtoBufferClass(o).getMethod("newBuilder").invoke(null);

        for (Field field : getBufferFields(o)) {
            ProtoBufferField annotation = field.getAnnotation(ProtoBufferField.class);

            Method getter = getGetter(o, field.getName());

            if (fieldShouldBeRecursed(field)) {
                Class<?> type = field.getType().getAnnotation(ProtoBufferData.class).protoBuffer();
                Object newBuilder = type.getMethod("newBuilder").invoke(null);
                Method setter = getSetter(builder, annotation.fieldName(), newBuilder.getClass());

                setter.invoke(builder, populateBuilder(getter.invoke(o)));
            } else {
                Method setter = getSetter(builder, annotation.fieldName(), field.getType());
                setter.invoke(builder, getter.invoke(o));
            }
        }

        return builder;
    }

    private boolean fieldShouldBeRecursed(Field field) {
        return field.getType().getAnnotation(ProtoBufferData.class) != null;
    }

    public Object decode(byte[] bytes) throws Exception {
        Message message = new MessageWrapper().unwrap(bytes);
        Object o = Class.forName(message.getMessageType()).newInstance();

        Class<?> bufferClass = getProtoBufferClass(o);
        Object personProto = bufferClass.getMethod("parseFrom", byte[].class).invoke(null, message.getPayload());
        return extractFromBuffer(o, personProto);
    }

    private Object extractFromBuffer(Object o, Object buffer) throws InstantiationException, IllegalAccessException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException {
        for (Field field : getBufferFields(o)) {
            Method getter = getGetter(buffer, field.getAnnotation(ProtoBufferField.class).fieldName());
            Method setter = getSetter(o, field.getName(), field.getType());

            if (fieldShouldBeRecursed(field)) {
                setter.invoke(o, extractFromBuffer(field.getType().newInstance(), getter.invoke(buffer)));
            } else {
                setter.invoke(o, getter.invoke(buffer));
            }

        }
        return o;
    }

    private Class<?> getProtoBufferClass(Object o) {
        return o.getClass().getAnnotation(ProtoBufferData.class).protoBuffer();
    }

    private Set<Field> getBufferFields(Object o) {
        Set<Field> fields = new HashSet<>();
        for (Field field : o.getClass().getDeclaredFields()) {
            for (Annotation declaredAnnotation : field.getDeclaredAnnotations()) {
                if (declaredAnnotation.annotationType().equals(ProtoBufferField.class)) {
                    fields.add(field);
                }
            }
        }
        return fields;
    }

    private Method getSetter(Object o, String field, Class<?> type) throws NoSuchMethodException {
        return o.getClass().getMethod("set" + capitalize(field), type);
    }

    private Method getGetter(Object o, String field) throws NoSuchMethodException {
        return o.getClass().getMethod("get" + capitalize(field), (Class<?>[]) null);
    }

    private String capitalize(String line) {
        return Character.toUpperCase(line.charAt(0)) + line.substring(1);
    }

}

@@ -654,32 +654,443 @@
 ayload);%0A    %7D%0A%0A
+    public Object decode(byte%5B%5D bytes) throws Exception %7B%0A        Message message = new MessageWrapper().unwrap(bytes);%0A        Object o = Class.forName(message.getMessageType()).newInstance();%0A%0A        Class%3C?%3E bufferClass = getProtoBufferClass(o);%0A        Object buffer = bufferClass.getMethod(%22parseFrom%22, byte%5B%5D.class).invoke(null, message.getPayload());%0A        return extractFromBuffer(o, buffer);%0A    %7D%0A%0A
     private Obje
@@ -1192,32 +1192,32 @@
 thodException %7B%0A
-
         Object b
@@ -1423,17 +1423,16 @@
 class);%0A
-%0A
         
@@ -2117,570 +2117,8 @@
  %7D%0A%0A
-    private boolean fieldShouldBeRecursed(Field field) %7B%0A        return field.getType().getAnnotation(ProtoBufferData.class) != null;%0A    %7D%0A%0A    public Object decode(byte%5B%5D bytes) throws Exception %7B%0A        Message message = new MessageWrapper().unwrap(bytes);%0A        Object o = Class.forName(message.getMessageType()).newInstance();%0A%0A        Class%3C?%3E bufferClass = getProtoBufferClass(o);%0A        Object personProto = bufferClass.getMethod(%22parseFrom%22, byte%5B%5D.class).invoke(null, message.getPayload());%0A        return extractFromBuffer(o, personProto);%0A    %7D%0A%0A
     
@@ -2822,148 +2822,8 @@
  %7D%0A%0A
-    private Class%3C?%3E getProtoBufferClass(Object o) %7B%0A        return o.getClass().getAnnotation(ProtoBufferData.class).protoBuffer();%0A    %7D%0A%0A
     
@@ -3223,32 +3223,32 @@
     %7D%0A        %7D%0A
-
         return f
@@ -3253,32 +3253,313 @@
  fields;%0A    %7D%0A%0A
+    private boolean fieldShouldBeRecursed(Field field) %7B%0A        return field.getType().getAnnotation(ProtoBufferData.class) != null;%0A    %7D%0A%0A    private Class%3C?%3E getProtoBufferClass(Object o) %7B%0A        return o.getClass().getAnnotation(ProtoBufferData.class).protoBuffer();%0A    %7D%0A%0A
     private Meth

reorder methods