/*
 *  Copyright (C) 2006-2012  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 org.apache.commons.lang3.StringUtils;
import org.dbgl.exception.InvalidMountstringException;
import org.dbgl.model.Mount;
import org.dbgl.util.FileUtils;
import org.dbgl.util.PlatformUtils;


public final class Autoexec {

    List<Mount> mountingpoints;
    Boolean loadfix;
    int loadfixValue;
    String main;
    String img1;
    String img2;
    String img3;
    Boolean exit;
    String params;
    String mixer;
    String keyb;
    String ipxnet;
    Boolean pause;
    
    
    Autoexec() {
		init();
	}

    Autoexec(final Autoexec other) {
        mountingpoints = new ArrayList<Mount>();
        for (Mount m: other.mountingpoints)
        	mountingpoints.add(new Mount(m));
        loadfix = other.loadfix;
        loadfixValue = other.loadfixValue;
        main = other.main;
        img1 = other.img1;
        img2 = other.img2;
        img3 = other.img3;
        exit = other.exit;
        params = other.params;
        mixer = other.mixer;
        keyb = other.keyb;
        ipxnet = other.ipxnet;
        pause = other.pause;
    }
    
    void init() {
        mountingpoints = new ArrayList<Mount>();
        loadfix = false;
        loadfixValue = 0;
        main = "";
        img1 = "";
        img2 = "";
        img3 = "";
        exit = false;
        params = "";
        mixer = "";
        keyb = "";
        ipxnet = "";
        pause = false;
    }

    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';
        char imgdriveletter1 = '\0';
        char imgdriveletter2 = '\0';
        char imgdriveletter3 = '\0';
        String remainder = "";
        String executable = "";
        String image1 = "";
        String image2 = "";
        String image3 = "";
        int exeIndex = -1;

        List<String> leftOvers = new ArrayList<String>();

        for (String orgLine: orgLines) {
            String line = orgLine.toLowerCase();
            boolean leftOverCandidate = false;
            if (line.startsWith("mount") || line.startsWith("imgmount")) {
                addMount(orgLine);
            } else if (line.endsWith(":") && line.length() == 2) {
                driveletter = line.charAt(0);
            } else if (line.startsWith("cd\\")) {
                remainder = PlatformUtils.toNativePath(orgLine).substring(2);
            } else if (line.startsWith("cd ")) {
                remainder = PlatformUtils.toNativePath(orgLine).substring(3);
            } 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 ((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 (line.startsWith("boot ")) {
                int spaceIndex = line.indexOf(' ', 5);
                if (spaceIndex == -1) {
                    spaceIndex = line.length();
                } else {
                    int secondSpaceIndex = line.indexOf(' ', spaceIndex + 2);
                    if (secondSpaceIndex == -1) {
                    	secondSpaceIndex = line.length();
                    } else {
                    	imgdriveletter3 = line.charAt(secondSpaceIndex + 1);
                        image3 = PlatformUtils.toNativePath(orgLine).substring(secondSpaceIndex + 3);
                    }
                    imgdriveletter2 = line.charAt(spaceIndex + 1);
                    image2 = PlatformUtils.toNativePath(orgLine).substring(spaceIndex + 3, secondSpaceIndex);
                }
                imgdriveletter1 = line.charAt(5);
                image1 = PlatformUtils.toNativePath(orgLine).substring(7, spaceIndex);
                if (imgdriveletter1 == '\\' && "ile".equals(image1)) {
                    img1 = "file"; // for template if . was unavailable
                }
            } else if (line.startsWith("exit")) {
                exit = true;
            } else {
            	leftOverCandidate = true;
            }

            if (line.startsWith("loadfix -") && !line.startsWith("loadfix -f")) {
                loadfix = true;
                int endIndex = line.indexOf(' ', 9);
                if (endIndex == -1) {
                    endIndex = line.length();
                }
                try {
                    this.loadfixValue = Integer.parseInt(line.substring(9, endIndex));
                } catch (NumberFormatException e) {
                    this.loadfixValue = 0;
                }
            } else if (leftOverCandidate) {
            	leftOvers.add(orgLine);
            }
        }
        
        if (executable.equals("") && leftOvers.size() == 1) {
        	executable = leftOvers.get(0).trim();
	        int spaceAfterIndex = executable.indexOf(' ');
	        if (spaceAfterIndex != -1) {
	            params = executable.substring(spaceAfterIndex + 1);
	            executable = executable.substring(0, spaceAfterIndex);
	        }
	        executable += FileUtils.EXECUTABLES[0];
        }

        for (Mount mount: mountingpoints) {
            char mount_drive = mount.getDriveletter();
            String mountPath = mount.getHostPathAsString();
            if (Character.toUpperCase(driveletter) == mount_drive) {
            	if (!executable.equals(""))
            		if (remainder.indexOf('~') > 0) {
            			main = FileUtils.makeRelativeToDosroot(new File(mountPath + remainder + File.separatorChar + executable)).getPath();
            			if (main.startsWith("./") || main.startsWith(".\\"))
            				main = main.substring(2); 
            		} else {
            			main = FileUtils.sanitizeToDosroot(mountPath + remainder + File.separatorChar + executable);
            		}
            } else {
                if (Character.toUpperCase(imgdriveletter1) == mount_drive) {
                    img1 = FileUtils.sanitizeToDosroot(mountPath + image1);
                }
                if (Character.toUpperCase(imgdriveletter2) == mount_drive) {
                    img2 = FileUtils.sanitizeToDosroot(mountPath + image2);
                }
                if (Character.toUpperCase(imgdriveletter3) == mount_drive) {
                    img3 = FileUtils.sanitizeToDosroot(mountPath + image3);
                }
            }
        }
        
        if (exit == null)
        	exit = false;
    }

    public String toString() {
    	return toString((Autoexec)null, false);
    }
    
    public String toString(final Autoexec base, final Boolean prepareOnly) {
		StringBuffer result = new StringBuffer();
        
        if (!"".equals(keyb) && (base == null || !base.keyb.equals(keyb))) {
       		result.append("keyb.com ").append(keyb).append(PlatformUtils.EOLN);
        }
        if (!"".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 (!"".equals(mixer) && (base == null || !base.mixer.equals(mixer))) {
        	result.append("mixer.com ").append(mixer).append(PlatformUtils.EOLN);
        }
        
        if (!"".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);
            }
            if (!prepareOnly) {
	            if (dosboxLocation[2].toLowerCase().endsWith(FileUtils.EXECUTABLES[2])) {
	                result.append("call ");
	            }
	            result.append(dosboxLocation[2]);
	            if (!"".equals(params)) {
	                result.append(' ').append(params);
	            }
	            result.append(PlatformUtils.EOLN);
            }

        } else if (!"".equals(img1)) {

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

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

		    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();
	}
    
    String[] convertToDosboxPath(final String hostFileLocation) {
        File hostFile = new File(hostFileLocation);
        String[] result = { "", "", 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) ? "": dosboxDir.getParent();
                maxLengthMount = mount.getPathAsString().length();
            }
        }
        // translate *nix paths to dosbox paths
        result[1] = PlatformUtils.toDosboxPath(result[1]);
        return result;
    }

    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 void migrateToDosroot(final File fromPath) {
        for (Mount mount: mountingpoints) {
            mount.migrateToDosroot(fromPath);
        }
        if (isBooter()) {
        	String newImg1 = FileUtils.makeRelativeTo(new File(img1), fromPath).getPath();
        	if (convertToDosboxPath(newImg1)[0].length() > 0) img1 = newImg1;
        	String newImg2 = FileUtils.makeRelativeTo(new File(img2), fromPath).getPath();
        	if (convertToDosboxPath(newImg2)[0].length() > 0) img2 = newImg2;
        	String newImg3 = FileUtils.makeRelativeTo(new File(img3), fromPath).getPath();
        	if (convertToDosboxPath(newImg3)[0].length() > 0) img3 = newImg3;
        } else {
        	String newMain = FileUtils.makeRelativeTo(new File(main), fromPath).getPath();
        	if (convertToDosboxPath(newMain)[0].length() > 0) main = newMain;
        }
    }

    public void migrateTo(final File fromPath, final File toPath) {
    	for (Mount mount: mountingpoints) {
        	mount.migrateTo(fromPath, toPath);
        }
        String newMain = FileUtils.makeRelativeTo(FileUtils.canonicalToDosroot(main), fromPath).getPath();
        newMain = FileUtils.makeRelativeToDosroot(new File(toPath, newMain)).getPath();
        if (convertToDosboxPath(newMain)[0].length() > 0) main = newMain;
    }

    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() {
        return (img1.length() == 0 && main.length() == 0);
    }
    
    public Boolean isExit() {
		return exit;
	}
    
    public boolean isBooter() {
        return img1.length() > 0;
    }
    
    public Boolean isLoadfix() {
		return loadfix;
	}

	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 "";
        }
		return img1;
	}
	
	public String getImg2() {
		return img2;
	}
	
	public String getImg3() {
		return img3;
	}

	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;
    }
    
	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.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;
        return result;
	}

	/**
	 * @return mounts as they would be effective after execution in DOSBox, keeping the original order
	 */
	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;
	}
}
