/*
 *  Copyright (C) 2006-2016  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.model;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.exception.DrivelettersExhaustedException;
import org.dbgl.exception.InvalidHostfileException;
import org.dbgl.exception.InvalidMountstringException;
import org.dbgl.util.FileUtils;
import org.dbgl.util.PlatformUtils;
import org.dbgl.util.StringRelatedUtils;


public class Mount {

	private final static Pattern MOUNT_PATRN = Pattern.compile(
		"^(?:mount)(?:(?:\\s+-u\\s+([a-y]))|(?:(?:\\s+([a-y]))(?:\\s+((?:\\S+)|(?:\"[^\"]+\")))(?:(?:(?:\\s+-t\\s+(dir|floppy|cdrom|iso))|(?:\\s+-label (\\S+))|(?:\\s+-(ioctl(?:_dx|_dio|_mci)?|noioctl|aspi))|(?:\\s+-freesize\\s+(\\d+))|(?:\\s+-usecd\\s+(\\d+))|(?:\\s+-size\\s+(\\d+,\\d+,\\d+,\\d+))){0,6})))\\s*$",
		Pattern.CASE_INSENSITIVE);
	private final static Pattern IMGMOUNT_PATRN = Pattern.compile(
		"^(?:imgmount)(?:\\s+([a-y0-3]))((?:\\s+(?:(?:[^-][^\\s]+)|(?:\"[^\"]+\")))+)(?:(?:(?:\\s+-t\\s+(hdd|floppy|cdrom|iso))|(?:\\s+-fs\\s+(fat|iso|none))|(?:\\s+-size\\s+(\\d+,\\d+,\\d+,\\d+))){0,3})\\s*$",
		Pattern.CASE_INSENSITIVE);

	public enum MountingType {
		DIR, IMAGE, PHYSFS
	};

	private MountingType mountingType;
	private String mountAs;
	private char driveletter;
	private String label;
	private File[] path;
	private File write;
	private String lowlevelCD;
	private String useCD;
	private String freesize;
	private String fs;
	private String size;
	private boolean unmounted;

	public Mount(final MountingType mountingType, final String mountAs, final String driveletter, final String[] paths, final String label, final String low, final String usecd, final String write,
			final String freesize, final String fs, final String size) {
		this.mountingType = mountingType;
		this.mountAs = mountAs.toLowerCase();
		this.driveletter = Character.toUpperCase(driveletter.charAt(0));
		this.path = new File[paths.length];
		for (int i = 0; i < paths.length; i++) {
			this.path[i] = new File(paths[i]);
		}
		this.write = (write == null) ? null: new File(write);
		this.label = label;
		this.lowlevelCD = low;
		this.useCD = usecd;
		this.freesize = freesize;
		this.fs = fs;
		this.size = size;
		this.unmounted = false;
	}

	public Mount(final Mount otherMount) {
		this.mountingType = otherMount.mountingType;
		this.mountAs = otherMount.mountAs;
		this.driveletter = otherMount.driveletter;
		this.label = otherMount.label;
		this.path = otherMount.path.clone();
		this.write = otherMount.write;
		this.lowlevelCD = otherMount.lowlevelCD;
		this.useCD = otherMount.useCD;
		this.freesize = otherMount.freesize;
		this.fs = otherMount.fs;
		this.size = otherMount.size;
		this.unmounted = otherMount.unmounted;
	}

	private void init() {
		mountingType = MountingType.DIR;
		mountAs = "";
		driveletter = '\0';
		label = "";
		path = new File[0];
		write = null;
		lowlevelCD = "";
		useCD = "";
		freesize = "";
		fs = "";
		size = "";
		unmounted = false;
	}

	private void initForPhysFS(final String physFSPath) {
		mountingType = MountingType.PHYSFS;
		int colonIndex1 = physFSPath.indexOf(':');
		if (colonIndex1 == 1) {
			colonIndex1 = physFSPath.indexOf(":", colonIndex1 + 1);
		}
		int colonIndex2 = physFSPath.lastIndexOf(":");
		path = new File[1];
		if (colonIndex1 == colonIndex2) {
			path[0] = new File(physFSPath.substring(0, colonIndex2));
		} else {
			write = new File(physFSPath.substring(0, colonIndex1));
			path[0] = new File(physFSPath.substring(colonIndex1 + 1, colonIndex2));
		}
	}

	private void initForIso(final String IsoPath) {
		mountingType = MountingType.IMAGE;
		mountAs = "iso";
		path = new File[1];
		path[0] = new File(IsoPath.substring(0, FileUtils.containsIso(IsoPath)));
	}

	private void initForFat(final String fatImagePath) {
		mountingType = MountingType.IMAGE;
		mountAs = "floppy";
		path = new File[1];
		path[0] = new File(fatImagePath.substring(0, FileUtils.containsFatImage(fatImagePath)));
	}

	public Mount(final boolean floppy, final String hostFileLocation, final Set<Character> usedDriveLetters) throws InvalidHostfileException, DrivelettersExhaustedException {
		init();
		if ("".equals(hostFileLocation)) {
			throw new InvalidHostfileException();
		}

		driveletter = getFreeDriveletter(floppy, usedDriveLetters);

		if (FileUtils.containsPhysFS(hostFileLocation) != -1) {
			initForPhysFS(hostFileLocation);
		} else if (FileUtils.containsIso(hostFileLocation) != -1) {
			initForIso(hostFileLocation);
		} else if (FileUtils.containsFatImage(hostFileLocation) != -1) {
			driveletter = getFreeDriveletter(true, usedDriveLetters);
			initForFat(hostFileLocation);
		} else {
			File hostFile = new File(hostFileLocation);
			path = new File[1];
			path[0] = hostFile.getParentFile();
			if (path[0] == null) {
				path[0] = new File(".");
			}
			if (FileUtils.isStoredOnFloppyDrive(hostFile))
				mountAs = "floppy";
			else if (FileUtils.isStoredOnCDRomDrive(hostFile))
				mountAs = "cdrom";
		}
	}

	public Mount(String hostFileLocation, final Set<Character> usedDriveLetters, File[] overrideFilesToMount) throws InvalidHostfileException, DrivelettersExhaustedException {
		this(false, hostFileLocation, usedDriveLetters);
		if (overrideFilesToMount != null && overrideFilesToMount.length > 0) {
			path = overrideFilesToMount;
		}
	}

	public Mount(final String mount) throws InvalidMountstringException {
		init();
		Matcher mountMatcher = MOUNT_PATRN.matcher(mount);
		Matcher imgmountMatcher = IMGMOUNT_PATRN.matcher(mount);

		if (mountMatcher.matches()) {
			if (mountMatcher.group(1) != null) {
				unmounted = true;
				driveletter = Character.toUpperCase(mountMatcher.group(1).charAt(0));
				return;
			}
			driveletter = Character.toUpperCase(mountMatcher.group(2).charAt(0));
			String mountLocation = StringUtils.strip(PlatformUtils.toNativePath(mountMatcher.group(3)), "\"");
			if (FileUtils.isPhysFS(mountLocation))
				initForPhysFS(mountLocation);
			else
				path = new File[] {FileUtils.makeRelativeToDosroot(FileUtils.canonicalToDosroot(mountLocation))};
			mountAs = StringUtils.defaultString(mountMatcher.group(4));
			label = StringUtils.defaultString(mountMatcher.group(5));
			lowlevelCD = StringUtils.defaultString(mountMatcher.group(6));
			freesize = StringUtils.defaultString(mountMatcher.group(7));
			useCD = StringUtils.defaultString(mountMatcher.group(8));
		} else if (imgmountMatcher.matches()) {
			mountingType = MountingType.IMAGE;
			driveletter = Character.toUpperCase(imgmountMatcher.group(1).charAt(0));
			String[] paths = StringUtils.stripAll(PlatformUtils.toNativePath(imgmountMatcher.group(2)).trim().split("\\s(?=([^\"]*\"[^\"]*\")*[^\"]*$)"), "\"");
			path = new File[paths.length];
			for (int i = 0; i < paths.length; i++)
				path[i] = FileUtils.makeRelativeToDosroot(FileUtils.canonicalToDosroot(paths[i]));
			mountAs = StringUtils.defaultString(imgmountMatcher.group(3));
			if (mountAs.equalsIgnoreCase("cdrom"))
				mountAs = "iso";
			fs = StringUtils.defaultString(imgmountMatcher.group(4));
			size = StringUtils.defaultString(imgmountMatcher.group(5));
		} else {
			throw new InvalidMountstringException();
		}
	}

	public static char getFreeDriveletter(final boolean floppy, final Set<Character> usedDriveLetters) throws DrivelettersExhaustedException {
		List<Character> freeDriveletters = new ArrayList<Character>();
		char start = floppy ? 'A': 'C';
		for (char i = start; i < 'Z'; i++) {
			freeDriveletters.add(i);
		}
		if (!floppy) {
			freeDriveletters.add('A');
			freeDriveletters.add('B');
		}
		for (Character c: usedDriveLetters) {
			freeDriveletters.remove(c);
		}
		if (freeDriveletters.isEmpty()) {
			throw new DrivelettersExhaustedException();
		}
		return freeDriveletters.get(0);
	}

	public String toString(final boolean forList) {
		StringBuffer result = new StringBuffer();
		switch (mountingType) {
			case DIR:
				result.append("mount ").append(driveletter).append(" \"").append(getPathAsString()).append('"');
				if (lowlevelCD.length() > 0) {
					result.append(" -").append(lowlevelCD);
				}
				if (useCD.length() > 0) {
					result.append(" -usecd ").append(useCD);
				}
				break;
			case PHYSFS:
				result.append("mount ").append(driveletter).append(" \"");
				if (write != null) {
					result.append(write.getPath()).append(':');
				}
				result.append(path[0].getPath()).append(":\\\"");
				break;
			case IMAGE:
				result.append("imgmount ").append(driveletter);
				for (int i = 0; i < path.length; i++) {
					result.append(" \"").append(path[i].getPath()).append('"');
				}
				break;
			default:
		}
		if (!"".equals(label)) {
			result.append(" -label ").append(label);
		}
		if (!"".equals(mountAs)) {
			result.append(" -t ").append(mountAs);
		}
		if (!"".equals(freesize)) {
			result.append(" -freesize ").append(freesize);
		}
		if (!"".equals(fs)) {
			result.append(" -fs ").append(fs);
		}
		if (!"".equals(size)) {
			result.append(" -size ").append(size);
		}
		if (unmounted) {
			if (forList) {
				result.append(" (UNMOUNTED)");
			} else {
				result = new StringBuffer("mount -u ").append(driveletter);
			}
		}
		return result.toString();
	}

	public String toString() {
		return toString(false);
	}

	public void toggleMount() {
		unmounted = !unmounted;
	}

	public char getDriveletter() {
		return driveletter;
	}

	public String getDriveletterString() {
		return String.valueOf(driveletter);
	}

	public String getLowlevelCD() {
		return lowlevelCD;
	}

	public String getUseCD() {
		return useCD;
	}

	public boolean isUnmounted() {
		return unmounted;
	}

	public String getLabel() {
		return label;
	}

	public String getPathAsString() {
		if (path.length <= 0) {
			return "";
		}
		return path[0].getPath();
	}

	public File getWrite() {
		return write;
	}

	public char getDriveletterFromPath() {
		return Character.toUpperCase(path[0].getAbsolutePath().charAt(0));
	}

	public String getImgMountAsString(final String delimiter) {
		String[] paths = new String[path.length];
		for (int i = 0; i < path.length; i++) {
			paths[i] = path[i].getPath();
		}
		return StringRelatedUtils.stringArrayToString(paths, delimiter);
	}

	public File[] getPath() {
		return path;
	}

	public String getHostPathAsString() {
		return (mountingType == MountingType.PHYSFS) ? getPathAsString() + ':': getPathAsString();
	}

	public MountingType getMountingType() {
		return mountingType;
	}

	public String getMountAs() {
		return mountAs;
	}

	public String getFreesize() {
		return freesize;
	}

	public String getFs() {
		return fs;
	}

	public String getSize() {
		return size;
	}

	public File canBeUsedFor(final File hostFile) {
		if (!unmounted) {
			File canHostFile = FileUtils.getCanMainFile(hostFile);
			File mountFile = FileUtils.canonicalToDosroot(getPathAsString());
			if (mountingType != MountingType.DIR && (FileUtils.isIsoFile(canHostFile.getPath()) || FileUtils.isArchive(canHostFile.getPath()) || FileUtils.isFatImage(canHostFile.getPath()))) {
				return (mountFile.equals(canHostFile)) ? FileUtils.makeRelativeTo(FileUtils.canonicalToDosroot(hostFile.getPath()), FileUtils.canonicalToDosroot(getHostPathAsString())): null;
			}
			if (FileUtils.areRelated(mountFile, canHostFile)) {
				if ((hostFile.getPath().indexOf('~') > 0)) {
					if (hostFile.isAbsolute())
						canHostFile = hostFile;
					else
						canHostFile = new File(FileUtils.getDosRoot(), hostFile.getPath());
				}
				return FileUtils.makeRelativeTo(canHostFile, mountFile);
			}
		}
		return null;
	}

	public boolean matchesImageMountPath(final String hostFile) {
		return (mountingType == MountingType.IMAGE && getPathAsString().equals(hostFile));
	}

	public void migrateToDosroot(final File fromPath, final boolean prefixDosroot) {
		for (int i = 0; i < path.length; i++) {
			path[i] = FileUtils.makeRelativeToDosroot(FileUtils.makeRelativeTo(prefixDosroot ? FileUtils.canonicalToDosroot(path[i].getPath()): path[i], fromPath));
		}
		if (write != null) {
			write = FileUtils.makeRelativeToDosroot(FileUtils.makeRelativeTo(prefixDosroot ? FileUtils.canonicalToDosroot(write.getPath()): write, fromPath));
		}
	}

	public void migrateTo(final File fromPath, final File toPath) {
		File from = FileUtils.makeRelativeToDosroot(fromPath);
		File to = FileUtils.makeRelativeToDosroot(toPath);
		for (int i = 0; i < path.length; i++) {
			if (path[i].getPath().equals(from.getPath()))
				path[i] = to;
		}
		if (write != null) {
			if (write.getPath().equals(from.getPath()))
				write = to;
		}
	}

	public void updateForTargetImportBaseDir(final File basePath) {
		for (int i = 0; i < path.length; i++) {
			path[i] = FileUtils.prefixAndSanitizeToDosroot(basePath, path[i]);
		}
		if (write != null) {
			write = FileUtils.prefixAndSanitizeToDosroot(basePath, write);
		}
	}

	public int hashCode() {
		return toString(false).hashCode();
	}

	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Mount otherMount = (Mount)obj;
		return (driveletter == otherMount.driveletter && unmounted == otherMount.unmounted);
	}
}
