package net.mcft.copy.backpacks.config.custom;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;

import net.minecraftforge.common.config.Configuration;

import net.mcft.copy.backpacks.WearableBackpacks;
import net.mcft.copy.backpacks.api.BackpackRegistry;
import net.mcft.copy.backpacks.api.BackpackRegistry.BackpackEntityEntry;
import net.mcft.copy.backpacks.api.BackpackRegistry.BackpackEntry;
import net.mcft.copy.backpacks.api.BackpackRegistry.ColorRange;
import net.mcft.copy.backpacks.api.BackpackRegistry.RenderOptions;
import net.mcft.copy.backpacks.config.Setting;
import net.mcft.copy.backpacks.misc.util.NbtUtils;
import net.mcft.copy.backpacks.misc.util.NbtUtils.NbtType;

public class SettingListEntities extends Setting<List<BackpackEntityEntry>> {
	
	public SettingListEntities() {
		super(Collections.emptyList());
		setConfigEntryClass("net.mcft.copy.backpacks.client.gui.config.custom.EntryListEntities");
		setSynced();
	}
	
	
	@Override
	protected void loadFromConfiguration(Configuration config) {
		set(BackpackRegistry.mergeEntityEntriesWithDefault(config.getCategoryNames().stream()
			.filter(name -> name.startsWith(getCategory() + Configuration.CATEGORY_SPLITTER))
			.sorted(Comparator.comparingInt(category -> config.get(category, "index", Integer.MAX_VALUE).getInt()))
			.map(category -> loadEntity(config, category))
			.collect(Collectors.toList())));
	}
	
	@Override
	protected void saveToConfiguration(Configuration config) {
		// Remove all existing categories that belong to this setting.
		// Just doing this to make sure that old ones are properly removed.
		config.getCategoryNames().stream()
			.filter(name -> name.startsWith(getCategory() + Configuration.CATEGORY_SPLITTER))
			.forEach(category -> config.removeCategory(config.getCategory(category)));
		
		int index = 0;
		for (BackpackEntityEntry entity : getOwn()) {
			String category = getCategory() + Configuration.CATEGORY_SPLITTER + entity.entityID;
			config.setCategoryPropertyOrder(category, Arrays.asList(
				"index", "translate", "rotate", "scale", "entries"));
			config.get(category, "index", 0).set(index++);
			saveEntity(config, category, entity);
		}
	}
	
	
	@Override
	public List<BackpackEntityEntry> read(NBTBase tag) {
		return NbtUtils.stream((NBTTagList)tag)
			.map(NBTTagCompound.class::cast)
			.map(SettingListEntities::deserializeEntity)
			.collect(Collectors.toList());
	}
	@Override
	public NBTBase write(List<BackpackEntityEntry> value) {
		return value.stream()
			.map(SettingListEntities::serializeEntity)
			.collect(NbtUtils.toList());
	}
	
	
	@Override
	public void update() {
		BackpackRegistry.updateEntityEntries(get());
		WearableBackpacks.PROXY.initBackpackLayers();
	}
	
	
	// Utility methods for cloning / equals / parsing / NBT.
	// Don't want these clogging up the API, I guess.
	
	// BackpackEntityEntry
	
	public static final String TAG_ENTITY_ID      = "id";
	public static final String TAG_RENDER_OPTIONS = "render";
	public static final String TAG_ENTRIES        = "entries";
	
	private static void saveEntity(Configuration config, String category, BackpackEntityEntry value) {
		saveRenderOptions(config, category, value.renderOptions);
		config.get(category, "entries", new String[0]).set(value.getEntries().stream()
			.map(SettingListEntities::toString).toArray(length -> new String[length]));
	}
	private static BackpackEntityEntry loadEntity(Configuration config, String category) {
		return new BackpackEntityEntry(
			category.split("\\" + Configuration.CATEGORY_SPLITTER, 2)[1],
			loadRenderOptions(config, category),
			Arrays.stream(config.get(category, "entries", new String[0]).getStringList())
				.map(SettingListEntities::parseBackpack).collect(Collectors.toList()), false);
	}
	
	private static NBTTagCompound serializeEntity(BackpackEntityEntry value) {
		return NbtUtils.createCompound(
			TAG_ENTITY_ID,      value.entityID,
			TAG_RENDER_OPTIONS, serializeRenderOptions(value.renderOptions));
		// Entries don't need to be synchronized to clients.
		// 	TAG_ENTRIES,        value.getEntries().stream()
		// 		.map(SettingListSpawn::serializeBackpack)
		// 		.collect(NbtUtils.toList()));
	}
	private static BackpackEntityEntry deserializeEntity(NBTTagCompound nbt) {
		return new BackpackEntityEntry(
			nbt.getString(TAG_ENTITY_ID),
			deserializeRenderOptions(nbt.getCompoundTag(TAG_RENDER_OPTIONS)),
			Collections.emptyList(), false);
		// Entries don't need to be synchronized to clients.
		// 	NbtUtils.stream(nbt.getTagList(TAG_ENTRIES, NbtType.COMPOUND))
		// 		.map(NBTTagCompound.class::cast)
		// 		.map(SettingListSpawn::deserializeBackpack)
		// 		.collect(Collectors.toList()));
	}
	
	// RenderOptions
	
	public static final String TAG_TRANSLATE = "translate";
	public static final String TAG_ROTATE    = "rotate";
	public static final String TAG_SCALE     = "scale";
	
	private static void saveRenderOptions(Configuration config, String category, RenderOptions value) {
		config.get(category, "translate", new double[3]).set(new double[]{ value.y, value.z });
		config.get(category, "rotate", 0.0).set(value.rotate);
		config.get(category, "scale", 0.0).set(value.scale);
	}
	private static RenderOptions loadRenderOptions(Configuration config, String category) {
		double[] translate = config.get(category, "translate", new double[2]).getDoubleList();
		return new RenderOptions(
			translate[0], translate[1],
			config.get(category, "rotate", 0.0).getDouble(),
			config.get(category, "scale", 0.0).getDouble());
	}
	
	private static NBTTagCompound serializeRenderOptions(RenderOptions value) {
		return NbtUtils.createCompound(
			TAG_TRANSLATE, NbtUtils.createList(value.y, value.z),
			TAG_ROTATE, value.rotate,
			TAG_SCALE, value.scale);
	}
	private static RenderOptions deserializeRenderOptions(NBTTagCompound nbt) {
		NBTTagList translate = nbt.getTagList(TAG_TRANSLATE, NbtType.DOUBLE);
		return new RenderOptions(
			translate.getDoubleAt(0),
			translate.getDoubleAt(1),
			nbt.getDouble(TAG_ROTATE),
			nbt.getDouble(TAG_SCALE));
	}
	
	// BackpackEntry
	
	/* Entries don't need to be synchronized to clients.
	 * Leaving this in for the sake of completion - or if needed in the future.
	public static final String TAG_BACKPACK_ID = "id";
	public static final String TAG_BACKPACK    = "backpack";
	public static final String TAG_CHANCE      = "chance";
	public static final String TAG_LOOT_TABLE  = "lootTable";
	public static final String TAG_COLOR_MIN   = "colorMin";
	public static final String TAG_COLOR_MAX   = "colorMax";
	
	private static NBTTagCompound serializeBackpack(BackpackEntry value) {
		return NbtUtils.createCompound(
			TAG_BACKPACK_ID, value.id,
			TAG_CHANCE,      value.chance,
			TAG_BACKPACK,    value.backpack,
			TAG_LOOT_TABLE,  value.lootTable,
			TAG_COLOR_MIN,   (value.colorRange != null) ? value.colorRange.min : null,
			TAG_COLOR_MAX,   (value.colorRange != null) ? value.colorRange.max : null);
	}
	private static BackpackEntry deserializeBackpack(NBTTagCompound nbt) {
		return new BackpackEntry(
			nbt.getString(TAG_BACKPACK_ID),
			nbt.getString(TAG_BACKPACK),
			nbt.getInteger(TAG_CHANCE),
			nbt.getString(TAG_LOOT_TABLE),
			nbt.hasKey(TAG_COLOR_MIN)
				? new ColorRange(nbt.getInteger(TAG_COLOR_MIN),
				                 nbt.getInteger(TAG_COLOR_MAX))
				: null);
	}
	*/
	
	private static final String SEPERATOR       = ",";
	private static final String ID_SEPERATOR    = "=";
	private static final String COLOR_SEPERATOR = "~";
	private static final String COLOR_NULL      = "DEFAULT";
	
	private static BackpackEntry parseBackpack(String str) {
		String id = null;
		if (str.indexOf('=') >= 0) {
			String[] values = str.split("\\" + ID_SEPERATOR, 2);
			id  = values[0].trim();
			str = values[1];
		}
		String[] values = str.split("\\" + SEPERATOR);
		if (values.length != 4) throw new IllegalArgumentException(
			"Expected 4 parts for backpack entry, got " + values.length);
		int chance       = Integer.parseInt(values[0].trim());
		String backpack  = values[1].trim();
		String lootTable = values[2].trim();
		ColorRange color = parseColorRange(values[3].trim());
		if (chance < 0) throw new IllegalArgumentException("Chance is negative");
		return new BackpackEntry(id, backpack, chance, lootTable, color, false);
	}
	private static String toString(BackpackEntry value) {
		String str = value.chance + SEPERATOR + " " +
		             value.backpack + SEPERATOR + " " +
		             value.lootTable + SEPERATOR + " " +
		             toString(value.colorRange);
		return (value.id != null) ? (value.id + " " + ID_SEPERATOR + " " + str) : str;
	}
	
	private static ColorRange parseColorRange(String str) {
		if (COLOR_NULL.equalsIgnoreCase(str)) return null;
		String[] minMax = str.split("\\" + COLOR_SEPERATOR, 2);
		if (minMax.length != 2) throw new IllegalArgumentException(
			"Expected 2 parts for color range, got " + minMax.length);
		String min = minMax[0].trim();
		String max = minMax[1].trim();
		if ((min.length() != 7) || (min.charAt(0) != '#') ||
		    (max.length() != 7) || (max.charAt(0) != '#')) throw new IllegalArgumentException(
				"Colors in the color range are not in the format #RRGGBB");
		return new ColorRange(Integer.parseInt(min.substring(1), 16),
		                      Integer.parseInt(max.substring(1), 16));
	}
	private static String toString(ColorRange colorRange) {
		return (colorRange != null)
			? String.format("#%06X %s #%06X", colorRange.min, COLOR_SEPERATOR, colorRange.max)
			: COLOR_NULL;
	}
	
}