package org.jalgo.module.ebnf.gui.wordalgorithm;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;

import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JToolBar;

import org.jalgo.main.gui.JAlgoGUIConnector;
import org.jalgo.main.gui.components.JToolbarButton;
import org.jalgo.main.util.Messages;
import org.jalgo.module.ebnf.controller.wordalgorithm.WordAlgorithmController;
import org.jalgo.module.ebnf.gui.wordalgorithm.events.ToolbarButtonAction;
import org.jalgo.module.ebnf.model.syndia.SynDiaSystem;
import org.jalgo.module.ebnf.util.ActionStack;

/**
 * This method represents the GuiController during the WordAlgorithm. This
 * GuiController could be called by the <code>MainController</code>. This
 * GuiController knows its <code>WordAlgorithmController</code> and its GUI.
 * 
 * @author Claas Wilke
 * 
 */
@SuppressWarnings("serial")
public class GuiController implements Observer {

	// Constants which define minimum and maximum values
	private final int MINIMUM_WIDTH = 800;

	private final int MINIMUM_HEIGHT = 200;

	private final int ALGOCTRL_HEIGHT = 130;

	public final int STACK_WIDTH = 100;

	private final int EXPLANATION_HEIGHT = 100;

	// MyWordAlgorithmController. Needed to perform Clicks and Actions
	// from the GUI.
	private WordAlgorithmController myAlgoController;

	// This Panel represents the whole space which could be used by the
	// GuiController.
	private JPanel contentPane;

	// This Panel represents the space on which the SyntaxDiagrams should be
	// drawn.
	private DrawPanel drawPanel;

	// This Panel represents the space on which the Buttons and the Textfield
	// are placed.
	private AlgoCtrlPanel algoCtrlPanel;

	// This Panel represents the space on which the Explanations and Warnings
	// should be drawn.
	private ExplanationPanel explanationPanel;

	// This Panel represents the space on which the stack should be drawn.
	private StackPanel stackPanel;

	// The Toolbar which can be used for undo and redo events
	private JToolBar myToolBar;

	ToolbarButtonAction undoAction;

	ToolbarButtonAction performAction;

	// The Algorithm-Menu
	private JMenu editMenu;

	private JMenuItem menuUndo;

	private JMenuItem menuUndoAll;

	private JMenuItem menuRedo;

	private JMenuItem menuRedoAll;

	private JMenuItem menuStartAlgo;

	private JMenuItem menuStopAlgo;

	/**
	 * Constructs a new GuiController.
	 * 
	 * @param contentPane
	 *            The <code>JPane</code> generated by the
	 *            <code>MainController</code> which should be used by this
	 *            GuiController as its contentPane.
	 * @param mySynDiaSystem
	 *            The SynDiaSystem which should used during the algorithm.
	 * @param myAlgoController
	 *            The <code>WordAlgorithmController</code> which controlles
	 *            the Model which the GUI should present.
	 */
	public GuiController(JPanel contentPane, SynDiaSystem aSynDiaSystem,
			WordAlgorithmController myAlgoController) {

		this.contentPane = contentPane;

		// Whatever is on the contentPane
		this.contentPane.removeAll();

		this.myAlgoController = myAlgoController;

		this.installToolbar();

		// Initialize all graphical Components
		init(aSynDiaSystem);

		// neded to observe the ActionStack to update the Toolbar Buttons undo
		// and redo
		this.myAlgoController.addActionStackObserver(this);

		this.installMenu();

		// Need an update to draw especially the Stack correctly
		this.myAlgoController.notifyModelObservers();
	}

	/**
	 * This method is called from the constructor to layout the GUI
	 * 
	 * @param aSynDiaSystem
	 *            The <code>SynDiaSystem</code> which schould be drawn.
	 *            (Needed to intialize the DrawPanel).
	 */
	private void init(SynDiaSystem aSynDiaSystem) {
		// INIT *************************************************************
		// The DrawPanel must be Observer of the AlgorithmModel.
		this.drawPanel = new DrawPanel(aSynDiaSystem, this);
		this.myAlgoController.addModelObserver(drawPanel);

		// The AlgoCtrlPanel must be Observer of the AlgorithmModel.
		this.algoCtrlPanel = new AlgoCtrlPanel(this, myAlgoController
				.getEbnfFont());
		this.myAlgoController.addModelObserver(algoCtrlPanel);

		// The ExplanationPanel must be Observer of the AlgorithmModel.
		explanationPanel = new ExplanationPanel(myAlgoController
				.getExplanation(), myAlgoController.getWarning(),
				myAlgoController.getEbnfFont());
		this.myAlgoController.addModelObserver(explanationPanel);

		// The StackPanel must be Observer of the AlgorithmModel.
		stackPanel = new StackPanel(myAlgoController
				.getAdressNumbersFromStack());
		this.myAlgoController.addModelObserver(stackPanel);

		// SET *************************************************************
		// Size of some Panels
//		drawPanel.setMinimumSize(new Dimension(MINIMUM_WIDTH - STACK_WIDTH,
//				MINIMUM_HEIGHT - ALGOCTRL_HEIGHT - EXPLANATION_HEIGHT));
//		drawPanel.setPreferredSize(new Dimension(contentPane.getVisibleRect().width - STACK_WIDTH,
//				contentPane.getVisibleRect().height - ALGOCTRL_HEIGHT - EXPLANATION_HEIGHT));
				
		algoCtrlPanel.setMinimumSize(new Dimension(MINIMUM_WIDTH,
				ALGOCTRL_HEIGHT));
		algoCtrlPanel.setPreferredSize(new Dimension(MINIMUM_WIDTH,
				ALGOCTRL_HEIGHT));
		algoCtrlPanel.setMaximumSize(new Dimension(Short.MAX_VALUE,
				ALGOCTRL_HEIGHT));

		stackPanel.setMinimumSize(new Dimension(STACK_WIDTH, MINIMUM_HEIGHT
				- EXPLANATION_HEIGHT - ALGOCTRL_HEIGHT));
		stackPanel.setMaximumSize(new Dimension(STACK_WIDTH, Short.MAX_VALUE));
		stackPanel.setPreferredSize(new Dimension(STACK_WIDTH, contentPane
				.getHeight()
				- EXPLANATION_HEIGHT - ALGOCTRL_HEIGHT));

		explanationPanel.setMinimumSize(new Dimension(MINIMUM_WIDTH,
				EXPLANATION_HEIGHT));
		explanationPanel.setMaximumSize(new Dimension(Short.MAX_VALUE,
				EXPLANATION_HEIGHT));
		explanationPanel.setPreferredSize(new Dimension(MINIMUM_WIDTH,
				EXPLANATION_HEIGHT));

		// LAYOUT *************************************************************
		JPanel centerPanel = new JPanel();

		// The center is divided into drawPanel and stackPanel
//		centerPanel.setLayout(new BorderLayout(0,0));
//		centerPanel.add(drawPanel, BorderLayout.WEST);
//		centerPanel.add(stackPanel, BorderLayout.EAST);
		BoxLayout centerLayout = new BoxLayout(centerPanel, BoxLayout.X_AXIS);
		centerPanel.setLayout(centerLayout);
		centerPanel.add(drawPanel);
		centerPanel.add(stackPanel);

		// The contenPane is divided into three parts:
		BoxLayout contentLayout = new BoxLayout(contentPane, BoxLayout.Y_AXIS);
		contentPane.setLayout(contentLayout);
		contentPane.add(algoCtrlPanel);
		contentPane.add(centerPanel);
		contentPane.add(explanationPanel);
		contentPane.validate();
	}

	/**
	 * This method sets up the toolbar.
	 */
	private void installToolbar() {
		// Get the Toolbar
		myToolBar = JAlgoGUIConnector.getInstance().getModuleToolbar(
				myAlgoController.getModuleConnector());
		
		myToolBar.addSeparator();

		// Add undo and redo Button
		undoAction = new ToolbarButtonAction(this, 2);
		undoAction.setEnabled(false);
		myToolBar.add(createToolbarButton(undoAction));

		performAction = new ToolbarButtonAction(this, 3);
		performAction.setEnabled(false);
		myToolBar.add(createToolbarButton(performAction));

		myToolBar.validate();
		myToolBar.repaint();
	}

	/**
	 * Installs the Algorithms own Menu in the j-Algo MenuBar
	 * 
	 */
	private void installMenu() {
		myAlgoController.getMainController().removeCustomMenu();
		editMenu = new JMenu(Messages.getString("ebnf", "WordAlgo.Menu_Title"));
		myAlgoController.getMainController().addMenu(editMenu);

		menuStartAlgo = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_Start"));
		menuStartAlgo.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				myAlgoController.startAlgorithm();
			}
		});
		editMenu.add(menuStartAlgo);

		menuStopAlgo = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_Reset"));
		menuStopAlgo.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				while (myAlgoController.isUndoPossible()) {
					myAlgoController.undo();
				}
			}
		});
		menuStopAlgo.setEnabled(false);
		editMenu.add(menuStopAlgo);

		editMenu.addSeparator();

		menuUndo = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_Undo"));
		menuUndo.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				myAlgoController.undo();
			}
		});
		menuUndo.setEnabled(false);
		editMenu.add(menuUndo);

		menuRedo = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_Redo"));
		menuRedo.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				myAlgoController.redo();
			}
		});
		menuRedo.setEnabled(false);
		editMenu.add(menuRedo);

		editMenu.addSeparator();

		menuUndoAll = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_UndoAll"));
		menuUndoAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				while (myAlgoController.isUndoPossible()) {
					myAlgoController.undo();
				}
			}
		});
		menuUndoAll.setEnabled(false);
		editMenu.add(menuUndoAll);

		menuRedoAll = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_RedoAll"));
		menuRedoAll.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				while (myAlgoController.isRedoPossible()) {
					myAlgoController.redo();
				}
			}
		});
		menuRedoAll.setEnabled(false);
		editMenu.add(menuRedoAll);

		editMenu.addSeparator();

		JMenuItem menuExit = new JMenuItem(Messages.getString("ebnf",
				"WordAlgo.Menu_Exit"));
		menuExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent anAction) {
				switchToSynDiaView();
			}
		});
		editMenu.add(menuExit);
	}

	/**
	 * Creates a <code>JButton</code> object without border and text, which
	 * can be used in <code>JToolBar</code>s
	 * 
	 * @return a <code>JButton</code> instance with the given
	 *         <code>Action</code>
	 */
	private JButton createToolbarButton(Action a) {
		JToolbarButton button = new JToolbarButton((Icon) a
				.getValue(Action.SMALL_ICON), null, null);
		button.setAction(a);
		button.setText("");
		return button;
	}

	/**
	 * This method switches to the SynDiaView and removes the gui before
	 * 
	 */
	public void switchToSynDiaView() {

		// Rearrange Menu
		myAlgoController.getMainController().removeCustomMenu();

		// empty content pane
		contentPane.removeAll();

		myAlgoController.switchToSynDiaView();
	}

	/**
	 * Enables the function of autosize in the DrawPanel.
	 * 
	 * @param autosize
	 *            True if autosize should be on.
	 * @return The new Size of the font if size changed. Else -1.
	 */
	public int setAutoSize(boolean autosize) {

		drawPanel.setAutoSize(autosize);
		if (autosize) {
			return drawPanel.resizeSystem(10, 0);
		}
		return -1;

	}

	/**
	 * This Method returns the <code>WordAlgorithmController</code> which is
	 * graphically represented by this GUI.
	 * 
	 * @return The <code>WordAlgorithmController</code> represented by this
	 *         GUI.
	 */
	public WordAlgorithmController getWordAlgoController() {
		return myAlgoController;
	}
	

	/**
	 * This method returns the actual size of the set font for rendering syntax
	 * diagrams
	 * 
	 * @return an int representing the font size
	 */
	public int getFontSize() {

		return drawPanel.getFontSize();

	}
	
	/**
	 * Updates the DrawPanel (where the SyntaxDiagrams are located).
	 * 
	 * @param size
	 *            The size of the Font associated to the SyntaxDiagram. Use size
	 *            0 if you don't want to change the size
	 */
	public void updateDrawPanel(int size) {

		drawPanel.resizeSystem(size, 0);

	}

	/**
	 * This method is called, if ActionStack has changed. To update the
	 * button-Status's in the Toolbar.
	 */
	public void update(Observable anObservable, Object arg) {
		if (anObservable instanceof ActionStack) {
			// Update Toolbar and Menu
			ActionStack myStack = (ActionStack) anObservable;
			if (myStack.isRedoPossible()) {
				performAction.setEnabled(true);
				menuRedo.setEnabled(true);
				menuRedoAll.setEnabled(true);
			} else {
				performAction.setEnabled(false);
				menuRedo.setEnabled(false);
				menuRedoAll.setEnabled(false);
			}
			if (myStack.isUndoPossible()) {
				undoAction.setEnabled(true);
				menuUndo.setEnabled(true);
				menuUndoAll.setEnabled(true);
			} else {
				undoAction.setEnabled(false);
				menuUndo.setEnabled(false);
				menuUndoAll.setEnabled(false);
			}
			if (myAlgoController.isAlgorithmRunning()
					|| myAlgoController.isAlgorithmFinished()) {
				menuStopAlgo.setEnabled(true);
				menuStartAlgo.setEnabled(false);
			} else {
				menuStopAlgo.setEnabled(false);
				menuStartAlgo.setEnabled(true);
			}
			myToolBar.validate();
			myToolBar.repaint();
		}
	}

}
