/*
 *  Copyright (C) 2006-2019  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 org.dbgl.gui.dialog.wizard;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.dbgl.gui.GeneralPurposeDialogs;
import org.dbgl.gui.abstractdialog.JobWizardDialog;
import org.dbgl.gui.dialog.ProgressDialog;
import org.dbgl.gui.interfaces.ProgressNotifyable;
import org.dbgl.gui.thread.ImportThread;
import org.dbgl.model.GamePack;
import org.dbgl.model.aggregate.DosboxVersion;
import org.dbgl.model.entity.GamePackEntry;
import org.dbgl.model.repository.DosboxVersionRepository;
import org.dbgl.service.FileLocationService;
import org.dbgl.service.ImportExportProfilesService;
import org.dbgl.service.TextService;
import org.dbgl.util.FilesUtils;
import org.dbgl.util.archive.SevenzipExtractSingleFileCallback;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import SevenZip.MyRandomAccessFile;
import SevenZip.Archive.IInArchive;
import SevenZip.Archive.SevenZipEntry;
import SevenZip.Archive.SevenZip.Handler;


public class ImportDialog extends JobWizardDialog<Boolean> {

	private Button fullGames_, fullSettingsButton_;
	private Button importCapturesButton_, importMapperfilesButton_, importNativeCommandsButton_, useOrgConf_;
	private Button customValues_, customFields_;
	private Table profilesTable_, impDbVersionsList_;

	private List<DosboxVersion> dbversionsList_;
	private File archive_;
	private GamePack gamePack_;
	private StringBuffer messageLog_;

	private List<Integer> dbmapping_; // mapping to dbversion IDs

	public ImportDialog(Shell parent, List<DosboxVersion> dbList, File archive) {
		super(parent, "import");
		dbversionsList_ = dbList;
		archive_ = archive;
	}

	@Override
	protected String getDialogTitle() {
		return text_.get("dialog.import.title");
	}

	@Override
	protected boolean prepare() {
		messageLog_ = new StringBuffer();
		try {
			Document doc = getProfilesXmlDocFromZip();
			if (doc == null)
				throw new ZipException(text_.get("dialog.import.error.gamepackarchivemissingprofilesxml"));
			gamePack_ = new GamePack();
			messageLog_.append(ImportExportProfilesService.doImport(doc, gamePack_));
		} catch (ParserConfigurationException | SAXException | ZipException e) {
			GeneralPurposeDialogs.fatalMessage(getParent(), e.getMessage(), e);
			return false;
		} catch (IOException e) {
			messageLog_.append(text_.get("general.error.openfile", new Object[] {archive_})).append('\n').append(e.toString()).append('\n');
			GeneralPurposeDialogs.fatalMessage(getParent(), messageLog_.toString(), e);
			e.printStackTrace();
			return false;
		} catch (ParseException e) {
			messageLog_.append(e.toString()).append('\n');
			e.printStackTrace();
		} catch (XPathExpressionException e) {
			messageLog_.append(text_.get("dialog.import.error.profilesxmlinvalidformat", new Object[] {e.toString()})).append('\n');
			e.printStackTrace();
		}
		if (messageLog_.length() == 0) {
			messageLog_.append(text_.get("dialog.import.notice.importinformation",
				new Object[] {gamePack_.getVersion(), gamePack_.getCreationDate(), gamePack_.getCreationApp(), gamePack_.getCreationAppVersion()})).append('\n');
		}
		return true;
	}

	public Document getProfilesXmlDocFromZip() throws IOException, ParserConfigurationException, SAXException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		String archive = archive_.getPath();

		Document doc = null;

		if (archive.toLowerCase().endsWith(FilesUtils.ARCHIVES[0])) { // zip
			ZipFile zf = new ZipFile(archive);
			for (Enumeration<? extends ZipEntry> entries = zf.entries(); entries.hasMoreElements();) {
				ZipEntry entry = entries.nextElement();
				if (!entry.isDirectory() && entry.getName().equalsIgnoreCase(ImportExportProfilesService.PROFILES_XML)) {
					doc = builder.parse(zf.getInputStream(entry));
					break;
				}
			}
			zf.close();
			return doc;
		} else if (archive.toLowerCase().endsWith(FilesUtils.ARCHIVES[1])) { // 7-zip
			MyRandomAccessFile istream = new MyRandomAccessFile(archive, "r");
			IInArchive zArchive = new Handler();
			if (zArchive.Open(istream) != 0) {
				throw new IOException(TextService.getInstance().get("general.error.opensevenzip", new Object[] {archive}));
			}

			ByteArrayOutputStream out = new ByteArrayOutputStream();
			for (int i = 0; i < zArchive.size(); i++) {
				SevenZipEntry entry = zArchive.getEntry(i);
				if (!entry.isDirectory() && entry.getName().equalsIgnoreCase(ImportExportProfilesService.PROFILES_XML)) {

					class Extract extends Thread {
						private int entryId_;
						private SevenzipExtractSingleFileCallback extractCallback_;
						private IOException exception_;

						Extract(int entryId, ProgressNotifyable prog) {
							entryId_ = entryId;
							extractCallback_ = new SevenzipExtractSingleFileCallback(prog, out);
							exception_ = null;
						}

						public void run() {
							try {
								zArchive.Extract(new int[] {entryId_}, 1, IInArchive.NExtract_NAskMode_kExtract, extractCallback_);
							} catch (IOException e) {
								exception_ = e;
							}
						}

						public IOException getIOException() {
							return exception_;
						}
					}

					ProgressDialog prog = new ProgressDialog(getParent(), text_.get("dialog.import.notice.processing7zip"));
					Extract extract = new Extract(i, prog);
					prog.setThread(extract);
					prog.open();

					if (extract.getIOException() != null) {
						zArchive.close();
						out.close();
						throw extract.getIOException();
					}

					doc = builder.parse(new ByteArrayInputStream(out.toByteArray()));
					break;
				}
			}
			zArchive.close();
			out.close();
			return doc;
		}

		return null;
	}

	@Override
	protected boolean onNext(int step) {
		if (step == 2) {
			return conditionsForStep3Ok() && refillImportedDBVersionsList();
		} else if (step == 4) {
			try {
				// check for equal gamedirs, if there are, set importedid to the first
				for (int i = 0; i < gamePack_.getEntries().size(); i++) {
					for (int j = 0; j < i; j++) {
						GamePackEntry pI = gamePack_.getEntries().get(i);
						GamePackEntry pJ = gamePack_.getEntries().get(j);
						if (pI.getGameDir().equals(pJ.getGameDir())) {
							pI.setImportedId(pJ.getImportedId());
						}
					}
				}

				// set correct dosboxversion
				for (int i = gamePack_.getEntries().size() - 1; i >= 0; i--) {
					TableItem it = profilesTable_.getItem(i);
					if (it.getChecked()) {
						GamePackEntry entry = gamePack_.getEntries().get(i);
						int dosboxId = entry.getProfile().getDosboxVersion().getId();
						int dosboxIndex = DosboxVersionRepository.findIndexById(new ArrayList<>(gamePack_.getDosboxVersions()), dosboxId);
						entry.getProfile().setDosboxVersion(DosboxVersionRepository.findById(dbversionsList_, dbmapping_.get(dosboxIndex)));
					} else {
						gamePack_.getEntries().remove(i);
					}
				}

				gamePack_.setCapturesAvailable(importCapturesButton_.getSelection());
				gamePack_.setMapperfilesAvailable(importMapperfilesButton_.getSelection());
				gamePack_.setNativecommandsAvailable(importNativeCommandsButton_.getSelection());
				gamePack_.setGamedataAvailable(fullGames_.getSelection());
				job_ = new ImportThread(log_, progressBar_, status_, archive_, gamePack_, useOrgConf_.getSelection(), fullSettingsButton_.getSelection(), customValues_.getSelection(),
						customFields_.getSelection());

			} catch (IOException | SQLException e) {
				GeneralPurposeDialogs.warningMessage(shell_, e);
				job_ = null;
			}
		} else if (step == 5) {
			if (job_.isEverythingOk()) {
				GeneralPurposeDialogs.infoMessage(shell_, text_.get("dialog.import.notice.importok"));
			} else {
				GeneralPurposeDialogs.warningMessage(shell_, text_.get("dialog.import.error.problem"));
			}
			status_.setText(text_.get("dialog.export.reviewlog"));
			status_.pack();

			result_ = Boolean.valueOf(job_ != null && customFields_.getSelection());
		}
		return true;
	}

	private boolean conditionsForStep3Ok() {
		GeneralPurposeDialogs.initErrorDialog();
		atLeastOneProfileSelected();
		if (fullGames_.getSelection()) {
			for (int i = 0; i < profilesTable_.getItemCount(); i++) {
				if (profilesTable_.getItem(i).getChecked()) {
					GamePackEntry entry = gamePack_.getEntries().get(i);
					if (entry.getCanonicalFullDir().exists())
						GeneralPurposeDialogs.addError(text_.get("dialog.import.error.gamedatadirexists", new Object[] {entry.getCanonicalFullDir()}), profilesTable_);
				}
			}
		}
		return !GeneralPurposeDialogs.displayErrorDialog(shell_);
	}

	private void atLeastOneProfileSelected() {
		for (TableItem item: profilesTable_.getItems()) {
			if (item.getChecked()) {
				return;
			}
		}
		GeneralPurposeDialogs.addError(text_.get("dialog.import.required.oneprofiletoimport"), profilesTable_);
	}

	private boolean refillImportedDBVersionsList() {
		int idx = 0;
		for (DosboxVersion dbversion: gamePack_.getDosboxVersions()) {
			int dbid = dbversion.getId();
			TableItem ti = impDbVersionsList_.getItem(idx);
			ti.setForeground(isUsed(dbid) ? null: display_.getSystemColor(SWT.COLOR_GRAY));
			idx++;
		}
		return true;
	}

	private boolean isUsed(int dbVersionId) {
		for (int i = 0; i < gamePack_.getEntries().size(); i++) {
			int dbid = gamePack_.getEntries().get(i).getProfile().getDosboxVersion().getId();
			if (profilesTable_.getItem(i).getChecked() && dbVersionId == dbid)
				return true;
		}
		return false;
	}

	@Override
	protected void onShellCreated() {
		Group step1 = createGroup(shell_, text_.get("dialog.import.step1"), 2);
		createLabelAndText(step1, text_.get("dialog.export.exporttitle"), gamePack_.getTitle()).setEditable(false);
		createLabelAndText(step1, text_.get("dialog.export.author"), gamePack_.getAuthor()).setEditable(false);
		createLabelAndTextarea(step1, text_.get("dialog.export.notes"), true, true, 1, 1, gamePack_.getNotes());
		Text confLogText = createLabelAndTextarea(step1, text_.get("dialog.import.log"), true, true, 1, 1, messageLog_.toString());
		GridData twoLines = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
		twoLines.heightHint = confLogText.getLineHeight() * 2;
		confLogText.setLayoutData(twoLines);
		addStep(step1);

		Group page2 = createGroup(shell_, text_.get("dialog.import.step2"), 2);
		createLabel(page2, text_.get("dialog.import.import"));
		Group settingsOrGamesGroup = createGroup(page2, new GridLayout());
		createRadioButton(settingsOrGamesGroup, text_.get("dialog.export.export.profiles"), 1, !gamePack_.isGamedataAvailable());
		fullGames_ = createRadioButton(settingsOrGamesGroup, text_.get("dialog.export.export.games"), 1, gamePack_.isGamedataAvailable());
		fullGames_.setEnabled(gamePack_.isGamedataAvailable());
		createLabel(page2, 1, 3);
		Group confGroup = createGroup(page2, new GridLayout());
		createRadioButton(confGroup, text_.get("dialog.import.import.incrconf"), 1, true);
		fullSettingsButton_ = createRadioButton(confGroup, text_.get("dialog.import.import.fullconf"));
		Group extrasGroup = createGroup(page2, new GridLayout());
		importCapturesButton_ = createCheckButton(extrasGroup, 1, text_.get("dialog.template.captures"), gamePack_.isCapturesAvailable());
		importCapturesButton_.setEnabled(gamePack_.isCapturesAvailable());
		importMapperfilesButton_ = createCheckButton(extrasGroup, 1, text_.get("dialog.template.mapperfile"), gamePack_.isMapperfilesAvailable());
		importMapperfilesButton_.setEnabled(gamePack_.isMapperfilesAvailable());
		importNativeCommandsButton_ = createCheckButton(extrasGroup, 1, text_.get("dialog.export.nativecommands"), false);
		importNativeCommandsButton_.setEnabled(gamePack_.isNativecommandsAvailable());
		Group customGroup = createGroup(page2, new GridLayout());
		customValues_ = createCheckButton(customGroup, 1, text_.get("dialog.import.import.customvalues"), true);
		customFields_ = createCheckButton(customGroup, 1, text_.get("dialog.import.import.customfields"), false);
		createLabel(page2, text_.get("dialog.main.profile.view.conf"));
		Group existingConfGroup = createGroup(page2, new GridLayout());
		boolean useGameDirForConfFile = settings_.getIntValue("profiledefaults", "confpath") == 1;
		useOrgConf_ = createRadioButton(existingConfGroup, text_.get("dialog.import.useorgconf"), 1, useGameDirForConfFile);
		useOrgConf_.setEnabled(useGameDirForConfFile);
		createRadioButton(existingConfGroup, text_.get("dialog.import.createnewconf")).setEnabled(useGameDirForConfFile);
		addStep(page2);

		Group profilesGroup = createGroup(shell_, text_.get("dialog.import.step3"), 2);
		profilesTable_ = new Table(profilesGroup, SWT.FULL_SELECTION | SWT.CHECK | SWT.BORDER);
		GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 2);
		gd.heightHint = 80;
		profilesTable_.setLayoutData(gd);
		profilesTable_.setHeaderVisible(true);
		profilesTable_.setLinesVisible(true);
		profilesTable_.addMouseListener(new MouseAdapter() {
			public void mouseDoubleClick(MouseEvent event) {
				int idx = profilesTable_.getSelectionIndex();
				GamePackEntry entry = gamePack_.getEntries().get(idx);
				DirectoryDialog dialog = new DirectoryDialog(shell_);
				dialog.setFilterPath(entry.getCanonicalBaseDir().getPath());
				String result = dialog.open();
				if (result != null) {
					entry.setBaseDir(result);
					profilesTable_.getSelection()[0].setText(1, entry.getBaseDir().getPath());
				}
			}
		});
		for (GamePackEntry entry: gamePack_.getEntries()) {
			TableItem item = new TableItem(profilesTable_, SWT.NONE);
			item.setText(entry.getProfile().getTitle());
			item.setText(1, entry.getBaseDir().getPath());
			item.setText(2, entry.getGameDir().getPath());
			item.setChecked(true);
		}
		createTableColumn(profilesTable_, 260, text_.get("dialog.main.profiles.column.title"));
		createTableColumn(profilesTable_, 100, text_.get("dialog.import.column.basedir"));
		createTableColumn(profilesTable_, 120, text_.get("dialog.export.column.gamedir"));
		createButton(profilesGroup, text_.get("button.all"), new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				for (TableItem item: profilesTable_.getItems())
					item.setChecked(true);
			}
		});
		createButton(profilesGroup, text_.get("button.none"), new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				for (TableItem item: profilesTable_.getItems())
					item.setChecked(false);
			}
		});
		Composite buttonsGroup = createInnerComposite(profilesGroup, new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1), 3);
		createButton(buttonsGroup, text_.get("button.setbasedir"), new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				DirectoryDialog dialog = new DirectoryDialog(shell_);
				dialog.setFilterPath(FileLocationService.getInstance().getDosroot().getPath());
				String result = dialog.open();
				if (result != null) {
					for (int i = 0; i < profilesTable_.getItems().length; i++) {
						TableItem item = profilesTable_.getItem(i);
						if (item.getChecked()) {
							gamePack_.getEntries().get(i).setBaseDir(result);
							item.setText(1, gamePack_.getEntries().get(i).getBaseDir().getPath());
						}
					}
				}
			}
		});
		createButton(buttonsGroup, text_.get("button.addgametitletobasedir"), new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				for (int i = 0; i < profilesTable_.getItems().length; i++) {
					TableItem item = profilesTable_.getItem(i);
					if (item.getChecked()) {
						gamePack_.getEntries().get(i).appendGameTitleToBaseDir();
						item.setText(1, gamePack_.getEntries().get(i).getBaseDir().getPath());
					}
				}
			}
		});
		createButton(buttonsGroup, text_.get("button.removegametitlefrombasedir"), new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				for (int i = 0; i < profilesTable_.getItems().length; i++) {
					TableItem item = profilesTable_.getItem(i);
					if (item.getChecked()) {
						gamePack_.getEntries().get(i).stripGameTitleFromBaseDir();
						item.setText(1, gamePack_.getEntries().get(i).getBaseDir().getPath());
					}
				}
			}
		});
		addStep(profilesGroup);

		Group dosboxVersionsGroup = createGroup(shell_, text_.get("dialog.import.step4"), 3);
		createLabel(dosboxVersionsGroup, text_.get("dialog.import.dosboxversioninimport"));
		createVerticalSeparator(dosboxVersionsGroup, 2);
		createLabel(dosboxVersionsGroup, text_.get("dialog.import.dosboxversioninstalled"));
		impDbVersionsList_ = new Table(dosboxVersionsGroup, SWT.FULL_SELECTION | SWT.BORDER);
		impDbVersionsList_.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		org.eclipse.swt.widgets.List myDbVersionsList = createList(dosboxVersionsGroup, 1, 1);
		myDbVersionsList.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				int idx = impDbVersionsList_.getSelectionIndex();
				if (idx != -1) {
					int myIdx = myDbVersionsList.getSelectionIndex();
					dbmapping_.set(idx, dbversionsList_.get(myIdx).getId());
				}
			}
		});
		impDbVersionsList_.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				selectDbVersionForSelectedImpDbVersion(myDbVersionsList);
			}
		});
		for (DosboxVersion ver: dbversionsList_) {
			myDbVersionsList.add(ver.getTitle() + " (" + ver.getVersion() + ")");
		}
		dbmapping_ = new ArrayList<>();
		for (DosboxVersion dbversion: gamePack_.getDosboxVersions()) {
			TableItem item = new TableItem(impDbVersionsList_, SWT.NONE);
			item.setText(dbversion.getTitle() + " (" + dbversion.getVersion() + ")");
			dbmapping_.add(DosboxVersionRepository.findBestMatch(dbversionsList_, dbversion).getId());
		}
		impDbVersionsList_.setSelection(0);
		selectDbVersionForSelectedImpDbVersion(myDbVersionsList);
		addStep(dosboxVersionsGroup);

		addFinalStep(text_.get("dialog.import.step5"), text_.get("dialog.import.start"));
	}

	private void selectDbVersionForSelectedImpDbVersion(org.eclipse.swt.widgets.List myDbVersionsList) {
		int impIdx = impDbVersionsList_.getSelectionIndex();
		int mappedId = dbmapping_.get(impIdx);
		int myIdx = DosboxVersionRepository.findIndexById(dbversionsList_, mappedId);
		myDbVersionsList.select(myIdx);
	}
}