/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation and others.
 *
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.texteditor;


import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.PlatformUI;


/**
 * Capable of handling input elements that have an associated status with them.
 * @since 2.0
 */
public class StatusTextEditor extends AbstractTextEditor {

	/** The root composite of this editor */
	private Composite fParent;
	/** The layout used to manage the regular and the status page */
	private StackLayout fStackLayout;
	/** The root composite for the regular page */
	private Composite fDefaultComposite;
	/** The status page */
	private Control fStatusControl;
	/** {@link #setFocus()} is still running */
	private boolean setFocusIsRunning;

	// No .options for plugin yet
	private static final boolean DEBUG = false;

	/*
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor.createPartControl(Composite)
	 */
	@Override
	public void createPartControl(Composite parent) {

		fParent= new Composite(parent, SWT.NONE);
		fStackLayout= new StackLayout();
		fParent.setLayout(fStackLayout);

		fDefaultComposite= new Composite(fParent, SWT.NONE);
		fDefaultComposite.setLayout(new FillLayout());
		super.createPartControl(fDefaultComposite);

		updatePartControl(getEditorInput());
	}

	/**
	 * Checks if the status of the given input is OK. If not the
	 * status control is shown rather than the default control.
	 *
	 * @param input the input whose status is checked
	 */
	public void updatePartControl(IEditorInput input) {
		final String where = "updatePartControl"; //$NON-NLS-1$
		if (setFocusIsRunning) {
			trace(where, "ERROR: trying to call update while processing focus", fStatusControl); //$NON-NLS-1$
		} else {
			trace(where, "START", fStatusControl); //$NON-NLS-1$
		}

		boolean restoreFocus= false;

		if (fStatusControl != null) {
			if (!fStatusControl.isDisposed()) {
				restoreFocus= containsFocus(fStatusControl);
			}
			fStatusControl.dispose();
			trace(where, "status control disposed", fStatusControl); //$NON-NLS-1$
			fStatusControl= null;
		}

		Control front= null;
		if (fParent != null && input != null) {
			if (getDocumentProvider() instanceof IDocumentProviderExtension) {
				IDocumentProviderExtension extension= (IDocumentProviderExtension) getDocumentProvider();
				IStatus status= extension.getStatus(input);
				if (!isErrorStatus(status)) {
					front= fDefaultComposite;
				} else {
					fStatusControl= createStatusControl(fParent, status);
					trace(where, "status control created", fStatusControl); //$NON-NLS-1$
					front= fStatusControl;
				}
			}
		}

		if (fStackLayout.topControl != front) {
			fStackLayout.topControl= front;
			fParent.layout();
			updateStatusFields();
		}

		if (restoreFocus && fStatusControl != null && !containsFocus(fStatusControl)) {
			fParent.setFocus();
		}
		trace(where, "END", fStatusControl); //$NON-NLS-1$
	}

	private boolean containsFocus(Control control) {
		Control focusControl= control.getDisplay().getFocusControl();
		if (focusControl != null) {
			focusControl= focusControl.getParent();
			while (focusControl != fParent && focusControl != null && !(focusControl instanceof Shell)) {
				focusControl= focusControl.getParent();
			}
		}
		return focusControl == fParent;
	}

	@Override
	public void setFocus() {
		final String where = "setFocus"; //$NON-NLS-1$
		if (setFocusIsRunning) {
			trace(where, "ERROR: trying to call setFocus while processing focus", fStatusControl); //$NON-NLS-1$
		} else {
			trace(where, "START", fStatusControl); //$NON-NLS-1$
		}
		setFocusIsRunning = true;

		if (fStatusControl != null && !fStatusControl.isDisposed()) {
			/* even if the control does not really take focus, we still have to set it
			 * to fulfill the contract and to make e.g. Ctrl+PageUp/Down work. */
			fStatusControl.setFocus();
		} else {
			super.setFocus();
		}

		setFocusIsRunning = false;

		trace(where, "END", fStatusControl); //$NON-NLS-1$
	}

	@Override
	public boolean validateEditorInputState() {
		if (!super.validateEditorInputState())
			return false;

		if (getDocumentProvider() instanceof IDocumentProviderExtension) {
			IDocumentProviderExtension extension= (IDocumentProviderExtension)getDocumentProvider();
			IStatus status= extension.getStatus(getEditorInput());
			return !isErrorStatus(status) && status.getSeverity() != IStatus.CANCEL;
		}

		return true;
	}

	/**
	 * Returns whether the given status indicates an error. Subclasses may override.
	 *
	 * @param status the status to be checked
	 * @return <code>true</code> if the status indicates an error, <code>false</code> otherwise\
	 * @since 3.0
	 */
	protected boolean isErrorStatus(IStatus status) {
		return status != null && status.getSeverity() == IStatus.ERROR;
	}

	/**
	 * Creates the status control for the given status.
	 * May be overridden by subclasses.
	 *
	 * @param parent the parent control
	 * @param status the status
	 * @return the new status control
	 */
	protected Control createStatusControl(Composite parent, IStatus status) {
		return createInfoForm(parent, status);
	}

	/**
	 * Helper to get rid of deprecation warnings.
	 *
	 * @param parent the parent
	 * @param status the status
	 * @return the control
	 * @since 3.5
	 * @deprecated As of 3.5
	 */
	@Deprecated
	private Control createInfoForm(Composite parent, IStatus status) {
		InfoForm infoForm= new InfoForm(parent);
		infoForm.setHeaderText(getStatusHeader(status));
		infoForm.setBannerText(getStatusBanner(status));
		infoForm.setInfo(getStatusMessage(status));
		return infoForm.getControl();
	}

	/**
	 * Returns a header for the given status
	 *
	 * @param status the status whose message is returned
	 * @return a header for the given status
	 */
	protected String getStatusHeader(IStatus status) {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Returns a banner for the given status.
	 *
	 * @param status the status whose message is returned
	 * @return a banner for the given status
	 */
	protected String getStatusBanner(IStatus status) {
		return ""; //$NON-NLS-1$
	}

	/**
	 * Returns a message for the given status.
	 *
	 * @param status the status whose message is returned
	 * @return a message for the given status
	 */
	protected String getStatusMessage(IStatus status) {
		return status.getMessage();
	}

	@Override
	protected void updateStatusField(String category) {
		IDocumentProvider provider= getDocumentProvider();
		if (provider instanceof IDocumentProviderExtension) {
			IDocumentProviderExtension extension= (IDocumentProviderExtension) provider;
			IStatus status= extension.getStatus(getEditorInput());
			if (isErrorStatus(status)) {
				IStatusField field= getStatusField(category);
				if (field != null) {
					field.setText(fErrorLabel);
					return;
				}
			}
		}

		super.updateStatusField(category);
	}

	@Override
	protected void doSetInput(IEditorInput input) throws CoreException {
		super.doSetInput(input);
		updatePartControl();
	}

	@Override
	public void doRevertToSaved() {
		// http://dev.eclipse.org/bugs/show_bug.cgi?id=19014
		super.doRevertToSaved();
		updatePartControl();
	}

	@Override
	protected void sanityCheckState(IEditorInput input) {
		// http://dev.eclipse.org/bugs/show_bug.cgi?id=19014
		super.sanityCheckState(input);
		if (!setFocusIsRunning) {
			updatePartControl();
		} else {
			trace("sanityCheck", "delaying update", fStatusControl); //$NON-NLS-1$ //$NON-NLS-2$
			PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
				trace("sanityCheck", "incoming update", fStatusControl); //$NON-NLS-1$ //$NON-NLS-2$
				updatePartControl();
			});
		}
	}

	@Override
	protected void handleEditorInputChanged() {
		super.handleEditorInputChanged();
		updatePartControl();
	}

	@Override
	protected void handleElementContentReplaced() {
		super.handleElementContentReplaced();
		updatePartControl();
	}

	private void updatePartControl() {
		if (fParent != null && !fParent.isDisposed()) {
			updatePartControl(getEditorInput());
		}
	}

	private static void trace(String where, String what, Control o) {
		if (!DEBUG) {
			return;
		}
		String id;
		if (o == null) {
			id = "null"; //$NON-NLS-1$
		} else {
			id = System.identityHashCode(o) + (o.isDisposed() ? "<disposed!>" : ""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		System.out.println(where + " |" + id + "| " + what); //$NON-NLS-1$//$NON-NLS-2$
	}
}
