package speiger.src.coreengine.assets;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.zip.ZipFile;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectLinkedOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap.Entry;
import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
import speiger.src.collections.objects.utils.maps.Object2ObjectMaps;
import speiger.src.coreengine.assets.impl.FolderAssetPackage;
import speiger.src.coreengine.assets.impl.ZipAssetPackage;
import speiger.src.coreengine.assets.reloader.IReloadableResource;

public class AssetManager implements IReloadableResource
{
	Object2ObjectMap<String, DomainAssets> domains = new Object2ObjectLinkedOpenHashMap<>();
	Object2ObjectMap<Class<?>, IAssetParser<?>> parsers = new Object2ObjectLinkedOpenHashMap<>();
	Path path;
	
	public AssetManager(File file)
	{
		this(file.toPath());
	}
	
	public AssetManager(Path path)
	{
		this.path = path;
	}
	
	public Path getMainAsset()
	{
		return path;
	}
	
	public <T> void registerAssetParser(Class<T> clz, IAssetParser<T> parser)
	{
		if(clz == null || parser == null) return;
		parsers.put(clz, parser);
	}
	
	@Override
	public void reload()
	{
		domains.clear();
		loadAssetPackage(path);
	}
	
	public void loadAssetPackage(File file)
	{
		loadAssetPackage(file.toPath());
	}
	
	public void loadAssetPackage(Path path)
	{
		if(Files.notExists(path)) return;
		if(Files.isDirectory(path)) loadAssets(new FolderAssetPackage(path));
		else if(isZipFile(path)) loadAssets(new ZipAssetPackage(path));
	}
	
	public void loadAssets(IAssetPackage packages)
	{
		for(String s : packages.getDomains())
		{
			DomainAssets entry = domains.get(s);
			if(entry == null)
			{
				entry = new DomainAssets();
				domains.put(s, entry);
			}
			entry.addAssets(packages);
		}
		packages.setParsers(parsers);
	}
	
	public Set<String> getDomains()
	{
		return domains.keySet();
	}
	
	public IAsset getAsset(AssetLocation location) throws IOException
	{
		DomainAssets asset = domains.get(location.getDomain());
		if(asset == null)
		{
			throw new FileNotFoundException("Domain & File["+location.toString()+"] not found");
		}
		return asset.getAsset(location);
	}
	
	public MultiAsset getAssets(AssetLocation...locations)
	{
		IAsset[] asset = new IAsset[locations.length];
		for(int i = 0,m=locations.length;i<m;i++)
		{
			try
			{
				asset[i] = getAsset(locations[i]);
			}
			catch(Exception e) {e.printStackTrace();}
		}
		return new MultiAsset(asset);
	}
	
	public MultiAsset getMultiAsset(AssetLocation location, AssetLocation...subLocations) throws IOException
	{
		DomainAssets asset = domains.get(location.getDomain());
		if(asset == null)
		{
			throw new FileNotFoundException("Domain & File["+location.toString()+"] not found");
		}
		return asset.getMultiAsset(location, subLocations);
	}
	
	public MultiAsset getAllAsset(AssetLocation location) throws IOException
	{
		DomainAssets asset = domains.get(location.getDomain());
		if(asset == null)
		{
			throw new FileNotFoundException("Domain & File["+location.toString()+"] not found");
		}
		return asset.getAllAssets(location);
	}
	
	public Collection<AssetLocation> gatherAssets(String location, int maxDepth, Predicate<String> filter)
	{
		Set<AssetLocation> result = new ObjectLinkedOpenHashSet<>();
		for(Entry<String, DomainAssets> asset : Object2ObjectMaps.fastIterable(domains))
		{
			asset.getValue().gatherAssets(AssetLocation.of(asset.getKey(), location), maxDepth, filter, result);
		}
		return result;
	}
	
	protected boolean isZipFile(Path file)
	{
		try(ZipFile zip = new ZipFile(file.toFile())) { return true; }
		catch(Exception e) { return false; }
	}
	
	static class DomainAssets
	{
		List<IAssetPackage> packages = new ObjectArrayList<IAssetPackage>();
		
		public void addAssets(IAssetPackage assets)
		{
			packages.add(assets);
		}
		
		public IAsset getAsset(AssetLocation location) throws IOException
		{
			for(int i = packages.size() - 1;i>=0;i--)
			{
				IAsset asset = packages.get(i).getAsset(location);
				if(asset != null) return asset;
			}
			throw new FileNotFoundException("File["+location.toString()+"] not found");
		}
		
		public MultiAsset getMultiAsset(AssetLocation location, AssetLocation... subLocations)
		{
			for(int i = packages.size() - 1;i>=0;i--)
			{
				IAssetPackage entry = packages.get(i);
				IAsset asset = entry.getAsset(location);
				if(asset != null)
				{
					IAsset[] result = new IAsset[1 + subLocations.length];
					result[0] = asset;
					for(int x = 0;x<subLocations.length;x++)
					{
						result[x+1] = entry.getAsset(subLocations[x]);
					}
					return new MultiAsset(result);
				}
			}
			return null;
		}
		
		public MultiAsset getAllAssets(AssetLocation location) throws IOException
		{
			List<IAsset> assets = new ObjectArrayList<IAsset>();
			for(IAssetPackage entry : packages)
			{
				IAsset asset = entry.getAsset(location);
				if(asset != null) assets.add(asset);
			}
			if(assets.isEmpty())
			{
				throw new FileNotFoundException("File["+location.toString()+"] not found");
			}
			return new MultiAsset(assets.toArray(new IAsset[assets.size()]));
		}
		
		public void gatherAssets(AssetLocation folder, int maxDepth, Predicate<String> filter, Collection<AssetLocation> result)
		{
			for(IAssetPackage entry : packages)
			{
				entry.getAllAssets(folder, filter, maxDepth, result);
			}
		}
	}
}
