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

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
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.api.AssetFilter;
import speiger.src.coreengine.assets.api.IAssetProvider;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.assets.api.MultiAsset;
import speiger.src.coreengine.assets.api.ReloadContext;
import speiger.src.coreengine.assets.api.SteppedResourceListener;
import speiger.src.coreengine.assets.impl.parsers.Serialized;
import speiger.src.coreengine.ui.gui.font.glyth.Glyth;
import speiger.src.coreengine.ui.gui.font.glyth.IGlythSheetInfo;
import speiger.src.coreengine.ui.gui.font.providers.FreeTypeProvider;
import speiger.src.coreengine.ui.gui.font.providers.IFontProvider;
import speiger.src.coreengine.ui.gui.font.providers.STBTrueTypeProvider;
import speiger.src.coreengine.utils.helpers.JsonUtil;

public class FontManager extends SteppedResourceListener<Map<ID, ObjectList<IFontProvider>>> {
	private static final AssetFilter FILTER = AssetFilter.json("font");
	Float2ObjectMap<Font> cachedFonts = Float2ObjectMap.builder().map();
	Map<ID, 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
	protected Map<ID, ObjectList<IFontProvider>> prepare(ReloadContext context) {
		Map<ID, IFontProvider> loadingCache = Object2ObjectMap.builder().linkedMap();
		Object2ObjectMap<ID, ObjectList<IFontProvider>> providers = Object2ObjectMap.builder().linkedMap();
		for(Entry<ID, MultiAsset> entry : FILTER.multiRoot(context.provider()).entrySet()) {
			ID id = entry.getKey();
			for(JsonObject obj : entry.getValue().map(Serialized.JSON_OBJ, JsonObject::new)) {
				JsonUtil.iterateValues(obj.get("providers"), T -> {
					ID location = ID.tryOf(T.getAsString());
					if(location == null || location.isRoot()) return;
					IFontProvider font = loadingCache.computeIfAbsent(location.prefix("font"), E -> getFont(E, context.provider()));
					if(font == null) return;
					providers.supplyIfAbsent(FILTER.id(id), ObjectArrayList::new).add(font);
				});
			}
		}
		return providers;
	}

	@Override
	protected void apply(Map<ID, ObjectList<IFontProvider>> value, ReloadContext context) {
		reset(context.mainThread());
		value.forEach((K, V) -> fonts.put(K, new FontGroup(K, V)));
	}
	
	public void destroy() {
		reset(Runnable::run);
		listeners.clear();
	}
	
	private void reset(Executor execute) {
		listeners.forEach(Runnable::run);
		execute.execute(() -> {
			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(512, info.isColored());
		textures.add(texture);
		Glyth glyth = texture.build(info);
		return glyth;
	}
	
	private IFontProvider getFont(ID location, IAssetProvider provider) {
		try {
			JsonObject obj = provider.get(location).jsonObj();
			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;
	}
	
}
