package speiger.src.coreengine.rendering.gui.font;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiFunction;

import com.google.gson.JsonObject;

import speiger.src.collections.floats.maps.interfaces.Float2ObjectMap;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.coreengine.assets.AssetFilter;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.assets.base.IAsset;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.assets.base.MultiAsset;
import speiger.src.coreengine.assets.base.SteppedReloadableAsset;
import speiger.src.coreengine.rendering.gui.font.glyth.Glyth;
import speiger.src.coreengine.rendering.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.rendering.gui.font.providers.FreeTypeProvider;
import speiger.src.coreengine.rendering.gui.font.providers.IFontProvider;
import speiger.src.coreengine.rendering.gui.font.providers.STBTrueTypeProvider;
import speiger.src.coreengine.utils.helpers.JsonUtil;

public class FontManager extends SteppedReloadableAsset<Map<AssetLocation, ObjectList<IFontProvider>>> {
	private static final int TEXTURE_SIZE = 512;
	private static final AssetFilter FILTER = AssetFilter.json("font");
	Float2ObjectMap<Font> cachedFonts = Float2ObjectMap.builder().map();
	Map<AssetLocation, FontGroup> fonts = Object2ObjectMap.builder().linkedMap();
	Map<String, BiFunction<JsonObject, IAssetProvider, IFontProvider>> fontParsers = Object2ObjectMap.builder().map();
	List<Runnable> listeners = new ObjectArrayList<>();
	List<FontTexture> textures = new ObjectArrayList<>();
	
	public FontManager() {
		registerParser("stb-ttf", STBTrueTypeProvider::create);
		registerParser("free-ttf", FreeTypeProvider::load);
	}
	
	public void registerParser(String id, BiFunction<JsonObject, IAssetProvider, IFontProvider> parser) {
		fontParsers.putIfAbsent(id, parser);
	}
	
	@Override
	public String getName() { return "Font Manager"; }
	
	@Override
	protected Map<AssetLocation, ObjectList<IFontProvider>> prepare(IAssetProvider provider) {
		Map<AssetLocation, IFontProvider> loadingCache = Object2ObjectMap.builder().linkedMap();
		Object2ObjectMap<AssetLocation, ObjectList<IFontProvider>> providers = Object2ObjectMap.builder().linkedMap();
		for(Entry<AssetLocation, MultiAsset> entry : FILTER.multiRoot(provider).entrySet()) {
			AssetLocation id = entry.getKey();
			for(JsonObject obj : entry.getValue().map(IAsset::json, JsonObject::new)) {
				JsonUtil.iterateValues(obj.get("providers"), T -> {
					AssetLocation location = AssetLocation.tryOf(T.getAsString());
					if(location == null || !location.isInFolder()) return;
					IFontProvider font = loadingCache.computeIfAbsent(location.prefix("font"), E -> getFont(E, provider));
					if(font == null) return;
					providers.supplyIfAbsent(FILTER.id(id), ObjectArrayList::new).add(font);
				});
			}
		}
		return providers;
	}
	
	@Override
	protected void apply(Map<AssetLocation, ObjectList<IFontProvider>> value, IAssetProvider provider) {
		reset();
		value.forEach((K, V) -> fonts.put(K, new FontGroup(K, V)));
	}
	
	@Override
	public void destroy() {
		reset();
		listeners.clear();
	}
	
	private void reset() {
		listeners.forEach(Runnable::run);
		textures.forEach(FontTexture::delete);
		textures.clear();
		fonts.values().forEach(FontGroup::close);
		fonts.clear();
		cachedFonts.clear();
	}
	
	public Font createFont() {
		return createFont(1F);
	}
	
	public Font createFont(float oversample) {
		return cachedFonts.computeIfAbsent(oversample, T -> new Font(fonts, this::stitch, T, listeners::add));
	}
	
	private Glyth stitch(IGlythSheetInfo info) {
		for(int i = 0,m=textures.size();i<m;i++) {
			Glyth glyth = textures.get(i).build(info);
			if(glyth != null) return glyth;
		}
		FontTexture texture = new FontTexture(TEXTURE_SIZE, info.isColored());
		textures.add(texture);
		Glyth glyth = texture.build(info);
		return glyth;
	}
	
	private IFontProvider getFont(AssetLocation location, IAssetProvider provider) {
		try(IAsset asset = provider.getAsset(location)) {
			JsonObject obj = asset.json();
			BiFunction<JsonObject, IAssetProvider, IFontProvider> builder = fontParsers.get(obj.get("type").getAsString());
			return builder == null ? null : builder.apply(obj, provider);
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}