package openmods.model;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;

public class ModelUpdater {

	@FunctionalInterface
	public interface ValueConverter<T> {
		public T convert(String name, JsonElement element);
	}

	public static final ValueConverter<ResourceLocation> RESOURCE_LOCATION = (name, element) -> {
		final String value = JsonUtils.getString(element, name);
		return new ResourceLocation(value);
	};

	public static final ValueConverter<ModelResourceLocation> MODEL_RESOURCE_LOCATION = (name, element) -> {
		final String value = JsonUtils.getString(element, name);
		return new ModelResourceLocation(value);
	};

	public static final ValueConverter<ResourceLocation> MODEL_LOCATION = (name, element) -> {
		final String value = JsonUtils.getString(element, name);
		return value.contains("#")? new ModelResourceLocation(value) : new ResourceLocation(value);
	};

	public static final ValueConverter<String> TO_STRING = (name, element) -> JsonUtils.getString(element, name);

	public static final ValueConverter<Integer> TO_INT = (name, element) -> JsonUtils.getInt(element, name);

	public static final ValueConverter<Long> TO_LONG = (name, element) -> {
		if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isNumber())
			return element.getAsLong();

		throw new JsonSyntaxException("Expected " + name + " to be a Int, was " + JsonUtils.toString(element));
	};

	public static <T extends Enum<T>> ValueConverter<T> enumConverter(Class<T> enumCls) {
		final ImmutableMap.Builder<String, T> valuesBuilder = ImmutableMap.builder();
		for (T c : enumCls.getEnumConstants())
			valuesBuilder.put(c.name().toLowerCase(Locale.ROOT), c);

		final ImmutableMap<String, T> values = valuesBuilder.build();

		return (name, element) -> {
			final String enumName = JsonUtils.getString(element, name);
			return values.get(enumName.toLowerCase(Locale.ROOT));
		};
	}

	private final Map<String, String> values;

	private final JsonParser parser = new JsonParser();

	private boolean changed;

	public ModelUpdater(Map<String, String> values) {
		this.values = values;
	}

	public boolean hasChanged() {
		return changed;
	}

	public void markChanged() {
		this.changed = true;
	}

	public <T> T get(String key, ValueConverter<T> converter, T current) {
		final String value = values.get(key);
		if (value != null) {
			final JsonElement element = parser.parse(value);

			final T result = converter.convert(key, element);
			if (!(result.equals(current))) {
				changed = true;
				return result;
			}
		}

		return current;
	}

	public <T> Optional<T> get(String key, final ValueConverter<T> converter, Optional<T> current) {
		return get(key, (String name, JsonElement element) -> Optional.of(converter.convert(name, element)), current);
	}

	public <T> Set<T> get(String key, ValueConverter<T> converter, Set<T> current) {
		final String value = values.get(key);
		if (value != null) {
			final JsonElement parsedValue = parser.parse(value);

			final ImmutableSet.Builder<T> resultBuilder = ImmutableSet.builder();
			if (parsedValue.isJsonArray()) {
				for (JsonElement e : parsedValue.getAsJsonArray())
					resultBuilder.add(converter.convert(key, e));
			} else {
				resultBuilder.add(converter.convert(key, parsedValue));
			}

			final Set<T> result = resultBuilder.build();
			if (!result.equals(current)) {
				changed = true;
				return result;
			}
		}

		return current;
	}
}