package com.github.aureliano.evtbridge.core.doc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import com.github.aureliano.evtbridge.annotation.doc.SchemaConfiguration;
import com.github.aureliano.evtbridge.annotation.doc.SchemaProperty;
import com.github.aureliano.evtbridge.common.helper.StringHelper;
import com.github.aureliano.evtbridge.core.config.InputConfigTypes;
import com.github.aureliano.evtbridge.core.config.OutputConfigTypes;
import com.github.aureliano.evtbridge.core.schedule.SchedulerTypes;

public abstract class SchemaBuilder<T> implements ISchemaBuilder<T> {

	protected Map<String, Object> schema;
	
	public SchemaBuilder() {
		this.schema = new LinkedHashMap<>();
	}
	
	protected void configureSchemaHeader(Class<?> configuration) {
		SchemaConfiguration schemaConfiguration = configuration.getAnnotation(SchemaConfiguration.class);
		
		if (schemaConfiguration == null) {
			return;
		}
		
		this.schema.put("$schema", schemaConfiguration.schema());
		this.schema.put("title", schemaConfiguration.title());
		this.schema.put("type", schemaConfiguration.type());
	}
	
	protected void configureSchemaProperties(Class<?> configuration) {
		Map<String, Object> properties = new HashMap<>();
		
		for (Method method : configuration.getMethods()) {
			SchemaProperty schemaProperty = method.getAnnotation(SchemaProperty.class);
			if (schemaProperty == null) {
				continue;
			}
			
			Map<String, Object> property = this.buildProperty(schemaProperty);
			String key = property.keySet().iterator().next();
			properties.put(key, property.get(key));
		}
		
		this.schema.put("properties", properties);
	}
	
	private Map<String, Object> buildProperty(SchemaProperty schemaProperty) {
		Map<String, Object> properties = new HashMap<>();
		Map<String, Object> property = new HashMap<>();
		
		property.put("type", this.configureTypes(schemaProperty.types()));
		property.put("description", schemaProperty.description());
		property.put("required", schemaProperty.required());
		if (!StringHelper.isEmpty(schemaProperty.defaultValue())) {
			property.put("default", schemaProperty.defaultValue());
		}
		properties.put(schemaProperty.property(), property);
		
		configureReference(schemaProperty, properties, property);
		
		return properties;
	}

	private void configureReference(SchemaProperty schemaProperty, Map<String, Object> properties,
			Map<String, Object> property) {
		if ("array".equals(schemaProperty.types()[0])) {
			property.put("items", this.mapItems(schemaProperty.reference()));
		} else {
			String ref = this.getReferenceLabel(schemaProperty.reference());
			if (!StringHelper.isEmpty(ref)) {
				property.put("$ref", ref);
			}
		}
	}
	
	private Object configureTypes(String[] types) {
		if (types.length == 1) {
			return types[0];
		} else {
			Map<String, Object> mtypes = new HashMap<>();
			mtypes.put("anyOf", types);
			
			return mtypes;
		}
	}

	private Map<String, Object> mapItems(Class<?> reference) {
		Map<String, Object> items = new HashMap<>();
		
		String ref = this.getReferenceLabel(reference);
		if (!StringHelper.isEmpty(ref)) {
			Map<String, Object> type = new HashMap<>();
			
			type.put("$ref", ref);
			items.put("type", type);
		} else {
			items.put("type", "string");
		}
		
		
		return items;
	}
	
	private String getReferenceLabel(Class<?> reference) {
		if (InputConfigTypes.class.equals(reference)) {
			return "input";
		} else if (OutputConfigTypes.class.equals(reference)) {
			return "output";
		} else if (SchedulerTypes.class.equals(reference)) {
			return "scheduler";
		} else {
			return null;
		}
	}
}
@@ -43,16 +43,56 @@
 e.doc;%0A%0A
+import java.lang.annotation.Annotation;%0A
 import j
@@ -1257,24 +1257,217 @@
 guration) %7B%0A
+%09%09Map%3CString, Object%3E properties = this.buildProperties(configuration);%0A%09%09this.schema.put(%22properties%22, properties);%0A%09%7D%0A%09%0A%09private Map%3CString, Object%3E buildProperties(Class%3C?%3E configuration) %7B%0A
 %09%09Map%3CString
@@ -1876,37 +1876,14 @@
 %09%0A%09%09
-this.schema.put(%22properties%22,
+return
  pro
@@ -1881,33 +1881,32 @@
 eturn properties
-)
 ;%0A%09%7D%0A%09%0A%09private 
@@ -2481,28 +2481,16 @@
 roperty,
- properties,
  propert
@@ -2608,43 +2608,8 @@
 ect%3E
- properties,%0A%09%09%09Map%3CString, Object%3E
  pro
@@ -2748,16 +2748,336 @@
  else %7B%0A
+%09%09%09Class%3C?%3E clazz = schemaProperty.reference();%0A%09%09%09if (clazz == null) %7B%0A%09%09%09%09return;%0A%09%09%09%7D%0A%09%09%09%0A%09%09%09Annotation annotation = clazz.getAnnotation(SchemaConfiguration.class);%0A%09%09%09if (annotation != null) %7B%0A%09%09%09%09Map%3CString, Object%3E properties = this.buildProperties(clazz);%0A%09%09%09%09property.put(%22properties%22, properties);%0A%09%09%09%7D else %7B%0A%09
 %09%09%09Strin
@@ -3135,24 +3135,25 @@
 ence());%0A%09%09%09
+%09
 if (!StringH
@@ -3174,24 +3174,25 @@
 ref)) %7B%0A%09%09%09%09
+%09
 property.put
@@ -3206,16 +3206,22 @@
 , ref);%0A
+%09%09%09%09%7D%0A
 %09%09%09%7D%0A%09%09%7D

Build nested object schema.