package com.narrowtux.fmm.io;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.narrowtux.fmm.model.Datastore;
import com.narrowtux.fmm.model.MatchedVersion;
import com.narrowtux.fmm.model.ModDependency;
import com.narrowtux.fmm.util.TreeOutput;
import com.narrowtux.fmm.model.Mod;
import com.narrowtux.fmm.model.ModReference;
import com.narrowtux.fmm.model.Modpack;
import com.narrowtux.fmm.model.Version;
import com.sun.org.apache.xerces.internal.impl.xpath.regex.Match;
import javafx.application.Platform;
import org.sat4j.minisat.SolverFactory;
import java.io.*;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ModpackDetectorVisitor implements FileVisitor<Path> {
private Modpack currentModpack;
private Collection<Modpack> modpacks;
private Datastore store;
private TreeOutput out = new TreeOutput().setMuted(true);
public ModpackDetectorVisitor(Collection<Modpack> modpacks, Datastore store) {
this.modpacks = modpacks;
this.store = store;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
out.println("Enter directory " + dir);
Path modList = dir.resolve("mod-list.json");
if (!dir.getFileName().toString().equals("mods") && !dir.getFileName().toString().equals("downloads") && !Files.exists(modList) && dir.getParent().equals(store.getFMMDir())) {
Modpack.writeModList(modList, false);
}
if (!dir.getFileName().toString().equals("mods") && !dir.getFileName().toString().equals("downloads") && Files.exists(modList)) {
currentModpack = modpacks.stream().filter(m -> m.getPath().equals(dir)).findAny().orElseGet(() -> new Modpack(dir.getFileName().toString(), dir));
out.println("Enter modpack " + currentModpack.getName());
//remove all mods that are missing in the directory
currentModpack.getMods().removeAll(currentModpack.getMods().stream().filter(mod -> !Files.exists(mod.getMod().getPath())).collect(Collectors.toList()));
out.push();
return FileVisitResult.CONTINUE;
}
//check if we are in the root directory because this will be passed to us once.
if (dir.getFileName().toString().equals("fmm")) {
out.push();
return FileVisitResult.CONTINUE;
}
//skip directories without a mod-list.json file, because those aren't modpacks
return FileVisitResult.SKIP_SUBTREE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (currentModpack == null) {
return FileVisitResult.CONTINUE;
}
out.println("Checking file " + file.toString());
if (currentModpack.getMods().stream().filter(mod -> mod.getMod().getPath().equals(file)).findAny().isPresent()) {
return FileVisitResult.CONTINUE;
}
if (file.getFileName().toString().endsWith(".zip")) {
Mod mod = parseMod(file);
if (mod != null) {
out.push();
out.println("Found mod " + mod.toSimpleString());
out.pull();
Path oldPath = mod.getPath();
Path newPath = store.getFMMDir().resolve("mods").resolve(oldPath.getFileName());
mod.setPath(newPath);
currentModpack.getMods().add(new ModReference(mod, currentModpack, true));
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (currentModpack != null) {
// read mod-list.json
Gson gson = new Gson();
Map<String, Object> modListData = gson.fromJson(new FileReader(new File(dir.toString(), "mod-list.json")), Map.class);
List<Map<String, Object>> modList = (List<Map<String, Object>>) modListData.get("mods");
for (Map<String, Object> mod : modList) {
String name = (String) mod.get("name");
String version = null;
if (mod.containsKey("version")) {
version = (String) mod.get("version");
}
Object enabledO = mod.get("enabled");
boolean enabled = enabledO instanceof Boolean ? (Boolean) enabledO : Boolean.valueOf((String) enabledO);
Optional<ModReference> ref = currentModpack.getMods().stream()
.filter(mod1 -> mod1.getMod().getName().equals(name))
.findAny();
ref.ifPresent(mod2 -> mod2.setEnabled(enabled));
if (!ref.isPresent()) {
Mod realMod = null;
if (version != null) {
realMod = store.getMod(name, Version.valueOf(version));
}
if (realMod == null) {
out.println("could not find mod from mod-list.json: " + name + "#" + version);
} else {
ModReference reference = new ModReference(realMod, currentModpack, enabled);
currentModpack.getMods().add(reference);
}
}
}
currentModpack.writeModList(true);
Datastore.getInstance().getModpacks().add(currentModpack);
currentModpack = null;
}
out.pull();
out.println("Leave");
return FileVisitResult.CONTINUE;
}
private static Pattern MOD_DEPENDENCY_PATTERN = Pattern.compile("(\\??)\\s?([a-zA-Z0-9\\-_]+)\\s?(([>=]+)\\s?([0-9\\.]+))?");
public static Mod parseMod(Path file) throws IOException {
FileInputStream inputStream = new FileInputStream(file.toFile());
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
ZipEntry current;
while ((current = zipInputStream.getNextEntry()) != null) {
if (current.getName().endsWith("info.json")) {
break;
}
}
if (current != null) {
Gson gson = new Gson();
JsonObject modInfo = null;
try {
modInfo = gson.fromJson(new InputStreamReader(zipInputStream), JsonObject.class);
} catch (JsonSyntaxException e) {
System.out.println("for file: " + file.toString());
e.printStackTrace();
return null;
} catch (JsonIOException e) {
e.printStackTrace();
return null;
}
String name = modInfo.get("name").getAsString();
Version version = Version.valueOf(modInfo.get("version").getAsString());
Mod mod = Datastore.getInstance().getMod(name, version);
mod.setPath(file);
try {
mod.setTitle(modInfo.get("title").getAsString());
} catch (Exception ignored) {}
try {
mod.setAuthor(modInfo.get("author").getAsString());
} catch (Exception ignored) {}
try {
mod.setContact(modInfo.get("contact").getAsString());
} catch (Exception ignored) {}
try {
mod.setHomepage(modInfo.get("homepage").getAsString());
} catch (Exception ignored) {}
try {
mod.setDescription(modInfo.get("description").getAsString());
} catch (Exception ignored) {}
JsonElement dependencies1 = modInfo.get("dependencies");
if (dependencies1 != null && dependencies1.isJsonArray()) {
JsonArray dependencies = dependencies1.getAsJsonArray();
for (JsonElement elem : dependencies) {
String depString = elem.getAsString();
Matcher matcher = MOD_DEPENDENCY_PATTERN.matcher(depString);
if (matcher.matches()) {
String optional = matcher.group(1);
String depName = matcher.group(2);
MatchedVersion matchedVersion = null;
if (matcher.group(3) != null) {
matchedVersion = MatchedVersion.valueOf(matcher.group(3));
}
ModDependency dep = new ModDependency(mod, depName, matchedVersion, optional.equals("?"));
mod.getDependencies().add(dep);
} else {
System.out.println("Mod dependency string '" + depString + "' is incorrect.");
}
}
}
zipInputStream.closeEntry();
zipInputStream.close();
return mod;
}
return null;
}
}