/*
 *  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.conf;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.exception.InvalidMountstringException;
import org.dbgl.model.Mount;
import org.dbgl.model.Mount.MountingType;
import org.dbgl.util.FileUtils;
import org.dbgl.util.PlatformUtils;


public final class Autoexec {

	private final static String[] CUSTOM_SECTION_MARKERS = {
		"@REM START", "@REM /START", "@REM PRE-LAUNCH", "@REM /PRE-LAUNCH", "@REM POST-LAUNCH", "@REM /POST-LAUNCH", "@REM FINISH", "@REM /FINISH" };
	public final static int SECTIONS = CUSTOM_SECTION_MARKERS.length / 2;

	private final static Pattern LOADHIGH_PATRN = Pattern.compile("^(?:lh|loadhigh)\\s+(.*)$", Pattern.CASE_INSENSITIVE);
	private final static Pattern LOADFIX_PATRN = Pattern.compile("^(?:loadfix\\s+(-f)\\s*|loadfix(\\s+-\\d+)?(\\s+.*)?)$", Pattern.CASE_INSENSITIVE);
	private final static Pattern BOOT_PATRN = Pattern.compile("^(?:boot)(?=\\s+.\\S+)((?:\\s+[^-][^\\s]*)*)(?:\\s+-l\\s+([acd]):?)?\\s*$", Pattern.CASE_INSENSITIVE);
	private final static Pattern BOOTIMGS_PATRN = Pattern.compile("^((?:\"[^\"]+\")|(?:[^\\s]+))(?:\\s((?:\"[^\"]+\")|(?:[^\\s]+)))?(?:\\s((?:\"[^\"]+\")|(?:[^\\s]+)))?$", Pattern.CASE_INSENSITIVE);
	
    List<Mount> mountingpoints;
    Boolean loadfix, loadhigh;
    int loadfixValue;
    String main;
    String img1, img2, img3;
    String imgDriveletter;
    Boolean exit;
    String params;
    String mixer;
    String keyb;
    String ipxnet;
    Boolean pause;
    String[] customSections;
    
    
    Autoexec() {
		init();
	}

    Autoexec(final Autoexec other) {
        mountingpoints = new ArrayList<Mount>();
        for (Mount m: other.mountingpoints)
        	mountingpoints.add(new Mount(m));
        loadfix = other.loadfix;
        loadhigh = other.loadhigh;
        loadfixValue = other.loadfixValue;
        main = other.main;
        img1 = other.img1;
        img2 = other.img2;
        img3 = other.img3;
        imgDriveletter = other.imgDriveletter;
        exit = other.exit;
        params = other.params;
        mixer = other.mixer;
        keyb = other.keyb;
        ipxnet = other.ipxnet;
        pause = other.pause;
        customSections = new String[SECTIONS];
        for (int i = 0; i < SECTIONS; i++)
        	customSections[i] = other.customSections[i];
    }
    
    void init() {
        mountingpoints = new ArrayList<Mount>();
        loadfix = false;
        loadhigh = false;
        loadfixValue = 0;
        main = StringUtils.EMPTY;
        img1 = StringUtils.EMPTY;
        img2 = StringUtils.EMPTY;
        img3 = StringUtils.EMPTY;
        imgDriveletter = StringUtils.EMPTY;
        exit = false;
        params = StringUtils.EMPTY;
        mixer = StringUtils.EMPTY;
        keyb = StringUtils.EMPTY;
        ipxnet = StringUtils.EMPTY;
        pause = false;
        customSections = new String[SECTIONS];
        for (int i = 0; i < SECTIONS; i++)
        	customSections[i] = StringUtils.EMPTY;
    }

    static List<Mount> getUniqueMountingpoints(final Autoexec base, final Autoexec ext) {
    	List<Mount> result = new ArrayList<Mount>();
    	int start = base == null? 0: base.mountingpoints.size();
        for (int i = start; i < ext.mountingpoints.size(); i++) {
            result.add(new Mount(ext.mountingpoints.get(i)));
        }
        return result;
    }

    void parseLines(final List<String> orgLines) {
    	char driveletter = '\0';
        String remainder = StringUtils.EMPTY;
        String executable = StringUtils.EMPTY;
        String image1 = StringUtils.EMPTY;
        String image2 = StringUtils.EMPTY;
        String image3 = StringUtils.EMPTY;
        int exeIndex = -1;

        String customEchos = StringUtils.EMPTY;
        List<String> leftOvers = new ArrayList<String>();
        int customSection = -1;

        line: for (String orgLine: orgLines) {
        	orgLine = orgLine.trim();

            for (int i = 0; i < SECTIONS; i++) {
            	if (orgLine.startsWith(CUSTOM_SECTION_MARKERS[i * 2])) {
            		customSection = i;
            		continue line;
            	}
        	}

            if (customSection > -1) {
        		if (orgLine.startsWith(CUSTOM_SECTION_MARKERS[customSection * 2 + 1]))
            		customSection = -1;
            	else
            		customSections[customSection] += orgLine + PlatformUtils.EOLN;
            	continue;
            }

            orgLine = StringUtils.stripStart(orgLine, "@").trim();

            Matcher loadhighMatcher = LOADHIGH_PATRN.matcher(orgLine);
            Matcher loadfixMatcher = LOADFIX_PATRN.matcher(orgLine);
            Matcher bootMatcher = BOOT_PATRN.matcher(orgLine);

            if (loadhighMatcher.matches()) {
                loadhigh = true;
                orgLine = loadhighMatcher.group(1).trim();
            }
            
            if (loadfixMatcher.matches()) {
            	if (!loadfix) {
            		loadfixValue = 64;
            		loadfix = true;
            	}
            	if (loadfixMatcher.group(1) != null) // -f
            		continue;
            	if (loadfixMatcher.group(2) != null) {
            		try {
                    	loadfixValue = Integer.parseInt(loadfixMatcher.group(2).trim().substring(1));
                    } catch (NumberFormatException e) {
                    	// use default value of 64
                    }
            	}
            	if (loadfixMatcher.group(3) == null)
            		continue;
            	orgLine = loadfixMatcher.group(3).trim();
            }

            String line = orgLine.toLowerCase();

            if (StringUtils.isEmpty(line)) {
            	continue;
            } else if (line.startsWith("mount ") || line.startsWith("imgmount ")) {
                addMount(orgLine);
            } else if ((line.endsWith(":") && line.length() == 2) || (line.endsWith(":\\") && line.length() == 3)) {
                driveletter = Character.toUpperCase(line.charAt(0));
                remainder = StringUtils.EMPTY;
            } else if (line.startsWith("cd\\")) {
            	if (driveletter != '\0') {
            		String add = PlatformUtils.toNativePath(orgLine).substring(2);
            		if (add.startsWith("\\"))
            			remainder = add;
            		else
            			remainder = new File(remainder, add).getPath();
            	}
            } else if (line.startsWith("cd ")) {
            	if (driveletter != '\0') {
            		String add = PlatformUtils.toNativePath(orgLine).substring(3);
            		if (add.startsWith("\\"))
            			remainder = add;
            		else
            			remainder = new File(remainder, add).getPath();
            	}
            } else if (line.startsWith("keyb ") || line.startsWith("keyb.com ")) {
                keyb = orgLine.substring(line.indexOf(' ') + 1);
            } else if (line.startsWith("mixer ") || line.startsWith("mixer.com ")) {
                mixer = orgLine.substring(line.indexOf(' ') + 1);
            } else if (line.startsWith("ipxnet ") || line.startsWith("ipxnet.com ")) {
                ipxnet = orgLine.substring(line.indexOf(' ') + 1);
            } else if (line.equals("pause")) {
                pause = true;
            } else if (line.startsWith("z:\\config.com")) {
                // just ignore
            } else if ((exeIndex = StringUtils.indexOfAny(line, FileUtils.EXECUTABLES)) != -1) {
                executable = orgLine;
                // If there is a space BEFORE executable name, strip everything before it
                int spaceBeforeIndex = executable.lastIndexOf(' ', exeIndex);
                if (spaceBeforeIndex != -1) {
                    executable = executable.substring(spaceBeforeIndex + 1);
                }
                // If there is a space AFTER executable name, define it as being parameters
                int spaceAfterIndex = executable.indexOf(' ');
                if (spaceAfterIndex != -1) {
                    params = orgLine.substring(spaceBeforeIndex + spaceAfterIndex + 2);
                    executable = executable.substring(0, spaceAfterIndex);
                }
            } else if (bootMatcher.matches()) {
            	Matcher bootImgsMatcher = BOOTIMGS_PATRN.matcher(bootMatcher.group(1).trim());
            	if (bootImgsMatcher.matches()) {
            		if (bootImgsMatcher.group(1) != null) image1 = StringUtils.strip(bootImgsMatcher.group(1), "\"");
            		if (bootImgsMatcher.group(2) != null) image2 = StringUtils.strip(bootImgsMatcher.group(2), "\"");
            		if (bootImgsMatcher.group(3) != null) image3 = StringUtils.strip(bootImgsMatcher.group(3), "\"");
            	}
            	if (bootMatcher.group(2) != null)
            		imgDriveletter = bootMatcher.group(2).trim().toUpperCase();
            	if (StringUtils.isEmpty(image1) && StringUtils.isNotEmpty(imgDriveletter)) {
            		char driveNumber = (char)((int)imgDriveletter.charAt(0) - 17);
            		String matchingImageMount = findImageMatchByDrive(driveNumber, imgDriveletter.charAt(0));
            		if (matchingImageMount != null)
            			image1 = matchingImageMount;
            	}
            	if ("\\file".equals(image1)) {
                    img1 = "file"; // for template if . was unavailable
                }
            } else if (line.equals("exit") || line.startsWith("exit ")) {
                exit = true;
            } else if (line.equals("echo off")) {
            	// just ignore
        	} else if (line.equals("echo") || line.startsWith("echo ") || line.startsWith("echo.")) {
        		customEchos += orgLine + PlatformUtils.EOLN;
            } else if (line.startsWith(":") || line.equals("cd") || line.equals("cls") || line.startsWith("cls ") || line.startsWith("cls\\")
            		|| line.equals("rem") || line.startsWith("rem ") || line.startsWith("goto ") || line.startsWith("if errorlevel ")) {
                // just ignore
            } else {
            	leftOvers.add(orgLine);
            }
        }
        
        if (StringUtils.isNotEmpty(customEchos) && !customEchos.equalsIgnoreCase("echo." + PlatformUtils.EOLN)) {
        	customSections[1] += "@echo off" + PlatformUtils.EOLN + customEchos; // add echo commands to pre-launch custom section
        	customSections[1] += "pause" + PlatformUtils.EOLN; // add pause statement to make it all readable
        }

        if (executable.equals(StringUtils.EMPTY) && !leftOvers.isEmpty()) {
        	for (int i = 0; i < leftOvers.size(); i++) {
        		executable = leftOvers.get(i).trim();
        		if (i == (leftOvers.size() - 1)) { // the last executable should be the main game
	            	boolean isCalledBatch = executable.toLowerCase().startsWith("call ");
	            	if (isCalledBatch) executable = executable.substring(5);
	    	        int spaceAfterIndex = executable.indexOf(' ');
	    	        if (spaceAfterIndex != -1) {
	    	            params = executable.substring(spaceAfterIndex + 1);
	    	            executable = executable.substring(0, spaceAfterIndex);
	    	        }
	            	executable += isCalledBatch ? FileUtils.EXECUTABLES[2] :FileUtils.EXECUTABLES[0];
        		} else {
        			customSections[1] += executable + PlatformUtils.EOLN; // add other statements to pre-launch custom section
        		}
        	}
        }

        for (Mount mount: mountingpoints) {
            char mount_drive = mount.getDriveletter();
            String mountPath = mount.getHostPathAsString();
            if (driveMatches(executable, mount_drive, driveletter))
            	main = splitInThree(executable, mountPath, remainder);
            else {
            	if (mount.matchesImageMountPath(image1))
            		img1 = image1;
                else if (driveMatches(image1, mount_drive, driveletter))
                	img1 = splitInThree(image1, mountPath, remainder);
            	if (mount.matchesImageMountPath(image2))
                	img2 = image2;
                else if (driveMatches(image2, mount_drive, driveletter))
                	img2 = splitInThree(image2, mountPath, remainder);
            	if (mount.matchesImageMountPath(image3))
                	img3 = image1;
                else if (driveMatches(image3, mount_drive, driveletter))
                	img3 = splitInThree(image3, mountPath, remainder);
            }
        }
        
        if (exit == null)
        	exit = false;
    }

    private String findImageMatchByDrive(char driveNumber, char driveLetter) {
		for (Mount m: mountingpoints) {
			if (m.getMountingType() == MountingType.IMAGE && (driveNumber == m.getDriveletter()))
				return m.getPathAsString();
		}
		for (Mount m: mountingpoints) {
			if (m.getMountingType() == MountingType.IMAGE && (driveLetter == m.getDriveletter()))
				return m.getPathAsString();
		}
		return null;
	}

	private boolean driveMatches(String main, char mount_drive, char driveletter) {
    	if (StringUtils.isEmpty(main)) return false;
    	if (main.length() > 2 && main.charAt(1) == ':')
    		return Character.toUpperCase(main.charAt(0)) == mount_drive;
   		return driveletter == mount_drive;
    }

    private String splitInThree(String main, String mountPath, String remainder) {
    	String[] result = { null, null };
    	if (main.length() > 2 && main.charAt(1) == ':')
    		main = main.substring(2);
    	File f1 = new File(main);
    	String parent = f1.getParent();
    	File mountPathFile = new File(mountPath);
    	if (main.charAt(0) == '\\') {			// absolute path, don't use cwd (remainder)
    		if (parent == null) {
    			result[0] = mountPath;
    		} else {
    			result[0] = new File(mountPathFile, parent).getPath();
    		}
    	} else {								// relative path, do use cwd (remainder)
    		if (parent == null) {
    			result[0] = new File(mountPathFile, remainder).getPath();
    		} else {
    			result[0] = new File(new File(mountPathFile, remainder), parent).getPath();
    		}
    	}
    	result[1] = f1.getName();

    	if (remainder.indexOf('~') > 0) {
    		String s = FileUtils.makeRelativeToDosroot(new File(result[0], result[1])).getPath();
			if (s.startsWith("./") || s.startsWith(".\\"))
				s = s.substring(2);
			return s;
		} else {
			return FileUtils.sanitizeToDosroot(new File(result[0], result[1]).getPath());
		}
    }

    public String toString() {
    	return toString((Autoexec)null, false);
    }
    
    public String toString(final Autoexec base, final Boolean prepareOnly) {
		StringBuffer result = new StringBuffer();

		insertCustomSection(result, 0);
        if (!StringUtils.EMPTY.equals(keyb) && (base == null || !base.keyb.equals(keyb))) {
       		result.append("keyb.com ").append(keyb).append(PlatformUtils.EOLN);
        }
        if (!StringUtils.EMPTY.equals(ipxnet) && (base == null || !base.ipxnet.equals(ipxnet))) {
        	result.append("ipxnet.com ").append(ipxnet).append(PlatformUtils.EOLN);
        }
        
        List<Mount> mnts = null;
        if (base == null) {
        	mnts = mountingpoints;
        } else {
        	mnts = new ArrayList<Mount>();
        	for (int i = 0; i < mountingpoints.size(); i++) {
                if (i >= base.mountingpoints.size() || !mountingpoints.get(i).equals(base.mountingpoints.get(i))) {
                    mnts.add(mountingpoints.get(i));
                }
            }
        }
        
        for (Mount mount: mnts) {
            result.append(mount.toString()).append(PlatformUtils.EOLN);
        }
        if (!StringUtils.EMPTY.equals(mixer) && (base == null || !base.mixer.equals(mixer))) {
        	result.append("mixer.com ").append(mixer).append(PlatformUtils.EOLN);
        }
        
        if (!StringUtils.EMPTY.equals(main)) {

            String[] dosboxLocation = convertToDosboxPath(main);
            result.append(dosboxLocation[0] + PlatformUtils.EOLN); // move to drive
            result.append("cd \\" + dosboxLocation[1] + PlatformUtils.EOLN); // move to dir
            if (loadfix) {
                result.append("loadfix -").append(loadfixValue > 0 ? loadfixValue: 64).append(PlatformUtils.EOLN);
            }
            insertCustomSection(result, 1);
            if (!prepareOnly) {
            	if (loadhigh) {
            		result.append("loadhigh ");
            	}
	            if (dosboxLocation[2].toLowerCase().endsWith(FileUtils.EXECUTABLES[2])) {
	                result.append("call ");
	            }
	            result.append(dosboxLocation[2]);
	            if (!StringUtils.EMPTY.equals(params)) {
	                result.append(' ').append(params);
	            }
	            result.append(PlatformUtils.EOLN);
	            insertCustomSection(result, 2);
            }

        } else if (!StringUtils.EMPTY.equals(img1)) {

            if (loadfix) { 
                result.append("loadfix -").append(loadfixValue > 0 ? loadfixValue: 64).append(PlatformUtils.EOLN);
            }
            insertCustomSection(result, 1);
            if (!prepareOnly) {
            	if (matchesMountedImage(img1)) {
            		result.append("boot \"").append(img1).append("\"");
            	} else {
		            String[] dosboxLocation = convertToDosboxPath(img1);
		            result.append("boot ").append(dosboxLocation[0]).append('\\').append(dosboxLocation[1]);
		            if (!dosboxLocation[1].equals(StringUtils.EMPTY)) {
		                result.append('\\');
		            }
		            result.append(dosboxLocation[2]);
            	}
            	if (matchesMountedImage(img2)) {
            		result.append(" \"").append(img2).append("\"");
            	} else {
		            if (!StringUtils.EMPTY.equals(img2)) {
		            	String[] dosboxLocation = convertToDosboxPath(img2);
		                result.append(' ').append(dosboxLocation[0]).append('\\').append(dosboxLocation[1]);
		                if (!dosboxLocation[1].equals(StringUtils.EMPTY)) {
		                    result.append('\\');
		                }
		                result.append(dosboxLocation[2]);
		            }
            	}
            	if (matchesMountedImage(img3)) {
            		result.append(" \"").append(img3).append("\"");
            	} else {
		            if (!StringUtils.EMPTY.equals(img3)) {
		            	String[] dosboxLocation = convertToDosboxPath(img3);
		                result.append(' ').append(dosboxLocation[0]).append('\\').append(dosboxLocation[1]);
		                if (!dosboxLocation[1].equals(StringUtils.EMPTY)) {
		                    result.append('\\');
		                }
		                result.append(dosboxLocation[2]);
		            }
            	}
	            if (!StringUtils.EMPTY.equals(imgDriveletter)) {
	            	result.append(" -l ").append(imgDriveletter);
	            }
	            result.append(PlatformUtils.EOLN);
	            insertCustomSection(result, 2);
            }

        } else {

        	// template
        	insertCustomSection(result, 1);
        	insertCustomSection(result, 2);

        }

        if (!prepareOnly) {
		    if (loadfix) {
		        result.append("loadfix -f").append(PlatformUtils.EOLN);
		    }

		    insertCustomSection(result, 3);

		    if (pause) {
		        result.append("pause").append(PlatformUtils.EOLN);
		    }

		    if (exit) {
		        result.append("exit").append(PlatformUtils.EOLN);
		    }
        }

        if (result.length() > 0) {
        	result.insert(0, "[autoexec]" + PlatformUtils.EOLN);
        }
        
        return result.toString();
	}

	private void insertCustomSection(final StringBuffer sb, final int sectionNr) {
		if (StringUtils.isNotEmpty(customSections[sectionNr])) {
			sb.append(CUSTOM_SECTION_MARKERS[sectionNr * 2 + 0]).append(PlatformUtils.EOLN);
			sb.append(StringUtils.chomp(customSections[sectionNr])).append(PlatformUtils.EOLN);
			sb.append(CUSTOM_SECTION_MARKERS[sectionNr * 2 + 1]).append(PlatformUtils.EOLN);
		}
	}
    
    String[] convertToDosboxPath(final String hostFileLocation) {
        File hostFile = new File(hostFileLocation);
        String[] result = { StringUtils.EMPTY, StringUtils.EMPTY, hostFile.getName() };
        int maxLengthMount = 0;
        for (Mount mount: mountingpoints) {
            File dosboxDir = mount.canBeUsedFor(hostFile);
            if (dosboxDir != null && (mount.getPathAsString().length() > maxLengthMount)) {
                result[0] = mount.getDriveletter() + ":";
                result[1] = (dosboxDir.getParent() == null) ? StringUtils.EMPTY: dosboxDir.getParent();
                maxLengthMount = mount.getPathAsString().length();
            }
        }
        // translate *nix paths to dosbox paths
        result[1] = PlatformUtils.toDosboxPath(result[1]);
        return result;
    }

    public boolean matchesMountedImage(final String hostFileLocation) {
        for (Mount mount: mountingpoints)
            if (mount.matchesImageMountPath(hostFileLocation))
                return true;
        return false;
    }

    void addMount(final String mount) {
        try {
            Mount mnt = new Mount(mount);
            if (mnt.isUnmounted()) {
                for (Mount m: mountingpoints) {
                    if (m.getDriveletter() == mnt.getDriveletter()) {
                        m.toggleMount();
                        break;
                    }
                }
            } else {
                mountingpoints.add(mnt);
            }
        } catch (InvalidMountstringException e) {
            // nothing we can do
        }
    }
    
    public void setMainExecutable(final String main, final String params) {
        this.main = main;
        this.params = params;
    }

    public File getCanonicalMainDir() {
    	if (isBooter()) {
            return FileUtils.getCanMainFile(new File(img1)).getParentFile();
        } else {
            return FileUtils.getCanMainFile(new File(main)).getParentFile();
        }
    }

    public String getActualMain() {
        return isBooter()? img1: main;
    }
    
    public void setActualMainPath(final File mainParentFile) {
        if (isBooter()) {
        	this.img1 = new File(mainParentFile, new File(img1).getName()).getPath();
        	if (StringUtils.isNotEmpty(this.img2))
        		this.img2 = new File(mainParentFile, new File(img2).getName()).getPath();
        	if (StringUtils.isNotEmpty(this.img3))
        		this.img3 = new File(mainParentFile, new File(img3).getName()).getPath();
        } else {
        	this.main = new File(mainParentFile, new File(main).getName()).getPath();
        }
    }

    public void setActualMain(final File mainFile) {
        if (isBooter()) {
        	this.img1 = mainFile.getPath();
        } else {
        	this.main = mainFile.getPath();
        }
    }

    public void migrateToDosroot(final File fromPath, final boolean prefixDosroot) {
        for (Mount mount: mountingpoints) {
            mount.migrateToDosroot(fromPath, prefixDosroot);
        }
        if (isBooter()) {
        	String newImg1 = FileUtils.makeRelativeTo(prefixDosroot? FileUtils.canonicalToDosroot(img1): new File(img1), fromPath).getPath();
        	if ((convertToDosboxPath(newImg1)[0].length() > 0) || matchesMountedImage(newImg1)) img1 = newImg1;
        	String newImg2 = FileUtils.makeRelativeTo(prefixDosroot? FileUtils.canonicalToDosroot(img2): new File(img2), fromPath).getPath();
        	if ((convertToDosboxPath(newImg2)[0].length() > 0) || matchesMountedImage(newImg2)) img2 = newImg2;
        	String newImg3 = FileUtils.makeRelativeTo(prefixDosroot? FileUtils.canonicalToDosroot(img3): new File(img3), fromPath).getPath();
        	if ((convertToDosboxPath(newImg3)[0].length() > 0) || matchesMountedImage(newImg3)) img3 = newImg3;
        } else {
        	String newMain = FileUtils.makeRelativeTo(prefixDosroot? FileUtils.canonicalToDosroot(main): new File(main), fromPath).getPath();
        	if (convertToDosboxPath(newMain)[0].length() > 0) main = newMain;
        }
    }

    public void migrateMountsTo(final File fromPath, final File toPath) {
    	for (Mount mount: mountingpoints)
        	mount.migrateTo(fromPath, toPath);
    }

    public void migrateTo(final File fromPath, final File toPath) {
    	migrateMountsTo(fromPath, toPath);
        String newMain = FileUtils.makeRelativeTo(FileUtils.canonicalToDosroot(main), fromPath).getPath();
        if (!new File(newMain).isAbsolute()) {
	        newMain = FileUtils.makeRelativeToDosroot(new File(toPath, newMain)).getPath();
	        if (convertToDosboxPath(newMain)[0].length() > 0) main = newMain;
        }
    }

    public void updateForTargetImportBaseDir(File baseDir) {
		for (Mount mount: mountingpoints) {
            mount.updateForTargetImportBaseDir(baseDir);
        }
        if (isBooter()) {
        	img1 = FileUtils.prefixAndSanitizeToDosroot(baseDir, new File(img1)).getPath();
        	if (StringUtils.isNotBlank(img2)) img2 = FileUtils.prefixAndSanitizeToDosroot(baseDir, new File(img2)).getPath();
        	if (StringUtils.isNotBlank(img3)) img3 = FileUtils.prefixAndSanitizeToDosroot(baseDir, new File(img3)).getPath();
        } else {
        	main = FileUtils.prefixAndSanitizeToDosroot(baseDir, new File(main)).getPath();
        }
	}
    
    void updateMountingPoints(final Autoexec base, final List<Mount> additionalMounts) {
    	this.mountingpoints = new ArrayList<Mount>();
    	for (Mount m: base.mountingpoints)
    		this.mountingpoints.add(new Mount(m));
    	for (Mount m: additionalMounts)
    		this.mountingpoints.add(new Mount(m));
    }
    
    public boolean isIncomplete() {
        if (img1.length() == 0 && main.length() == 0) return true;
        if (isBooter()) {
        	if ((convertToDosboxPath(img1)[0].length() == 0) && !matchesMountedImage(img1)) return true;
        } else {
        	if (convertToDosboxPath(main)[0].length() == 0) return true;
        }
        return false;
    }
    
    public Boolean isExit() {
		return exit;
	}
    
    public boolean isBooter() {
        return img1.length() > 0;
    }
    
    public Boolean isLoadfix() {
		return loadfix;
	}

    public Boolean isLoadhigh() {
		return loadhigh;
	}

	public int getLoadfixValue() {
		return loadfixValue;
	}
    
    public String getMixer() {
		return mixer;
	}

	public String getKeyb() {
		return keyb;
	}

	public String getIpxnet() {
		return ipxnet;
	}

	public String getImg1() {
		if ("file".equals(img1)) {
            return StringUtils.EMPTY;
        }
		return img1;
	}
	
	public String getImg2() {
		return img2;
	}
	
	public String getImg3() {
		return img3;
	}

	public String getImgDriveletter() {
		return imgDriveletter;
	}

	public String getMain() {
		return main;
	}

	public String getMainParameters() {
		return params;
	}

    public String[] getMountingpoints() {
        String[] result = new String[mountingpoints.size()];
        int mountIndex = 0;
        for (Mount mount: mountingpoints) {
            result[mountIndex++] = mount.toString(true);
        }
        return result;
    }
    
    public String getCustomSection(final int i) {
    	return customSections[i];
    }

	static Autoexec createCombination(Autoexec a1, Autoexec a2) {
		Autoexec result = new Autoexec();
		result.mountingpoints = new ArrayList<Mount>();
        for (Mount m1: a1.mountingpoints) {
        	for (Mount m2: a2.mountingpoints) {
        		if (m1.toString(false).equals(m2.toString(false))) {
        			result.mountingpoints.add(new Mount(m1));
        		}
        	}
        }
        result.loadfix = a1.loadfix != Conf.CONFLICTING_BOOL_SETTING && a2.loadfix != Conf.CONFLICTING_BOOL_SETTING && 
        	a1.loadfix.equals(a2.loadfix)? a1.loadfix: Conf.CONFLICTING_BOOL_SETTING;
        result.loadfixValue = (a1.loadfixValue == a2.loadfixValue)? a1.loadfixValue: Conf.CONFLICTING_INT_SETTING;
        result.main = a1.main.equals(a2.main)? a1.main: Conf.CONFLICTING_STRING_SETTING;
        result.img1 = a1.img1.equals(a2.img1)? a1.img1: Conf.CONFLICTING_STRING_SETTING;
        result.img2 = a1.img2.equals(a2.img2)? a1.img2: Conf.CONFLICTING_STRING_SETTING;
        result.img3 = a1.img3.equals(a2.img3)? a1.img3: Conf.CONFLICTING_STRING_SETTING;
        result.imgDriveletter = a1.imgDriveletter.equals(a2.imgDriveletter)? a1.imgDriveletter: Conf.CONFLICTING_STRING_SETTING;
        result.exit = a1.exit != Conf.CONFLICTING_BOOL_SETTING && a2.exit != Conf.CONFLICTING_BOOL_SETTING &&
        	a1.exit.equals(a2.exit)? a1.exit: Conf.CONFLICTING_BOOL_SETTING;
        result.params = a1.params.equals(a2.params)? a1.params: Conf.CONFLICTING_STRING_SETTING;
        result.mixer = a1.mixer.equals(a2.mixer)? a1.mixer: Conf.CONFLICTING_STRING_SETTING;
        result.keyb = a1.keyb.equals(a2.keyb)? a1.keyb: Conf.CONFLICTING_STRING_SETTING;
        result.ipxnet = a1.ipxnet.equals(a2.ipxnet)? a1.ipxnet: Conf.CONFLICTING_STRING_SETTING;
        result.pause = a1.pause != Conf.CONFLICTING_BOOL_SETTING && a2.pause != Conf.CONFLICTING_BOOL_SETTING &&
            	a1.pause.equals(a2.pause)? a1.pause: Conf.CONFLICTING_BOOL_SETTING;
        for (int i = 0; i < a1.customSections.length; i++)
        	result.customSections[i] = a1.customSections[i].equals(a2.customSections[i])? a1.customSections[i]: Conf.CONFLICTING_STRING_SETTING;
        return result;
	}

	/**
	 * @return mounts as they would be effective after execution in DOSBox, keeping the original order
	 */
	public Map<Character, Mount> nettoMounts() {
		return nettoMounts(new LinkedHashMap<Character, Mount>());
	}

	/**
	 * @param existingMap containing netto DOSBox mounts
	 * @return mounts as they would be effective after execution in DOSBox, starting with the DOSBox mounts
	 *         and continuing with this Autoexec's mounts, keeping the original order
	 */
	Map<Character, Mount> nettoMounts(Map<Character, Mount> existingMap) {
		Map<Character, Mount> map = new LinkedHashMap<Character, Mount>();
		for (Map.Entry<Character, Mount> entry : existingMap.entrySet()) {
			map.put(entry.getKey(), entry.getValue());
		}
		for (Mount m: mountingpoints) {
			if (map.containsKey(m.getDriveletter())) {
				if (m.isUnmounted()) {
					map.remove(m.getDriveletter());
				}
			} else {
				if (!m.isUnmounted()) {
					map.put(m.getDriveletter(), m);
				}
			}
		}
		return map;
	}

	void removeFloppyMounts() {
    	for (Iterator<Mount> it = mountingpoints.iterator(); it.hasNext();)
            if (it.next().getMountAs().equals("floppy"))
                it.remove();
    }

	/**
	 * @param dosboxAutoexec
	 * Set the mountingpoints for this Autoexec so that it effectively unmounts all existing netto DOSBox mounts, with the minimum required mounts.
	 */
	void setUnmountsFor(Autoexec dosboxAutoexec) {
		Map<Character, Mount> dbMap = dosboxAutoexec.nettoMounts();

		List<Mount> result = new ArrayList<Mount>();

		for (Map.Entry<Character, Mount> entry : dbMap.entrySet()) {
			Mount u = new Mount(entry.getValue());
			u.toggleMount();
			result.add(u);
		}

		mountingpoints = result;
	}

	/**
	 * @param dosboxAutoexec
	 * Set the mountingpoints for this Autoexec so that it reaches the same nettoMounts with the minimum required mounts.
	 * In other words, unnecessary mounts are removed. 
	 * The order of mounts is likely to be changed, unless dosboxAutoexec has no mounts, then the original order will be kept.
	 */
	void removeUnnecessaryMounts(Autoexec dosboxAutoexec) {
		Map<Character, Mount> dbMap = dosboxAutoexec.nettoMounts();
		Map<Character, Mount> fullMap = this.nettoMounts(dbMap);

		List<Mount> result = new ArrayList<Mount>();

		// determine mounts from dosbox that have been removed - unmounts, or have remained the same, or have changed
		for (Map.Entry<Character, Mount> entry : dbMap.entrySet()) {
			Mount mnt = entry.getValue();
			if (!fullMap.containsKey(entry.getKey())) {
				Mount umnt = new Mount(mnt);
				umnt.toggleMount();
				result.add(umnt);
			} else {
				if (mnt.toString().equals(fullMap.get(entry.getKey()).toString())) {
					result.add(mnt);
				} else {
					Mount umnt = new Mount(mnt);
					umnt.toggleMount();
					result.add(umnt);
					Mount m = new Mount(fullMap.get(entry.getKey()));
					result.add(m);
				}
			}
		}

		// determine added mounts
		for (Map.Entry<Character, Mount> entry : fullMap.entrySet()) {
			Mount mnt = entry.getValue();
			if (!dbMap.containsKey(entry.getKey())) {
				result.add(mnt);
			}
		}

		mountingpoints = result;
	}
}
