/*******************************************************************************
 * 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.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;

import com.ibm.icu.text.MessageFormat;

/**
 * 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 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) {
            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.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.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 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.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;
    }

}
