package com.github.monster860.fastdmm;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.KeyboardFocusManager;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.*;
import java.util.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileNameExtensionFilter;

import com.github.monster860.fastdmm.dmirender.DMI;
import com.github.monster860.fastdmm.dmirender.IconState;
import com.github.monster860.fastdmm.dmirender.IconSubstate;
import com.github.monster860.fastdmm.dmirender.RenderInstance;
import com.github.monster860.fastdmm.dmmmap.DMM;
import com.github.monster860.fastdmm.dmmmap.Location;
import com.github.monster860.fastdmm.dmmmap.TileInstance;
import com.github.monster860.fastdmm.editing.*;
import com.github.monster860.fastdmm.editing.placement.DefaultPlacementMode;
import com.github.monster860.fastdmm.editing.placement.DeletePlacementMode;
import com.github.monster860.fastdmm.editing.placement.PlacementHandler;
import com.github.monster860.fastdmm.editing.placement.PlacementMode;
import com.github.monster860.fastdmm.editing.placement.SelectPlacementMode;
import com.github.monster860.fastdmm.editing.ui.EditorTabComponent;
import com.github.monster860.fastdmm.editing.ui.EmptyTabPanel;
import com.github.monster860.fastdmm.editing.ui.NoDmeTreeModel;
import com.github.monster860.fastdmm.editing.ui.ObjectTreeRenderer;
import com.github.monster860.fastdmm.objtree.InstancesRenderer;
import com.github.monster860.fastdmm.objtree.ModifiedType;
import com.github.monster860.fastdmm.objtree.ObjInstance;
import com.github.monster860.fastdmm.objtree.ObjectTree;
import com.github.monster860.fastdmm.objtree.ObjectTreeParser;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.*;

import org.json.*;

import static org.lwjgl.opengl.GL11.*;

public class FastDMM extends JFrame implements ActionListener, TreeSelectionListener, ListSelectionListener {
	private static final long serialVersionUID = 1L;
	public File dme;
	public DMM dmm;
	public List<DMM> loadedMaps = new ArrayList<DMM>();
	public Map<String, ModifiedType> modifiedTypes = new HashMap<>();
	
	private Stack<Undoable> undostack = new Stack<Undoable>();
	private Stack<Undoable> redostack = new Stack<Undoable>();

	public float viewportX = 0;
	public float viewportY = 0;
	public int viewportZoom = 32;

	int selX = 0;
	int selY = 0;

	boolean selMode = false;

	public String statusstring = " ";

	private JPanel leftPanel;
	private JPanel objTreePanel;
	private JPanel instancesPanel;
	private JPanel vpData;
	private JLabel coords;
	public JLabel selection;
	private JTabbedPane leftTabs;
	private JPanel editorPanel;
	private JTabbedPane editorTabs;
	private Canvas canvas;

	private JMenuBar menuBar;
	private JMenu menuRecent;
	private JMenu menuRecentMaps;
	private JMenuItem menuItemNew;
	private JMenuItem menuItemOpen;
	private JMenuItem menuItemSave;
	private JMenuItem menuItemExpand;
	private JMenuItem menuItemMapImage;
	private JMenuItem menuItemUndo;
	private JMenuItem menuItemRedo;

	private JPopupMenu currPopup;

	public JTree objTreeVis;
	public JList<ObjInstance> instancesVis;

	SortedSet<String> filters;
	public ObjectTree objTree;

	public ObjectTree.Item selectedObject;
	public ObjInstance selectedInstance;

	private boolean hasLoadedImageThisFrame = false;

	private PlacementHandler currPlacementHandler = null;
	public PlacementMode placementMode = null;

	public boolean isCtrlPressed = false;
	public boolean isShiftPressed = false;
	public boolean isAltPressed = false;

	private boolean areMenusFrozen = false;

	public static final void main(String[] args) throws IOException, LWJGLException {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (ClassNotFoundException | InstantiationException | UnsupportedLookAndFeelException
				| IllegalAccessException e) {
			e.printStackTrace();
		}

		FastDMM fastdmm = new FastDMM();

		fastdmm.initSwing();
		fastdmm.interface_dmi = new DMI(Util.getFile("interface.dmi"));

		try {
			fastdmm.init();
			fastdmm.loop();
		} catch (Exception ex) {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			ex.printStackTrace(pw);
			JOptionPane.showMessageDialog(fastdmm, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			System.exit(1);
		} finally {
			Display.destroy();
		}
	}

	public FastDMM() {
	}

	public void initSwing() {
		SwingUtilities.invokeLater(() -> {
			setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			canvas = new Canvas();

			ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);

			leftPanel = new JPanel();
			leftPanel.setLayout(new BorderLayout());
			leftPanel.setSize(350, 1);
			leftPanel.setPreferredSize(leftPanel.getSize());

			vpData = new JPanel();
			vpData.setLayout(new BorderLayout());
			vpData.setSize(350, 25);
			vpData.setPreferredSize(vpData.getSize());
			coords = new JLabel(" No DME loaded.");
			if (currPlacementHandler != null) {
				statusstring = "No tiles selected. ";
			}
			selection = new JLabel(statusstring);
			vpData.add(coords, BorderLayout.WEST);
			vpData.add(selection, BorderLayout.EAST);
			leftPanel.add(vpData, BorderLayout.SOUTH);

			instancesPanel = new JPanel();
			instancesPanel.setLayout(new BorderLayout());

			instancesVis = new JList<>();
			instancesVis.addListSelectionListener(FastDMM.this);
			instancesVis.setLayoutOrientation(JList.VERTICAL);
			instancesVis.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			ToolTipManager.sharedInstance().registerComponent(instancesVis);
			instancesVis.setCellRenderer(new InstancesRenderer(FastDMM.this));
			instancesPanel.add(new JScrollPane(instancesVis));

			objTreePanel = new JPanel();
			objTreePanel.setLayout(new BorderLayout());

			objTreeVis = new JTree(new NoDmeTreeModel());
			objTreeVis.addTreeSelectionListener(FastDMM.this);
			ToolTipManager.sharedInstance().registerComponent(objTreeVis);
			objTreeVis.setCellRenderer(new ObjectTreeRenderer(FastDMM.this));
			objTreePanel.add(new JScrollPane(objTreeVis));

			leftTabs = new JTabbedPane();
			leftTabs.addTab("Objects", objTreePanel);
			leftTabs.addTab("Instances", instancesPanel);
			leftPanel.add(leftTabs, BorderLayout.CENTER);
			
			editorPanel = new JPanel();
			editorPanel.setLayout(new BorderLayout());
			editorPanel.add(canvas, BorderLayout.CENTER);
			
			editorTabs = new JTabbedPane();
			editorTabs.addChangeListener(new ChangeListener() {
		        public void stateChanged(ChangeEvent e) {
		        	if(editorTabs.getSelectedIndex() == -1) {
		        		dmm = null;
		        		FastDMM.this.setTitle(dme.getName().replaceFirst("[.][^.]+$", ""));
		        		return;
		        	}
		        	if(loadedMaps.get(editorTabs.getSelectedIndex()) == dmm)
		        		return;
		            synchronized(FastDMM.this) {
		            	if(dmm != null) {
		            		dmm.storedViewportX = viewportX;
		            		dmm.storedViewportY = viewportY;
		            		dmm.storedViewportZoom = viewportZoom;
		            	}
		            	dmm = loadedMaps.get(editorTabs.getSelectedIndex());
		            	viewportX = dmm.storedViewportX;
		            	viewportY = dmm.storedViewportY;
		            	viewportZoom = dmm.storedViewportZoom;
		            	FastDMM.this.setTitle(dme.getName().replaceFirst("[.][^.]+$", "") + ": "
								+ dmm.file.getName().replaceFirst("[.][^.]+$", ""));
		            }
		        }
		    });
			editorPanel.add(editorTabs, BorderLayout.NORTH);

			getContentPane().add(editorPanel, BorderLayout.CENTER);
			getContentPane().add(leftPanel, BorderLayout.WEST);

			setSize(1280, 720);
			setPreferredSize(getSize());
			pack();

			menuBar = new JMenuBar();

			JMenu menu = new JMenu("File");
			menu.setMnemonic(KeyEvent.VK_O);
			menu.getPopupMenu().setLightWeightPopupEnabled(false);
			menuBar.add(menu);

			menuItemNew = new JMenuItem("New");
			menuItemNew.setActionCommand("new");
			menuItemNew.addActionListener(FastDMM.this);
			menuItemNew.setEnabled(false);
			menu.add(menuItemNew);

			menuItemOpen = new JMenuItem("Open");
			menuItemOpen.setActionCommand("open");
			menuItemOpen.addActionListener(FastDMM.this);
			menuItemOpen.setEnabled(false);
			menu.add(menuItemOpen);

			menuRecentMaps = new JMenu("Recent Maps");
			menuRecentMaps.setMnemonic(KeyEvent.VK_O);
			menuRecentMaps.getPopupMenu().setLightWeightPopupEnabled(false);
			menuRecentMaps.setVisible(false);
			menuRecentMaps.setEnabled(false);
			menu.add(menuRecentMaps);

			menuItemSave = new JMenuItem("Save");
			menuItemSave.setActionCommand("save");
			menuItemSave.addActionListener(FastDMM.this);
			menuItemSave.setEnabled(false);
			menu.add(menuItemSave);

			JMenuItem menuItem = new JMenuItem("Open DME");
			menuItem.setActionCommand("open_dme");
			menuItem.addActionListener(FastDMM.this);
			menu.add(menuItem);

			menuRecent = new JMenu("Recent Environments");
			menuRecent.setMnemonic(KeyEvent.VK_O);
			menuRecent.getPopupMenu().setLightWeightPopupEnabled(false);
			menu.add(menuRecent);
			
			menu.addSeparator();
			
			menuItemMapImage = new JMenuItem("Create map image");
			menuItemMapImage.setActionCommand("mapimage");
			menuItemMapImage.addActionListener(FastDMM.this);
			menuItemMapImage.setEnabled(false);
			menu.add(menuItemMapImage);

			initRecent("dme");
			
			menu = new JMenu("Edit");
			menuBar.add(menu);

			menuItemUndo = new JMenuItem("Undo", KeyEvent.VK_U);
			menuItemUndo.setActionCommand("undo");
			menuItemUndo.addActionListener(FastDMM.this);
			menuItemUndo.setEnabled(false);
			menu.add(menuItemUndo);

			menuItemRedo = new JMenuItem("Redo", KeyEvent.VK_R);
			menuItemRedo.setActionCommand("redo");
			menuItemRedo.addActionListener(FastDMM.this);
			menuItemRedo.setEnabled(false);
			menu.add(menuItemRedo);
			
			menu = new JMenu("Options");
			menu.setMnemonic(KeyEvent.VK_O);
			menuBar.add(menu);

			menuItem = new JMenuItem("Change Filters", KeyEvent.VK_F);
			menuItem.setActionCommand("change_filters");
			menuItem.addActionListener(FastDMM.this);
			menu.add(menuItem);

			menuItemExpand = new JMenuItem("Expand Map");
			menuItemExpand.setActionCommand("expand");
			menuItemExpand.addActionListener(FastDMM.this);
			menuItemExpand.setEnabled(false);
			menu.add(menuItemExpand);

			menu.addSeparator();

			ButtonGroup placementGroup = new ButtonGroup();

			menuItem = new JRadioButtonMenuItem("Default Placement", true);
			menuItem.addItemListener(e -> { // I know this is ugly, but what can
											// you do
				statusstring = "Default Placement Mode ";
				if (dme == null || dmm == null) {
					statusstring = " ";
				}
				selection.setText(statusstring);
				selMode = false;
			});
			menuItem.addActionListener(new PlacementModeListener(this, placementMode = new DefaultPlacementMode()));
			placementGroup.add(menuItem);
			menu.add(menuItem);

			menuItem = new JRadioButtonMenuItem("Select", false);
			menuItem.addActionListener(new PlacementModeListener(this, new SelectPlacementMode()));
			menuItem.addItemListener(e -> {
				selMode = true;
			});
			placementGroup.add(menuItem);
			menu.add(menuItem);

			menuItem = new JRadioButtonMenuItem("Delete", false);
			menuItem.addActionListener(new PlacementModeListener(this, new DeletePlacementMode()));
			menuItem.addItemListener(e -> {
				selMode = false;
			});
			placementGroup.add(menuItem);
			menu.add(menuItem);

			setJMenuBar(menuBar);

			filters = new TreeSet<>(new FilterComparator());
			filters.add("/obj");
			filters.add("/turf");
			filters.add("/mob");
			filters.add("/area");

			// Yes, there's a good reason input is being handled in 2 places:
			// For some reason, this doesn't work when the LWJGL Canvas is in
			// focus.
			KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(e -> {
				isCtrlPressed = e.isControlDown();
				isShiftPressed = e.isShiftDown();
				isAltPressed = e.isAltDown();
				return false;
			});
		});
	}

	@Override
	public void valueChanged(TreeSelectionEvent arg0) {
		if (arg0.getPath().getLastPathComponent() instanceof ObjectTree.Item) {
			selectedObject = (ObjectTree.Item) arg0.getPath().getLastPathComponent();
			instancesVis.setModel(selectedObject);
			if(selectedInstance == null || objTree.get(selectedInstance.typeString()) != selectedObject)
				selectedInstance = selectedObject;
			instancesVis.setSelectedValue(selectedInstance, true);
		}
	}

	@Override
	public void valueChanged(ListSelectionEvent arg0) {
		if(instancesVis.getSelectedValue() == null)
			return;
		selectedInstance = instancesVis.getSelectedValue();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (areMenusFrozen)
			return;
		if ("change_filters".equals(e.getActionCommand())) {
			JTextArea ta = new JTextArea(20, 40);
			StringBuilder taText = new StringBuilder();
			for (String filter : filters) {
				taText.append(filter);
				taText.append('\n');
			}
			ta.setText(taText.toString());
			if (JOptionPane.showConfirmDialog(canvas, new JScrollPane(ta), "Input filter",
					JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION)
				return;
			synchronized (filters) {
				filters.clear();
				for (String filter : ta.getText().split("(\\r\\n|\\r|\\n)")) {
					if (!filter.trim().isEmpty()) {
						if(filter.startsWith("~"))
							filter = '~' + ObjectTreeParser.cleanPath(filter.substring(1));
						else
							filter = ObjectTreeParser.cleanPath(filter);
						filters.add(filter);
					}
				}
			}
		} else if ("open_dme".equals(e.getActionCommand())) {
			openDME();
		} else if ("open".equals(e.getActionCommand())) {
			openDMM();
		} else if ("save".equals(e.getActionCommand())) {
			try {
				synchronized(this) {
					placementMode.flush(this);
				}
				dmm.save();
			} catch (Exception ex) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				ex.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			}
		} else if ("new".equals(e.getActionCommand())) {
			statusstring = " ";
			selection.setText(statusstring);
			String usePath = JOptionPane.showInputDialog(canvas,
					"Please enter the path of the new DMM file relative to your DME: ", "FastDMM",
					JOptionPane.QUESTION_MESSAGE);
			String strMaxX = (String) JOptionPane.showInputDialog(canvas, "Select the X-size of your new map",
					"FastDMM", JOptionPane.QUESTION_MESSAGE, null, null, "255");
			String strMaxY = (String) JOptionPane.showInputDialog(canvas, "Select the Y-size of your new map",
					"FastDMM", JOptionPane.QUESTION_MESSAGE, null, null, "255");
			String strMaxZ = (String) JOptionPane.showInputDialog(canvas,
					"Select the number of Z-levels of your new map", "FastDMM", JOptionPane.QUESTION_MESSAGE, null,
					null, "1");

			if (usePath == null || usePath.isEmpty())
				return;

			int maxX = 0;
			int maxY = 0;
			int maxZ = 0;

			try {
				maxX = Integer.parseInt(strMaxX);
				maxY = Integer.parseInt(strMaxY);
				maxZ = Integer.parseInt(strMaxZ);
			} catch (NumberFormatException ex) {
				return;
			}

			synchronized (this) {
				placementMode.flush(this);
				try {
					dmm = new DMM(new File(dme.getParentFile(), usePath), objTree, this);
					dmm.setSize(1, 1, 1, maxX, maxY, maxZ);
					menuItemExpand.setEnabled(true);
					menuItemMapImage.setEnabled(true);
				} catch (IOException ex) {
					ex.printStackTrace();
				}
			}
			if (selMode) {
				SelectPlacementMode spm = (SelectPlacementMode) placementMode;
				spm.clearSelection();
			}
		} else if ("expand".equals(e.getActionCommand())) {
			if (dmm == null)
				return;
			String strMaxX = (String) JOptionPane.showInputDialog(canvas, "Select the new X-size", "FastDMM",
					JOptionPane.QUESTION_MESSAGE, null, null, "" + dmm.maxX);
			String strMaxY = (String) JOptionPane.showInputDialog(canvas, "Select the new Y-size", "FastDMM",
					JOptionPane.QUESTION_MESSAGE, null, null, "" + dmm.maxY);
			String strMaxZ = (String) JOptionPane.showInputDialog(canvas, "Select the new number of Z-levels",
					"FastDMM", JOptionPane.QUESTION_MESSAGE, null, null, "" + dmm.maxZ);

			int maxX = 0;
			int maxY = 0;
			int maxZ = 0;

			try {
				maxX = Integer.parseInt(strMaxX);
				maxY = Integer.parseInt(strMaxY);
				maxZ = Integer.parseInt(strMaxZ);
			} catch (NumberFormatException ex) {
				return;
			}

			synchronized (this) {
				dmm.setSize(1, 1, 1, maxX, maxY, maxZ);
			}
		} else if("mapimage".equals(e.getActionCommand())) {
			JFileChooser fc = new JFileChooser();
			if (fc.getChoosableFileFilters().length > 0)
				fc.removeChoosableFileFilter(fc.getChoosableFileFilters()[0]);
			fc.addChoosableFileFilter(new FileNameExtensionFilter("Image (*.png)", "png"));
			int returnVal = fc.showSaveDialog(this);
			if (returnVal == JFileChooser.APPROVE_OPTION) {
				BufferedImage mapImage;
				synchronized(this) {
					mapImage = createMapImage(1);
				}
				try {
					ImageIO.write(mapImage, "png", fc.getSelectedFile());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		} else if("undo".equals(e.getActionCommand())) {
			undoAction();
		} else if("redo".equals(e.getActionCommand())) {
			redoAction();
		}
	}

	private void openDME(File filetoopen) {
		statusstring = " ";
		selection.setText(statusstring);
		synchronized (this) {
			objTree = null;
			dmm = null;
			if (selMode) {
				SelectPlacementMode spm = (SelectPlacementMode) placementMode;
				spm.clearSelection();
			}
			dme = filetoopen;
		}
		areMenusFrozen = true;
		menuItemOpen.setEnabled(false);
		menuItemSave.setEnabled(false);
		menuItemNew.setEnabled(false);
		menuItemExpand.setEnabled(false);
		menuRecent.setEnabled(false);
		menuRecentMaps.setEnabled(false);
		menuItemMapImage.setEnabled(false);
		menuRecentMaps.setVisible(false);
		modifiedTypes = new HashMap<>();
		while(editorTabs.getTabCount() > 0)
			editorTabs.removeTabAt(0);
		loadedMaps.clear();
		// PARSE TREE
		new Thread() {
			public void run() {
				try {
					ObjectTreeParser parser = new ObjectTreeParser();
					parser.modalParent = FastDMM.this;
					parser.parseDME(dme);
					parser.tree.completeTree();
					objTree = parser.tree;
					objTree.dmePath = dme.getAbsolutePath();
					objTreeVis.setModel(objTree);
					menuItemOpen.setEnabled(true);
					menuItemSave.setEnabled(true);
					menuItemNew.setEnabled(true);
					menuRecent.setEnabled(true);
					menuRecentMaps.setEnabled(true);
					menuRecentMaps.setVisible(true);
					areMenusFrozen = false;
				} catch (Exception ex) {
					StringWriter sw = new StringWriter();
					PrintWriter pw = new PrintWriter(sw);
					ex.printStackTrace(pw);
					JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
					dme = null;
					objTree = null;
				} finally {
					areMenusFrozen = false;
					addToRecent(dme, dmm);
					FastDMM.this.setTitle(dme.getName().replaceFirst("[.][^.]+$", ""));
					initRecent("both");
				}
			}
		}.start();
	}

	private void openDME() {
		JFileChooser fc = new JFileChooser();
		if (fc.getChoosableFileFilters().length > 0)
			fc.removeChoosableFileFilter(fc.getChoosableFileFilters()[0]);
		fc.addChoosableFileFilter(new FileNameExtensionFilter("BYOND Environments (*.dme)", "dme"));
		int returnVal = fc.showOpenDialog(this);
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			openDME(fc.getSelectedFile());
		}
	}

	private void openDMM(File filetoopen) {
		synchronized (this) {
			for(DMM map : loadedMaps) {
				try {
					if(map.file.getCanonicalPath().equals(filetoopen.getCanonicalPath())) {
						dmm = map;
						editorTabs.setSelectedIndex(loadedMaps.indexOf(map));
						return;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			int insertIndex = loadedMaps.size();
			if(dmm != null)
				insertIndex = loadedMaps.contains(dmm) ? insertIndex : loadedMaps.indexOf(dmm);
			
			dmm = null;
			areMenusFrozen = true;
			DMM newDmm;
			try {
				newDmm = new DMM(filetoopen, objTree, this);
				dmm = newDmm;
				// Tabs!
				loadedMaps.add(dmm);
				int mapIndex = loadedMaps.indexOf(dmm);;
				editorTabs.insertTab(dmm.relPath, null, new EmptyTabPanel(editorPanel), dmm.relPath, mapIndex);
				editorTabs.setTabComponentAt(mapIndex, new EditorTabComponent(this, dmm));
				editorTabs.setSelectedIndex(mapIndex);
				viewportX = 0;
				viewportY = 0;
				viewportZoom = 32;
				menuItemExpand.setEnabled(true);
				menuItemMapImage.setEnabled(true);
			} catch (Exception ex) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				ex.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
				dmm = null;
				menuItemExpand.setEnabled(false);
				menuItemMapImage.setEnabled(false);
			} finally {
				areMenusFrozen = false;
				if (!selMode) {
					statusstring = "Default Placement Mode ";
				}
				selection.setText(statusstring);
				if (selMode) {
					SelectPlacementMode spm = (SelectPlacementMode) placementMode;
					spm.clearSelection();
				}
				addToRecent(dme, dmm);
				this.setTitle(dme.getName().replaceFirst("[.][^.]+$", "") + ": "
						+ dmm.file.getName().replaceFirst("[.][^.]+$", ""));
				initRecent("both");
			}
		}
	}

	private void openDMM() {
		List<File> dmms = getDmmFiles(dme.getParentFile());
		JList<File> dmmList = new JList<>(dmms.toArray(new File[dmms.size()]));

		if (JOptionPane.showConfirmDialog(canvas, new JScrollPane(dmmList), "Select a DMM",
				JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION)
			return;

		if (dmmList.getSelectedValue() == null)
			return;

		openDMM(dmmList.getSelectedValue());
	}
	
	public void closeTab(DMM map) {
		synchronized(this) {
			int idx = loadedMaps.indexOf(map);
			loadedMaps.remove(idx);
			dmm = null;
			editorTabs.removeTabAt(idx);
		}
	}

	public static List<File> getDmmFiles(File directory) {
		List<File> l = new ArrayList<>();
		for (File f : directory.listFiles()) {
			if (f.getName().endsWith(".dmm") || f.getName().endsWith(".dmp")) {
				l.add(f);
			} else if (!f.getName().equals(".git") && !f.getName().equals("node_modules") && f.isDirectory()) { 
				// .git and node_modules usually contain fucktons of files and no dmm's.
				l.addAll(getDmmFiles(f));
			}
		}
		return l;
	}

	private void init() throws LWJGLException {
		String path = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator;
		Path AppDataPath = Paths.get(path);
		if (Files.notExists(AppDataPath)) {
			new File(path).mkdirs();
		}
		String dummy = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator + "dummy";
		File dummyFile = new File(dummy);
		if (!dummyFile.exists()) {
			convertintojson();
		}
		try {
			synchronized (this) {
				while (filters == null) {
					wait(1000);
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		setVisible(true);
		// Display.setDisplayMode(new DisplayMode(640, 480));
		// Display.setResizable(true);
		Display.setParent(canvas);
		Display.create();
		this.setTitle("FastDMM");

		if (interface_dmi != null) {
			interface_dmi.createGL();
		}
	}

	private Map<String, DMI> dmis = new HashMap<>();
	public DMI interface_dmi;

	public DMI getDmi(String name, boolean doInitGL) {
		if (dmis.containsKey(name)) {
			DMI dmi = dmis.get(name);
			if (dmi != null && doInitGL && dmi.glID == -1)
				dmi.createGL();
			return dmi;
		} else {
			if (hasLoadedImageThisFrame && doInitGL) {
				return interface_dmi;
			} else {
				hasLoadedImageThisFrame = true;
			}
			DMI dmi = null;
			try {
				if (name != null && name.trim().length() > 0) {
					dmi = new DMI(new File(dme.getParentFile(), objTree.filePath(Util.separatorsToSystem(name))));
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (dmi != null && doInitGL) {
				dmi.createGL();
			}
			if (dmi == null)
				dmi = interface_dmi;
			dmis.put(name, dmi);
			return dmi;
		}
	}

	private void processInput() {
		float xpos = Mouse.getX();
		float ypos = Display.getHeight() - Mouse.getY();
		double dx = Mouse.getDX();
		double dy = -Mouse.getDY();

		if (dmm == null) {
			viewportX = 0;
			viewportY = 0;
		}

		float xScrOff = (float) Display.getWidth() / viewportZoom / 2;
		float yScrOff = (float) Display.getHeight() / viewportZoom / 2;

		int prevSelX = selX, prevSelY = selY;

		selX = (int) Math.floor(viewportX + (xpos / viewportZoom) - xScrOff);
		selY = (int) Math.floor(viewportY - (ypos / viewportZoom) + yScrOff);

		if ((prevSelX != selX || prevSelY != selY) && currPlacementHandler != null) {
			currPlacementHandler.dragTo(new Location(selX, selY, 1));
		}

		if (dme == null || dmm == null) { // putting this here because it's the
											// only func that updates regularly
											// besides loop()
			statusstring = " ";
		}

		float dwheel = Mouse.getDWheel();
		if (dwheel != 0) {
			if (dwheel > 0)
				viewportZoom *= 2;
			else if (dwheel < 0)
				viewportZoom /= 2;
			if (viewportZoom < 8)
				viewportZoom = 8;
			if (viewportZoom > 128)
				viewportZoom = 128;
		}

		while (Keyboard.next()) {
			if (Keyboard.getEventKeyState()) {
				if (Keyboard.getEventKey() == Keyboard.KEY_LCONTROL || Keyboard.getEventKey() == Keyboard.KEY_RCONTROL)
					isCtrlPressed = true;
				if (Keyboard.getEventKey() == Keyboard.KEY_LSHIFT || Keyboard.getEventKey() == Keyboard.KEY_RSHIFT)
					isShiftPressed = true;
				if (Keyboard.getEventKey() == Keyboard.KEY_LMENU || Keyboard.getEventKey() == Keyboard.KEY_RMENU)
					isAltPressed = true;
			} else {
				if (Keyboard.getEventKey() == Keyboard.KEY_LCONTROL || Keyboard.getEventKey() == Keyboard.KEY_RCONTROL)
					isCtrlPressed = false;
				if (Keyboard.getEventKey() == Keyboard.KEY_LSHIFT || Keyboard.getEventKey() == Keyboard.KEY_RSHIFT)
					isShiftPressed = false;
				if (Keyboard.getEventKey() == Keyboard.KEY_LMENU || Keyboard.getEventKey() == Keyboard.KEY_RMENU)
					isAltPressed = false;
			}
		}

		if (Mouse.isButtonDown(2) || (Mouse.isButtonDown(0) && isAltPressed)) {
			viewportX -= (dx / viewportZoom);
			viewportY += (dy / viewportZoom);
		}

		if (dme != null) {
			if (dmm != null) {
				if (selX >= 1 && selY >= 1 && selX <= dmm.maxX && selY <= dmm.maxY) {
					String tcoord = " " + String.valueOf(selX) + ", " + String.valueOf(selY);
					coords.setText(tcoord);
				} else {
					coords.setText(" Out of bounds.");
				}
			} else {
				coords.setText(" No DMM loaded.");
			}
		} else {
			coords.setText(" No DME loaded.");
		}

		if (dmm == null || dme == null)
			return;

		while (Mouse.next()) {
			if (isAltPressed)
				continue;
			if (Mouse.getEventButtonState()) {
				if (currPopup != null && !currPopup.isVisible())
					currPopup = null;
				if (currPopup != null) {
					currPopup.setVisible(false);
					currPopup = null;
					continue;
				}
				Location l = new Location(selX, selY, 1);
				String key = dmm.map.get(l);
				if (Mouse.getEventButton() == 1) {
					if (key != null) {
						SwingUtilities.invokeLater(() -> {
							TileInstance tInstance = dmm.instances.get(key);
							currPopup = new JPopupMenu();
							currPopup.setLightWeightPopupEnabled(false);
							placementMode.addToTileMenu(this, l, tInstance, currPopup);
							List<ObjInstance> layerSorted = tInstance.getLayerSorted();
							for (int idx = layerSorted.size() - 1; idx >= 0; idx--) {
								ObjInstance i = layerSorted.get(idx);
								if (i == null)
									continue;
								boolean valid = inFilter(i);
	
								JMenu menu = new JMenu(i.getVar("name") + " (" + i.typeString() + ")");
								DMI dmi = getDmi(i.getIcon(), false);
								if (dmi != null) {
									String iconState = i.getIconState();
									IconSubstate substate = dmi.getIconState(iconState).getSubstate(i.getDir());
									menu.setIcon(substate.getScaled());
								}
								if (valid)
									menu.setFont(menu.getFont().deriveFont(Font.BOLD)); // Make it bold if is visible by the filter.
								currPopup.add(menu);
	
								JMenuItem item = new JMenuItem("Make Active Object");
								item.addActionListener(new MakeActiveObjectListener(this, l, i));
								menu.add(item);
	
								item = new JMenuItem("Delete");
								item.addActionListener(new DeleteListener(this, l, i));
								menu.add(item);
	
								item = new JMenuItem("View Variables");
								item.addActionListener(new EditVarsListener(this, l, i));
								menu.add(item);
	
								item = new JMenuItem("Move to Top");
								item.addActionListener(new MoveToTopListener(this, l, i));
								menu.add(item);
	
								item = new JMenuItem("Move to Bottom");
								item.addActionListener(new MoveToBottomListener(this, l, i));
								menu.add(item);
							}
							canvas.getParent().add(currPopup);
							currPopup.show(canvas, Mouse.getX(), Display.getHeight() - Mouse.getY());
						});
					}
				} else if (Mouse.getEventButton() == 0) {
					currPlacementHandler = placementMode.getPlacementHandler(this, selectedInstance, l);
					if (currPlacementHandler != null)
						currPlacementHandler.init(this, selectedInstance, l);
				}
			} else {
				if (Mouse.getEventButton() == 0 && currPlacementHandler != null) {
					synchronized (this) {
						currPlacementHandler.finalizePlacement();
					}
					currPlacementHandler = null;
				}
			}
		}
	}

	private void loop() {

		// Set the clear color
		glClearColor(0.25f, 0.25f, 0.5f, 1.0f);

		int width;
		int height;

		while (!Display.isCloseRequested()) {

			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			width = Display.getWidth();
			height = Display.getHeight();
			glViewport(0, 0, width, height);
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			float xScrOff = (float) width / viewportZoom / 2;
			float yScrOff = (float) height / viewportZoom / 2;
			glOrtho(-xScrOff, xScrOff, -yScrOff, yScrOff, 100, -100);
			glMatrixMode(GL_MODELVIEW);
			glLoadIdentity();
			glTranslatef(.5f, .5f, 0);
			float roundPlace = (objTree != null ? objTree.icon_size : 32) * viewportZoom / 32f;
			glTranslatef(-Math.round(viewportX*roundPlace)/roundPlace, -Math.round(viewportY*roundPlace)/roundPlace, 0);

			glEnable(GL_TEXTURE_2D);
			glEnable(GL_BLEND);

			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

			hasLoadedImageThisFrame = false;
			Set<RenderInstance> rendInstanceSet = buildViewport(true,
					(int)Math.floor(viewportX - xScrOff - 2),
					(int)Math.ceil(viewportX + xScrOff + 2),
					(int)Math.floor(viewportY - yScrOff - 2),
					(int)Math.ceil(viewportY + yScrOff + 2), 1, true);
					

			for (RenderInstance ri : rendInstanceSet) {
				glColor3f(ri.color.getRed() / 255f, ri.color.getGreen() / 255f, ri.color.getBlue() / 255f);
				glBindTexture(GL_TEXTURE_2D, ri.substate.dmi.glID);

				glPushMatrix();
				glTranslatef(ri.x, ri.y, 0);
				glBegin(GL_QUADS);
				glTexCoord2f(ri.substate.x2, ri.substate.y1);
				glVertex3f(-.5f + (ri.substate.dmi.width / (float) objTree.icon_size),
						-.5f + (ri.substate.dmi.height / (float) objTree.icon_size), 0);
				glTexCoord2f(ri.substate.x1, ri.substate.y1);
				glVertex3f(-.5f, -.5f + (ri.substate.dmi.height / (float) objTree.icon_size), 0);
				glTexCoord2f(ri.substate.x1, ri.substate.y2);
				glVertex3f(-.5f, -.5f, 0);
				glTexCoord2f(ri.substate.x2, ri.substate.y2);
				glVertex3f(-.5f + (ri.substate.dmi.width / (float) objTree.icon_size), -.5f, 0);
				glEnd();
				glPopMatrix();
			}

			glBindTexture(GL_TEXTURE_2D, -1);
			glColor4f(1, 1, 1, .25f);
			glPushMatrix();
			glTranslatef(selX, selY, 1);
			glBegin(GL_QUADS);
			glVertex3f(.5f, .5f, 0);
			glVertex3f(-.5f, .5f, 0);
			glVertex3f(-.5f, -.5f, 0);
			glVertex3f(.5f, -.5f, 0);
			glEnd();
			glPopMatrix();

			Display.update();

			processInput();

			Display.sync(60);
		}
	}
	
	private Set<RenderInstance> buildViewport(boolean editingElements, int minx, int maxx, int miny, int maxy, int zlev, boolean glIcons) {
		int currCreationIndex = 0;
		Set<RenderInstance> rendInstanceSet = new TreeSet<>();
		Location l = new Location(1, 1, zlev);
		if (dme != null && dmm != null) {
			synchronized (this) {
				for (int x = minx; x <= maxx; x++) {
					for (int y = miny; y <= maxy; y++) {
						l.x = x;
						l.y = y;
						String instanceID = dmm.map.get(l);
						if (instanceID == null)
							continue;
						TileInstance instance = dmm.instances.get(instanceID);
						if (instance == null)
							continue;
						for (ObjInstance oInstance : instance.getLayerSorted()) {
							if (oInstance == null)
								continue;
							boolean valid = inFilter(oInstance);
							if (!valid)
								continue;
							DMI dmi = getDmi(oInstance.getIcon(), glIcons);
							if (dmi == null)
								continue;
							String iconState = oInstance.getIconState();
							IconSubstate substate = dmi.getIconState(iconState).getSubstate(oInstance.getDir());

							RenderInstance ri = new RenderInstance(currCreationIndex++);
							ri.layer = oInstance.getLayer();
							ri.plane = oInstance.getPlane();
							ri.x = x + (oInstance.getPixelX() / (float) objTree.icon_size);
							ri.y = y + (oInstance.getPixelY() / (float) objTree.icon_size);
							ri.substate = substate;
							ri.color = oInstance.getColor();

							rendInstanceSet.add(ri);
						}
						if(editingElements) {
							int dirs = 0;
							for (int i = 0; i < 4; i++) {
								int cdir = IconState.indexToDirArray[i];
								Location l2 = l.getStep(cdir);
								String instId = dmm.map.get(l2);
								if (instId == null) {
									dirs |= cdir;
									continue;
								}
								TileInstance instance2 = dmm.instances.get(instId);
								if (instance2 == null) {
									dirs |= cdir;
									continue;
								}
								if (!instance.getArea().equals(instance2.getArea())) {
									dirs |= cdir;
								}
							}
							if (dirs != 0) {
								RenderInstance ri = new RenderInstance(currCreationIndex++);
								ri.plane = 101;
								ri.x = x;
								ri.y = y;
								ri.substate = interface_dmi.getIconState("" + dirs).getSubstate(2);
								ri.color = new Color(200, 200, 200);
								
								rendInstanceSet.add(ri);
							}
						}
					}
				}
			}
		}
		
		if(editingElements) {
			if (currPlacementHandler != null) {
				currCreationIndex = currPlacementHandler.visualize(rendInstanceSet, currCreationIndex);
			}
			
			currCreationIndex = placementMode.visualize(rendInstanceSet, currCreationIndex);
		}
		return rendInstanceSet;
	}

	private void addToRecent(File dme, DMM dmm) {
		String path = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator + "recent.json";
		File file = new File(path);
		JSONObject main = new JSONObject();

		if (!file.exists()) {
			JSONObject env = new JSONObject();
			JSONArray maplist = new JSONArray();
			env.put("dme", dme.getPath());
			env.put("maplist", maplist);
			main.put("dme1", env);
			try {
				Files.write(Paths.get(path), main.toString().getBytes(), StandardOpenOption.CREATE);
			} catch (IOException e) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				e.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			}
		}

		FileReader reader = null;
		try {
			reader = new FileReader(path);
		} catch (FileNotFoundException e) {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
		}
		main = new JSONObject(new JSONTokener(Objects.requireNonNull(reader)));

		Iterator<?> keys = main.keys();
		String checkmaps = null;
		String listmap = null;
		boolean createDME = true;

		while (keys.hasNext()) {
			String key = (String) keys.next();
			if (main.get(key) instanceof JSONObject) {
				JSONObject environ = main.getJSONObject(key);
				if (environ.get("dme").equals(dme.getPath())) {
					checkmaps = key;
					createDME = false;
				}
			}
		}

		if (createDME) {
			JSONObject environ = new JSONObject();
			JSONArray listmaps = new JSONArray();
			environ.put("dme", dme.getPath());
			environ.put("maplist", listmaps);
			main.put("dme" + String.valueOf(JSONObject.getNames(main).length + 1), environ);
			checkmaps = "dme" + String.valueOf(JSONObject.getNames(main).length);
		}

		JSONObject tocheck = main.getJSONObject(checkmaps);
		Iterator<?> checkkeys = tocheck.keys();

		if (dmm != null) {
			while (checkkeys.hasNext()) {
				String key = (String) checkkeys.next();
				if (tocheck.get(key) instanceof JSONArray) {
					JSONArray array = tocheck.getJSONArray(key);
					listmap = key;
					for (int i = 0; i < array.length(); i++) {
						String mapcheck = array.getString(i);
						if (mapcheck.equals(dmm.file.getPath())) {
							return;
						}
					}
				}
			}

			JSONObject addmap = main.getJSONObject(checkmaps);
			JSONArray listofmaps = addmap.getJSONArray(listmap);

			if (listofmaps.length() == 0) {
				listofmaps.put(listofmaps.length(), dmm.file.getPath());
			} else {
				listofmaps.put(listofmaps.length() + 1, dmm.file.getPath());
			}
		}

		String toWrite = main.toString();
		toWrite = toWrite.replaceAll(",null", ""); // yes, it's bad but I have
													// no idea why the null
													// thing happens

		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(file));
			writer.write(toWrite);
			writer.flush();
			writer.close();
		} catch (IOException e) {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
		}
	}

	private void initRecent(String mode) {
		String recentPath = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator
				+ "recent.json";
		File recent = new File(recentPath);
		if (recent.exists()) {
			JSONObject main;
			FileReader reader = null;
			try {
				reader = new FileReader(recentPath);
			} catch (FileNotFoundException e) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				e.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			}
			main = new JSONObject(new JSONTokener(Objects.requireNonNull(reader)));

			if (mode.equals("dme") || mode.equals("both")) {
				Iterator<?> keys = main.keys();
				menuRecent.removeAll();
				while (keys.hasNext()) {
					String key = (String) keys.next();
					if (main.get(key) instanceof JSONObject) {
						JSONObject environ = main.getJSONObject(key);
						File f = new File(environ.getString("dme"));
						JMenuItem menuItem = new JMenuItem(environ.getString("dme"));
						menuItem.addActionListener(arg0 -> openDME(f));
						menuItem.setEnabled(true);
						menuRecent.add(menuItem);
					}
				}
			}

			if (mode.equals("dmm") || mode.equals("both")) {
				Iterator<?> keys = main.keys();
				menuRecentMaps.removeAll();
				String checkmaps = null;
				while (keys.hasNext()) {
					String key = (String) keys.next();
					if (main.get(key) instanceof JSONObject) {
						JSONObject environ = main.getJSONObject(key);
						if (environ.get("dme").equals(dme.getPath())) {
							checkmaps = key;
						}
					}
				}

				if (checkmaps != null) {
					JSONObject tocheck = main.getJSONObject(checkmaps);
					Iterator<?> checkkeys = tocheck.keys();

					while (checkkeys.hasNext()) {
						String keymap = (String) checkkeys.next();
						if (tocheck.get(keymap) instanceof JSONArray) {
							JSONArray array = tocheck.getJSONArray(keymap);
							for (int i = 0; i < array.length(); i++) {
								String map = array.getString(i);
								File f = new File(map);
								JMenuItem menuItem = new JMenuItem(map);
								menuItem.addActionListener(arg0 -> openDMM(f));
								menuItem.setEnabled(true);
								menuRecentMaps.add(menuItem);
							}
						}
					}
				}
			}
		}
	}

	private void convertintojson() {
		String recentPath = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator
				+ "recent.txt";
		File recent = new File(recentPath);
		if (recent.exists()) {
			try (BufferedReader br = new BufferedReader(new FileReader(recentPath))) {
				String line;
				while ((line = br.readLine()) != null) {
					File dmetoadd = new File(line);
					if (dmetoadd.exists()) {
						addToRecent(dmetoadd, null);
					}
				}
			} catch (IOException e) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				e.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			}
			recent.delete();
			String dummy = System.getProperty("user.home") + File.separator + ".fastdmm" + File.separator + "dummy";
			try {
				Files.write(Paths.get(dummy), "".getBytes(), StandardOpenOption.CREATE);
			} catch (IOException e) {
				StringWriter sw = new StringWriter();
				PrintWriter pw = new PrintWriter(sw);
				e.printStackTrace(pw);
				JOptionPane.showMessageDialog(FastDMM.this, sw.getBuffer(), "Error", JOptionPane.ERROR_MESSAGE);
			}
		}
	}
	
	private BufferedImage createMapImage(int zlev) {
		int imgwidth = (dmm.maxX+1-dmm.minX)*objTree.icon_size;
		int imgheight = (dmm.maxY+1-dmm.minY)*objTree.icon_size;
		
		BufferedImage img = new BufferedImage(imgwidth, imgheight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = img.createGraphics();
		
		Set<RenderInstance> rendInstanceSet = buildViewport(false, dmm.minX, dmm.maxX, dmm.minY, dmm.maxY, 1, false);
		
		for (RenderInstance ri : rendInstanceSet) {
			DMI dmi = ri.substate.dmi;
			int px = (int)((ri.x - dmm.minX) * objTree.icon_size);
			int py = (int)((dmm.maxY - ri.y + dmm.minY - 1) * objTree.icon_size) - (dmi.height - objTree.icon_size);
			
			if(ri.color.getRed() == 255 && ri.color.getGreen() == 255 && ri.color.getBlue() == 255) {
				// Easy time!
				g.drawImage(dmi.image, px, py, px+dmi.width, py+dmi.height, ri.substate.i_x1, ri.substate.i_y1, ri.substate.i_x2+1, ri.substate.i_y2+1, null);
			} else {
				float rm = ri.color.getRed() / 255f;
				float gm = ri.color.getGreen() / 255f;
				float bm = ri.color.getBlue() / 255f;
				float am = ri.color.getAlpha() / 255f;
				if(am == 0)
					continue;
				for(int x = 0; x < dmi.width; x++) {
					for(int y = 0; y < dmi.height; y++) {
						int pixel = dmi.image.getRGB(x+ri.substate.i_x1, y+ri.substate.i_y1);
						if(((pixel >> 24) & 0xFF) == 0)
							continue;
						g.setColor(new Color((int)(((pixel >> 16) & 0xFF) * rm),(int)(((pixel >> 8) & 0xFF) * gm),(int)((pixel & 0xFF) * bm),(int)(((pixel >> 24) & 0xFF) * am)));
						g.drawRect(px+x, py+y, 0, 0);
					}
				}
			}
		}
		g.dispose();
		return img;
	}
	
	public boolean inFilter(ObjInstance i) {
		boolean valid = false;
		synchronized (filters) {
			for (String s : filters) {
				boolean newValid = true;
				if(s.startsWith("~")) {
					s = s.substring(1);
					newValid = false; 
				}
					
				if(i.toString().startsWith(s)) {
					valid = newValid;
				}
			}
		}
		return valid;
	}
	
	public void addToUndoStack(Undoable action){
		if(action == null) {
			return;
		}
		undostack.push(action);
		redostack.clear();
		menuItemUndo.setEnabled(true);
		menuItemRedo.setEnabled(false);
	}
	
	public boolean undoAction(){
		if(undostack.empty()) {
			return false;
		}
		Undoable action = undostack.pop();
		redostack.push(action);
		menuItemRedo.setEnabled(true);
		menuItemUndo.setEnabled(!undostack.isEmpty());
		return action.undo();
	}
	
	public boolean redoAction(){
		if(redostack.empty()) {
			return false;
		}
		Undoable action = redostack.pop();
		undostack.push(action);
		menuItemUndo.setEnabled(true);
		menuItemRedo.setEnabled(!redostack.isEmpty());
		return action.redo();
	}
	
}