package speiger.src.coreengine.assets.oldImpl;

import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

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.AssetLocation;
import speiger.src.coreengine.assets.base.IAsset;
import speiger.src.coreengine.assets.base.IAssetPackage;
import speiger.src.coreengine.assets.base.IAssetProvider;
import speiger.src.coreengine.assets.base.IAssetProvider.ICloseableAssetProvider;
import speiger.src.coreengine.assets.base.MultiAsset;
import speiger.src.coreengine.math.ArrayUtil;

public class LayeredAssetProvider implements ICloseableAssetProvider {
	public static final FileTime DEFAULT_TIME = FileTime.fromMillis(0L);
	Map<String, DomainAssetProvider> domains = Object2ObjectMap.builder().linkedMap();
	List<IAssetPackage> packages;
	
	public LayeredAssetProvider(List<IAssetPackage> packages) {
		this.packages = packages;
		for(IAssetPackage pack : packages) {
			for(String domain : pack.getDomains()) {
				domains.computeIfAbsent(domain, DomainAssetProvider::new).addPackage(pack);
			}
		}
		domains.values().forEach(DomainAssetProvider::buildCaches);
	}
	
	@Override
	public void close() {
		packages.forEach(IAssetPackage::close);
	}
	
	@Override
	public FileTime getModifiedTime(AssetLocation location) {
		DomainAssetProvider provider = domains.get(location.domain());
		return provider == null ? DEFAULT_TIME : provider.getModifiedTime(location);
	}
	
	@Override
	public FileTime getLatestTime(AssetLocation... locations) {
		FileTime time = DEFAULT_TIME;
		for(AssetLocation location : locations) {
			time = ArrayUtil.higher(time, getModifiedTime(location));
		}
		return time;
	}
	
	@Override
	public IAsset getAsset(AssetLocation location) {
		DomainAssetProvider provider = domains.get(location.domain());
		return provider == null ? null : provider.getAsset(location);
	}
	
	@Override
	public MultiAsset getAllAssets(AssetLocation location) {
		DomainAssetProvider provider = domains.get(location.domain());
		return provider == null ? null : provider.getAllAssets(location);
	}
	
	@Override
	public Map<AssetLocation, IAsset> listAssets(String folder, Predicate<AssetLocation> filter) {
		Map<AssetLocation, IAsset> result = Object2ObjectMap.builder().linkedMap();
		for(DomainAssetProvider provider : domains.values()) {
			result.putAll(provider.listAssets(folder, filter));
		}
		return result;
	}
	
	@Override
	public Map<AssetLocation, MultiAsset> listAllAssets(String folder, Predicate<AssetLocation> filter) {
		Map<AssetLocation, MultiAsset> result = Object2ObjectMap.builder().linkedMap();
		for(DomainAssetProvider provider : domains.values()) {
			result.putAll(provider.listAllAssets(folder, filter));
		}
		return result;
	}
	
	private static class DomainAssetProvider implements IAssetProvider {
		final String domain;
		ObjectList<IAssetPackage> packages = new ObjectArrayList<>();
		Map<AssetLocation, FileTime> lastChanges = Object2ObjectMap.builder().map();
		
		public DomainAssetProvider(String domain) {
			this.domain = domain;
		}
		
		public void addPackage(IAssetPackage pack) {
			packages.add(pack);
		}
		
		private void buildCaches() {
			for(int i = packages.size()-1;i>=0;i--) {
				packages.get(i).getModifiedTime(domain, lastChanges::putIfAbsent);
			}
		}
		
		@Override
		public FileTime getModifiedTime(AssetLocation location) {
			return lastChanges.getOrDefault(location, DEFAULT_TIME);
		}
		
		@Override
		public FileTime getLatestTime(AssetLocation... locations) {
			throw new UnsupportedOperationException();
		}
		
		@Override
		public IAsset getAsset(AssetLocation location) {
			for(int i = packages.size()-1;i>=0;i--) {
				IAsset asset = packages.get(i).getAsset(location);
				if(asset != null) return asset;
			}
			return null;
		}

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

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

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