/*******************************************************************************
 * Copyright (c) MOBAC developers
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package mobac.gui.atlastree;

import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import mobac.gui.MainGUI;
import mobac.gui.mapview.PreviewMap;
import mobac.gui.mapview.layer.MapAreaHighlightingLayer;
import mobac.program.interfaces.AtlasInterface;
import mobac.program.interfaces.AtlasObject;
import mobac.program.interfaces.CapabilityDeletable;
import mobac.program.interfaces.LayerInterface;
import mobac.program.interfaces.MapInterface;
import mobac.program.interfaces.RequiresSQLite;
import mobac.program.interfaces.ToolTipProvider;
import mobac.program.model.Atlas;
import mobac.program.model.AtlasOutputFormat;
import mobac.program.model.AtlasTreeModel;
import mobac.program.model.EastNorthCoordinate;
import mobac.program.model.MapSelection;
import mobac.program.model.Profile;
import mobac.program.model.TileImageParameters;
import mobac.utilities.GUIExceptionHandler;
import mobac.utilities.I18nUtils;
import mobac.utilities.jdbc.SQLiteLoader;

import org.apache.log4j.Logger;

public class JAtlasTree extends JTree implements Autoscroll {

	private static final long serialVersionUID = 1L;
	private static final int margin = 12;

	private static final String MSG_ATLAS_VERSION_MISMATCH = I18nUtils.localizedStringForKey("msg_atlas_version_mismatch");

	private static final String MSG_ATLAS_DATA_CHECK_FAILED = I18nUtils.localizedStringForKey("msg_atlas_data_check_failed");

	private static final String MSG_ATLAS_EMPTY = I18nUtils.localizedStringForKey("msg_atlas_is_empty");

	private static final String ACTION_DELETE_NODE = "DELETE_NODE";

	private static final Logger log = Logger.getLogger(JAtlasTree.class);

	private AtlasTreeModel treeModel;

	private PreviewMap mapView;

	protected NodeRenderer nodeRenderer;

	protected String defaultToolTiptext;

	protected KeyStroke deleteNodeKS;

	protected DragDropController ddc;

	protected boolean displaySelectedMapArea = false;

	public JAtlasTree(PreviewMap mapView) {
		super(new AtlasTreeModel());
		if (mapView == null)
			throw new NullPointerException("MapView parameter is null");
		this.mapView = mapView;
		getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		ddc = new DragDropController(this);
		treeModel = (AtlasTreeModel) getModel();
		// setRootVisible(false);
		setShowsRootHandles(true);
		nodeRenderer = new NodeRenderer();
		setCellRenderer(nodeRenderer);
		setCellEditor(new NodeEditor(this));
		setToolTipText("");
		defaultToolTiptext = I18nUtils.localizedStringForKey("lp_atlas_default_tip");
		setAutoscrolls(true);
		addMouseListener(new MouseController(this));

		InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
		ActionMap actionMap = getActionMap();

		// map moving
		inputMap.put(deleteNodeKS = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), ACTION_DELETE_NODE);
		actionMap.put(ACTION_DELETE_NODE, new AbstractAction(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_delete_node")) {

			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent e) {
				deleteSelectedNode();
				JAtlasTree.this.mapView.repaint();
			}

		});

	}

	public boolean testAtlasContentValid() {
		AtlasInterface atlas = getAtlas();
		if (RequiresSQLite.class.isAssignableFrom(atlas.getOutputFormat().getMapCreatorClass())) {
			if (!SQLiteLoader.loadSQLiteOrShowError())
				return false;
		}
		if (atlas.calculateTilesToDownload() == 0) {
			JOptionPane.showMessageDialog(null, "<html>" + MSG_ATLAS_EMPTY + "</html>", "Error - atlas has no content",
					JOptionPane.ERROR_MESSAGE);
			return false;
		}
		return true;
	}

	@Override
	public String getToolTipText(MouseEvent event) {
		if (getRowForLocation(event.getX(), event.getY()) == -1)
			return defaultToolTiptext;
		TreePath curPath = getPathForLocation(event.getX(), event.getY());
		Object o = curPath.getLastPathComponent();
		if (o == null || !(o instanceof ToolTipProvider))
			return null;
		return ((ToolTipProvider) o).getToolTip();
	}

	@Override
	public boolean isPathEditable(TreePath path) {
		return super.isPathEditable(path) && (path.getLastPathComponent() instanceof AtlasObject);
	}

	public AtlasTreeModel getTreeModel() {
		return treeModel;
	}

	public void newAtlas(String name, AtlasOutputFormat format) {
		log.debug("Creating new atlas");
		Atlas newAtlas = Atlas.newInstance();
		newAtlas.setOutputFormat(format);
		newAtlas.setName(name);
		treeModel.setAtlas(newAtlas);
		mapView.repaint();
	}

	public void newAtlas() {
		log.debug("Resetting atlas tree model");
		Atlas newAtlas = Atlas.newInstance();
		newAtlas.setName(MainGUI.getMainGUI().getUserText());
		treeModel.setAtlas(newAtlas);
		mapView.repaint();
	}

	/**
	 * Changes the atlas format
	 */
	public void convertAtlas(AtlasOutputFormat format) {
		log.debug("Converting the atlas format to " + format);
		treeModel.getAtlas().setOutputFormat(format);
	}

	public void deleteSelectedNode() {
		TreePath path = getSelectionPath();
		if (path == null)
			return;
		TreeNode selected = (TreeNode) path.getLastPathComponent();
		int[] selectedRows = getSelectionRows();

		if (!(selected instanceof CapabilityDeletable))
			return;
		treeModel.notifyNodeDelete(selected);
		((CapabilityDeletable) selected).delete();

		int selRow = Math.min(selectedRows[0], getRowCount() - 1);
		TreePath path1 = path.getParentPath();
		TreePath path2 = getPathForRow(selRow).getParentPath();
		if (path1 != path2) {
			// next row belongs to different parent node -> we select parent
			// node instead
			setSelectionPath(path1);
		} else {
			setSelectionRow(selRow);
			scrollRowToVisible(selRow);
		}
	}

	public AtlasInterface getAtlas() {
		return treeModel.getAtlas();
	}

	public boolean load(Profile profile) {
		log.debug("Loading profile " + profile);
		try {
			treeModel.load(profile);
			if (treeModel.getAtlas() instanceof Atlas) {
				Atlas atlas = (Atlas) treeModel.getAtlas();
				if (atlas.getVersion() < Atlas.CURRENT_ATLAS_VERSION) {
					JOptionPane.showMessageDialog(null, MSG_ATLAS_VERSION_MISMATCH, "Outdated atlas version",
							JOptionPane.WARNING_MESSAGE);
					return true;
				}
			}
			boolean problemsDetected = Profile.checkAtlas(treeModel.getAtlas());
			if (problemsDetected) {
				JOptionPane.showMessageDialog(null, MSG_ATLAS_DATA_CHECK_FAILED, "Atlas loading problem",
						JOptionPane.WARNING_MESSAGE);
			}
			return true;
		} catch (Exception e) {
			GUIExceptionHandler.processException(e);
			return false;
		}
	}

	public boolean save(Profile profile) {
		try {
			treeModel.save(profile);
			return true;
		} catch (Exception e) {
			GUIExceptionHandler.processException(e);
			return false;
		}
	}

	protected void showNodePopupMenu(MouseEvent event) {
		JPopupMenu pm = new JPopupMenu();
		final TreePath selPath = getPathForLocation(event.getX(), event.getY());
		setSelectionPath(selPath);
		JMenuItem mi = null;
		if (selPath != null) {
			// not clicked on empty area
			final Object o = selPath.getLastPathComponent();
			if (o == null)
				return;
			if (o instanceof ToolTipProvider) {
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_show_detail"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						ToolTipProvider ttp = (ToolTipProvider) o;
						JOptionPane.showMessageDialog(MainGUI.getMainGUI(), ttp.getToolTip());
					}
				});
				pm.add(mi);
			}
			if (o instanceof AtlasObject) {
				final JCheckBoxMenuItem cbmi = new JCheckBoxMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_display_select_area"));
				final MapAreaHighlightingLayer msl = new MapAreaHighlightingLayer(this);
				cbmi.setSelected(displaySelectedMapArea);
				cbmi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						if (displaySelectedMapArea) {
							MapAreaHighlightingLayer.removeHighlightingLayers();
						} else {
							mapView.setSelectionByTileCoordinate(null, null, false);
							MapAreaHighlightingLayer.removeHighlightingLayers();
							mapView.mapLayers.add(msl);
						}
						displaySelectedMapArea = !displaySelectedMapArea;
						mapView.repaint();
					}
				});
				pm.add(cbmi);
			}
			if (o instanceof MapInterface) {
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_select_map_box"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						MapInterface map = (MapInterface) o;
						mapView.setMapSource(map.getMapSource());
						mapView.setSelectionByTileCoordinate(map.getZoom(), map.getMinTileCoordinate(), map
								.getMaxTileCoordinate(), true);
					}
				});
				pm.add(mi);
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_zoom_to_map_box"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						MapInterface map = (MapInterface) o;
						MapSelection ms = new MapSelection(map);
						mapView.setMapSource(map.getMapSource());
						mapView.setSelectionAndZoomTo(ms, true);
						mapView.setSelectionByTileCoordinate(map.getZoom(), map.getMinTileCoordinate(), map
								.getMaxTileCoordinate(), true);
					}
				});
				pm.add(mi);
			}
			if (o instanceof LayerInterface) {
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_zoom_to"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						LayerInterface layer = (LayerInterface) o;
						EastNorthCoordinate max = new EastNorthCoordinate(Double.NEGATIVE_INFINITY,
								Double.NEGATIVE_INFINITY);
						EastNorthCoordinate min = new EastNorthCoordinate(Double.POSITIVE_INFINITY,
								Double.POSITIVE_INFINITY);
						for (MapInterface map : layer) {
							MapSelection ms = new MapSelection(map);
							EastNorthCoordinate mapMax = ms.getMax();
							EastNorthCoordinate mapMin = ms.getMin();
							max.lat = Math.max(max.lat, mapMax.lat);
							max.lon = Math.max(max.lon, mapMax.lon);
							min.lat = Math.min(min.lat, mapMin.lat);
							min.lon = Math.min(min.lon, mapMin.lon);
						}
						MapSelection ms = new MapSelection(mapView.getMapSource(), max, min);
						mapView.zoomTo(ms);
					}
				});
				pm.add(mi);
			}
			if (o instanceof AtlasObject) {
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_rename"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						JAtlasTree.this.startEditingAtPath(selPath);
					}
				});
				pm.add(mi);
				mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_apply_tile_process"));
				mi.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent e) {
						AtlasObject atlasObject = (AtlasObject) o;
						TileImageParameters p = MainGUI.getMainGUI().getSelectedTileImageParameters();
						applyTileImageParameters(atlasObject, p);
					}
				});
				pm.add(mi);
			}
			if (o instanceof CapabilityDeletable) {
				pm.addSeparator();
				mi = new JMenuItem(getActionMap().get(ACTION_DELETE_NODE));
				mi.setAccelerator(deleteNodeKS);
				pm.add(mi);
			}
		}
		if (pm.getComponentCount() > 0)
			pm.addSeparator();
		mi = new JMenuItem(I18nUtils.localizedStringForKey("lp_atlas_pop_menu_clear_atals"));
		mi.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				newAtlas();
			}
		});
		pm.add(mi);
		pm.show(this, event.getX(), event.getY());
	}

	protected void applyTileImageParameters(Object o, TileImageParameters p) {
		if (o instanceof Iterable<?>) {
			Iterable<?> it = (Iterable<?>) o;
			for (Object ao : it) {
				applyTileImageParameters(ao, p);
			}
		} else if (o instanceof MapInterface) {
			((MapInterface) o).setParameters(p);
		}
	}

	protected void selectElementOnMap(Object o) {
		if (o instanceof MapInterface) {
			MapInterface map = (MapInterface) o;
			mapView.setMapSource(map.getMapSource());
			mapView.setSelectionByTileCoordinate(map.getZoom(), map.getMinTileCoordinate(), map.getMaxTileCoordinate(),
					true);
		}
	}

	public void autoscroll(Point cursorLocn) {
		int realrow = getRowForLocation(cursorLocn.x, cursorLocn.y);
		Rectangle outer = getBounds();
		realrow = (cursorLocn.y + outer.y <= margin ? realrow < 1 ? 0 : realrow - 1
				: realrow < getRowCount() - 1 ? realrow + 1 : realrow);
		scrollRowToVisible(realrow);
	}

	public Insets getAutoscrollInsets() {
		Rectangle outer = getBounds();
		Rectangle inner = getParent().getBounds();
		return new Insets(inner.y - outer.y + margin, inner.x - outer.x + margin, outer.height - inner.height - inner.y
				+ outer.y + margin, outer.width - inner.width - inner.x + outer.x + margin);
	}

}
