package speiger.src.coreengine.assets;

import java.util.Map;
import java.util.Objects;

import speiger.src.collections.objects.maps.impl.concurrent.Object2ObjectConcurrentOpenHashMap;

public final class AssetLocation implements Comparable<AssetLocation> {
	static final Map<String, AssetLocation> LOCATION = new Object2ObjectConcurrentOpenHashMap<>();
	final String domain;
	final String location;
	
	private AssetLocation() {
		throw new UnsupportedOperationException("use AssetLocation.of instead");
	}
	
	private AssetLocation(String domain, String location) {
		this.domain = domain;
		this.location = location;
	}
	
	public String domain() { return domain; }
	public String location() { return location; }
	public String actualLocation() { return "assets/"+domain+"/"+location; }
	public boolean endsWith(String suffix) { return location.endsWith(suffix); }
	public boolean isInFolder() { return location.contains("/"); }
	public boolean isInFolder(String prefix) { return location.indexOf("/", prefix.length()+1) >= 0; }
	
	public AssetLocation prefix(String prefix) {
		return of(domain, prefix+"/"+location);
	}
	
	public AssetLocation subAsset(String subPath) {
		return of(domain+":"+location+"/"+subPath);
	}
	
	public AssetLocation alternate(String alterversion) {
		return of(domain+":"+location+"."+alterversion);
	}
	
	@Override
	public String toString() {
		return domain+":"+location;
	}
	
	@Override
	public int hashCode() {
		return Objects.hash(domain, location);
	}
	
	@Override
	public boolean equals(Object obj) {
		return obj == this || (obj instanceof AssetLocation location && matches(location));
	}
	
	@Override
	public int compareTo(AssetLocation o) {
		int result = domain.compareToIgnoreCase(o.domain);
		return result != 0 ? result : location.compareToIgnoreCase(location);
	}
	
	public boolean matches(AssetLocation location) {
		return location.domain.equals(domain) && location.location.equals(this.location);
	}
	
	public static final AssetLocation of(String domain, String location) {
		return of((domain == null || domain.isEmpty() ? "base" : domain)+":"+location);
	}
	
	public static final AssetLocation of(String location) {
		return LOCATION.computeIfAbsent(location.indexOf(":") == -1 ? "base:"+location : location, AssetLocation::compute);
	}
	
	public static final AssetLocation tryOf(String location) {
		try { return of(location); }
		catch(Exception e) {}
		return null;
	}
	
	private static final AssetLocation compute(String s) {
		int index = s.indexOf(":");
		String domain = s.substring(0, index);
		String path = s.substring(index + 1);
		if(!isValidDomain(domain)) throw new IllegalArgumentException("Non [a-zA-Z0-9_.-] Character found in domain of location ["+domain+":"+path+"]");
		if(!isValidPath(path)) throw new IllegalArgumentException("Non [a-zA-Z0-9/_.-] Character found in path of location ["+domain+":"+path+"]");
		return new AssetLocation(domain, path);
	}
	
	private static boolean isValidDomain(String s) {
		for(int i = 0,m=s.length();i<m;i++) {
			if(!isValidDomainChar(s.charAt(i))) return false;
		}
		return true;
	}
	
	private static boolean isValidPath(String s) {
		for(int i = 0,m=s.length();i<m;i++) {
			if(!isValidPathChar(s.charAt(i))) return false;
		}
		return true;
	}
	
	private static boolean isValidPathChar(char c) {
		return c == '/' || isValidDomainChar(c);
	}
	
	private static boolean isValidDomainChar(char c) {
		return c == '_' || c == '-' || c == '.' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
	}
}
