/*
 *  Copyright (C) 2006-2022  Ronald Blankendaal
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package exodos;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.gui.interfaces.PreProgressNotifyable;
import org.dbgl.model.aggregate.DosboxVersion;
import org.dbgl.model.aggregate.Profile;
import org.dbgl.model.repository.ProfileRepository;
import org.dbgl.service.DatabaseService;
import org.dbgl.service.FileLocationService;
import org.dbgl.util.FilesUtils;
import org.dbgl.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public class Import {

	private static final String IMPORTER_VERSION = "0.94";

	private static boolean listOnly_ = false;
	private static boolean analyzeOnly_ = false;
	private static boolean verboseOutput_ = false;

	private static void displaySyntax() {
		System.out.println("Use: Import <inputexodosdir> [-l] [-a] [-v] [game-1] [game-2] [game-N]");
		System.out.println("-l\t\tList game titles and abbreviations, don't import");
		System.out.println("-a\t\tAnalyze only, don't import");
		System.out.println("-v\t\tVerbose output");
		System.out.println("Optional: game(s) to import based on title or abbreviation");
		System.exit(1);
	}

	public static void main(String[] args) {
		System.out.println("Imports eXoDOS games into DBGL (v" + IMPORTER_VERSION + ")");
		System.out.println();

		if (args.length < 1)
			displaySyntax();

		File srcDir = new File(args[0]);
		List<String> impTitles = new ArrayList<>();

		if (args.length > 1) {
			for (int i = 1; i < args.length; i++) {
				if (args[i].equalsIgnoreCase("-l"))
					listOnly_ = true;
				else if (args[i].equalsIgnoreCase("-a"))
					analyzeOnly_ = true;
				else if (args[i].equalsIgnoreCase("-v"))
					verboseOutput_ = true;
				else
					impTitles.add(args[i].toLowerCase());
			}
		}

		if (listOnly_)
			System.out.println("* List only");
		if (analyzeOnly_)
			System.out.println("* Analyze only");
		if (verboseOutput_)
			System.out.println("* Verbose output");
		if (!impTitles.isEmpty())
			System.out.println("* Processing: " + StringUtils.join(impTitles, ", "));
		else
			System.out.println("* Processing all games");

		System.out.println();

		if (ExoUtils.validateExoV5Parameters(srcDir)) {
			System.out.println("eXoDOS V5 found");

			DosboxVersion defaultDosboxVersion = ExoUtils.findDefaultDosboxVersion(verboseOutput_);

			new Import().importData(impTitles, srcDir, defaultDosboxVersion);

			try {
				DatabaseService.getInstance().shutdown();
			} catch (SQLException e) {
				// nothing we can do
			}
		}
	}

	private void importData(final List<String> impTitles, final File srcDir, final DosboxVersion dosboxVersion) {
		final File contentDir = new File(srcDir, ExoUtils.EXODOS_V5_CONTENT_DIR);
		final File gameZipsDir = new File(srcDir, ExoUtils.EXODOS_V5_GAMEZIPS_DIR);

		try (ZipFile xodosZipfile = new ZipFile(new File(contentDir, ExoUtils.XODOS_METADATA_ARC), ExoUtils.CP437);
				ZipFile dosZipfile = new ZipFile(new File(contentDir, ExoUtils.DOS_METADATA_ARC), ExoUtils.CP437)) {

			final List<ZipEntry> xodosZipEntries = ExoUtils.listEntries(xodosZipfile, true);
			final List<ZipEntry> dosZipEntries = ExoUtils.listEntries(dosZipfile, true);

			final List<ZipEntry> dosboxConfEntries = dosZipEntries.parallelStream().filter(x -> x.getName().toLowerCase().endsWith(FileLocationService.DOSBOX_CONF_STRING)).collect(
				Collectors.toList());
			final List<ZipEntry> imageEntries = xodosZipEntries.parallelStream().filter(x -> x.getName().startsWith(ExoUtils.METADATA_IMAGESDIR)).collect(Collectors.toList());
			final List<ZipEntry> extrasEntries = dosZipEntries.parallelStream().filter(x -> ExoUtils.EXTRAFILES.contains(FilenameUtils.getExtension(x.getName()).toLowerCase())).collect(
				Collectors.toList());
			final List<ZipEntry> manualEntries = xodosZipEntries.parallelStream().filter(
				x -> x.getName().startsWith(ExoUtils.METADATA_MANUALSDIR) && ExoUtils.EXTRAFILES.contains(FilenameUtils.getExtension(x.getName()).toLowerCase())).collect(Collectors.toList());
			final List<ZipEntry> musicEntries = xodosZipEntries.parallelStream().filter(
				x -> x.getName().startsWith(ExoUtils.METADATA_MUSICDIR) && ExoUtils.EXTRAFILES.contains(FilenameUtils.getExtension(x.getName()).toLowerCase())).collect(Collectors.toList());
			final ZipEntry msdosXmlEntry = xodosZipEntries.parallelStream().filter(x -> x.getName().equals(ExoUtils.XODOS_METADATA_XML)).findAny().orElse(null);

			final Document doc = XmlUtils.getDocumentBuilder().parse(xodosZipfile.getInputStream(msdosXmlEntry));
			final NodeList gameNodes = (NodeList)XPathFactory.newInstance().newXPath().evaluate("/LaunchBox/Game", doc, XPathConstants.NODESET);

			int processed = 0;

			for (int i = 0; i < gameNodes.getLength(); i++) {
				Element gameNode = (Element)gameNodes.item(i);

				String gameApplicationPath = XmlUtils.getTextValue(gameNode, "ApplicationPath");
				String gameTitle = StringUtils.defaultString(XmlUtils.getTextValue(gameNode, "Title"));
				String fullGameTitle = FilenameUtils.getBaseName(gameApplicationPath);
				File gamePath = new File(gameApplicationPath).getParentFile();
				String gameDirName = gamePath != null ? gamePath.getName(): StringUtils.EMPTY;
				if (StringUtils.isBlank(fullGameTitle) || (!impTitles.isEmpty() && !impTitles.contains(fullGameTitle.toLowerCase()) && !impTitles.contains(gameDirName.toLowerCase())))
					continue;

				if (listOnly_) {
					System.out.println(String.format("%4d %-100s %-10s", i + 1, fullGameTitle, gameDirName));
					continue;
				}

				File gameSrcZipfile = new File(gameZipsDir, fullGameTitle + ".zip");
				if (!FilesUtils.isExistingFile(gameSrcZipfile)) {
					System.err.println(fullGameTitle + ": Zip file " + gameSrcZipfile + " is missing, skipping");
					continue;
				}

				File canonicalGamePath = new File(FileLocationService.getInstance().getDosroot(), gameDirName);
				String confPathAndFile = FilenameUtils.separatorsToUnix(new File(gamePath, FileLocationService.DOSBOX_CONF_STRING).getPath());
				ZipEntry confEntry = dosboxConfEntries.parallelStream().filter(x -> x.getName().equalsIgnoreCase(confPathAndFile)).findAny().orElse(null);
				if (confEntry == null) {
					System.err.println(fullGameTitle + ": Zip file " + dosZipfile.getName() + " does not contain " + confPathAndFile + ", skipping");
					continue;
				}

				Collection<ZipEntry> gameImageEntries = ExoUtils.getImages(imageEntries, gameTitle);
				List<ZipReference> gameCombinedExtraEntries = ExoUtils.getCombinedExtras(xodosZipfile, dosZipfile, extrasEntries, manualEntries, musicEntries, fullGameTitle, gameTitle, gameDirName);

				if (gameImageEntries.isEmpty() && verboseOutput_)
					System.out.println(fullGameTitle + ": No images found");

				try (ZipFile gameZipfile = new ZipFile(gameSrcZipfile, ExoUtils.CP437)) {
					List<ZipEntry> gameZipEntries = ExoUtils.listEntries(gameZipfile, false);

					Profile profile = ExoUtils.createProfile(gameNode, fullGameTitle, gameTitle, gamePath, gameDirName, dosboxVersion, confEntry, dosZipfile, gameSrcZipfile.getPath(), gameZipEntries,
						gameCombinedExtraEntries, verboseOutput_);

					if (!analyzeOnly_) {
						profile = new ProfileRepository().add(profile);

						PreProgressNotifyable prog = new AsciiProgressBar(fullGameTitle, Stream.of(gameImageEntries, gameZipEntries).flatMap(Collection::stream).mapToLong(ZipEntry::getSize).sum()
								+ gameCombinedExtraEntries.stream().mapToLong(x -> x.zipEntry_.getSize()).sum());

						if (!gameImageEntries.isEmpty())
							ExoUtils.unzip(xodosZipfile, gameImageEntries, profile.getCanonicalCaptures(), false, true, prog);
						if (!gameCombinedExtraEntries.isEmpty())
							ExoUtils.unzip(gameCombinedExtraEntries, new File(canonicalGamePath, ExoUtils.EXTRAS_DIR), prog);
						ExoUtils.unzip(gameZipfile, gameZipEntries, canonicalGamePath, true, false, prog);

						double p = (impTitles != null) && !impTitles.isEmpty() ? (double)++processed / (double)impTitles.size() * 100d: (double)(i + 1) / (double)gameNodes.getLength() * 100d;
						System.out.println(String.format("\r%s Imported. Overall progress: %3.1f%%", StringUtils.rightPad(StringUtils.abbreviate(fullGameTitle, 68), 70, '.'), p));
					}
				}
			}

			System.out.println(StringUtils.LF + StringUtils.LF + "Finished.");

		} catch (SQLException | IOException | SAXException | ParserConfigurationException | XPathExpressionException e) {
			e.printStackTrace();
		}
	}
}