/*
 *  Copyright (C) 2006-2015  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);
    }
}
