package speiger.src.coreengine.assets.providers;

import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import org.jspecify.annotations.Nullable;

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.collections.objects.utils.maps.Object2ObjectMaps;
import speiger.src.coreengine.assets.api.IAsset;
import speiger.src.coreengine.assets.api.IAssetPackage;
import speiger.src.coreengine.assets.api.IAssetProvider;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.assets.api.MultiAsset;

public class LayeredProvider implements BaseProvider {
	Map<String, DomainProvider> domains = Object2ObjectMap.builder().linkedMap();
	List<IAssetPackage> packages;
	
	public LayeredProvider(List<IAssetPackage> packages) {
		this.packages = packages;
		for(IAssetPackage pack : packages) {
			for(String domain : pack.domains()) {
				domains.computeIfAbsent(domain, DomainProvider::new).add(pack);
			}
		}
	}
	
	@Override
	public List<IAssetPackage> packages() {
		return packages;
	}
	
	@Override
	public void close() throws Exception {
		for(IAssetPackage pack : packages) {
			pack.close();
		}
	}
	
	@Override
	public @Nullable IAsset get(ID id) {
		DomainProvider provider = domains.get(id.domain());
		return provider == null ? null : provider.get(id);
	}
	
	@Override
	public @Nullable MultiAsset getAll(ID id) {
		DomainProvider provider = domains.get(id.domain());
		return provider == null ? null : provider.getAll(id);
	}
	
	@Override
	public Map<ID, IAsset> list(String folder, Predicate<ID> filter) {
		Map<ID, IAsset> result = Object2ObjectMap.builder().linkedMap();
		for(DomainProvider provider : domains.values()) {
			result.putAll(provider.list(folder, filter));
		}
		return result;
	}
	
	@Override
	public Map<ID, MultiAsset> listAll(String folder, Predicate<ID> filter) {
		Map<ID, MultiAsset> result = Object2ObjectMap.builder().linkedMap();
		for(DomainProvider provider : domains.values()) {
			result.putAll(provider.listAll(folder, filter));
		}
		return result;
	}
	
	private static class DomainProvider implements IAssetProvider {
		final String domain;
		ObjectList<IAssetPackage> packages = new ObjectArrayList<>();
		
		public DomainProvider(String domain) {
			this.domain = domain;
		}
		
		public void add(IAssetPackage pack) {
			packages.add(pack);
		}
		
		@Override
		public void close() {}
		
		@Override
		public @Nullable IAsset get(ID id) {
			for(int i = packages.size()-1;i>=0;i--) {
				IAsset asset = packages.get(i).get(id);
				if(asset != null) return asset;
			}
			return null;
		}

		@Override
		public @Nullable MultiAsset getAll(ID id) {
			ObjectList<IAsset> result = new ObjectArrayList<>();
			for(int i = packages.size()-1;i>=0;i--) {
				IAsset asset = packages.get(i).get(id);
				if(asset != null) result.add(asset);
			}
			return new MultiAsset(result);
		}

		@Override
		public Map<ID, IAsset> list(String folder, Predicate<ID> filter) {
			ID base = ID.of(domain, folder);
			Map<ID, IAsset> result = Object2ObjectMap.builder().linkedMap();
			for(int i = packages.size()-1;i>=0;i--) {
				packages.get(i).getAll(base, (K, V) -> {
					if(filter.test(K)) result.putIfAbsent(K, V);
				});
			}
			return result;
		}

		@Override
		public Map<ID, MultiAsset> listAll(String folder, Predicate<ID> filter) {
			ID base = ID.of(domain, folder);
			Object2ObjectMap<ID, ObjectList<IAsset>> assets = Object2ObjectMap.builder().linkedMap();
			for(int i = packages.size()-1;i>=0;i--) {
				packages.get(i).getAll(base, (K, V) -> {
					if(filter.test(K)) assets.supplyIfAbsent(K, ObjectArrayList::new).add(V);
				});
			}
			Map<ID, MultiAsset> result = Object2ObjectMap.builder().linkedMap();
			for(Object2ObjectMap.Entry<ID, ObjectList<IAsset>> entry : Object2ObjectMaps.fastIterable(assets)) {
				result.put(entry.getKey(), new MultiAsset(entry.getValue()));
			}
			return result;
		}
		
	}
}
