/*******************************************************************************
 * Copyright (c) 2012, 2014 Ericsson
 *
 * All rights reserved. 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:
 *     Francois Chouinard - Initial API and implementation
 *     Marc-Andre Laperle - Add time zone preference
 *     Patrick Tasse - Updated for fraction of seconds
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ui.properties;

import java.util.Map;
import java.util.TimeZone;

import org.eclipse.jface.preference.ComboFieldEditor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.preference.RadioGroupFieldEditor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.tracecompass.internal.tmf.ui.Activator;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTimestampFormatUpdateSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimePreferencesConstants;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimePreferences;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestampFormat;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;

/**
 * The TMF timestamp format configuration page. This page is used to select the
 * global timestamp and interval time formats (for display and parsing). The
 * user can either pick a pre-defined format or enter his/her own.
 *
 * @author Francois Chouinard
 */
public class TmfTimestampFormatPage extends PreferencePage implements IWorkbenchPreferencePage, SelectionListener, IPropertyChangeListener {

    // ------------------------------------------------------------------------
    // Constants
    // ------------------------------------------------------------------------

    // Date and Time formats
    private static final String[][] fDateTimeFormats = new String[][] {
            { ITmfTimePreferencesConstants.DATE_YEAR_FMT, ITmfTimePreferencesConstants.DATE_YEAR_FMT },
            { ITmfTimePreferencesConstants.DATE_YEAR2_FMT, ITmfTimePreferencesConstants.DATE_YEAR2_FMT },
            { ITmfTimePreferencesConstants.DATE_MONTH_FMT, ITmfTimePreferencesConstants.DATE_MONTH_FMT },
            { ITmfTimePreferencesConstants.DATE_DAY_FMT, ITmfTimePreferencesConstants.DATE_DAY_FMT },
            { ITmfTimePreferencesConstants.DATE_JDAY_FMT, ITmfTimePreferencesConstants.DATE_JDAY_FMT },
            { ITmfTimePreferencesConstants.TIME_HOUR_FMT, ITmfTimePreferencesConstants.TIME_HOUR_FMT },
            { ITmfTimePreferencesConstants.TIME_MINUTE_FMT, ITmfTimePreferencesConstants.TIME_MINUTE_FMT },
            { ITmfTimePreferencesConstants.TIME_SECOND_FMT, ITmfTimePreferencesConstants.TIME_SECOND_FMT },
            { ITmfTimePreferencesConstants.TIME_ELAPSED_FMT + " (secs in epoch)", ITmfTimePreferencesConstants.TIME_ELAPSED_FMT }, //$NON-NLS-1$
            { "(none)", ITmfTimePreferencesConstants.TIME_NO_FMT }, //$NON-NLS-1$
    };

    // Sub-second formats
    private static final String[][] fSubSecondFormats = new String[][] {
            { ITmfTimePreferencesConstants.SUBSEC_MILLI_FMT + " (ms)", ITmfTimePreferencesConstants.SUBSEC_MILLI_FMT }, //$NON-NLS-1$
            { ITmfTimePreferencesConstants.SUBSEC_MICRO_FMT + " (µs)", ITmfTimePreferencesConstants.SUBSEC_MICRO_FMT }, //$NON-NLS-1$
            { ITmfTimePreferencesConstants.SUBSEC_NANO_FMT + " (ns)", ITmfTimePreferencesConstants.SUBSEC_NANO_FMT }, //$NON-NLS-1$
    };

    // Date and Time delimiters
    private static final String[][] fDateTimeDelimiters = new String[][] {
            { "(none)", ITmfTimePreferencesConstants.DELIMITER_NONE }, //$NON-NLS-1$
            { "  (space)", ITmfTimePreferencesConstants.DELIMITER_SPACE }, //$NON-NLS-1$
            { ", (comma)", ITmfTimePreferencesConstants.DELIMITER_COMMA }, //$NON-NLS-1$
            { "- (dash)", ITmfTimePreferencesConstants.DELIMITER_DASH }, //$NON-NLS-1$
            { "_ (underline)", ITmfTimePreferencesConstants.DELIMITER_UNDERLINE }, //$NON-NLS-1$
            { ": (colon)", ITmfTimePreferencesConstants.DELIMITER_COLON }, //$NON-NLS-1$
            { "; (semicolon)", ITmfTimePreferencesConstants.DELIMITER_SEMICOLON }, //$NON-NLS-1$
            { "/ (slash)", ITmfTimePreferencesConstants.DELIMITER_SLASH }, //$NON-NLS-1$
            { "' (quote)", ITmfTimePreferencesConstants.DELIMITER_QUOTE }, //$NON-NLS-1$
            { "\" (dbl-quote)", ITmfTimePreferencesConstants.DELIMITER_DQUOT }, //$NON-NLS-1$
    };

    // Sub-Second delimiters
    private static final String[][] fSubSecondDelimiters = new String[][] {
            { "(none)", ITmfTimePreferencesConstants.DELIMITER_NONE }, //$NON-NLS-1$
            { "  (space)", ITmfTimePreferencesConstants.DELIMITER_SPACE }, //$NON-NLS-1$
            { ", (comma)", ITmfTimePreferencesConstants.DELIMITER_COMMA }, //$NON-NLS-1$
            { "- (dash)", ITmfTimePreferencesConstants.DELIMITER_DASH }, //$NON-NLS-1$
            { "_ (underline)", ITmfTimePreferencesConstants.DELIMITER_UNDERLINE }, //$NON-NLS-1$
            { ": (colon)", ITmfTimePreferencesConstants.DELIMITER_COLON }, //$NON-NLS-1$
            { "; (semicolon)", ITmfTimePreferencesConstants.DELIMITER_SEMICOLON }, //$NON-NLS-1$
            { "/ (slash)", ITmfTimePreferencesConstants.DELIMITER_SLASH }, //$NON-NLS-1$
            { "' (quote)", ITmfTimePreferencesConstants.DELIMITER_QUOTE }, //$NON-NLS-1$
            { "\" (dbl-quote)", ITmfTimePreferencesConstants.DELIMITER_DQUOT }, //$NON-NLS-1$
            { ". (period)", ITmfTimePreferencesConstants.DELIMITER_PERIOD }, //$NON-NLS-1$
    };

    // Time zones
    @SuppressWarnings("nls")
    private static final String[] timeZones = new String[] {
            Messages.TmfTimestampFormatPage_LocalTime,
            "GMT-12",
            "GMT-11",
            "GMT-10",
            "GMT-9:30",
            "GMT-9",
            "GMT-7",
            "GMT-6",
            "GMT-5",
            "GMT-4",
            "GMT-3:30",
            "GMT-3",
            "GMT-2",
            "GMT-1",
            "GMT",
            "GMT+1",
            "GMT+2",
            "GMT+3",
            "GMT+3:30",
            "GMT+4",
            "GMT+4:30",
            "GMT+5",
            "GMT+5:30",
            "GMT+6",
            "GMT+7",
            "GMT+8",
            "GMT+9",
            "GMT+9:30",
            "GMT+10",
            "GMT+10:30",
            "GMT+11",
            "GMT+11:30",
            "GMT+12",
            "GMT+13:00",
            "GMT+14:00"
    };

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    // General stuff
    private Composite fPage;
    private IPreferenceStore fPreferenceStore;

    // Example section
    private Composite fExampleSection;
    private Text fPatternDisplay;
    private Text fExampleDisplay;

    // Timezone section
    private ComboFieldEditor fCombo;

    // Date/Time format section
    private RadioGroupFieldEditor fDateTimeFields;
    private RadioGroupFieldEditor fSSecFields;

    // Delimiters section
    private RadioGroupFieldEditor fDateFieldDelim;
    private RadioGroupFieldEditor fTimeFieldDelim;
    private RadioGroupFieldEditor fSSecFieldDelim;

    // IPropertyChangeListener data
    private String fProperty;
    private String fChangedProperty;

    private Map<String, String> fPreferenceMap;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * The default constructor
     */
    public TmfTimestampFormatPage() {
        fPreferenceStore = getPreferenceStore();
        fPreferenceMap = TmfTimePreferences.getPreferenceMap();
    }

    // ------------------------------------------------------------------------
    // IWorkbenchPreferencePage
    // ------------------------------------------------------------------------

    @Override
    protected IPreferenceStore doGetPreferenceStore() {
        return Activator.getDefault().getCorePreferenceStore();
    }

    @Override
    public void init(IWorkbench workbench) {
        // Do nothing
    }

    // ------------------------------------------------------------------------
    // PreferencePage
    // ------------------------------------------------------------------------

    @Override
    protected Control createContents(Composite parent) {

        // Overall preference page layout
        GridLayout gl = new GridLayout();
        gl.marginHeight = 0;
        gl.marginWidth = 0;
        parent.setLayout(gl);
        fPage = new Composite(parent, SWT.NONE);
        fPage.setLayout(new GridLayout());
        fPage.setLayoutData(new GridData(
                GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL | GridData.VERTICAL_ALIGN_FILL));

        // Example section
        fExampleSection = new Composite(fPage, SWT.NONE);
        fExampleSection.setLayout(new GridLayout(2, false));
        fExampleSection.setLayoutData(new GridData(GridData.FILL_BOTH));

        Label patternLabel = new Label(fExampleSection, SWT.HORIZONTAL);
        patternLabel.setText("Current Format: "); //$NON-NLS-1$
        fPatternDisplay = new Text(fExampleSection, SWT.BORDER | SWT.READ_ONLY);
        fPatternDisplay.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        Label exampleLabel = new Label(fExampleSection, SWT.NONE);
        exampleLabel.setText("Sample Display: "); //$NON-NLS-1$
        fExampleDisplay = new Text(fExampleSection, SWT.BORDER | SWT.READ_ONLY);
        fExampleDisplay.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        Label separator = new Label(fPage, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.SHADOW_NONE);
        separator.setLayoutData(
                new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        // Time Zones
        String[][] timeZoneIntervals = new String[timeZones.length][2];
        timeZoneIntervals[0][0] = timeZones[0];
        timeZoneIntervals[0][1] = fPreferenceStore.getDefaultString(ITmfTimePreferencesConstants.TIME_ZONE);
        for (int i = 1; i < timeZones.length; i++) {
            TimeZone tz = null;
            try {
                tz = TimeZone.getTimeZone(timeZones[i]);
                timeZoneIntervals[i][0] = tz.getDisplayName();
                timeZoneIntervals[i][1] = tz.getID();
            } catch (NullPointerException e) {
                Activator.getDefault().logError("TimeZone " + timeZones[i] + " does not exist.", e); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

        fCombo = new ComboFieldEditor(ITmfTimePreferencesConstants.TIME_ZONE, "Time Zone", timeZoneIntervals, fPage); //$NON-NLS-1$
        fCombo.setPreferenceStore(fPreferenceStore);
        fCombo.load();
        fCombo.setPropertyChangeListener(this);

        // Date and Time section
        fDateTimeFields = new RadioGroupFieldEditor(
                ITmfTimePreferencesConstants.DATIME, "Date and Time format", 3, fDateTimeFormats, fPage, true); //$NON-NLS-1$
        fDateTimeFields.setPreferenceStore(fPreferenceStore);
        fDateTimeFields.load();
        fDateTimeFields.setPropertyChangeListener(this);

        // Sub-second section
        fSSecFields = new RadioGroupFieldEditor(
                ITmfTimePreferencesConstants.SUBSEC, "Sub-second format", 3, fSubSecondFormats, fPage, true); //$NON-NLS-1$
        fSSecFields.setPreferenceStore(fPreferenceStore);
        fSSecFields.load();
        fSSecFields.setPropertyChangeListener(this);

        // Separators section
        fDateFieldDelim = new RadioGroupFieldEditor(
                ITmfTimePreferencesConstants.DATE_DELIMITER, "Date delimiter", 5, fDateTimeDelimiters, fPage, true); //$NON-NLS-1$
        fDateFieldDelim.setPreferenceStore(fPreferenceStore);
        fDateFieldDelim.load();
        fDateFieldDelim.setPropertyChangeListener(this);

        fTimeFieldDelim = new RadioGroupFieldEditor(
                ITmfTimePreferencesConstants.TIME_DELIMITER, "Time delimiter", 5, fDateTimeDelimiters, fPage, true); //$NON-NLS-1$
        fTimeFieldDelim.setPreferenceStore(fPreferenceStore);
        fTimeFieldDelim.load();
        fTimeFieldDelim.setPropertyChangeListener(this);

        fSSecFieldDelim = new RadioGroupFieldEditor(
                ITmfTimePreferencesConstants.SSEC_DELIMITER, "Sub-Second Delimiter", 5, fSubSecondDelimiters, fPage, true); //$NON-NLS-1$
        fSSecFieldDelim.setPreferenceStore(fPreferenceStore);
        fSSecFieldDelim.load();
        fSSecFieldDelim.setPropertyChangeListener(this);

        refresh();
        return fPage;
    }

    @Override
    protected void performDefaults() {
        fDateTimeFields.loadDefault();
        fSSecFields.loadDefault();
        fDateFieldDelim.loadDefault();
        fTimeFieldDelim.loadDefault();
        fSSecFieldDelim.loadDefault();
        fCombo.loadDefault();

        fPreferenceMap = TmfTimePreferences.getDefaultPreferenceMap();
        displayExample();
    }

    @Override
    protected void performApply() {
        fDateTimeFields.store();
        fSSecFields.store();
        fDateFieldDelim.store();
        fTimeFieldDelim.store();
        fSSecFieldDelim.store();
        fCombo.store();

        TmfTimestampFormat.updateDefaultFormats();
        TmfSignalManager.dispatchSignal(new TmfTimestampFormatUpdateSignal(null));
        displayExample();
    }

    @Override
    public boolean performOk() {
        performApply();
        return super.performOk();
    }

    // ------------------------------------------------------------------------
    // SelectionListener
    // ------------------------------------------------------------------------

    @Override
    public void widgetSelected(SelectionEvent e) {
        // Do nothing
    }

    @Override
    public void widgetDefaultSelected(SelectionEvent e) {
        // Do nothing
    }

    // ------------------------------------------------------------------------
    // IPropertyChangeListener
    // ------------------------------------------------------------------------

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        Object source = event.getSource();
        Object value = event.getNewValue();
        if (source instanceof RadioGroupFieldEditor && value instanceof String &&
                !(value.equals(fChangedProperty) && source.equals(fProperty)))
        {
            fProperty = ((RadioGroupFieldEditor) source).getPreferenceName();
            fChangedProperty = (String) value;
            refresh();
        }
    }

    // ------------------------------------------------------------------------
    // Helper functions
    // ------------------------------------------------------------------------

    private void refresh() {
        updatePatterns();
        displayExample();
    }

    void updatePatterns() {
        if (ITmfTimePreferencesConstants.DATIME.equals(fProperty) ||
                ITmfTimePreferencesConstants.SUBSEC.equals(fProperty) ||
                ITmfTimePreferencesConstants.DATE_DELIMITER.equals(fProperty) ||
                ITmfTimePreferencesConstants.TIME_DELIMITER.equals(fProperty) ||
                ITmfTimePreferencesConstants.SSEC_DELIMITER.equals(fProperty)) {
            fPreferenceMap.put(fProperty, fChangedProperty);
        }
    }

    private void displayExample() {
        long ts = 1332170682500677380L;
        String timePattern = TmfTimePreferences.computeTimePattern(fPreferenceMap);
        fPatternDisplay.setText(timePattern);
        fPatternDisplay.redraw();

        fExampleDisplay.setText(new TmfTimestampFormat(timePattern).format(ts));
        fExampleDisplay.redraw();
    }

}
