package exodos;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.gui.interfaces.PreProgressNotifyable;
import org.dbgl.model.FileLocation;
import org.dbgl.model.Link;
import org.dbgl.model.SearchResult;
import org.dbgl.model.SearchResult.ResultType;
import org.dbgl.model.aggregate.DosboxVersion;
import org.dbgl.model.aggregate.Profile;
import org.dbgl.model.conf.Autoexec;
import org.dbgl.model.conf.mount.DirMount;
import org.dbgl.model.conf.mount.ImageMount;
import org.dbgl.model.conf.mount.Mount;
import org.dbgl.model.factory.ProfileFactory;
import org.dbgl.model.repository.BaseRepository;
import org.dbgl.model.repository.DosboxVersionRepository;
import org.dbgl.service.FileLocationService;
import org.dbgl.service.TextService;
import org.dbgl.util.FilesUtils;
import org.dbgl.util.ShortFilenameUtils;
import org.dbgl.util.XmlUtils;
import org.w3c.dom.Element;


class ExoUtils {

	static final String DOS_METADATA_ARC = "!DOSmetadata.zip";
	static final String XODOS_METADATA_ARC = "XODOSMetadata.zip";
	static final String EXODOS_DIR = "eXoDOS";
	static final String UTIL_DIR = "util";
	static final String EXODOS_V5_CONTENT_DIR = "Content";
	static final String EXODOS_V5_EXO_DIR = "eXo";
	static final String EXODOS_V5_GAMEZIPS_DIR = EXODOS_V5_EXO_DIR + File.separator + EXODOS_DIR;
	static final String XODOS_METADATA_XML = "xml/MS-DOS.xml";
	static final String METADATA_IMAGESDIR = "Images/MS-DOS";
	static final String METADATA_MANUALSDIR = "Manuals/MS-DOS";
	static final String METADATA_MUSICDIR = "Music/MS-DOS";
	static final String DOS_GAMEDIR = "eXo/eXoDOS/!dos/";
	static final String EXTRAS_DIR = "Extras";
	static final List<String> EXTRAFILES = Arrays.asList("rtf", "pdf", "doc", "xls", "htm", "html", "txt", "jpg", "png", "bmp", "gif", "flac", "wav", "ogg", "mp2", "mp3", "mp4", "mod", "s3m", "amf",
		"voc", "xm");

	private static final Pattern IMG_FILENAME_PTRN = Pattern.compile("^(.*?)(\\.........\\-....\\-....\\-....\\-............)?\\-[0-9][0-9]( \\(.\\))?\\.(.*)$");

	static final Charset CP437 = Charset.forName("CP437");

	private static final String[] DISKIMAGES = {".ISO", ".CUE", ".BIN", ".IMG", ".GOG", ".IMA"};
	private static final String[] DOS_EXECUTABLES = {".BAT", ".COM", ".EXE"};
	private static final List<String> DOS_EXTENSIONS = Arrays.asList("BAT", "COM", "EXE");
	private static final List<String> BOOTER_EXTENSIONS = Arrays.asList("IMG", "JRC");

	private ExoUtils() {
	}

	static boolean validateExoV5Parameters(File inputDir) {
		if (!inputDir.exists()) {
			System.out.println("The directory [" + inputDir + "] does not exist.");
			return false;
		}
		File contentDir = new File(inputDir, EXODOS_V5_CONTENT_DIR);
		if (!contentDir.exists()) {
			System.out.println("The directory [" + inputDir + "] does not contain the [" + EXODOS_V5_CONTENT_DIR + "] directory.");
			return false;
		}
		File exoDir = new File(inputDir, EXODOS_V5_EXO_DIR);
		if (!exoDir.exists()) {
			System.out.println("The directory [" + inputDir + "] does not contain the [" + EXODOS_V5_EXO_DIR + "] directory.");
			return false;
		}
		File gameZipsDir = new File(exoDir, EXODOS_DIR);
		if (!gameZipsDir.exists()) {
			System.out.println("The directory [" + exoDir + "] does not contain the [" + EXODOS_DIR + "] directory.");
			return false;
		}
		File utilDir = new File(exoDir, UTIL_DIR);
		if (!utilDir.exists()) {
			System.out.println("The directory [" + exoDir + "] does not contain the [" + UTIL_DIR + "] directory.");
			return false;
		}
		File dosMetadataFile = new File(contentDir, DOS_METADATA_ARC);
		if (!FilesUtils.isExistingFile(dosMetadataFile)) {
			System.out.println("The file [" + dosMetadataFile + "] does not exist.");
			return false;
		}
		File xodosMetadataFile = new File(contentDir, XODOS_METADATA_ARC);
		if (!FilesUtils.isExistingFile(xodosMetadataFile)) {
			System.out.println("The file [" + xodosMetadataFile + "] does not exist.");
			return false;
		}
		return true;
	}

	static DosboxVersion findDefaultDosboxVersion(boolean verboseOutput) {
		List<DosboxVersion> dbversionsList = null;
		try {

			DosboxVersionRepository dosboxRepo = new DosboxVersionRepository();
			dbversionsList = dosboxRepo.listAll();

			if (BaseRepository.findDefault(dbversionsList) == null) {
				SearchResult result = FileLocationService.getInstance().findDosbox();
				if (result.result_ == ResultType.COMPLETE) {
					new DosboxVersionRepository().add(result.dosbox_);
					dbversionsList = dosboxRepo.listAll();
				}
				if (BaseRepository.findDefault(dbversionsList) == null) {
					System.out.println("DOSBox installation could not be located, exiting.");
					System.exit(1);
				}
			}

			if (verboseOutput)
				System.out.println("Using DOSBox installation located in: [" + BaseRepository.findDefault(dbversionsList).getConfigurationCanonicalFile().getPath() + "]");
		} catch (SQLException e) {
			e.printStackTrace();
			System.exit(1);
		}

		return BaseRepository.findDefault(dbversionsList);
	}

	static Collection<ZipEntry> getImages(List<ZipEntry> imageEntries, String gameTitle) {
		String gameTitleClean = cleanupGameTitle(gameTitle);
		return imageEntries.parallelStream().filter(x -> {
			Matcher matcher = IMG_FILENAME_PTRN.matcher(FilenameUtils.getName(x.getName()));
			return matcher.matches() && matcher.groupCount() == 4 && matcher.group(1).equalsIgnoreCase(gameTitleClean);
		}).collect(Collectors.toMap(ZipEntry::getCrc, Function.identity(), (entry1, entry2) -> entry1)).values();
	}

	static List<ZipReference> getCombinedExtras(ZipFile xodosZipfile, ZipFile dosZipfile, List<ZipEntry> extrasEntries, List<ZipEntry> manualEntries, List<ZipEntry> musicEntries, String fullGameTitle,
			String gameTitle, String gameDirName) {
		String gameTitleClean = cleanupGameTitle(gameTitle);
		List<ZipReference> gameCombinedExtraEntries = new ArrayList<>();
		addToCollection(dosZipfile, extrasEntries.parallelStream().filter(x -> x.getName().startsWith(DOS_GAMEDIR + gameDirName + '/' + EXTRAS_DIR)).collect(
			Collectors.toMap(ZipEntry::getCrc, Function.identity(), (entry1, entry2) -> entry1)).values(), gameCombinedExtraEntries);
		addToCollection(xodosZipfile,
			manualEntries.parallelStream().filter(x -> FilenameUtils.getBaseName(x.getName()).equals(fullGameTitle) || FilenameUtils.getBaseName(x.getName()).equals(gameTitleClean)).collect(
				Collectors.toMap(ZipEntry::getCrc, Function.identity(), (entry1, entry2) -> entry1)).values(),
			gameCombinedExtraEntries);
		addToCollection(xodosZipfile,
			musicEntries.parallelStream().filter(x -> FilenameUtils.getBaseName(x.getName()).equals(fullGameTitle) || FilenameUtils.getBaseName(x.getName()).equals(gameTitleClean)).collect(
				Collectors.toMap(ZipEntry::getCrc, Function.identity(), (entry1, entry2) -> entry1)).values(),
			gameCombinedExtraEntries);
		return gameCombinedExtraEntries;
	}

	static Profile createProfile(Element gameNode, String fullGameTitle, String titleString, File gamePath, String gameDirName, DosboxVersion dosboxVersion, ZipEntry confEntry, ZipFile dosZipfile,
			String gameSrcZipfile, List<ZipEntry> gameZipEntries, List<ZipReference> gameExtraEntries, boolean verboseOutput) throws IOException {
		HashSet<String> filesInZip = gameZipEntries.stream().filter(x -> !x.isDirectory()).map(x -> FilenameUtils.separatorsToWindows(x.getName().toUpperCase())).collect(
			Collectors.toCollection(HashSet::new));

		String developerString = StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "Developer"));
		String publisherString = StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "Publisher"));
		String genreString = StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "Genre"));
		String yearString = StringUtils.left(StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "ReleaseDate")), 4);
		String statusString = StringUtils.EMPTY;
		String notesString = StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "Notes"));
		String ratingString = XmlUtils.getTextValue(gameNode, "CommunityStarRating");
		boolean favorite = false;
		String[] links = new String[Profile.NR_OF_LINK_DESTINATIONS];
		Arrays.fill(links, StringUtils.EMPTY);
		String[] linkTitles = new String[Profile.NR_OF_LINK_TITLES];
		Arrays.fill(linkTitles, StringUtils.EMPTY);
		String confPathAndFile = new File(gameDirName, FileLocationService.DOSBOX_CONF_STRING).getPath();

		List<Link> extraLinks = gameExtraEntries.parallelStream().map(x -> new Link(x.name_, FileLocationService.DOSROOT_DIR_STRING + gameDirName + '/' + EXTRAS_DIR + '/' + x.name_)).limit(
			Profile.NR_OF_LINK_DESTINATIONS).collect(Collectors.toList());

		IntStream.range(0, extraLinks.size()).forEach(l -> {
			links[l] = extraLinks.get(l).getDestination();
			linkTitles[l] = extraLinks.get(l).getTitle();
		});

		Profile profile = ProfileFactory.create(titleString, developerString, publisherString, genreString, yearString, statusString, notesString, favorite, links, linkTitles, ratingString,
			dosboxVersion, confPathAndFile);

		String warnings = profile.loadConfigurationData(TextService.getInstance(), StringUtils.join(IOUtils.readLines(dosZipfile.getInputStream(confEntry), StandardCharsets.UTF_8), StringUtils.LF),
			new File(gameSrcZipfile));
		if (StringUtils.isNotBlank(warnings)) {
			System.out.println(fullGameTitle + ": " + warnings);
		}

		// minor bit of clean up of the actual dosbox configuration
		if ("64".equals(profile.getConfiguration().getValue("dosbox", "memsize")))
			profile.getConfiguration().setValue("dosbox", "memsize", "63");
		if ("overlay".equals(profile.getConfiguration().getValue("sdl", "output")))
			profile.getConfiguration().removeValue("sdl", "output");

		Autoexec autoexec = profile.getConfiguration().getAutoexec();
		autoexec.migrate(new FileLocation(EXODOS_DIR, FileLocationService.getInstance().dosrootRelative()), FileLocationService.getInstance().getDosrootLocation());

		if (!fixupFileLocations(fullGameTitle, profile, gameSrcZipfile, filesInZip, verboseOutput)) {
			String main = autoexec.getGameMain();
			if (StringUtils.isNotEmpty(main))
				System.out.println(fullGameTitle + ": WARNING - Main file [" + main + "] not found inside [" + gameSrcZipfile + "]");
		}

		// Check for multiple root entries
		if (filesInZip.parallelStream().filter(x1 -> x1.indexOf('\\') == -1).limit(2).count() == 2) {
			autoexec.setBaseDir(gamePath);
			if (verboseOutput)
				System.out.println(fullGameTitle + ": " + gameDirName + " is moved one directory level deeper");
		}

		autoexec.migrate(FileLocationService.getInstance().getDosrootLocation(), new FileLocation(gameDirName, FileLocationService.getInstance().dosrootRelative()));

		return profile;
	}

	private static boolean fixupFileLocations(String fullGameTitle, Profile profile, String zipFile, Set<String> list, boolean verboseOutput) {
		for (Mount m: profile.getNettoMountingPoints()) {
			if (m instanceof ImageMount) {
				File[] files = ((ImageMount)m).getImgPaths();
				String[] newFiles = ((ImageMount)m).getImgPathStrings();
				for (int i = 0; i < files.length; i++) {
					String filename = files[i].getPath().toUpperCase();
					if (!list.contains(filename)) {
						String dst = list.stream().filter(x -> FilenameUtils.getName(x).equals(FilenameUtils.getName(filename))).findFirst().orElse(null);
						if (dst != null) {
							newFiles[i] = dst;
							if (verboseOutput)
								System.out.println(fullGameTitle + ": Image-mounted file [" + filename + "] redirected to [" + dst + "]");
						} else {
							System.out.println(fullGameTitle + ": WARNING - Image-mounted file [" + filename + "] not found inside [" + zipFile + "]");
						}
					}
				}
				((ImageMount)m).setImgPaths(newFiles);
			}
		}

		Autoexec autoexec = profile.getConfiguration().getAutoexec();

		String main = getActualFilename(FilenameUtils.separatorsToWindows(autoexec.getGameMain()).toUpperCase());

		Set<String> fileSet = FilenameUtils.getName(main).contains("~") ? ShortFilenameUtils.convertToShortFileSet(list): list;
		if (fileSet.contains(main))
			return true;

		List<String> exts = autoexec.isDos() ? DOS_EXTENSIONS: BOOTER_EXTENSIONS;
		Set<String> exeSet = fileSet.stream().filter(x -> exts.contains(FilenameUtils.getExtension(x))).sorted(new FilesUtils.FilenameComparator()).collect(
			Collectors.toCollection(LinkedHashSet::new));
		Set<String> mainsOtherExtensions = DOS_EXTENSIONS.stream().filter(x -> !x.equals(FilenameUtils.getExtension(main))).map(
			x -> FilenameUtils.getPath(main) + FilenameUtils.getBaseName(main) + '.' + x).collect(Collectors.toSet());

		Optional<String> mainOtherExtension = exeSet.stream().filter(x -> mainsOtherExtensions.contains(x) || FilenameUtils.getName(x).equals(FilenameUtils.getName(main))
				|| mainsOtherExtensions.stream().anyMatch(y -> FilenameUtils.getName(x).equals(FilenameUtils.getName(y)))).findFirst();
		if (mainOtherExtension.isPresent()) {
			autoexec.setGameMainPath(new File(mainOtherExtension.get()).getParentFile());
			if (verboseOutput)
				System.out.println(fullGameTitle + ": Main file [" + main + "] located at [" + mainOtherExtension.get() + "]");
			return true;
		}

		String[] setPaths = autoexec.getSetPathsFromCustomSections();
		if (FilenameUtils.getName(main).startsWith("WIN")) {
			String mainBaseFolder = FilenameUtils.getPath(main);
			for (String setPath: setPaths) {
				char pd = setPath.toUpperCase().charAt(0);

				for (Mount m: profile.getNettoMountingPoints()) {
					if (m instanceof DirMount && m.getDrive() == pd) {
						File cp = new File(((DirMount)m).getPathString(), setPath.substring(3));
						File f1 = new File(cp, main);
						String newMain = findSuitableExtension(FilenameUtils.separatorsToWindows(f1.getPath().toUpperCase()), fileSet);
						if (newMain != null) {
							autoexec.setGameMain(newMain);
							if (verboseOutput)
								System.out.println(fullGameTitle + ": Main file [" + main + "] located using set path, changed to [" + newMain + "]");

							// Check and fix path to Windows parameter executable(s)
							String params = autoexec.getParameters();
							if (StringUtils.isNotEmpty(params)) {
								String[] paramArray = StringUtils.split(params);
								String[] fixedParamArray = StringUtils.split(params);
								for (int i = 0; i < paramArray.length; i++) {
									if (paramArray[i].startsWith("/") || paramArray[i].startsWith("-"))
										continue; // unlikely to be file parameter, accept in any case

									String p = fixParameterPath(fileSet, mainBaseFolder, ((DirMount)m).getPathString(), paramArray[i]);
									if (p == null) {
										if (verboseOutput)
											System.out.println(fullGameTitle + ": Parameter [" + paramArray[i] + "] not found, might not be a file or folder");
									} else {
										fixedParamArray[i] = p;
									}
								}
								autoexec.setParameters(StringUtils.join(fixedParamArray, ' '));
								if (verboseOutput)
									System.out.println(fullGameTitle + ": Main file parameter(s) [" + params + "] changed to [" + autoexec.getParameters() + "]");
							}
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	private static String getActualFilename(String main) {
		OptionalInt idx = Stream.of(DISKIMAGES).filter(x -> main.contains(x + '\\')).mapToInt(x -> main.indexOf(x + '\\') + x.length()).findFirst();
		return idx.isPresent() ? main.substring(0, idx.getAsInt()): main;
	}

	private static String fixParameterPath(Set<String> fileSet, String mainBaseFolder, String mountPath, String param) {
		String newParamFile = findSuitableExtension(FilenameUtils.normalize(new File(mainBaseFolder, param).getPath()), fileSet);
		if (newParamFile != null)
			return newParamFile.substring(mountPath.length());
		newParamFile = findSuitableExtension(FilenameUtils.normalize(new File(FilenameUtils.getPath(mainBaseFolder), param).getPath()), fileSet);
		if (newParamFile != null)
			return newParamFile.substring(mountPath.length());
		newParamFile = findSuitableExtension(FilenameUtils.normalize(new File(mountPath, param).getPath()), fileSet);
		if (newParamFile != null)
			return newParamFile.substring(mountPath.length());
		return null;
	}

	private static String findSuitableExtension(String main, Set<String> fileSet) {
		return Stream.of(DOS_EXECUTABLES).filter(x -> fileSet.contains(FilenameUtils.removeExtension(main) + x)).map(x -> FilenameUtils.removeExtension(main) + x).findAny().orElse(null);
	}

	static void addToCollection(ZipFile zipFile, Collection<ZipEntry> entries, List<ZipReference> combinedEntries) {
		for (ZipEntry entry: entries) {
			StringBuilder suggestion = new StringBuilder(FilenameUtils.getName(cleanup(entry)));
			int c = 1;
			do {
				if (c > 1) {
					suggestion.setLength(0);
					suggestion.append(FilenameUtils.getBaseName(entry.getName()) + "(" + c + ")" + FilenameUtils.EXTENSION_SEPARATOR + FilenameUtils.getExtension(entry.getName()));
				}
				c++;
			} while (combinedEntries.parallelStream().anyMatch(x -> x.name_.equalsIgnoreCase(suggestion.toString())));

			combinedEntries.add(new ZipReference(zipFile, entry, suggestion.toString()));
		}
	}

	static List<ZipEntry> listEntries(ZipFile zipFile, boolean ignoreDirectories) {
		List<ZipEntry> entries = new ArrayList<>();
		for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) {
			try {
				ZipEntry entry = e.nextElement();
				if (!ignoreDirectories || !entry.isDirectory())
					entries.add(entry);
			} catch (IllegalArgumentException iae) {
				System.out.println("WARNING - Zip file [" + zipFile + "] contains an entry with problematic characters in its filename");
			}
		}
		return entries;
	}

	static void unzip(ZipFile zipFile, Collection<ZipEntry> entries, File dstDir, boolean keepDirStructure, boolean prependCounter, PreProgressNotifyable prog) throws IOException {
		if (keepDirStructure) {
			Set<String> dirs = new HashSet<>();
			for (ZipEntry entry: entries) {
				String fullPath = FilenameUtils.getFullPath(entry.getName());
				if (dirs.stream().noneMatch(x -> x.startsWith(fullPath))) {
					dirs.removeIf(fullPath::startsWith);
					dirs.add(fullPath);
				}
			}
			dirs.forEach(x -> new File(dstDir, x).mkdirs());
		} else {
			dstDir.mkdirs();
		}

		int counter = 1;
		for (ZipEntry entry: entries) {
			if (!entry.isDirectory())
				try {
					File dstFile = new File(dstDir, keepDirStructure ? entry.getName(): FilenameUtils.getName(entry.getName()));
					if (prependCounter)
						dstFile = new File(dstFile.getParentFile(), String.format("%02d", counter++) + "_" + dstFile.getName());
					try (InputStream zis = zipFile.getInputStream(entry)) {
						prog.setPreProgress(entry.getSize());
						prog.incrProgress(Files.copy(zis, dstFile.toPath()));
					}
				} catch (IllegalArgumentException e) {
					System.out.println("WARNING - Zip file [" + zipFile + "] contains an entry with problematic characters in its filename");
				}
		}
	}

	static void unzip(List<ZipReference> gameCombinedExtraEntries, File dstDir, PreProgressNotifyable prog) throws IOException {
		dstDir.mkdirs();

		for (ZipReference dst: gameCombinedExtraEntries) {
			try {
				File dstFile = new File(dstDir, dst.name_);
				try (InputStream zis = dst.zipFile_.getInputStream(dst.zipEntry_)) {
					prog.setPreProgress(dst.zipEntry_.getSize());
					prog.incrProgress(Files.copy(zis, dstFile.toPath()));
				}
			} catch (IllegalArgumentException e) {
				System.out.println("WARNING - Zip file [" + dst.zipFile_ + "] contains an entry with problematic characters in its filename");
			}
		}
	}

	static void copyZipData(File srcFile, File baseDirectory, ZipOutputStream zos, PreProgressNotifyable prog) throws IOException {
		try (ZipFile zipFile = new ZipFile(srcFile, CP437)) {
			for (Enumeration<? extends ZipEntry> entries = zipFile.entries(); entries.hasMoreElements();) {
				try {
					ZipEntry srcEntry = entries.nextElement();
					zipEntry(zipFile, srcEntry, FilesUtils.toArchivePath(new File(baseDirectory, cleanup(srcEntry)), srcEntry.isDirectory()), zos, prog);
				} catch (IllegalArgumentException e) {
					System.out.println("\nWARNING: Zip file [" + zipFile.getName() + "] contains an entry with problematic characters in its filename");
				}
			}
		}
	}

	public static void copyZipData(ZipFile zipFile, Collection<ZipEntry> captures, File baseDirectory, ZipOutputStream zos, PreProgressNotifyable prog) throws IOException {
		int counter = 1;
		for (ZipEntry srcEntry: captures) {
			try {
				String name = String.format("%02d", counter++) + "_" + FilenameUtils.getName(cleanup(srcEntry));
				zipEntry(zipFile, srcEntry, FilenameUtils.separatorsToUnix(new File(baseDirectory, name).getPath()), zos, prog);
			} catch (IllegalArgumentException e) {
				System.out.println("\nWARNING: Zip file [" + zipFile.getName() + "] contains an entry with problematic characters in its filename");
			}
		}
	}

	public static void copyZipData(List<ZipReference> extras, File baseDirectory, ZipOutputStream zos, PreProgressNotifyable prog) throws IOException {
		for (ZipReference srcReference: extras) {
			try {
				zipEntry(srcReference.zipFile_, srcReference.zipEntry_, FilenameUtils.separatorsToUnix(new File(baseDirectory, srcReference.name_).getPath()), zos, prog);
			} catch (IllegalArgumentException e) {
				System.out.println("\nWARNING: Zip file [" + srcReference.zipFile_.getName() + "] contains an entry with problematic characters in its filename");
			}
		}
	}

	private static ZipEntry zipEntry(ZipFile zipFile, ZipEntry srcEntry, String dstName, ZipOutputStream zos, PreProgressNotifyable prog) throws IOException {
		ZipEntry dstEntry = new ZipEntry(dstName);
		dstEntry.setComment(srcEntry.getComment());
		dstEntry.setTime(srcEntry.getTime());
		zos.putNextEntry(dstEntry);
		if (!srcEntry.isDirectory()) {
			prog.setPreProgress(srcEntry.getSize());
			prog.incrProgress(IOUtils.copy(zipFile.getInputStream(srcEntry), zos));
		}
		zos.closeEntry();
		return dstEntry;
	}

	private static String cleanupGameTitle(String gameTitle) {
		return gameTitle.replace("/'", "_").replace("':", "_").replace("/", "_").replace("?", "_").replace(":", "_").replace("'", "_").replace("*", "_").replace("\\", "_").replace("\u2219",
			"\u00B7").replace("\u014D", "o").replace("\u0142", "l").replace("\u015B", "s").replace("\u010D", "c").replace("\u25CF", "#u25cf").replace("\u015B", "s").replace("\u0107", "c");
	}

	private static String cleanup(ZipEntry entry) {
		return entry.getName().replace((char)15, '\u263C');
	}
}
