/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.image.metadata.values;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import com.lightcrafts.image.metadata.ImageMetadataDirectory;
import com.lightcrafts.image.metadata.ImageMetaType;
import com.lightcrafts.utils.LCArrays;
import com.lightcrafts.utils.Rational;

import static com.lightcrafts.image.metadata.ImageMetaType.META_SLONG;

/**
 * A <code>LongMetaValue</code> is-a {@link NumericMetaValue} for holding a
 * signed long (32-bit) metadata value.  Note that the number of bits in a
 * metadata long is not the same as that of a Java <code>long</code>.
 *
 * @author Paul J. Lucas [paul@lightcrafts.com]
 */
public class LongMetaValue extends NumericMetaValue {

    ////////// public /////////////////////////////////////////////////////////

    /**
     * Construct a <code>LongMetaValue</code>.
     * This constructor exists only for externalization.
     */
    public LongMetaValue() {
        // do nothing
    }

    /**
     * Construct a <code>LongMetaValue</code>.
     *
     * @param values The array of values.
     */
    public LongMetaValue( long... values ) {
        m_value = constrain( values );
    }

    /**
     * Construct a <code>LongMetaValue</code>.
     *
     * @param values The array of values.
     */
    public LongMetaValue( String... values ) {
        setValuesImpl( values );
    }

    /**
     * {@inheritDoc}
     */
    public final LongMetaValue clone() {
        final LongMetaValue copy = (LongMetaValue)super.clone();
        copy.m_value = m_value.clone();
        return copy;
    }

    /**
     * {@inheritDoc}
     */
    public final int compareTo( Object o ) {
        if ( o instanceof NumericMetaValue ) {
            final NumericMetaValue rightValue = (NumericMetaValue)o;
            final long leftLong = getLongValue();
            final long rightLong = rightValue.getLongValue();
            return (int)(leftLong - rightLong);
        }
        return super.compareTo( o );
    }

    /**
     * Compares this <code>ImageMetaValue</code> to a {@link String}.  The
     * string is first parsed either as a <code>long</code> or as a
     * {@link Rational} the converted to a <code>long</code>, then a numeric
     * comparison is done.
     *
     * @param s The {@link String} to compare to.
     * @return Returns a negative integer, zero, or a positive integer as this
     * <code>ImageMetaValue</code> is less than, equal to, or greater than the
     * <code>long</code> value of the string.  If the string can not be parsed
     * as either a <code>long</code> or a {@link Rational}, returns 1.
     * @see #compareTo(Object)
     */
    public final int compareTo( String s ) {
        try {
            final long rightLong;
            if ( s.indexOf( '/' ) > 0 )
                rightLong = Rational.parseRational( s ).longValue();
            else
                rightLong = Long.parseLong( s );
            final long leftLong = getLongValue();
            return (int)(leftLong - rightLong);
        }
        catch ( NumberFormatException e ) {
            return 1;
        }
    }

    /**
     * {@inheritDoc}
     */
    public final long getLongValue() {
        return getLongValueAt( 0 );
    }

    /**
     * Gets the <code>long</code> value at the given index.
     *
     * @param index The index to get the value for.
     * @return Returns said value.
     */
    public final long getLongValueAt( int index ) {
        return m_value[ index ];
    }

    /**
     * Gets the native <code>long</code> array value.
     *
     * @return Returns said array.
     */
    public final long[] getLongValues() {
        return m_value;
    }

    /**
     * Gets the type of this metadata value.
     *
     * @return Always returns <code>META_LONG</code>.
     */
    public ImageMetaType getType() {
        return META_SLONG;
    }

    /**
     * {@inheritDoc}
     */
    public final int getValueCount() {
        return m_value != null ? m_value.length : 0;
    }

    /**
     * {@inheritDoc}
     */
    public final boolean isLegalValue( String value ) {
        try {
            parseValue( value );
            return true;
        }
        catch ( NumberFormatException e ) {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public final void setLongValue( long newValue ) {
        setLongValueAt( newValue, 0 );
    }

    /**
     * Sets the <code>long</code> value at the given index.
     *
     * @param newValue The new value.
     * @param index The index to set the value of.
     */
    public final synchronized void setLongValueAt( long newValue, int index ) {
        checkIsEditable();
        if ( m_value == null )
            m_value = new long[ index + 1 ];
        else if ( index >= m_value.length )
            m_value = (long[])LCArrays.resize( m_value, index + 1 );
        m_value[ index ] = constrain( newValue );
        dirty();
    }

    /**
     * {@inheritDoc}
     */
    public void readExternal( ObjectInput in ) throws IOException {
        final int length = readHeader( in );
        m_value = new long[ length ];
        for ( int i = 0; i < length; ++i )
            m_value[i] = in.readLong();
    }

    /**
     * @serialData The header (see {@link #writeHeader(ObjectOutput)}) followed
     * by the <code>long</code> values.
     */
    public void writeExternal( ObjectOutput out ) throws IOException {
        writeHeader( out );
        for ( long value : m_value )
            out.writeLong( value );
    }

    ////////// protected //////////////////////////////////////////////////////

    /**
     * Parse and append a new value.
     *
     * @param newValue The new value.
     * @throws NumberFormatException if the {@link String} does not contain a
     * parsable long.
     */
    protected final void appendValueImpl( String newValue ) {
        final long newLong = parseValue( newValue );
        if ( m_value == null )
            m_value = new long[]{ newLong };
        else {
            m_value = (long[])LCArrays.resize( m_value, m_value.length + 1 );
            m_value[ m_value.length - 1 ] = newLong;
        }
    }

    /**
     * Constrain the value of a <code>long</code>.
     *
     * @param value The value to constrain.
     * @return Returns the constrained value.
     */
    protected long constrain( long value ) {
        return value;
    }

    /**
     * Gets the values as an array of {@link String}.
     *
     * @return Returns said array.
     */
    protected final String[] getValuesImpl() {
        if ( m_value == null )
            return null;
        final String[] value = new String[ m_value.length ];
        for ( int i = 0; i < m_value.length; ++i )
            value[i] = Long.toString( getLongValueAt(i) );
        return value;
    }

    /**
     * Parse a signed long from a {@link String}.
     *
     * @param newValue The {@link String} to parse.
     * @return Returns an signed integer in the range -2^32 to (2^32)-1.
     * @throws NumberFormatException if the {@link String} does not contain a
     * parsable long.
     */
    protected long parseValue( String newValue ) {
        return Long.parseLong( newValue );
    }

    /**
     * Parse and set the values.
     *
     * @param newValue The array of new values.
     * @throws NumberFormatException if any one of the {@link String}s do not
     * contain a parsable long.
     */
    protected final void setValuesImpl( String[] newValue ) {
        if ( m_value == null || m_value.length != newValue.length )
            m_value = new long[ newValue.length ];
        for ( int i = 0; i < newValue.length; ++i )
            m_value[i] = parseValue( newValue[i] );
    }

    /**
     * Convert this value to its {@link String} representation.  Multiple
     * values are separated by commas.
     *
     * @return Returns said string.
     */
    protected final String toStringImpl() {
        if ( m_value == null )
            return null;
        final ImageMetadataDirectory owningDir = getOwningDirectory();
        final int tagID = getOwningTagID();
        final StringBuilder sb = new StringBuilder();
        boolean comma = false;
        for ( long value : m_value ) {
            if ( !comma )
                comma = true;
            else
                sb.append( ',' );
            final String valueAsString = owningDir != null ?
                owningDir.getTagValueLabelFor( tagID, value ) :
                Long.toString( value );
            sb.append( valueAsString );
        }
        return sb.toString();
    }

    ////////// protected //////////////////////////////////////////////////////

    protected long[] m_value;

    ////////// private ////////////////////////////////////////////////////////

    /**
     * Constrain the values of the elements of a <code>long</code> array.
     *
     * @param values The values to trim.
     * @return Returns a new array with constrained values.
     */
    private long[] constrain( long[] values ) {
        final long[] newValues = new long[ values.length ];
        for ( int i = 0; i < values.length; ++i )
            newValues[i] = constrain( values[i] );
        return newValues;
    }


}
/* vim:set et sw=4 ts=4: */
