
package uk.co.wingpath.gui;

import java.util.*;
import java.util.List;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import uk.co.wingpath.util.*;
import uk.co.wingpath.event.*;
import uk.co.wingpath.event.Event;


public class WNumericField
    extends WAbstractComponent<Numeric.Value>
{
    private final WTextField field;
    private Numeric.Value value;
    private final StatusBar statusBar;
    private final String label;
    private int radix;
    private double offset;
    private double scale;
    private final boolean mayBeEmpty;
    private final NumericVerifier verifier;

    public WNumericField (String label)
    {
        this (null, label);
    }

    public WNumericField (StatusBar statusBar, String label)
    {
        this (statusBar, label, false);
    }

    public WNumericField (StatusBar statusBar, String label,
        boolean mayBeEmpty)
    {
        Event.checkIsEventDispatchThread ();
        this.statusBar = statusBar;
        this.label = label;
        this.mayBeEmpty = mayBeEmpty;
        value = Numeric.Type.int16.zero;
        radix = 10;
        offset = 0.0;
        scale = 1.0;
        field = new WTextField (statusBar, null);
        displayValue ();
        initialize (field.getComponent (), label);
        verifier = new NumericVerifier ();
        field.setVerifier (verifier);
        field.addValueListener (new ValueListener ()
            {
                public void valueChanged (ValueEvent e)
                {
                    try
                    {
                        if (radix == 10)
                        {
                            value = value.getType ().fromString (
                                field.getValue (), offset, scale);
                        }
                        else
                        {
                            value = value.getType ().fromString (
                                field.getValue (), radix);
                        }
                    }
                    catch (ValueException ex)
                    {
                        // This shouldn't normally happen since we have a
                        // verifier on the field. However, the conversion to
                        // UTF-8 isn't always invertible, so we may get a
                        // ValueException here because the original value
                        // wasn't valid UTF-8.
                        assert radix == -1 :
                            field.getValue () + " " + ex.getMessage ();
                    }
                    fireValueChanged (e.isChanging ());
                }
            });
    }

    public Numeric.Value getValue ()
    {
        return value;
    }

    public void setValue (Numeric.Value val)
    {
        Event.checkIsEventDispatchThread ();
        value = val;
        displayValue ();
    }

    private void displayValue ()
    {
        field.setValue (toString (value));
    }

    private String toString (Numeric.Value val)
    {
        if (radix == 10)
            return val.toString (offset, scale);
        else
            return val.toString (radix);
    }

    public void setRadix (int radix)
    {
        Event.checkIsEventDispatchThread ();
        if (radix != this.radix)
        {
            this.radix = radix;
            displayValue ();
        }
    }

    public void setOffset (double offset)
    {
        Event.checkIsEventDispatchThread ();
        this.offset = offset;
        displayValue ();
    }

    public void setScale (double scale)
    {
        Event.checkIsEventDispatchThread ();
        this.scale = scale;
        displayValue ();
    }

    public Numeric.Type getType ()
    {
        return value.getType ();
    }

    public void setType (Numeric.Type type)
    {
        Event.checkIsEventDispatchThread ();
        if (type == value.getType ())
            return;
        try
        {
            value = type.createValue (value);
        }
        catch (ValueException e)
        {
            value = type.zero;
        }
        displayValue ();
    }

    public int getRadix ()
    {
        return radix;
    }

    public boolean hasValueChanged (Numeric.Value oldValue)
    {
        return field.hasValueChanged (toString (oldValue));
    }

    /**
    * Checks whether the value that the user has entered is valid.
    * <p>If the value is valid, it is stored as the value of the component,
    * a value event is fired, and <code>true</code> is returned.
    * If the value is not valid, an error
    * message is displayed and <code>false</code> is returned.
    * <p>If the user is not editing the value, <code>true</code> is returned.
    * @return <code>false</code> if the user has entered an invalid value,
    * <code>true</code> otherwise.
    */
    @Override
    public boolean checkValue ()
    {
        return field.checkValue ();
    }

    /**
     * Sets the horizontal alignment of the text.
     * Valid parameter values are:
     * <ul>
     * <li><code>SwingConstants.LEFT</code>
     * <li><code>SwingConstants.CENTER</code>
     * <li><code>SwingConstants.RIGHT</code>
     * <li><code>SwingConstants.LEADING</code>
     * <li><code>SwingConstants.TRAILING</code>
     * </ul>
     * @param alignment the required alignment
     */
    @Override
    public void setAlignment (int alignment)
    {
        field.setAlignment (alignment);
    }

    /**
    * Specifies whether or not this component should be editable.
    * @param editable whether the component should be editable.
    */
    @Override
    public void setEditable (boolean editable)
    {
        field.setEditable (editable);
    }

    public void setBackground (Color bg)
    {
        field.setBackground (bg);
    }

    public void setMirror (MirrorField mirror, String mirrorLabel)
    {
        field.setMirror (mirror, mirrorLabel);
    }

    public void setMirror (MirrorField mirror)
    {
        field.setMirror (mirror, label);
    }

    private class NumericVerifier
        implements Verifier
    {
        private void reportError (String msg)
        {
            if (label != null)
                msg = label + ": " + msg;
            statusBar.showError (msg);
        }

        public String verify (String str, boolean isChanging)
        {
            if (isChanging)
            {
                if (statusBar != null)
                    statusBar.clear ();
                return str;
            }
            if (str.equals (""))
            {
                if (mayBeEmpty)
                    return "";
                reportError ("Value missing");
                return null;
            }

            try
            {
                if (radix == 10)
                {
                    Numeric.Value val =
                        value.getType ().fromString (str, offset, scale);
                    if (statusBar != null)
                        statusBar.clear ();
                    return val.toString (offset, scale);
                }
                else
                {
                    Numeric.Value val =
                        value.getType ().fromString (str, radix);
                    if (statusBar != null)
                        statusBar.clear ();
                    return val.toString (radix);
                }
            }
            catch (ValueException e)
            {
                reportError (e.getMessage ());
                return null;
            }
        }
    }
}

