/*
 * TreeViewerController.java
 *
 * Copyright (C) 2006-2014 Andrew Rambaut
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package figtree.treeviewer;

import figtree.treeviewer.treelayouts.*;
import jam.controlpalettes.AbstractController;
import jam.panels.OptionsPanel;
import jam.util.IconUtils;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.prefs.Preferences;

/**
 * @author Andrew Rambaut
 * @version $Id$
 *
 * $HeadURL$
 *
 * $LastChangedBy$
 * $LastChangedDate$
 * $LastChangedRevision$
 */
public class TreeViewerController extends AbstractController {

	public enum TreeLayoutType {
		RECTILINEAR("Rectangle"),
		POLAR("Polar"),
		RADIAL("Radial");

		TreeLayoutType(String name) {
			this.name = name;
		}

		public String toString() {
			return name;
		}

		private final String name;
	}

	private static final String CONTROLLER_TITLE = "Layout";

	private static Preferences PREFS = Preferences.userNodeForPackage(TreeViewerController.class);

	private static final String CONTROLLER_KEY = "layout";

	private static final String LAYOUT_KEY = "layoutType";
	private static final String ZOOM_KEY = "zoom";
	private static final String EXPANSION_KEY = "expansion";

	// The defaults if there is nothing in the preferences
	private static String DEFAULT_LAYOUT = TreeLayoutType.RECTILINEAR.name();

	private final static int MAX_ZOOM_SLIDER = 1000;
	private final static int DELTA_ZOOM_SLIDER = 1;

	public TreeViewerController(final TreeViewer treeViewer) {

		this.treeViewer = treeViewer;

		final TreeLayoutType defaultLayout = TreeLayoutType.valueOf(PREFS.get(CONTROLLER_KEY + "." + LAYOUT_KEY, DEFAULT_LAYOUT));

		titleLabel = new JLabel(CONTROLLER_TITLE);

		optionsPanel = new ControllerOptionsPanel(2, 2);

		rectilinearTreeLayout = new RectilinearTreeLayout();
		rectilinearTreeLayoutController = new RectilinearTreeLayoutController(rectilinearTreeLayout);

		polarTreeLayout = new PolarTreeLayout();
		polarTreeLayoutController = new PolarTreeLayoutController(polarTreeLayout);

		radialTreeLayout = new RadialTreeLayout();
		radialTreeLayoutController = new RadialTreeLayoutController(radialTreeLayout);

		JPanel panel1 = new JPanel();
		panel1.setOpaque(false);
		panel1.setLayout(new BoxLayout(panel1, BoxLayout.LINE_AXIS));
		Icon rectangularTreeIcon = IconUtils.getIcon(this.getClass(), "images/rectangularTree.png");
		Icon polarTreeIcon = IconUtils.getIcon(this.getClass(), "images/polarTree.png");
		Icon radialTreeIcon = IconUtils.getIcon(this.getClass(), "images/radialTree.png");
		rectangularTreeToggle = new JToggleButton(rectangularTreeIcon);
		polarTreeToggle = new JToggleButton(polarTreeIcon);
		radialTreeToggle = new JToggleButton(radialTreeIcon);
		rectangularTreeToggle.setToolTipText("Rectangular tree layout");
		polarTreeToggle.setToolTipText("Polar tree layout");
		radialTreeToggle.setToolTipText("Radial tree layout");
        rectangularTreeToggle.setFocusable(false);
        polarTreeToggle.setFocusable(false);
        radialTreeToggle.setFocusable(false);
		rectangularTreeToggle.putClientProperty("Quaqua.Button.style", "toggleWest");
		rectangularTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
		rectangularTreeToggle.putClientProperty("JButton.segmentPosition", "first");
		polarTreeToggle.putClientProperty("Quaqua.Button.style", "toggleCenter");
		polarTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
		polarTreeToggle.putClientProperty("JButton.segmentPosition", "middle");
		radialTreeToggle.putClientProperty("Quaqua.Button.style", "toggleEast");
		radialTreeToggle.putClientProperty("JButton.buttonType", "segmentedTextured");
		radialTreeToggle.putClientProperty("JButton.segmentPosition", "last");
		ButtonGroup buttonGroup = new ButtonGroup();
		buttonGroup.add(rectangularTreeToggle);
		buttonGroup.add(polarTreeToggle);
		buttonGroup.add(radialTreeToggle);
		rectangularTreeToggle.setSelected(true);
		panel1.add(Box.createHorizontalGlue());
		panel1.add(rectangularTreeToggle);
		panel1.add(polarTreeToggle);
		panel1.add(radialTreeToggle);
		panel1.add(Box.createHorizontalGlue());

		optionsPanel.addSpanningComponent(panel1);

		zoomSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
		zoomSlider.setOpaque(false);
		zoomSlider.setAlignmentX(Component.LEFT_ALIGNMENT);

//        zoomSlider.setPaintTicks(true);
//        zoomSlider.setPaintLabels(true);

		zoomSlider.setValue(0);

		zoomSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent changeEvent) {
				final int value = zoomSlider.getValue();
				treeViewer.setZoom(((double) value) / MAX_ZOOM_SLIDER);
			}
		});

		optionsPanel.addComponentWithLabel("Zoom:", zoomSlider, true);

		verticalExpansionSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
		verticalExpansionSlider.setOpaque(false);
//        verticalExpansionSlider.setPaintTicks(true);
//        verticalExpansionSlider.setPaintLabels(true);

		verticalExpansionSlider.setValue(0);

		verticalExpansionSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent changeEvent) {
				final int value = verticalExpansionSlider.getValue();
				treeViewer.setVerticalExpansion(((double) value) / MAX_ZOOM_SLIDER);
			}
		});

		verticalExpansionLabel = new JLabel("Expansion:");
		optionsPanel.addComponents(verticalExpansionLabel, false, verticalExpansionSlider, true);

		fishEyeSlider = new JSlider(SwingConstants.HORIZONTAL, 0, MAX_ZOOM_SLIDER, 0);
		fishEyeSlider.setOpaque(false);

		fishEyeSlider.setValue(0);

		fishEyeSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent changeEvent) {
				final double fishEye = ((double) fishEyeSlider.getValue()) / MAX_ZOOM_SLIDER;
				rectilinearTreeLayout.setFishEye(fishEye);
				polarTreeLayout.setFishEye(fishEye);
				radialTreeLayout.setFishEye(fishEye);
			}
		});

		fishEyeLabel = new JLabel("Fish Eye:");
		optionsPanel.addComponents(fishEyeLabel, false, fishEyeSlider, true);

		optionsPanel.addSeparator();

		layoutPanel = new JPanel(new BorderLayout());
		layoutPanel.setOpaque(false);
		setTreeLayout(defaultLayout);
		setExpansion();

		optionsPanel.addSpanningComponent(layoutPanel);

		rectangularTreeToggle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                if (rectangularTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RECTILINEAR);
            }
        });
		polarTreeToggle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
				if (polarTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.POLAR);
			}
		});
		radialTreeToggle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
				if (radialTreeToggle.isSelected()) setTreeLayout(TreeLayoutType.RADIAL);
			}
		});

		// Set some InputMaps and ActionMaps for key strokes. The ActionMaps are set in setExpansion()
		// because they differ by whether vertical expansion is allowed for the current layout.
		// The key strokes could be obtained from preferences and set in a preference dialog box
		optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("meta 0"), "resetZoom");
		optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("meta EQUALS"), "increasePrimaryZoom");
		optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("meta MINUS"), "decreasePrimaryZoom");
		optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("meta alt EQUALS"), "increaseSecondaryZoom");
		optionsPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
				KeyStroke.getKeyStroke("meta alt MINUS"), "decreaseSecondaryZoom");

		optionsPanel.getActionMap().put("resetZoom", resetZoomAction);

	}

	public JComponent getTitleComponent() {
		return titleLabel;
	}

	public JPanel getPanel() {
		return optionsPanel;
	}

	public boolean isInitiallyVisible() {
		return true;
	}

	public void setColouringAttributeName(String attributeName) {
		rectilinearTreeLayout.setBranchColouringAttributeName(attributeName);
		polarTreeLayout.setBranchColouringAttributeName(attributeName);
		radialTreeLayout.setBranchColouringAttributeName(attributeName);
	}

	public void initialize() {
		// nothing to do
	}

	public void setSettings(Map<String,Object> settings) {
		String treeLayoutName = (String)settings.get(CONTROLLER_KEY + "." + LAYOUT_KEY);
		final TreeLayoutType layout = TreeLayoutType.valueOf(treeLayoutName);
		switch (layout) {
			case RECTILINEAR:
				rectangularTreeToggle.setSelected(true);
				break;
			case POLAR:
				polarTreeToggle.setSelected(true);
				break;
			case RADIAL:
				radialTreeToggle.setSelected(true);
				break;
		}
        setTreeLayout(layout);

		zoomSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + ZOOM_KEY));
		verticalExpansionSlider.setValue((Integer)settings.get(CONTROLLER_KEY + "." + EXPANSION_KEY));

		// These controllers are internal to TreeViewerController so settings must be done here
		rectilinearTreeLayoutController.setSettings(settings);
		polarTreeLayoutController.setSettings(settings);
		radialTreeLayoutController.setSettings(settings);
	}

	public void getSettings(Map<String, Object> settings) {
		if (rectangularTreeToggle.isSelected()) {
			settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RECTILINEAR.name());
		} else if (polarTreeToggle.isSelected()) {
			settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.POLAR.name());
		} else if (radialTreeToggle.isSelected()) {
			settings.put(CONTROLLER_KEY + "." + LAYOUT_KEY, TreeLayoutType.RADIAL.name());
		}
		settings.put(CONTROLLER_KEY + "." + ZOOM_KEY, zoomSlider.getValue());
		settings.put(CONTROLLER_KEY + "." + EXPANSION_KEY, verticalExpansionSlider.getValue());

		// These controllers are internal to TreeViewerController so settings must be done here
		rectilinearTreeLayoutController.getSettings(settings);
		polarTreeLayoutController.getSettings(settings);
		radialTreeLayoutController.getSettings(settings);
	}

	private void setTreeLayout(TreeLayoutType layoutType) {
		switch (layoutType) {
			case RECTILINEAR:
				treeViewer.setTreeLayout(rectilinearTreeLayout);
				setExpansion();
				fishEyeLabel.setEnabled(true);
				fishEyeSlider.setEnabled(true);
				layoutPanel.removeAll();
				layoutPanel.add(rectilinearTreeLayoutController.getPanel(), BorderLayout.CENTER);
				fireControllerChanged();
				break;
			case POLAR:
				treeViewer.setTreeLayout(polarTreeLayout);
				setExpansion();
				fishEyeLabel.setEnabled(true);
				fishEyeSlider.setEnabled(true);
				layoutPanel.removeAll();
				layoutPanel.add(polarTreeLayoutController.getPanel(), BorderLayout.CENTER);
				fireControllerChanged();
				break;
			case RADIAL:
				treeViewer.setTreeLayout(radialTreeLayout);
				setExpansion();
				fishEyeLabel.setEnabled(false);
				fishEyeSlider.setEnabled(false);
				layoutPanel.removeAll();
				layoutPanel.add(radialTreeLayoutController.getPanel(), BorderLayout.CENTER);
				fireControllerChanged();
				break;
			default:
				new RuntimeException("Unknown TreeLayoutType: " + layoutType);
		}

	}

	private void setExpansion() {
		if (treeViewer.verticalExpansionAllowed()) {
			verticalExpansionLabel.setEnabled(true);
			verticalExpansionSlider.setEnabled(true);
			optionsPanel.getActionMap().put("increasePrimaryZoom", increaseVerticalExpansionAction);
			optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseVerticalExpansionAction);
			optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
			optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
		} else {
			verticalExpansionLabel.setEnabled(false);
			verticalExpansionSlider.setEnabled(false);
			optionsPanel.getActionMap().put("increasePrimaryZoom", increaseZoomAction);
			optionsPanel.getActionMap().put("decreasePrimaryZoom", decreaseZoomAction);
			optionsPanel.getActionMap().put("increaseSecondaryZoom", increaseZoomAction);
			optionsPanel.getActionMap().put("decreaseSecondaryZoom", decreaseZoomAction);
		}
	}

	private Action resetZoomAction = new AbstractAction("Reset Zoom") {
		public void actionPerformed(ActionEvent actionEvent) {
			zoomSlider.setValue(0);
			verticalExpansionSlider.setValue(0);
		}
	};

	private Action increaseZoomAction = new AbstractAction("Zoom In") {
		public void actionPerformed(ActionEvent actionEvent) {
			zoomSlider.setValue(zoomSlider.getValue() + DELTA_ZOOM_SLIDER);
		}
	};

	private Action decreaseZoomAction = new AbstractAction("Zoom In") {
		public void actionPerformed(ActionEvent actionEvent) {
			zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
		}
	};

	private Action increaseVerticalExpansionAction = new AbstractAction("Expand Vertically") {
		public void actionPerformed(ActionEvent actionEvent) {
			verticalExpansionSlider.setValue(verticalExpansionSlider.getValue() + DELTA_ZOOM_SLIDER);
		}
	};

	private Action decreaseVerticalExpansionAction = new AbstractAction("Unexpand Vertically") {
		public void actionPerformed(ActionEvent actionEvent) {
			int value = verticalExpansionSlider.getValue();
			if (value > 0) {
				verticalExpansionSlider.setValue(value - DELTA_ZOOM_SLIDER);
			} else {
				// If the vertical expansion was zero then assume the user is trying to un-zoom
				zoomSlider.setValue(zoomSlider.getValue() - DELTA_ZOOM_SLIDER);
			}
		}
	};


	private JToggleButton rectangularTreeToggle;
	private JToggleButton polarTreeToggle;
	private JToggleButton radialTreeToggle;
	private JSlider zoomSlider;
	private JSlider verticalExpansionSlider;
	private JLabel verticalExpansionLabel;
	private JSlider fishEyeSlider;
	private JLabel fishEyeLabel;

	private final JPanel layoutPanel;

	private final JLabel titleLabel;
	private final OptionsPanel optionsPanel;

	private final RectilinearTreeLayout rectilinearTreeLayout;
	private final PolarTreeLayout polarTreeLayout;
	private final RadialTreeLayout radialTreeLayout;

	private final RectilinearTreeLayoutController rectilinearTreeLayoutController;
	private final PolarTreeLayoutController polarTreeLayoutController;
	private final RadialTreeLayoutController radialTreeLayoutController;

	private final TreeViewer treeViewer;

}
