package com.elphel.imagej.mcp;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import com.elphel.imagej.cameras.EyesisCorrectionParameters;
import com.elphel.imagej.cameras.EyesisCorrectionParameters.CorrectionParameters;

public class McpFsAccess {
	private static final String PROP_ALLOWED_CONFIG = "elphel.mcp.allowed.config";
	private static final String PROP_ALLOWED_CONFIG_DIR = "elphel.mcp.allowed.configdir";
	private static final String PROP_ALLOWED_EXTRA = "elphel.mcp.allowed.extra";
	private static final String CONFIG_LIST_KEY = "CORRECTION_PARAMETERS.sourceSequencesList";

	private static List<Path> cachedRoots = null;
	private static String cachedConfig = null;
	private static long cachedConfigMtime = -1L;

	private McpFsAccess() {
	}

	public static synchronized List<Path> getAllowedRoots() {
		String configPath = System.getProperty(PROP_ALLOWED_CONFIG);
		String configDir = System.getProperty(PROP_ALLOWED_CONFIG_DIR);
		long mtime = getMtime(configPath, configDir);
		if (cachedRoots != null && configEquals(configPath, cachedConfig) && mtime == cachedConfigMtime) {
			return cachedRoots;
		}
		cachedRoots = buildAllowedRoots(configPath, configDir);
		cachedConfig = configPath;
		cachedConfigMtime = mtime;
		return cachedRoots;
	}

	public static boolean isAllowed(Path path) {
		if (path == null) {
			return false;
		}
		List<Path> roots = getAllowedRoots();
		if (roots == null || roots.isEmpty()) {
			return false;
		}
		for (Path root : roots) {
			if (path.startsWith(root)) {
				return true;
			}
		}
		return false;
	}

	public static Path normalizePath(String pathStr) throws IOException {
		if (pathStr == null || pathStr.trim().isEmpty()) {
			return null;
		}
		Path path = Paths.get(pathStr).toAbsolutePath().normalize();
		if (Files.exists(path)) {
			return path.toRealPath();
		}
		return path;
	}

	private static List<Path> buildAllowedRoots(String configPath, String configDir) {
		Set<Path> roots = new HashSet<Path>();
		if (configPath != null && !configPath.trim().isEmpty()) {
			Path configFile = Paths.get(configPath).toAbsolutePath().normalize();
			roots.add(configFile);
			Path configDir = configFile.getParent();
			if (configDir != null) {
				roots.add(configDir);
			}
			addRootsFromConfig(configFile, roots);
		}
		if (configDir != null && !configDir.trim().isEmpty()) {
			Path dirPath = Paths.get(configDir).toAbsolutePath().normalize();
			roots.add(dirPath);
			addRootsFromConfigDir(dirPath, roots);
		}
		addExtraRoots(roots);
		List<Path> result = new ArrayList<Path>(roots.size());
		for (Path root : roots) {
			try {
				Path normalized = root.toRealPath();
				result.add(normalized);
			} catch (IOException e) {
				result.add(root.toAbsolutePath().normalize());
			}
		}
		return result;
	}

	private static void addRootsFromConfig(Path configFile, Set<Path> roots) {
		if (configFile == null) {
			return;
		}
		Properties props = new Properties();
		try {
			props.loadFromXML(Files.newInputStream(configFile));
		} catch (Exception e) {
			return;
		}
		String listPath = props.getProperty(CONFIG_LIST_KEY);
		if (listPath == null || listPath.trim().isEmpty()) {
			return;
		}
		Path listFile = Paths.get(listPath).toAbsolutePath().normalize();
		roots.add(listFile);
		Path listDir = listFile.getParent();
		if (listDir != null) {
			roots.add(listDir);
		}
		CorrectionParameters cp = new EyesisCorrectionParameters.CorrectionParameters();
		EyesisCorrectionParameters.CorrectionParameters.PathFirstLast[] sets = cp.getSourceSets(listFile.toString());
		addIfPresent(roots, cp.sourceDirectory);
		addIfPresent(roots, cp.linkedModels);
		addIfPresent(roots, cp.linkedCenters);
		addIfPresent(roots, cp.videoDirectory);
		addIfPresent(roots, cp.x3dDirectory);
		addIfPresent(roots, cp.resultsDirectory);
		addIfPresent(roots, cp.cuasSeedDir);
		addIfPresent(roots, cp.cuasUasLogs);
		addIfPresent(roots, cp.cuasSkyMask);
		if (sets != null) {
			for (EyesisCorrectionParameters.CorrectionParameters.PathFirstLast set : sets) {
				if (set != null && set.path != null) {
					File parent = new File(set.path).getParentFile();
					if (parent != null) {
						roots.add(parent.toPath());
					}
				}
			}
		}
	}

	private static void addRootsFromConfigDir(Path configDir, Set<Path> roots) {
		if (configDir == null || !Files.isDirectory(configDir)) {
			return;
		}
		try (java.util.stream.Stream<Path> stream = Files.list(configDir)) {
			java.util.Iterator<Path> it = stream.iterator();
			while (it.hasNext()) {
				Path entry = it.next();
				if (Files.isRegularFile(entry) && entry.getFileName().toString().endsWith(".corr-xml")) {
					roots.add(entry);
					addRootsFromConfig(entry, roots);
				}
			}
		} catch (IOException e) {
			return;
		}
	}

	private static void addIfPresent(Set<Path> roots, String value) {
		if (value == null || value.trim().isEmpty()) {
			return;
		}
		roots.add(Paths.get(value).toAbsolutePath().normalize());
	}

	private static void addExtraRoots(Set<Path> roots) {
		String extra = System.getProperty(PROP_ALLOWED_EXTRA);
		if (extra == null || extra.trim().isEmpty()) {
			return;
		}
		String[] parts = extra.split("[,;]");
		for (String part : parts) {
			String trimmed = part.trim();
			if (!trimmed.isEmpty()) {
				roots.add(Paths.get(trimmed).toAbsolutePath().normalize());
			}
		}
	}

	private static long getMtime(String configPath, String configDir) {
		long max = -1L;
		if (configPath != null && !configPath.trim().isEmpty()) {
			try {
				max = Math.max(max, Files.getLastModifiedTime(Paths.get(configPath)).toMillis());
			} catch (IOException e) {
				// ignore
			}
		}
		if (configDir != null && !configDir.trim().isEmpty()) {
			try {
				max = Math.max(max, Files.getLastModifiedTime(Paths.get(configDir)).toMillis());
			} catch (IOException e) {
				// ignore
			}
		}
		if (max >= 0) {
			return max;
		}
		return -1L;
	}

	private static long getMtime(String configPath) {
		if (configPath == null || configPath.trim().isEmpty()) {
			return -1L;
		}
		try {
			return Files.getLastModifiedTime(Paths.get(configPath)).toMillis();
		} catch (IOException e) {
			return -1L;
		}
	}

	private static boolean configEquals(String a, String b) {
		if (a == null && b == null) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}
		return a.equals(b);
	}
}
