package speiger.src.coreengine.assets.impl;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.hash.Object2LongOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2LongMap;
import speiger.src.collections.objects.utils.ObjectLists;
import speiger.src.coreengine.assets.api.IAsset;
import speiger.src.coreengine.assets.api.IAssetPackage;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.assets.api.IFileWatcher;
import speiger.src.coreengine.assets.impl.FolderWatcher.ChangeType;

public class ZipAssetPackage implements IAssetPackage {
	Object2LongMap<ID> crcCache = new Object2LongOpenHashMap<>();
	Path baseFile;
	String startFolder;
	FileSystem cache;
	
	public ZipAssetPackage(Path baseFile, String startFolder) {
		this.baseFile = baseFile;
		this.startFolder = startFolder;
	}
	
	@Override
	public void close() throws Exception {
		if(cache == null) return;
		FileSystem cache = this.cache;
		this.cache = null;
		cache.close();
	}
	
	protected FileSystem getReference() throws IOException {
		if(cache == null) cache = FileSystems.newFileSystem(baseFile);
		return cache;
	}
	
	@Override
	public void registerWatchers(IFileWatcher watcher) throws IOException {
		initCache();
		watcher.addFile(baseFile);
	}
	
	private void initCache() {
		try(AutoCloseable _ = this) {
			Path root = getReference().getPath(startFolder);
			for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) {
				crcCache.put(ID.of(root.relativize(path)), getCrc(path));
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	private long getCrc(Path path) throws IOException {
		return (Long)Files.readAttributes(path, "crc").get("crc");
	}
	
	@Override
	public boolean handleChange(Path file, ChangeType type, Consumer<ID> changed) throws IOException {
		if(!baseFile.equals(file)) return true;
		if(Files.notExists(baseFile)) return false;
		if(type == ChangeType.MODIFIED) {
			try {
				Path root = getReference().getPath(startFolder);
				for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) {
					ID id = ID.of(root.relativize(path));
					long fileCRC = getCrc(path);
					if(crcCache.getOrDefault(id, -1L) != fileCRC) {
						crcCache.put(id, fileCRC);
						changed.accept(id);
					}
				}
			}
			catch(Exception e) {
				e.printStackTrace();
			}
			return true;
		}
		return false;
	}
	
	@Override
	public List<String> domains() {
		try(AutoCloseable _ = this; DirectoryStream<Path> dirs = Files.newDirectoryStream(getReference().getPath(startFolder))) {
			List<String> domains = new ObjectArrayList<>();
			dirs.forEach(T -> domains.add(T.getFileName().toString()));
			return domains;
		}
		catch(Exception e) {
			e.printStackTrace();
			return ObjectLists.empty();
		}
	}
	
	@Override
	public IAsset get(ID location) {
		try {
			Path path = getReference().getPath(startFolder, location.fileLocation());
			return Files.notExists(path) ? null : new SimpleAsset(this, location, path);
		}
		catch(Exception e) { e.printStackTrace(); }
		return null;
	}
	
	@Override
	public void getAll(ID folder, BiConsumer<ID, IAsset> assets) {
		try {
			Path root = getReference().getPath(startFolder, folder.fileLocation());
			if(Files.notExists(root)) return;
			for(Path path : Files.walk(root).filter(Files::isRegularFile).toList()) {
				ID id = ID.of(root.relativize(path));
				assets.accept(id, new SimpleAsset(this, id, path));
			}
		}
		catch(Exception e) { e.printStackTrace(); }
	}
	
}
