/*******************************************************************************
 * Copyright (c) 2000, 2017 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
 *     Patrik Suzzi <psuzzi@gmail.com> - Bug 489250
 *******************************************************************************/
package org.eclipse.ui.internal.ide.dialogs;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import org.eclipse.core.resources.IWorkspaceDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IIDEHelpContextIds;

/**
 * The FileStatesPage is the page used to set the file states sizes for the workbench.
 */
public class FileStatesPage extends PreferencePage implements
		IWorkbenchPreferencePage, Listener {

	private static final int FAILED_VALUE = -1;

	//Set the length of the day as we have to convert back and forth
	private static final long DAY_LENGTH = 86400000;

	private static final long MEGABYTES = 1024 * 1024;

	private Text longevityText;

	private Text maxStatesText;

	private Text maxStateSizeText;

	private Button applyPolicyButton;

	private Button keepDerivedStateButton;

	private ArrayList<Control> dependentControls = new ArrayList<>();

	//Choose a maximum to prevent OutOfMemoryErrors
	private int FILE_STATES_MAXIMUM = 10000;

	private long STATE_SIZE_MAXIMUM = 100;

	private static final int INDENT = 20;

	/**
	 * This method takes the string for the title of a text field and the value for the
	 * text of the field.
	 * @return org.eclipse.swt.widgets.Text
	 * @param labelString java.lang.String
	 * @param textValue java.lang.String
	 * @param parent Composite
	 */
	private Text addDependentLabelAndText(String labelString, String textValue,
			Composite parent) {
		Label label = new Label(parent, SWT.LEFT);
		label.setText(labelString);
		dependentControls.add(label);

		Text text = new Text(parent, SWT.LEFT | SWT.BORDER);
		GridData data = new GridData();
		text.addListener(SWT.Modify, this);
		data.horizontalAlignment = GridData.FILL;
		data.grabExcessHorizontalSpace = true;
		data.verticalAlignment = GridData.CENTER;
		data.grabExcessVerticalSpace = false;
		text.setLayoutData(data);
		text.setText(textValue);
		dependentControls.add(text);

		return text;
	}

	private Button addCheckBox(String label, boolean selected, Composite parent) {
		Button button = new Button(parent, SWT.CHECK | SWT.LEFT);
		button.addListener(SWT.Selection, this);
		GridData data = new GridData();
		data.horizontalSpan = 2;
		data.horizontalIndent= -INDENT;
		button.setLayoutData(data);
		button.setText(label);
		button.setSelection(selected);
		return button;
	}

	/**
	 * Recomputes the page's error state by validating all
	 * the fields.
	 */
	private void checkState() {
		// Assume invalid if the controls not created yet
		if (longevityText == null || maxStatesText == null || maxStateSizeText == null || applyPolicyButton == null
				|| keepDerivedStateButton == null) {
			setValid(false);
			return;
		}

		boolean newState= applyPolicyButton.getSelection();
		Iterator<Control> iter = dependentControls.iterator();
		while (iter.hasNext())
			iter.next().setEnabled(newState);

		if (validateLongTextEntry(longevityText, DAY_LENGTH) == FAILED_VALUE) {
			setValid(false);
			return;
		}

		if (validateMaxFileStates() == FAILED_VALUE) {
			setValid(false);
			return;
		}

		if (validateMaxFileStateSize() == FAILED_VALUE) {
			setValid(false);
			return;
		}

		setValid(true);
		setErrorMessage(null);
	}

	/*
	 * Create the contents control for the workspace file states.
	 * @returns Control
	 * @param parent Composite
	 */
	@Override
	protected Control createContents(Composite parent) {

		PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
				IIDEHelpContextIds.FILE_STATES_PREFERENCE_PAGE);

		IWorkspaceDescription description = getWorkspaceDescription();

		//Get the current value and make sure we get at least one day out of it.
		long days = description.getFileStateLongevity() / DAY_LENGTH;
		if (days < 1) {
			days = 1;
		}

		long megabytes = description.getMaxFileStateSize() / MEGABYTES;
		if (megabytes < 1) {
			megabytes = 1;
		}

		// button group
		Composite composite= new Composite(parent, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginLeft= INDENT;
		layout.marginWidth= 0;
		composite.setLayout(layout);

		this.keepDerivedStateButton = addCheckBox(IDEWorkbenchMessages.FileHistory_keepDerivedState,
				description.isKeepDerivedState(), composite);

		this.applyPolicyButton = addCheckBox(IDEWorkbenchMessages.FileHistory_applyPolicy, description
				.isApplyFileStatePolicy(), composite);

		this.longevityText= addDependentLabelAndText(IDEWorkbenchMessages.FileHistory_longevity, String
				.valueOf(days), composite);
		this.maxStatesText= addDependentLabelAndText(IDEWorkbenchMessages.FileHistory_entries, String
				.valueOf(description.getMaxFileStates()), composite);
		this.maxStateSizeText= addDependentLabelAndText(IDEWorkbenchMessages.FileHistory_diskSpace,
				String.valueOf(megabytes), composite);

		checkState();

		//Create a spacing label to breakup the note from the fields
		Label spacer = new Label(composite, SWT.NONE);
		GridData spacerData = new GridData();
		spacerData.horizontalSpan = 2;
		spacer.setLayoutData(spacerData);

		Composite noteComposite = createNoteComposite(parent.getFont(),
				composite, IDEWorkbenchMessages.Preference_note, IDEWorkbenchMessages.FileHistory_restartNote);
		GridData noteData = new GridData();
		noteData.horizontalSpan = 2;
		noteComposite.setLayoutData(noteData);
		dependentControls.addAll(Arrays.asList(noteComposite.getChildren()));

		applyDialogFont(composite);

		return composite;
	}

	/**
	 * Get the Workspace Description this page is operating on.
	 * @return org.eclipse.core.resources.IWorkspaceDescription
	 */
	private IWorkspaceDescription getWorkspaceDescription() {
		return ResourcesPlugin.getWorkspace().getDescription();
	}

	/**
	 * Sent when an event that the receiver has registered for occurs.
	 *
	 * @param event the event which occurred
	 */
	@Override
	public void handleEvent(Event event) {
		checkState();
	}

	/**
	 * Initializes this preference page for the given workbench.
	 * <p>
	 * This method is called automatically as the preference page is being created
	 * and initialized. Clients must not call this method.
	 * </p>
	 *
	 * @param workbench the workbench
	 */
	@Override
	public void init(org.eclipse.ui.IWorkbench workbench) {
	}

	/**
	 * Performs special processing when this page's Defaults button has been pressed.
	 * Reset the entries to their default values.
	 */
	@Override
	protected void performDefaults() {
		super.performDefaults();

		Preferences prefs = ResourcesPlugin.getPlugin().getPluginPreferences();

		long days = prefs
				.getDefaultLong(ResourcesPlugin.PREF_FILE_STATE_LONGEVITY)
				/ DAY_LENGTH;
		long megabytes = prefs
				.getDefaultLong(ResourcesPlugin.PREF_MAX_FILE_STATE_SIZE)
				/ MEGABYTES;
		this.longevityText.setText(String.valueOf(days));
		this.maxStatesText.setText(prefs
				.getDefaultString(ResourcesPlugin.PREF_MAX_FILE_STATES));
		this.maxStateSizeText.setText(String.valueOf(megabytes));
		this.keepDerivedStateButton.setSelection(prefs.getDefaultBoolean(ResourcesPlugin.PREF_KEEP_DERIVED_STATE));
		this.applyPolicyButton.setSelection(prefs
				.getDefaultBoolean(ResourcesPlugin.PREF_APPLY_FILE_STATE_POLICY));
		checkState();
	}

	/**
	 * Perform the result of the OK from the receiver.
	 */
	@Override
	public boolean performOk() {

		long longevityValue = validateLongTextEntry(longevityText, DAY_LENGTH);
		int maxFileStates = validateMaxFileStates();
		long maxStateSize = validateMaxFileStateSize();
		boolean keepDerivedState = keepDerivedStateButton.getSelection();
		boolean applyPolicy = applyPolicyButton.getSelection();
		if (longevityValue == FAILED_VALUE || maxFileStates == FAILED_VALUE
				|| maxStateSize == FAILED_VALUE) {
			return false;
		}

		IWorkspaceDescription description = getWorkspaceDescription();
		description.setFileStateLongevity(longevityValue * DAY_LENGTH);
		description.setMaxFileStates(maxFileStates);
		description.setMaxFileStateSize(maxStateSize * MEGABYTES);
		description.setKeepDerivedState(keepDerivedState);
		description.setApplyFileStatePolicy(applyPolicy);

		try {
			//As it is only a copy save it back in
			ResourcesPlugin.getWorkspace().setDescription(description);
		} catch (CoreException exception) {
			ErrorDialog.openError(getShell(), IDEWorkbenchMessages.FileHistory_exceptionSaving, exception
					.getMessage(), exception.getStatus());
			return false;
		}

		return true;

	}

	/**
	 * Validate a text entry for an integer field. Return the result if there are
	 * no errors, otherwise return -1 and set the entry field error.
	 * @return int
	 */
	private int validateIntegerTextEntry(Text text) {

		int value;

		try {
			value = Integer.parseInt(text.getText());

		} catch (NumberFormatException exception) {
			setErrorMessage(MessageFormat.format(IDEWorkbenchMessages.FileHistory_invalid,
					exception.getLocalizedMessage()));
			return FAILED_VALUE;
		}

		//Be sure all values are non zero and positive
		if (value <= 0) {
			setErrorMessage(IDEWorkbenchMessages.FileHistory_mustBePositive);
			return FAILED_VALUE;
		}

		return value;
	}

	/**
	 * Validate a text entry for a long field. Return the result if there are
	 * no errors, otherwise return -1 and set the entry field error.
	 * @param scale the scale (factor by which the value is multiplied when it is persisted)
	 * @return long
	 */
	private long validateLongTextEntry(Text text, long scale) {

		long value;

		try {
			String string = text.getText();
			value = Long.parseLong(string);
			if (value * scale / scale != value)
				throw new NumberFormatException(string);

		} catch (NumberFormatException exception) {
			setErrorMessage(MessageFormat.format(IDEWorkbenchMessages.FileHistory_invalid,
					exception.getLocalizedMessage()));
			return FAILED_VALUE;
		}

		//Be sure all values are non zero and positive
		if (value <= 0) {
			setErrorMessage(IDEWorkbenchMessages.FileHistory_mustBePositive);
			return FAILED_VALUE;
		}

		return value;
	}

	/**
	 * Validate the maximum file states.
	 * Return the value if successful, otherwise
	 * return FAILED_VALUE.
	 * Set the error message if it fails.
	 * @return int
	 */
	private int validateMaxFileStates() {
		int maxFileStates = validateIntegerTextEntry(this.maxStatesText);
		if (maxFileStates == FAILED_VALUE) {
			return maxFileStates;
		}

		if (maxFileStates > FILE_STATES_MAXIMUM) {
			setErrorMessage(NLS.bind(IDEWorkbenchMessages.FileHistory_aboveMaxEntries, String.valueOf(FILE_STATES_MAXIMUM)));
			return FAILED_VALUE;
		}

		return maxFileStates;
	}

	/**
	 * Validate the maximum file state size.
	 * Return the value if successful, otherwise
	 * return FAILED_VALUE.
	 * Set the error message if it fails.
	 * @return long
	 */
	private long validateMaxFileStateSize() {
		long maxFileStateSize = validateLongTextEntry(this.maxStateSizeText, MEGABYTES);
		if (maxFileStateSize == FAILED_VALUE) {
			return maxFileStateSize;
		}

		if (maxFileStateSize > STATE_SIZE_MAXIMUM) {
			setErrorMessage(NLS.bind(IDEWorkbenchMessages.FileHistory_aboveMaxFileSize, String.valueOf(STATE_SIZE_MAXIMUM)));
			return FAILED_VALUE;
		}

		return maxFileStateSize;
	}

}
