package com.ibm.nmon.gui.interval;

import java.awt.BorderLayout;

import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;

import java.awt.Insets;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.JSpinner.DateEditor;

import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;

import com.ibm.nmon.gui.Styles;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.gui.time.TimeMaskFormatter;
import com.ibm.nmon.interval.Interval;

/**
 * Panel for entering intervals using relative times. Start and end times are specified in 24 hour
 * format. The start time is relative to a base datetime. The end time also supports specifying a
 * number of days. Also displays absolute time labels for the start and the end that update as the
 * user changes inputs.
 */
final class RelativeTimeIntervalPanel extends BaseIntervalPanel {
    private static final long serialVersionUID = -1611515609994676644L;

    // base date time
    private final JSpinner base;

    // start and end times in 24H format
    private final JFormattedTextField start;
    private final JFormattedTextField end;

    // days the interval covers; used with the end time
    private final JFormattedTextField days;

    private final JLabel startLabel;
    private final JLabel endLabel;

    private final JLabel startAbsolute;
    private final JLabel endAbsolute;

    private final SimpleDateFormat FORMAT = new SimpleDateFormat(Styles.DATE_FORMAT_STRING);

    private final RelativeTimeDocumentListener startListener;
    private final RelativeTimeDocumentListener endListener;
    private final RelativeTimeDocumentListener daysListener;

    RelativeTimeIntervalPanel(NMONVisualizerGui gui) {
        super(gui);

        setLayout(new BorderLayout());

        add.addActionListener(addInterval);

        JLabel baseLabel = new JLabel("Base Time:");
        baseLabel.setHorizontalAlignment(SwingConstants.TRAILING);
        baseLabel.setFont(Styles.LABEL);

        base = new JSpinner(new SpinnerDateModel(new Date(getDefaultStartTime()), null, null, Calendar.MINUTE));
        base.setEditor(new DateEditor(base, Styles.DATE_FORMAT_STRING_WITH_YEAR));
        base.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                long startTime = getStartTime();
                long endTime = getEndTime();

                startAbsolute.setText(FORMAT.format(new Date(getStartTime())));
                endAbsolute.setText((FORMAT.format(new Date(getEndTime()))));

                if (startTime < endTime) {
                    Interval i = new Interval(startTime, endTime);

                    firePropertyChange("interval", null, i);
                }
            }
        });

        startLabel = new JLabel("Start:");
        startLabel.setFont(Styles.LABEL);
        startLabel.setHorizontalAlignment(SwingConstants.TRAILING);

        start = new JFormattedTextField();
        start.setName("start");
        start.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(true));
        // may not be enough columns with larger fonts
        start.setColumns(5);
        start.setValue(0);

        endLabel = new JLabel("End:");
        endLabel.setFont(Styles.LABEL);
        endLabel.setHorizontalAlignment(SwingConstants.TRAILING);

        end = new JFormattedTextField();
        end.setName("end");
        end.setFormatterFactory(TimeMaskFormatter.createFormatterFactory(false));
        // may not be enough columns with larger fonts
        end.setColumns(5);
        end.setValue(0);

        DefaultFormatter formatter = new DefaultFormatter();
        formatter.setValueClass(Integer.class);

        days = new JFormattedTextField();
        days.setFormatterFactory(new DefaultFormatterFactory(formatter));
        // assume a small number of days will be entered
        days.setColumns(3);
        days.setValue(0);
        days.setHorizontalAlignment(SwingConstants.TRAILING);

        startAbsolute = new JLabel();
        startAbsolute.setFont(Styles.BOLD);

        endAbsolute = new JLabel();
        endAbsolute.setFont(Styles.BOLD);

        // main panel contains name at top, buttons at bottom, all others in the CENTER
        JPanel namePanel = new JPanel();
        namePanel.add(nameLabel);
        namePanel.add(name);

        JPanel centerPanel = new JPanel(new GridBagLayout());

        GridBagConstraints labelConstraints = new GridBagConstraints();
        GridBagConstraints fieldConstraints = new GridBagConstraints();

        labelConstraints.gridx = 0;
        fieldConstraints.gridx = 1;

        labelConstraints.gridy = 0;
        fieldConstraints.gridy = 0;

        labelConstraints.insets = new Insets(0, 0, 0, 5);
        fieldConstraints.insets = new Insets(5, 0, 0, 5);

        labelConstraints.fill = GridBagConstraints.HORIZONTAL;
        fieldConstraints.fill = GridBagConstraints.HORIZONTAL;

        JLabel hhmmss = new JLabel("HH:mm:ss");

        // stretch out base time
        fieldConstraints.gridwidth = 2;

        centerPanel.add(baseLabel, labelConstraints);
        centerPanel.add(base, fieldConstraints);

        fieldConstraints.gridwidth = 1;
        fieldConstraints.gridx = 1;

        ++labelConstraints.gridy;
        ++fieldConstraints.gridy;

        // end also has days text box and label, so space out the absolute start label
        centerPanel.add(startLabel, labelConstraints);
        centerPanel.add(start, fieldConstraints);
        ++fieldConstraints.gridx;
        fieldConstraints.gridwidth = 3;
        centerPanel.add(hhmmss, fieldConstraints);
        // put absolute time in last column
        fieldConstraints.gridx += 3;
        fieldConstraints.gridwidth = 1;
        centerPanel.add(startAbsolute, fieldConstraints);

        fieldConstraints.gridx = 1;

        ++labelConstraints.gridy;
        ++fieldConstraints.gridy;

        hhmmss = new JLabel("HH:mm:ss");

        JLabel daysLabel = new JLabel("days");

        centerPanel.add(endLabel, labelConstraints);
        centerPanel.add(end, fieldConstraints);
        ++fieldConstraints.gridx;
        centerPanel.add(hhmmss, fieldConstraints);
        ++fieldConstraints.gridx;
        centerPanel.add(days, fieldConstraints);
        ++fieldConstraints.gridx;
        centerPanel.add(daysLabel, fieldConstraints);
        ++fieldConstraints.gridx;
        centerPanel.add(endAbsolute, fieldConstraints);

        JPanel buttonsPanel = new JPanel();
        buttonsPanel.add(add);
        buttonsPanel.add(endToStart);
        buttonsPanel.add(reset);

        add(namePanel, BorderLayout.PAGE_START);
        add(centerPanel, BorderLayout.CENTER);
        add(buttonsPanel, BorderLayout.PAGE_END);

        // note the parent field in the listener is paired to the correct text box
        startListener = new RelativeTimeDocumentListener(this, start, start, end, days);
        endListener = new RelativeTimeDocumentListener(this, end, start, end, days);
        daysListener = new RelativeTimeDocumentListener(this, days, start, end, days);

        start.getDocument().addDocumentListener(startListener);
        end.getDocument().addDocumentListener(endListener);
        days.getDocument().addDocumentListener(daysListener);

        startListener.addPropertyChangeListener(this);
        endListener.addPropertyChangeListener(this);
        daysListener.addPropertyChangeListener(this);
    }

    private long getBaseTime() {
        return ((Date) base.getValue()).getTime();
    }

    @Override
    protected long getStartTime() {
        return getStartTime((Integer) start.getValue());
    }

    long getStartTime(int start) {
        return getBaseTime() + start * 1000L;
    }

    @Override
    long getEndTime() {
        return getEndTime((Integer) end.getValue(), (Integer) days.getValue());
    }

    long getEndTime(int end, int days) {
        return getBaseTime() + end * 1000L + days * 86400000L;
    }

    @Override
    void setTimes(long start, long end) {
        // set the base time so that the current start offset gives the correct time
        long base = start - (Integer) this.start.getValue() * 1000;

        if (end > start) {
            long diff = end - base;
            int numDays = (int) (diff / 86400000L);
            int endTime = (int) (diff / 1000 % 86400);

            days.setValue(numDays);
            this.end.setValue(endTime);
        }

        // set base last since it fires a ChangeEvent which needs the new end time
        this.base.setValue(new Date(base));
    }

    @Override
    TimeZone getTimeZone() {
        DateEditor de = (DateEditor) base.getEditor();
        return de.getFormat().getTimeZone();
    }

    /**
     * @param base
     *            epoch start time
     * @param start
     *            milliseconds < 1 day
     * @param end
     *            epoch end time
     */
    @Override
    protected void setStartToEnd() {
        start.setValue(end.getValue());
        requestFocus(start);
    }

    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);

        if (enabled) {
            requestFocus(start);
        }
    }

    @Override
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        // document listeners should also propagate property changes to listeners
        startListener.addPropertyChangeListener(listener);
        endListener.addPropertyChangeListener(listener);
        daysListener.addPropertyChangeListener(listener);

        super.addPropertyChangeListener(propertyName, listener);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        // all but timeZone fired by IntervalDocumentListeners
        if ("interval".equals(evt.getPropertyName())) {
            if (evt.getNewValue() == null) {
                startLabel.setFont(Styles.LABEL_ERROR);
                endLabel.setFont(Styles.LABEL_ERROR);

                startLabel.setForeground(Styles.ERROR_COLOR);
                endLabel.setForeground(Styles.ERROR_COLOR);

                start.setForeground(Styles.ERROR_COLOR);
                end.setForeground(Styles.ERROR_COLOR);
                days.setForeground(Styles.ERROR_COLOR);
            }
            else {
                startLabel.setFont(Styles.LABEL);
                endLabel.setFont(Styles.LABEL);

                startLabel.setForeground(Styles.DEFAULT_COLOR);
                endLabel.setForeground(Styles.DEFAULT_COLOR);

                start.setForeground(Styles.DEFAULT_COLOR);
                end.setForeground(Styles.DEFAULT_COLOR);
                days.setForeground(Styles.DEFAULT_COLOR);
            }
        }
        else if ("start".equals(evt.getPropertyName())) {
            startAbsolute.setText(FORMAT.format(new Date((Long) evt.getNewValue())));
        }
        else if ("end".equals(evt.getPropertyName())) {
            endAbsolute.setText(FORMAT.format(new Date((Long) evt.getNewValue())));
        }
        else if ("timeZone".equals(evt.getPropertyName())) {
            TimeZone timeZone = (TimeZone) evt.getNewValue();

            DateEditor de = (DateEditor) base.getEditor();
            de.getFormat().setTimeZone(timeZone);

            // hack to get the spinner to fire a state change and update the displayed value
            // toggle the calendar field back to its original value
            ((SpinnerDateModel) base.getModel()).setCalendarField(Calendar.MINUTE);
            ((SpinnerDateModel) base.getModel()).setCalendarField(Calendar.SECOND);

            FORMAT.setTimeZone(timeZone);
            startAbsolute.setText(FORMAT.format(new Date(getStartTime())));
            endAbsolute.setText((FORMAT.format(new Date(getEndTime()))));
        }
    }
}