package org.monospark.actionpermissions.config;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;

import org.monospark.actionpermissions.ActionHandler;
import org.monospark.actionpermissions.ActionSettings;
import org.monospark.actionpermissions.group.Group;

import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public final class ConfigParser {

	private static final Gson GSON = createGson();
	
	private static Gson createGson() {
		GsonBuilder builder = new GsonBuilder()
				.registerTypeAdapter(ConfigGroup.class, new ConfigGroup.Deserializer())
				.registerTypeAdapter(Config.class, new Config.Deserializer());
		for(ActionHandler<?, ?> handler : ActionHandler.ALL) {
			builder.registerTypeAdapter(handler.getSettingsClass(), handler.getSettingsDeserializer());
		}
		return builder.create();
	}
	
	private ConfigParser() {}
	
	public static Set<Group> parseConfig(Path configFile) throws IOException, ConfigParseException {
		String contents = new String(Files.readAllBytes(configFile));
		Config config = GSON.fromJson(contents, Config.class);
		return createGroups(config);
	}

	private static Set<Group> createGroups(Config config) throws ConfigParseException {
		validateParents(config);
		
		Set<Group> superGroups = new HashSet<Group>();
		Map<String, ConfigGroup> childGroups = new HashMap<String, ConfigGroup>();
		
		for(Entry<String, ConfigGroup> entry : config.getGroups().entrySet()) {
			if(!entry.getValue().getParent().isPresent()) {
				superGroups.add(new Group(entry.getKey(), entry.getValue().getSettings()));
			} else {
				childGroups.put(entry.getKey(), entry.getValue());
			}
		}
		
		while(childGroups.size() > 0) {
			processGroups(superGroups, childGroups);
		}
		
		return superGroups;
	}
	
	private static void validateParents(Config config) throws ConfigParseException {
		out: for(Entry<String, ConfigGroup> entry1 : config.getGroups().entrySet()) {
			Optional<String> parent = entry1.getValue().getParent();
			if(!parent.isPresent()) {
				continue;
			}
			
			for(Entry<String, ConfigGroup> entry2 : config.getGroups().entrySet()) {
				if(entry1.getKey().equals(entry2.getKey())) {
					continue;
				}

				if(parent.get().equals(entry2.getKey())) {
					continue out;
				}
			}
			
			throw new ConfigParseException("Invalid parent group: \"" + parent.get() + "\"");
		}
	}
	
	private static void processGroups(Set<Group> superGroups, Map<String, ConfigGroup> childGroups)
			throws ConfigParseException {
		for(Entry<String, ConfigGroup> entry : Maps.newHashMap(childGroups).entrySet()) {
			String parentName = entry.getValue().getParent().get();
			Group parent = getGroupByName(parentName, superGroups);
			if(parent != null) {
				Group united = uniteGroups(entry.getKey(), entry.getValue(), parent);
				superGroups.add(united);
				childGroups.remove(entry.getKey());
			}
		}
	}
	
	private static Group getGroupByName(String name, Set<Group> superGroups) {
		for(Group g : superGroups) {
			if(g.getName().equals(name)) {
				return g;
			}
		}
		
		return null;
	}
	
	private static Group uniteGroups(String name, ConfigGroup child, Group parent) throws ConfigParseException {
		Map<ActionHandler<?, ?>, ActionSettings> unitedSettings = Maps.newHashMap();
		for(ActionHandler<?, ?> handler : ActionHandler.ALL) {
			ActionSettings united = uniteSettings(handler, name, child, parent);
			unitedSettings.put(handler, united);
		}
		return new Group(name, unitedSettings);
	}
	
	private static <T extends ActionSettings> T uniteSettings(ActionHandler<?, T> handler, String name,
			ConfigGroup child, Group parent) throws ConfigParseException {
		T parentSettings = parent.getActionSettings(handler);
		@SuppressWarnings("unchecked")
		T childSettings = (T) child.getSettings().get(handler);
		if(parentSettings.getResponse() != childSettings.getResponse()) {
			throw new ConfigParseException("name can't inherit the " + handler.getName() +
					" settings, since their types are different");
		}
		return handler.uniteSettings(parentSettings, childSettings);
	}
}

@@ -542,16 +542,59 @@
 Builder;
+%0Aimport com.google.gson.JsonParseException;
 %0A%0Apublic
@@ -1290,16 +1290,33 @@
 onfig = 
+null;%0A%09%09try %7B%0A%09%09%09
 GSON.fro
@@ -1346,16 +1346,93 @@
 class);%0A
+%09%09%7D catch (JsonParseException e) %7B%0A%09%09%09throw new ConfigParseException(e);%0A%09%09%7D%0A
 %09%09return

Throw a ConfigParseException when a JsonParseException is thrown