/*******************************************************************************
 * Copyright (c) 2011, 2012 Anton Gorenkov
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Anton Gorenkov - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.testsrunner.internal.ui.view;

import org.eclipse.cdt.testsrunner.internal.TestsRunnerPlugin;
import org.eclipse.cdt.testsrunner.internal.model.TestingSessionsManager;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.HistoryDropDownAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.RerunAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ScrollLockAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowFailedOnlyAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowFileNameOnlyAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowNextFailureAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowPreviousFailureAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowTestsInHierarchyAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ShowTimeAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.StopAction;
import org.eclipse.cdt.testsrunner.internal.ui.view.actions.ToggleOrientationAction;
import org.eclipse.cdt.testsrunner.model.ITestingSession;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.ViewPart;

/**
 * Represents a view part showing the testing results (count statistics,
 * red/green bar, tests hierarchy and test messages).
 */
public class ResultsView extends ViewPart {

	/**
	 * Represents view orientation
	 *
	 * @note <code>Auto</code> state may be not acceptable for some methods (see
	 * their comments for details).
	 */
	public enum Orientation {
		Horizontal, Vertical, Auto,
	}

	/** View parent. */
	private Composite parent;

	/** Child widget: statistics viewer. */
	private ProgressCountPanel progressCountPanel;

	/** Tests hierarchy and message viewer. */
	private ResultsPanel resultsPanel;

	/** User interface updater instance. */
	private UIUpdater uiUpdater;

	/** The reference to the testing sessions manager instance. */
	private TestingSessionsManager sessionsManager;

	/** Shows whether the results view was disposed. */
	private boolean isDisposed = false;

	// Toolbar & view menu actions
	private Action nextAction;
	private Action previousAction;
	private Action rerunAction;
	private Action stopAction;
	private ToggleOrientationAction[] toggleOrientationActions;
	private Action historyAction;
	private Action showFailedOnly;
	private Action showTestsInHierarchyAction;
	private Action showTimeAction;
	private Action scrollLockAction;
	private Action showFileNameOnlyAction;

	/**
	 * The current orientation preference (Horizontal, Vertical, Auto).
	 */
	private Orientation orientation = Orientation.Auto;

	/**
	 * The current view orientation (Horizontal or Vertical).
	 */
	private Orientation currentOrientation;

	/**
	 * Previously saved state. It is used to store the same state if the view
	 * was not opened.
	 */
	private IMemento memento;

	// Persistence tags
	static final String TAG_ORIENTATION = "orientation"; //$NON-NLS-1$
	static final String TAG_SHOW_FAILED_ONLY = "showFailedOnly"; //$NON-NLS-1$
	static final String TAG_SHOW_TESTS_IN_HIERARCHY = "showTestsInHierarchy"; //$NON-NLS-1$
	static final String TAG_SHOW_TIME = "showTime"; //$NON-NLS-1$
	static final String TAG_SCROLL_LOCK = "scrollLock"; //$NON-NLS-1$
	static final String TAG_SHOW_FILE_NAME_ONLY_ACTION = "showFileNameOnly"; //$NON-NLS-1$
	static final String TAG_HISTORY_SIZE = "history_size"; //$NON-NLS-1$

	@Override
	public void createPartControl(Composite parent) {
		sessionsManager = TestsRunnerPlugin.getDefault().getTestingSessionsManager();
		IWorkbench workbench = TestsRunnerPlugin.getDefault().getWorkbench();
		Clipboard clipboard = new Clipboard(parent.getDisplay());

		this.parent = parent;
		GridLayout gridLayout = new GridLayout();
		gridLayout.marginWidth = 0;
		gridLayout.marginHeight = 0;
		parent.setLayout(gridLayout);
		currentOrientation = getActualOrientation();

		progressCountPanel = new ProgressCountPanel(parent, currentOrientation);
		resultsPanel = new ResultsPanel(parent, sessionsManager, workbench, getViewSite(), clipboard);
		uiUpdater = new UIUpdater(this, resultsPanel.getTestsHierarchyViewer(), progressCountPanel, sessionsManager);
		configureActionsBars();

		parent.addControlListener(new ControlListener() {
			@Override
			public void controlMoved(ControlEvent e) {
			}

			@Override
			public void controlResized(ControlEvent e) {
				computeOrientation();
			}
		});

		restoreState(memento);
		uiUpdater.reapplyActiveSession();
	}

	@Override
	public void setFocus() {
		resultsPanel.getTestsHierarchyViewer().getTreeViewer().getControl().setFocus();
	}

	/**
	 * Configures the view tool bar and menu.
	 */
	private void configureActionsBars() {
		IActionBars actionBars = getViewSite().getActionBars();

		// Create common action
		toggleOrientationActions = new ToggleOrientationAction[] {
				new ToggleOrientationAction(this, Orientation.Vertical),
				new ToggleOrientationAction(this, Orientation.Horizontal),
				new ToggleOrientationAction(this, Orientation.Auto), };

		nextAction = new ShowNextFailureAction(resultsPanel.getTestsHierarchyViewer());
		nextAction.setEnabled(false);
		actionBars.setGlobalActionHandler(ActionFactory.NEXT.getId(), nextAction);

		previousAction = new ShowPreviousFailureAction(resultsPanel.getTestsHierarchyViewer());
		previousAction.setEnabled(false);
		actionBars.setGlobalActionHandler(ActionFactory.PREVIOUS.getId(), previousAction);

		showFailedOnly = new ShowFailedOnlyAction(resultsPanel);
		showTestsInHierarchyAction = new ShowTestsInHierarchyAction(resultsPanel.getTestsHierarchyViewer());
		showTimeAction = new ShowTimeAction(resultsPanel.getTestsHierarchyViewer());
		scrollLockAction = new ScrollLockAction(uiUpdater);
		showFileNameOnlyAction = new ShowFileNameOnlyAction(resultsPanel.getMessagesViewer());
		rerunAction = new RerunAction(sessionsManager);
		rerunAction.setEnabled(false);
		stopAction = new StopAction(sessionsManager);
		stopAction.setEnabled(false);

		historyAction = new HistoryDropDownAction(sessionsManager, parent.getShell());

		// Configure toolbar
		IToolBarManager toolBar = actionBars.getToolBarManager();
		toolBar.add(nextAction);
		toolBar.add(previousAction);
		toolBar.add(showFailedOnly);
		toolBar.add(scrollLockAction);
		toolBar.add(new Separator());
		toolBar.add(rerunAction);
		toolBar.add(stopAction);
		toolBar.add(historyAction);

		// Configure view menu
		IMenuManager viewMenu = actionBars.getMenuManager();
		viewMenu.add(showTestsInHierarchyAction);
		viewMenu.add(showTimeAction);
		viewMenu.add(new Separator());
		MenuManager layoutSubMenu = new MenuManager(UIViewMessages.ResultsView_layout_menu_text);
		for (int i = 0; i < toggleOrientationActions.length; ++i) {
			layoutSubMenu.add(toggleOrientationActions[i]);
		}
		viewMenu.add(layoutSubMenu);
		viewMenu.add(new Separator());
		viewMenu.add(showFailedOnly);
		viewMenu.add(showFileNameOnlyAction);
	}

	@Override
	public void dispose() {
		isDisposed = true;
		if (uiUpdater != null) {
			uiUpdater.dispose();
		}
	}

	/**
	 * Changes the view orientation
	 *
	 * @param orientation new view orientation (Horizontal, Vertical, Auto)
	 */
	public void setOrientation(Orientation orientation) {
		this.orientation = orientation;
		computeOrientation();
	}

	/**
	 * Checks whether actual orientation is changed and changes orientation of
	 * the child widgets.
	 */
	private void computeOrientation() {
		Orientation newActualOrientation = getActualOrientation();
		if (newActualOrientation != currentOrientation) {
			currentOrientation = newActualOrientation;
			progressCountPanel.setPanelOrientation(currentOrientation);
			resultsPanel.setPanelOrientation(currentOrientation);
			for (int i = 0; i < toggleOrientationActions.length; ++i) {
				toggleOrientationActions[i].setChecked(orientation == toggleOrientationActions[i].getOrientation());
			}
			parent.layout();
		}
	}

	/**
	 * Recalculates actual view orientation depending on the specified by user
	 * orientation value and current view size.
	 *
	 * @param orientation by user specified orientation
	 * @return actual orientation
	 */
	private Orientation getActualOrientation() {
		switch (orientation) {
		case Horizontal:
		case Vertical:
			return orientation;
		case Auto:
			Point size = parent.getSize();
			return (size.x > size.y) ? Orientation.Horizontal : Orientation.Vertical;
		}
		return null;
	}

	/**
	 * Updates view actions state from the active session.
	 */
	public void updateActionsFromSession() {
		ITestingSession session = sessionsManager.getActiveSession();
		boolean hasErrors = session != null && session.hasErrors();
		previousAction.setEnabled(hasErrors);
		nextAction.setEnabled(hasErrors);
		rerunAction.setEnabled(session != null && session.isFinished());
		stopAction.setEnabled(session != null && !session.isFinished());
	}

	/**
	 * Changes the view caption.
	 *
	 * @param message new view caption
	 */
	public void setCaption(String message) {
		setContentDescription(message);
	}

	@Override
	public void init(IViewSite site, IMemento memento) throws PartInitException {
		super.init(site, memento);
		this.memento = memento;
	}

	/**
	 * Restores the value of the checkable action.
	 *
	 * @param memento previously saved state to restore the action value from
	 * @param key tag name that is used to restore the value
	 * @param action action to restore
	 */
	private void restoreActionChecked(IMemento memento, String key, Action action) {
		Boolean checked = memento.getBoolean(key);
		if (checked != null) {
			action.setChecked(checked);
			action.run();
		}
	}

	/**
	 * Restores the state of the view.
	 *
	 * @param memento previously saved state
	 */
	private void restoreState(IMemento memento) {
		if (memento != null) {
			Integer orientationIndex = memento.getInteger(TAG_ORIENTATION);
			if (orientationIndex != null) {
				setOrientation(Orientation.values()[orientationIndex]);
			}
			resultsPanel.restoreState(memento);
			restoreActionChecked(memento, TAG_SHOW_FAILED_ONLY, showFailedOnly);
			restoreActionChecked(memento, TAG_SHOW_TESTS_IN_HIERARCHY, showTestsInHierarchyAction);
			restoreActionChecked(memento, TAG_SHOW_TIME, showTimeAction);
			restoreActionChecked(memento, TAG_SCROLL_LOCK, scrollLockAction);
			restoreActionChecked(memento, TAG_SHOW_FILE_NAME_ONLY_ACTION, showFileNameOnlyAction);
			Integer historySize = memento.getInteger(TAG_HISTORY_SIZE);
			if (historySize != null) {
				sessionsManager.setHistorySizeLimit(historySize);
			}
		}
	}

	@Override
	public void saveState(IMemento memento) {
		//Keep the old state;
		if (parent == null) {
			if (this.memento != null) {
				memento.putMemento(this.memento);
			}
			return;
		}

		memento.putInteger(TAG_ORIENTATION, orientation.ordinal());
		resultsPanel.saveState(memento);
		memento.putBoolean(TAG_SHOW_FAILED_ONLY, showFailedOnly.isChecked());
		memento.putBoolean(TAG_SHOW_TESTS_IN_HIERARCHY, showTestsInHierarchyAction.isChecked());
		memento.putBoolean(TAG_SHOW_TIME, showTimeAction.isChecked());
		memento.putBoolean(TAG_SCROLL_LOCK, scrollLockAction.isChecked());
		memento.putBoolean(TAG_SHOW_FILE_NAME_ONLY_ACTION, showFileNameOnlyAction.isChecked());
		memento.putInteger(TAG_HISTORY_SIZE, sessionsManager.getHistorySizeLimit());
	}

	/**
	 * Returns whether the view was disposed.
	 *
	 * @return true if the view was disposed
	 */
	public boolean isDisposed() {
		return isDisposed;
	}

}
