
package uk.co.wingpath.util;

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import uk.co.wingpath.event.*;

/**
* This class supports numeric values that may be floating-point or integral,
* and that may be of various sizes.
* <p>The enum {@link Type Numeric.Type} specifies the possible types and sizes.
* Actual numeric values are represented by immutable instances of private
* classes that implement the {@link Value Numeric.Value} interface.
* <p>Each type has an {@link Numeric.Type#undef undef} value, which
* converts to and from an empty string. An undefined value is equal to an
* undefined value of the same type, but not equal to anything else.
* The {@link Numeric#matches Numeric.matches} and
* {@link Numeric.Value#matches Numeric.Value.matches} methods
* interpret an undefined value as a pattern that will match any value.
* <p>Instances of the class {@link Variable Numeric.Variable} may be used to
* store numeric values and monitor changes to them.
*/
public final class Numeric
{
    /**
    * Special "radix" used for packing up to 8 UTF-8 characters in an
    * integer value.
    */
    public static final int RADIX_CHAR = -1;

    private static final BigInteger MIN_SIGNED_LONG =
        BigInteger.valueOf (Long.MIN_VALUE);
    private static final BigInteger MAX_SIGNED_LONG =
        BigInteger.valueOf (Long.MAX_VALUE);
    public static final BigInteger TWO_POWER_64 =
        MAX_SIGNED_LONG.add (BigInteger.valueOf (1)).
            multiply (BigInteger.valueOf (2));

    private Numeric ()
    {
    }

    /**
    * Interface implemented by Value and Pattern.
    */
    public interface PatVal
    {
        Type getType ();
        boolean matches (Value v);
        String toString (int radix);
    }

    /**
    * This provides a uniform interface to numeric values of various types.
    */
    public interface Value
        extends Comparable<Value>, PatVal
    {
        /**
        * Gets the type of the numeric value.
        * @return the numeric type.
        */
        Type getType ();

        /**
        * Gets the numeric value as a <code>long</code>.
        * @return the numeric value converted to a <code>long</code>.
        */
        long getLongValue ();

        /**
        * Gets the numeric value as a <code>double</code>.
        * @return the numeric value converted to a <code>double</code>.
        */
        double getDoubleValue ();

        /**
        * Tests whether the value is defined.
        * @return true if the value is defined, false otherwise.
        */
        boolean isDefined ();

        /**
        * Converts the numeric value to a string using the specifed radix.
        * <p>A radix of 10 is interpreted as signed decimal.
        * A radix of -1 is interpreted as UTF-8 characters.
        * All other radixes are interpreted as unsigned.
        * <p>The radix may be 0, which is interpreted to mean unsigned decimal.
        * <p>If the numeric value is of a floating-point type, the radix is
        * ignored and the conversion is done using signed decimal.
        * An undefined value converts to an empty string.
        * @param radix the radix to be used for the conversion.
        * @return the converted value.
        */
        String toString (int radix);

        /**
        * Converts the numeric value to a signed decimal string using the
        * specifed offset and scale.
        * <p>The conversion uses the formula:
        * <blockquote>result = (signed_value + offset) / scale</blockquote>
        * or:
        * <blockquote>result = (unsigned_value + offset) / scale</blockquote>
        * depending on whether the initial value is to be interpreted as signed
        * or unsigned.
        * An undefined value converts to an empty string.
        * @param offset offset to be used for the conversion.
        * @param scale scale to be used for the conversion.
        * @return the converted value.
        */
        String toString (double offset, double scale);

        /**
        * Tests whether this value matches the specified value.
        * An undefined value will match any value of the same type.
        * @param value the value to be matched.
        * @return true if the value matches, false otherwise.
        */
        boolean matches (Value value);
    }

    /**
    * Tests whether a Numeric.Pattern array matches a Numeric.Value array.
    * <p>Matching of corresponding pairs of values is done using the
    * {@link Numeric.Pattern#matches Numeric.Pattern.matches} method.
    * <p>The match will fail if the pattern array is bigger than the value
    * array.
    * @param pa first array of patterns to be matched.
    * @param va second array of values to be matched.
    * @return true if the arrays match, false otherwise.
    */
    public static boolean matches (Pattern [] pa, Value [] va)
    {
        if (pa.length > va.length)
            return false;

        for (int i = 0 ; i < pa.length ; ++i)
        {
            if (!pa [i].matches (va [i]))
                return false;
        }

        return true;
    }

    /**
    * Tests whether a Numeric.Value array matches another Numeric.Value array.
    * <p>Matching of corresponding pairs of values is done using the
    * {@link Numeric.Value#matches Numeric.Value.matches} method.
    * <p>The arrays will not match if they are different sizes.
    * @param pa first array of patterns to be matched.
    * @param va second array of values to be matched.
    * @return true if the arrays match, false otherwise.
    */
    public static boolean matches (Value [] pa, Value [] va)
    {
        if (pa.length != va.length)
            return false;

        for (int i = 0 ; i < pa.length ; ++i)
        {
            if (!pa [i].matches (va [i]))
                return false;
        }

        return true;
    }

    /**
    * Tests whether integral operations can be used to convert values.
    * @param offset offset to be used to convert values.
    * @param scale scale to be used to convert values.
    * @return {@code true} if integral operations can be used.
    */
    private static boolean canUseIntegral (double offset, double scale)
    {
        double recipScale = 1.0 / scale;
        return
            Math.floor (recipScale) == recipScale &&
            Math.floor (offset) == offset &&
            recipScale >= Long.MIN_VALUE && recipScale <= Long.MAX_VALUE &&
            offset >= Long.MIN_VALUE && offset <= Long.MAX_VALUE;
    }

    private static String radixString (int radix)
    {
        String radixStr = "a";
        switch (radix)
        {
        case 2:
            radixStr = "a binary";
            break;
        case 8:
            radixStr = "an octal";
            break;
        case 10:
            radixStr = "a decimal";
            break;
        case 16:
            radixStr = "a hexadecimal";
            break;
        }
        return radixStr;
    }

    private static String typeString (double offset, double scale)
    {
        if (canUseIntegral (offset, scale))
            return "an integer";
        return "a number";
    }

    /**
    * This enum specifies the possible types of numeric values.
    */
    public enum Type
    {
        /**
        * 1-bit integer.
        */
        int1 (false, false, 0)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                throw new ValueException ("Value out of range");
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val == 0 || val == 1;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 1;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be 0 or 1");
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    toString (0, offset, scale) + " or " +
                    toString (1, offset, scale));
            }
        },

        /**
        * 8-bit signed integer.
        */
        int8 (false, true, 1)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Byte.MIN_VALUE || value > 0xff)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, (byte) value);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type unsigned ()
            {
                return uint8;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xff;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be integer in the range " +
                    Long.toString (Byte.MIN_VALUE) + " to " +
                    Long.toString (Byte.MAX_VALUE));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (Byte.MIN_VALUE, offset, scale) + " to " +
                    toString (Byte.MAX_VALUE, offset, scale));
            }
        },

        /**
        * 8-bit unsigned integer.
        */
        uint8 (false, false, 1)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Byte.MIN_VALUE || value > 0xff)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, value & 0xff);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type signed ()
            {
                return int8;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= 0 && val <= 0xff;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xff;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    radixString (radix) + " integer in the range " +
                    "0 to " + toString (0xff, radix));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (0, offset, scale) + " to " +
                    toString (0xff, offset, scale));
            }
        },

        /**
        * 16-bit signed integer.
        */
        int16 (false, true, 2)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Short.MIN_VALUE || value > 0xffff)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, (short) value);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type unsigned ()
            {
                return uint16;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= Short.MIN_VALUE && val <= Short.MAX_VALUE;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xffff;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    radixString (radix) + " integer in the range " +
                    Long.toString (Short.MIN_VALUE) + " to " +
                    Long.toString (Short.MAX_VALUE));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (Short.MIN_VALUE, offset, scale) + " to " +
                    toString (Short.MAX_VALUE, offset, scale));
            }
        },

        /**
        * 16-bit unsigned integer.
        */
        uint16 (false, false, 2)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Short.MIN_VALUE || value > 0xffff)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, value & 0xffff);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type signed ()
            {
                return int16;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= 0 && val <= 0xffff;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xffff;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    radixString (radix) + " integer in the range " +
                    "0 to " + toString (0xffff, radix));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (0, offset, scale) + " to " +
                    toString (0xffff, offset, scale));
            }
        },

        /**
        * 32-bit signed integer
        */
        int32 (false, true, 4)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Integer.MIN_VALUE || value > 0xffffffffL)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, (int) value);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type unsigned ()
            {
                return uint32;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xffffffffL;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    radixString (radix) + " integer in the range " +
                    Long.toString (Integer.MIN_VALUE) + " to " +
                    Integer.toString (Integer.MAX_VALUE));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (Integer.MIN_VALUE, offset, scale) +
                    " to " + toString (Integer.MAX_VALUE, offset, scale));
            }
        },

        /**
        * 32-bit unsigned integer
        */
        uint32 (false, false, 4)
        {
            @Override
            public Value createValue (long value)
                throws ValueException
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                if (value < Integer.MIN_VALUE || value > 0xffffffffL)
                    throw new ValueException ("Value out of range");
                return new LongValue (this, value & 0xffffffffL);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type signed ()
            {
                return int32;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return val >= 0 && val <= 0xffffffffL;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value & 0xffffffffL;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    radixString (radix) + " integer in the range " +
                    "0 to " + toString (0xffffffffL, radix));
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (0, offset, scale) + " to " +
                    toString (0xffffffffL, offset, scale));
            }
        },

        /**
        * 64-bit signed integer.
        */
        int64 (false, true, 8)
        {
            @Override
            public Value createValue (long value)
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                return new LongValue (this, value);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type unsigned ()
            {
                return uint64;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return true;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be an integer");
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                if (scale == 1.0 && offset == 0.0)
                    throw new ValueException ("Must be an integer");
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (Long.MIN_VALUE, offset, scale) + " to " +
                    toString (Long.MAX_VALUE, offset, scale));
            }
        },

        /**
        * 64-bit unsigned integer.
        */
        uint64 (false, false, 8)
        {
            @Override
            public Value createValue (long value)
            {
                if (value == 0)
                    return zero;
                if (value == 1)
                    return one;
                return new LongValue (this, value);
            }

            @Override
            public Value createValue (double value)
                throws ValueException
            {
                return createValue (longValue (value));
            }

            @Override
            public Type signed ()
            {
                return int64;
            }

            @Override
            protected boolean checkRange (long val)
            {
                return true;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                throw new ValueException ("Must be an unsigned integer");
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                if (scale == 1.0 && offset == 0.0)
                    throw new ValueException ("Must be an unsigned integer");
                throw new ValueException ("Must be " +
                    typeString (offset, scale) + " in the range " +
                    toString (0, offset, scale) + " to " +
                    toString (Long.MAX_VALUE - (double) Long.MIN_VALUE,
                            offset, scale));
            }
        },

        /**
        * 32-bit float.
        */
        float32 (true, true, 4)
        {
            @Override
            public Value createValue (long value)
            {
                return createValue ((double) value);
            }

            @Override
            public Value createValue (double value)
            {
                if (value == 0.0)
                    return zero;
                if (value == 1.0)
                    return one;
                // Don't bother with range check - if value is too big, it will
                // convert to an infinity.
                return new DoubleValue (this, (float) value);
            }

            @Override
            protected boolean checkRange (long val)
            {
                return true;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                // Don't bother telling user the range - we aren't checking it!
                // Values out of range will convert to infinity.
                throw new ValueException ("Must be a decimal number");
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                // Don't bother telling user the range - we aren't checking it!
                // Values out of range will convert to infinity.
                throw new ValueException ("Must be a decimal number");
            }

            @Override
            public Numeric.Value fromString (String value, int radix)
                throws ValueException
            {
                if (value.equals (""))
                    return undef;

                try
                {
                    float f = Float.parseFloat (value.trim ());
                    return createValue (f);
                }
                catch (NumberFormatException e)
                {
                    valueError (radix);
                }

                throw new AssertionError ("Unreachable");
            }

            @Override
            protected String toString (double value, double offset,
                double scale)
            {
                if (offset == 0.0 && scale == 1.0)
                    return Float.toString ((float) value);
                double d = (value + offset) / scale;
                return Double.toString (d);
            }

            @Override
            protected String toString (double value)
            {
                return Float.toString ((float) value);
            }
        },

        /**
        * 64-bit float.
        */
        float64 (true, true, 8)
        {
            @Override
            public Value createValue (long value)
            {
                return createValue ((double) value);
            }

            @Override
            public Value createValue (double value)
            {
                if (value == 0.0)
                    return zero;
                if (value == 1.0)
                    return one;
                return new DoubleValue (this, value);
            }

            @Override
            protected boolean checkRange (long val)
            {
                return true;
            }

            @Override
            protected long unsignedValue (long value)
            {
                return value;
            }

            @Override
            protected void valueError (int radix)
                throws ValueException
            {
                // Don't bother telling user the range - we aren't checking it!
                // Values out of range will convert to infinity.
                throw new ValueException ("Must be a decimal number");
            }

            @Override
            protected void valueError (double offset, double scale)
                throws ValueException
            {
                // Don't bother telling user the range - we aren't checking it!
                // Values out of range will convert to infinity.
                throw new ValueException ("Must be a decimal number");
            }
        };

        public final Value zero;
        public final Value one;
        public final Value undef;
        public final Pattern emptyPattern;
        private final boolean isFloat;
        private final boolean isSigned;
        private final int size;

        private Type (boolean isFloat, boolean isSigned, int size)
        {
            this.isFloat = isFloat;
            this.isSigned = isSigned;
            this.size = size;
            zero =
                isFloat ? new DoubleValue (this, 0) : new LongValue (this, 0);
            one = isFloat ? new DoubleValue (this, 1) : new LongValue (this, 1);
            undef = isFloat ? new DoubleValue (this) : new LongValue (this);
            emptyPattern = new Pattern (this, new Range [0]);
        }

        /**
        * Gets the number of bytes used by values of this type.
        * <p>For example, 4 is returned for the <code>int32</code> type, and
        * 8 is returned for the <code>float64</code> type.
        * As a special case, 0 is returned for the <code>int1</code> type.
        * @return size in bytes.
        */
        public int getSize ()
        {
            return size;
        }

        /**
        * Returns whether this is a floating-point type.
        * @return <code>true</code> for floating-point types, <code>false</code>
        * for integral types.
        * @return whether type is floating-point.
        */
        public boolean isFloat ()
        {
            return isFloat;
        }

        /**
        * Returns whether this is a signed type.
        * @return <code>true</code> for signed integral and floating-point
        * types, <code>false</code> for unsigned integral types.
        * @return whether type is signed.
        */
        public boolean isSigned ()
        {
            return isSigned;
        }

        /**
        * If this is a signed type, returns the unsigned version of this
        * type, if any. Otherwise, returns this type.
        * @return unsigned version of this type.
        */
        public Type unsigned ()
        {
            return this;
        }

        /**
        * If this is an unsigned type, returns the signed version of this
        * type, if any. Otherwise, returns this type.
        * @return signed version of this type.
        */
        public Type signed ()
        {
            return this;
        }

        /**
        * For internal use only.
        */
        protected abstract boolean checkRange (long val);

        /**
        * For internal use only.
        */
        protected abstract long unsignedValue (long value);

        /**
        * For internal use only.
        */
        protected abstract void valueError (int radix)
            throws ValueException;

        /**
        * For internal use only.
        */
        protected abstract void valueError (double offset, double scale)
            throws ValueException;

        /**
        * Constructs a numeric value of this type by parsing the specified
        * string using the specified radix.
        * @param value the string to be parsed.
        * @param radix the radix to be used for parsing the string.
        * @return the numeric value.
        * @throws ValueException if the string cannot be converted to a value
        * of this type.
        */
        public Numeric.Value fromString (String value, int radix)
            throws ValueException
        {
            try
            {
                if (isFloat)
                {
                    if (value.equals (""))
                        return undef;
                    double d = Double.parseDouble (value.trim ());
                    return createValue (d);
                }
                else
                {
                    if (radix == RADIX_CHAR)
                    {
                        try
                        {
                            byte [] data = value.getBytes ("UTF-8");
                            if (data.length > size)
                            {
                                throw new ValueException (
                                    "Must be character" +
                                    (size == 1 ? "" : "s") + 
                                    " representible in " +
                                    "UTF-8 using " + size +
                                    " byte" + (size == 1 ? "" : "s"));
                            }
                            long n = 0;
                            for (int i = 0 ; i < size ; i++)
                            {
                                n <<= 8;
                                if (i < data.length)
                                    n |= (data [i] & 0xff);
                                else
                                    n |= ' ';
                            }
                            return createValue (n);
                        }
                        catch (UnsupportedEncodingException e)
                        {
                            // Shouldn't happen - UTF-8 should be supported.
                            throw new AssertionError ("Unreachable");
                        }
                    }
                    if (value.equals (""))
                        return undef;
                    if (!isSigned && size == 8)
                    {
                        // Unsigned decimal long - parseLong can't handle this
                        // case, so use BigInteger.

                        BigInteger bi = new BigInteger (value.trim (), radix);
                        if (bi.compareTo (BigInteger.ZERO) < 0)
                            valueError (radix);
                        if (bi.compareTo (TWO_POWER_64) >= 0)
                            valueError (radix);
                        if (bi.compareTo (MAX_SIGNED_LONG) > 0)
                            bi = bi.subtract (TWO_POWER_64);
                        return createValue (bi.longValue ());
                    }

                    long val = Long.parseLong (value.trim (), radix);

                    if (!checkRange (val))
                        valueError (radix);

                    return createValue (val);
                }
            }
            catch (NumberFormatException e)
            {
                valueError (radix);
            }

            throw new AssertionError ("Unreachable");
        }

        /**
        * Constructs a numeric value of this type by parsing the specified
        * string as signed decimal and converting it using the specified
        * offset and scale.
        * The conversion is done using the formula:
        * <blockquote>result = value * scale - offset</blockquote>
        * An empty string will convert to an undefined value.
        * @param value the string to be parsed.
        * @param offset offset to be used to convert the value
        * @param scale scale to be used to convert the value
        * @return the numeric value.
        * @throws ValueException if the string cannot be converted to a value
        * of this type.
        */
        public Numeric.Value fromString (String value, double offset,
                double scale)
            throws ValueException
        {
            if (value.equals (""))
                return undef;

            if (offset == 0.0 && scale == 1.0)
                return fromString (value, 10);

            try
            {
                if (isFloat)
                {
                    double d = Double.parseDouble (value.trim ());
                    d = d * scale - offset;
                    return createValue (d);
                }
                if (canUseIntegral (offset, scale))
                {
                    // Entered value has to be integral, and in a range
                    // where we can convert it using integral operations.
                    BigInteger bi = new BigInteger (value.trim ());
                    bi = bi.divide (BigInteger.valueOf ((long) (1.0 / scale)));
                    bi = bi.subtract (BigInteger.valueOf ((long) offset));
                    long val = bi.longValue ();
                    if (isSigned)
                    {
                        if (bi.compareTo (MIN_SIGNED_LONG) < 0 ||
                            bi.compareTo (MAX_SIGNED_LONG) > 0 ||
                            !checkRange (val))
                        {
                            valueError (offset, scale);
                        }
                    }
                    else
                    {
                        if (bi.compareTo (BigInteger.ZERO) < 0 ||
                            bi.compareTo (TWO_POWER_64) >= 0 ||
                            !checkRange (val))
                        {
                            valueError (offset, scale);
                        }
                    }
                    return createValue (val);
                }

                double d = Double.parseDouble (value.trim ());
                d = d * scale - offset;
                if (d < Long.MIN_VALUE || d > Long.MAX_VALUE ||
                    Double.isNaN (d))
                {
                    valueError (offset, scale);
                }
                long val = (long) d;

                if (!checkRange (val))
                    valueError (offset, scale);

                return createValue (val);
            }
            catch (NumberFormatException e)
            {
                valueError (offset, scale);
            }

            throw new AssertionError ("Unreachable");
        }

        protected String toString (long value, int radix)
        {
            switch (radix)
            {
            case RADIX_CHAR:
                {
                    byte [] data = new byte [size];
                    long n = value;
                    for (int i = data.length - 1 ; i >= 0 ; i--)
                    {
                        data [i] = (byte) n;
                        n >>= 8;
                    }
                    try
                    {
                        // Not all byte sequences are valid UTF-8, so
                        // the string returned here is not guaranteed to
                        // convert back to the same sequence of bytes.
                        return new String (data, "UTF-8");
                    }
                    catch (UnsupportedEncodingException e)
                    {
                        // Shouldn't happen - UTF-8 should be supported.
                        throw new AssertionError ("Unreachable");
                    }
                }
            case 2:
                String s = Long.toBinaryString (unsignedValue (value));
                while (s.length () < size * 8)
                    s = "0" + s;
                return s;

            case 8:
                return Long.toOctalString (unsignedValue (value));

            case 16:
                return Long.toHexString (unsignedValue (value));

            case 10:
                if (isSigned)
                    return "" + value;

                // Unsigned decimal

                if (size != 8 || value >= 0)
                    return "" + unsignedValue (value);

                // We have a negative long that we want to convert to
                // unsigned decimal. Use BigInteger to do it.

                BigInteger bi = BigInteger.valueOf (value);
                bi = bi.add (TWO_POWER_64);
                return bi.toString ();

            default:
                throw new IllegalArgumentException (
                    "Invalid radix " + radix);
            }
        }

        protected String toString (long value, double offset, double scale)
        {
            if (canUseIntegral (offset, scale))
            {
                BigInteger bi = BigInteger.valueOf (value);
                bi = bi.add (BigInteger.valueOf ((long) offset));
                bi = bi.multiply (BigInteger.valueOf ((long) (1.0 / scale)));
                return bi.toString ();
            }
            return toString ((double) value, offset, scale);
        }

        protected String toString (double value, double offset, double scale)
        {
            double d = (value + offset) / scale;
            return Double.toString (d);
        }

        protected String toString (double value)
        {
            return Double.toString (value);
        }

        /**
        * Constructs a numeric value of this type corresponding to the
        * specified long value.
        * <p>The range checking is relaxed to allow signed and unsigned values
        * of the same size to be converted to/from each other.
        * @param value the long value to be used for constructing the numeric
        * value.
        * @return a numeric value corresponding to the long value.
        * @throws ValueException if the value is out of range for
        * this type.
        */
        public abstract Value createValue (long value)
            throws ValueException;

        /**
        * Constructs a numeric value of this type corresponding to the
        * specified double value.
        * @param value the double value to be used for constructing the numeric
        * value.
        * @return a numeric value corresponding to the double value.
        * @throws ValueException if the value is out of range for
        * this type.
        */
        public abstract Value createValue (double value)
            throws ValueException;

        /**
        * Constructs a numeric range of this type with the specified minimum
        * and maximum values.
        * @param min the minimum value.
        * @param max the maximum value.
        * @return a range with the specified minimum and maximum values.
        */
        public Range createRange (long min, long max)
            throws ValueException
        {
            if (min > max)
                throw new ValueException ("min is greater than max");
            return new Range (createValue (min), createValue (max));
        }

        /**
        * Constructs a numeric range of this type with the specified minimum
        * and maximum values.
        * @param min the minimum value.
        * @param max the maximum value.
        * @return a range with the specified minimum and maximum values.
        */
        public Range createRange (double min, double max)
            throws ValueException
        {
            if (min > max)
                throw new ValueException ("min is greater than max");
            return new Range (createValue (min), createValue (max));
        }

        /**
        * Constructs a numeric range of this type with equal minimum
        * and maximum values.
        * @param minmax the value.
        * @return a range with the specified minimum and maximum value.
        */
        public Range createRange (long minmax)
            throws ValueException
        {
            Value v = createValue (minmax);
            return new Range (v, v);
        }

        /**
        * Constructs a numeric range of this type with equal minimum
        * and maximum values.
        * @param minmax the value.
        * @return a range with the specified minimum and maximum value.
        */
        public Range createRange (double minmax)
            throws ValueException
        {
            Value v = createValue (minmax);
            return new Range (v, v);
        }

        /**
        * Constructs a numeric range of this type with the specified minimum
        * value and no maximum.
        * @param min the minimum value.
        * @return a range with the specified minimum value.
        */
        public Range createMinimum (long min)
            throws ValueException
        {
            return new Range (createValue (min), undef);
        }

        /**
        * Constructs a numeric range of this type with the specified minimum
        * value and no maximum.
        * @param min the minimum value.
        * @return a range with the specified minimum value.
        */
        public Range createMinimum (double min)
            throws ValueException
        {
            return new Range (createValue (min), undef);
        }

        /**
        * Constructs a numeric range of this type with the specified maximum
        * value and no minimum.
        * @param max the maximum value.
        * @return a range with the specified maximum value.
        */
        public Range createMaximum (long max)
            throws ValueException
        {
            return new Range (undef, createValue (max));
        }

        /**
        * Constructs a numeric range of this type with the specified maximum
        * value and no minimum.
        * @param max the maximum value.
        * @return a range with the specified maximum value.
        */
        public Range createMaximum (double max)
            throws ValueException
        {
            return new Range (undef, createValue (max));
        }

        /**
        * Constructs a numeric range of this type from the specified string.
        * The string may contain the minimum and maximum values separated
        * by a colon. Either or both the minimum and maximum may be empty.
        * Alternatively, the string may contain a single value (no colon),
        * which will be used as the maximum and minimum.
        * @param str the range specified as a string.
        * @param radix the radix used for the minimum and maximum values.
        * @return a range constructed from the string.
        * @throws ValueException if a valid range cannot be constructed from
        * the string.
        */
        public Range createRange (String str, int radix)
            throws ValueException
        {
            int i = str.indexOf (':');
            if (i < 0)
            {
                Value v = fromString (str, radix);
                return new Range (v, v);
            }
            else
            {
                Value min = fromString (str.substring (0, i), radix);
                Value max = fromString (str.substring (i + 1), radix);
                if (min.isDefined () && max.isDefined () &&
                    min.compareTo (max) > 0)
                {
                    throw new ValueException ("min is greater than max");
                }
                return new Range (min, max);
            }
        }

        /**
        * Creates a Pattern from the supplied string.
        * The string should contain zero or more ranges in the format
        * specified by {@link #createRange(String,int) createRange}, separated
        * by whitespace or commas.
        * @param str the pattern specified as a string.
        * @param radix the radix used for the minimum and maximum values.
        * @return a pattern constructed from the string.
        * @throws ValueException if a valid pattern cannot be constructed from
        * the string.
        */
        public Pattern createPattern (String str, int radix)
            throws ValueException
        {
            if (str.equals (""))
                return emptyPattern;
            String [] ss = str.split ("\\s+");
            Range [] ranges = new Range [ss.length];
            for (int i = 0 ; i < ss.length ; ++i)
            {
                ranges [i] = createRange (ss [i], radix);
            }

            return new Pattern (this, ranges);
        }

        /**
        * For internal use only.
        */
        protected long longValue (double value)
            throws ValueException
        {
            if (value < Long.MIN_VALUE ||
                value > Long.MAX_VALUE ||
                Double.isNaN (value))
            {
                throw new ValueException ("Value out of range");
            }
            return (long) value;
        }

        /**
        * Constructs a numeric value of this type corresponding to the
        * specified numeric value.
        * @param value the numeric value to be used for constructing the numeric
        * value.
        * @return a numeric value corresponding to the supplied numeric value.
        * @throws ValueException if the value is out of range for
        * this type.
        */
        public Value createValue (Value value)
            throws ValueException
        {
            if (!value.isDefined ())
                return undef;
            if (isFloat ())
                return createValue (value.getDoubleValue ());
            return createValue (value.getLongValue ());
        }

        /**
        * Constructs an array of zero values of this type.
        * @param size array size.
        * @return array of zero numeric values.
        */
        public Value [] createZeroArray (int size)
        {
            Value [] a = new Value [size];

            for (int i = 0 ; i < size ; i++)
                a [i] = zero;

            return a;
        }

        /**
        * Constructs an array of undefined values of this type.
        * @param size array size.
        * @return array of undefined numeric values.
        */
        public Value [] createUndefArray (int size)
        {
            Value [] a = new Value [size];

            for (int i = 0 ; i < size ; i++)
                a [i] = undef;

            return a;
        }

        /**
        * Constructs an array of empty patterns of this type.
        * @param size array size.
        * @return array of empty patterns.
        */
        public Pattern [] createEmptyPatternArray (int size)
        {
            Pattern [] a = new Pattern [size];

            for (int i = 0 ; i < size ; i++)
                a [i] = emptyPattern;

            return a;
        }
    }

    private static class LongValue
        implements Value
    {
        private final Type type;
        private final long value;
        private final boolean defined;

        private LongValue (Type type, long value, boolean defined)
        {
            this.type = type;
            this.value = value;
            this.defined = defined;
        }

        private LongValue (Type type, long value)
        {
            this (type, value, true);
        }

        private LongValue (Type type)
        {
            this (type, 0, false);
        }

        @Override
        public Type getType ()
        {
            return type;
        }

        @Override
        public long getLongValue ()
        {
            return value;
        }

        @Override
        public double getDoubleValue ()
        {
            return value;
        }

        @Override
        public boolean isDefined ()
        {
            return defined;
        }

        @Override
        public boolean equals (Object obj)
        {
            if (!(obj instanceof LongValue))
                return false;
            LongValue lv = (LongValue) obj;
            if (lv.getType () != type)
                return false;
            if (lv.defined != defined)
                return false;
            return lv.value == value;
        }

        @Override
        public int compareTo (Value v)
        {
            if (equals (v))
                return 0;
            if (!defined)
                return 1;
            if (!v.isDefined ())
                return -1;
            long lv = v.getLongValue ();
            if (value < lv)
                return -1;
            if (value > lv)
                return 1;
            return 0;
        }

        @Override
        public boolean matches (Value v)
        {
            if (!(v instanceof LongValue))
                return false;
            LongValue lv = (LongValue) v;
            if (lv.getType () != type)
                return false;
            if (!defined || !lv.defined)
                return true;
            return lv.value == value;
        }

        @Override
        public int hashCode ()
        {
            return (int) value;
        }

        @Override
        public String toString (int radix)
        {
            if (!defined)
                return "";
            return type.toString (value, radix);
        }

        @Override
        public String toString (double offset, double scale)
        {
            if (!defined)
                return "";
            return type.toString (value, offset, scale);
        }

        @Override
        public String toString ()
        {
            return toString (10);
        }
    }

    private static class DoubleValue
        implements Value
    {
        private final Type type;
        private final double value;
        private final boolean defined;

        private DoubleValue (Type type, double value, boolean defined)
        {
            this.type = type;
            this.value = value;
            this.defined = defined;
        }

        private DoubleValue (Type type, double value)
        {
            this (type, value, true);
        }

        private DoubleValue (Type type)
        {
            this (type, 0.0, false);
        }

        @Override
        public Type getType ()
        {
            return type;
        }

        @Override
        public long getLongValue ()
        {
            if (value < Long.MIN_VALUE ||
                value > Long.MAX_VALUE ||
                Double.isNaN (value))
            {
                return 0;
            }
            return (long) value;
        }

        @Override
        public double getDoubleValue ()
        {
            return value;
        }

        @Override
        public boolean isDefined ()
        {
            return defined;
        }

        @Override
        public boolean equals (Object obj)
        {
            if (!(obj instanceof DoubleValue))
                return false;
            DoubleValue dv = (DoubleValue) obj;
            if (dv.getType () != type)
                return false;
            if (dv.defined != defined)
                return false;
            if (Double.isNaN (value))
                return Double.isNaN (dv.value);
            return dv.value == value;
        }

        @Override
        public int compareTo (Value v)
        {
            return Double.compare (value, v.getDoubleValue ());
        }

        @Override
        public boolean matches (Value v)
        {
            if (!(v instanceof DoubleValue))
                return false;
            DoubleValue dv = (DoubleValue) v;
            if (dv.getType () != type)
                return false;
            if (!defined || !dv.defined)
                return true;
            return dv.value == value;
        }

        @Override
        public int hashCode ()
        {
            return (int) value;
        }

        @Override
        public String toString (int radix)
        {
            if (!defined)
                return "";
            return type.toString (value);
        }

        @Override
        public String toString (double offset, double scale)
        {
            if (!defined)
                return "";
            return type.toString (value, offset, scale);
        }

        @Override
        public String toString ()
        {
            return toString (10);
        }
    }

    public static class Range
    {
        private final Value min;
        private final Value max;

        /**
        * Constructs a range with the specified minimum and maximum values.
        * @param min the minimum value.
        * @param max the maximum value.
        */
        public Range (Value min, Value max)
        {
            if (min.getType () != max.getType ())
            {
                throw new IllegalArgumentException (
                    "min and max have different types");
            }
            if (min.isDefined () && max.isDefined () &&
                min.compareTo (max) > 0)
            {
                throw new IllegalArgumentException ("min is greater than max");
            }
            this.min = min;
            this.max = max;
        }

        /**
        * Constructs a numeric range with equal minimum and maximum values.
        * @param minmax the value.
        */
        public Range (Numeric.Value minmax)
        {
            this (minmax, minmax);
        }

        public Value getMinimum ()
        {
            return min;
        }

        public Value getMaximum ()
        {
            return max;
        }

        public Type getType ()
        {
            return min.getType ();
        }

        @Override
        public boolean equals (Object obj)
        {
            if (!(obj instanceof Range))
                return false;
            Range r = (Range) obj;
            return r.min.equals (min) && r.max.equals (max);
        }

        public boolean matches (Value v)
        {
            if (!v.isDefined ())
                return true;
            if (min.isDefined () && min.compareTo (v) > 0)
                return false;
            if (max.isDefined () && max.compareTo (v) < 0)
                return false;
            return true;
        }

        @Override
        public int hashCode ()
        {
            return min.hashCode () * max.hashCode ();
        }

        public boolean isDefined ()
        {
            return min.isDefined () || max.isDefined ();
        }

        public String toString (int radix)
        {
            if (!min.isDefined () && !max.isDefined ())
                return "";
            if (min.isDefined () && min.equals (max))
                return min.toString (radix);
            return min.toString (radix) + ":" + max.toString (radix);
        }

        @Override
        public String toString ()
        {
            return toString (10);
        }
    }

    public static class Pattern
        implements PatVal
    {
        private final Type type;
        private final Range [] ranges;

        public Pattern (Type type, Range [] ranges)
        {
            for (Range r : ranges)
            {
                if (r.getType () != type)
                {
                    throw new IllegalArgumentException (
                        "Wrong type in pattern: " + type + " " +
                            r.getType ());
                }
            }

            this.type = type;
            this.ranges = ranges;
        }

        public Pattern (Numeric.Value value)
        {
            this (value.getType (),
                value.isDefined () ? new Range [] { new Range (value) } :
                new Range [0]);
        }

        public Type getType ()
        {
            return type;
        }

        public Range [] getRanges ()
        {
            return ranges;
        }

        @Override
        public boolean equals (Object obj)
        {
            if (!(obj instanceof Pattern))
                return false;
            Pattern p = (Pattern) obj;
            if (!Arrays.equals (p.ranges, ranges))
                return false;
            return true;
        }

        /**
        * Tests whether this pattern matches the specified value.
        * @param v the value to be matched.
        * @return true if the range matches, false otherwise.
        */
        public boolean matches (Value v)
        {
            if (!(v instanceof Value))
                return false;
            if (!v.isDefined ())
                return true;
            if (ranges.length == 0)
                return true;
            for (Range r : ranges)
            {
                if (r.matches (v))
                    return true;
            }
            return false;
        }

        @Override
        public int hashCode ()
        {
            int hash = 0;

            for (Range r : ranges)
            {
                hash *= 23;
                hash += r.hashCode ();
            }

            return hash;
        }

        public String toString (int radix)
        {
            StringBuilder str = new StringBuilder ();

            for (int i = 0 ; i < ranges.length ; i++)
            {
                if (i != 0)
                    str.append (' ');
                str.append (ranges [i].toString (radix));
            }

            return str.toString ();
        }

        @Override
        public String toString ()
        {
            return toString (10);
        }
    }

    /**
    * <p>Instances of this class may be used to store numeric values and
    * monitor changes to them.
    */
    public static class Variable
        extends uk.co.wingpath.util.SimpleVariable<Value>
    {
        /**
        * Constructs a <code>Numeric.Variable</code> with initial type
        * <code>float64</code> and initial value 0.
        */
        public Variable ()
        {
            super (Type.float64.zero);
        }

        /**
        * Sets the value of the variable using the supplied long value.
        * The new value is constructed from the supplied long value
        * using the type of the old value.
        * @param value the long value to be used to construct the new numeric
        * value.
        */
        public void setValue (long value)
            throws ValueException
        {
            setValue (getValue ().getType ().createValue (value));
        }

        /**
        * Sets the value of the variable using the supplied double value.
        * The new value is constructed from the supplied double value
        * using the type of the old value.
        * @param value the double value to be used to construct the new numeric
        * value.
        */
        public void setValue (double value)
            throws ValueException
        {
            setValue (getValue ().getType ().createValue (value));
        }
    }

    /**
    * Converts a Value array to a Pattern array.
    * @param values the array of values.
    * @return the array of patterns.
    */
    public static Pattern [] createPatternArray (Value [] values)
    {
        Numeric.Pattern [] patterns = new Numeric.Pattern [values.length];

        for (int i = 0 ; i < patterns.length ; ++i)
            patterns [i] = new Pattern (values [i]);

        return patterns;
    }

    /**
    * Converts a Numeric.Value array to a byte array.
    * @param a the array to be coenverted.
    * @return the converted array.
    */
    public static byte [] toByteArray (Numeric.Value [] a)
    {
        byte [] b = new byte [a.length];

        for (int i = 0 ; i < a.length ; ++i)
            b [i] = (byte) a [i].getLongValue ();

        return b;
    }

    /**
    * Converts a byte array to a Numeric.Value array.
    * @param a the array to be coenverted.
    * @return the converted array.
    */
    public static Numeric.Value [] fromByteArray (byte [] a)
    {
        Numeric.Value [] b = Type.uint8.createUndefArray (a.length);

        try
        {
            for (int i = 0 ; i < a.length ; ++i)
                b [i] = Type.uint8.createValue (a [i]);
        }
        catch (ValueException e)
        {
        }

        return b;
    }
}

