package org.zstack.rest;

import org.apache.commons.lang.StringUtils;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.GsonTransient;
import org.zstack.header.message.NoJsonSchema;
import org.zstack.utils.FieldUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Created by xing5 on 2016/12/12.
 */
public class JsonSchemaBuilder {
    Object object;

    private LinkedHashMap<String, String> schema = new LinkedHashMap<>();

    public JsonSchemaBuilder(Object object) {
        this.object = object;
    }

    private boolean isSkip(Field f) {
        return f.isAnnotationPresent(NoJsonSchema.class) || Modifier.isStatic(f.getModifiers())
                || f.isAnnotationPresent(GsonTransient.class);
    }

    private void build(Object o, Stack<String> paths) throws IllegalAccessException {
        List<Field> fields = FieldUtils.getAllFields(o.getClass());

        for (Field f : fields) {
            if (isSkip(f)) {
                continue;
            }

            f.setAccessible(true);
            Object value = f.get(o);
            if (value == null) {
                // null value
                continue;
            }

            if (value.getClass().getCanonicalName().startsWith("java.")) {
                // for JRE classes, only deal with Collection and Map
                if (value instanceof Collection) {
                    Collection c = (Collection) value;

                    Class gtype = FieldUtils.getGenericType(f);

                    if (gtype != null && !gtype.getName().startsWith("java.")) {
                        int i = 0;
                        for (Object co : c) {
                            paths.push(String.format("%s[%s]", f.getName(), i++));
                            build(co, paths);
                            paths.pop();
                        }
                    }

                } else if (value instanceof Map) {
                    Class gtype = FieldUtils.getGenericType(f);

                    if (gtype != null && !gtype.getName().startsWith("java.")) {
                        for (Object me : ((Map) value).entrySet()) {
                            Map.Entry e = (Map.Entry) me;
                            paths.push(String.format("%s.%s", f.getName(), e.getKey().toString()));
                            build(e.getValue(), paths);
                            paths.pop();
                        }
                    }
                }

                // don't record standard JRE classes
                continue;
            }


            paths.push(f.getName());
            build(value, paths);
            paths.pop();
        }

        if (!paths.isEmpty()) {
            schema.put(StringUtils.join(paths, "."), o.getClass().getName());
        }
    }

    // support Map and org.zstack.* objects
    public Map<String, String> build() {
        try {
            if (!object.getClass().getName().startsWith("org.zstack") && !(object instanceof Map)) {
                throw new CloudRuntimeException(String.format("only a org.zstack.* object can be built schema, %s is not", object.getClass()));
            }

            if (object instanceof Map) {
                Map m = (Map) object;
                for (Object o : m.entrySet()) {
                    Map.Entry e = (Map.Entry) o;
                    if (e.getValue() == null) {
                        continue;
                    }

                    if (e.getValue().getClass().getName().startsWith("java.") &&
                            !Collection.class.isAssignableFrom(e.getValue().getClass())) {
                        continue;
                    }

                    if (Collection.class.isAssignableFrom(e.getValue().getClass())) {
                        Collection c = (Collection) e.getValue();
                        Iterator it = c.iterator();
                        int i = 0;
                        while (it.hasNext()) {
                            build(it.next(), new Stack<String>() {
                                {
                                    add(String.format("%s[%s]", e.getKey(), i));
                                }
                            });
                        }

                    } else {
                        build(e.getValue(), new Stack<String>() {
                            {
                                add(e.getKey().toString());
                            }
                        });
                    }
                }
            } else {
                build(object, new Stack<>());
            }

            return schema;
        } catch (IllegalAccessException e) {
            throw new CloudRuntimeException(e);
        }
    }
}

@@ -3872,60 +3872,8 @@
 ();%0A
-                        Iterator it = c.iterator();%0A
     
@@ -3931,27 +3931,26 @@
     
-while (it.hasNext()
+for (Object it : c
 ) %7B%0A
@@ -3981,80 +3981,43 @@
     
-build(it.next(), new Stack%3CString%3E() %7B%0A                                %7B
+Stack%3CString%3E path = new Stack%3C%3E();
 %0A   
@@ -4037,32 +4037,29 @@
             
-        
+path.
 add(String.f
@@ -4087,16 +4087,18 @@
 Key(), i
+++
 ));%0A    
@@ -4106,37 +4106,16 @@
         
-          
-         %7D%0A
         
@@ -4117,38 +4117,38 @@
             
-             %7D
+build(it, path
 );%0A         
@@ -4156,33 +4156,32 @@
               %7D%0A
-%0A
                 

fix rest schema for map.list