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

package com.lightcrafts.image.export;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.lightcrafts.image.types.ImageType;
import com.lightcrafts.utils.xml.XMLException;
import com.lightcrafts.utils.xml.XmlNode;

/**
 * An <code>ImageExportOptions</code> is a collection of options that can be
 * set for exporting an image to a particular image file format such as JPEG or
 * TIFF.
 *
 * @author Paul J. Lucas [paul@lightcrafts.com]
 */
public abstract class ImageExportOptions {

    public final OriginalWidthOption    originalWidth;
    public final OriginalHeightOption   originalHeight;

    /**
     * Checks this <code>ImageExportOptions</code> to another object for
     * equality.
     *
     * @param o The other object to compare to.
     * @return Returns <code>true</code> only if the other object is also an
     * <code>ImageExportOptions</code> and they both are for the same
     * {@link ImageType} and all of their options are equal.
     */
    public boolean equals( Object o ) {
        if ( o == null || !o.getClass().equals( getClass() ) )
            return false;
        final ImageExportOptions otherOpts = (ImageExportOptions)o;

        final File otherExportFile = otherOpts.getExportFile();
        if ( otherExportFile == null ) {
            if ( getExportFile() != null )
                return false;
        } else if ( !otherExportFile.equals( getExportFile() ) )
            return false;

        if ( !otherOpts.getImageType().equals( getImageType() ))
            return false;

        final Map<String,ImageExportOption> om = otherOpts.m_exportOptions;
        if ( om.size() != m_exportOptions.size() )
            return false;
        for ( Map.Entry<String,ImageExportOption> me : om.entrySet() ) {
            final ImageExportOption opt = m_exportOptions.get( me.getKey() );
            if ( opt == null || !opt.equals( me.getValue() ) )
                return false;
        }
        return true;
    }

    /**
     * Gets the auxiliary data that is to be or was used to export an image
     * file.
     *
     * @return Returns said data or <code>null</code> is no data was ever set.
     * @see #setAuxData(byte[])
     */
    public final byte[] getAuxData() {
        return m_auxData;
    }

    /**
     * Gets the {@link File} that is to be or was used to export an image file.
     *
     * @return Returns said file or <code>null</code> is no such file was ever
     * set.
     * @see #setExportFile(File)
     */
    public final File getExportFile() {
        return m_exportFile;
    }

    /**
     * Gets the {@link ImageType} these options are for.
     *
     * @return Returns said {@link ImageType}.
     */
    public final ImageType getImageType() {
        return m_imageType;
    }

    /**
     * Gets the integer value of an option assumed to be an
     * {@link IntegerExportOption}.
     *
     * @param className The name of the class of the option to get the value
     * of.
     * @return If this set of options contains the requested option, returns
     * its integer value; zero otherwise.
     * @throws ClassCastException if the requested option is not an
     * {@link IntegerExportOption}.
     * @see #getOption(String)
     * @see #setIntValueOf(String,int)
     */
    public final int getIntValueOf( String className ) {
        final IntegerExportOption option =
            (IntegerExportOption)getOption( fixClassName( className ) );
        return option != null ? option.getValue() : 0;
    }

    /**
     * Gets a given {@link ImageExportOption}.
     *
     * @param className The name of the class of the option.
     * @return Returns the {@link ImageExportOption} if this set of options
     * contains the requested option, <code>null</code> otherwise.
     * @see #getIntValueOf(String)
     */
    public final ImageExportOption getOption( String className ) {
        return m_exportOptions.get( fixClassName( className ) );
    }

    /**
     * Returns the hash code for this <code>ImageExportOptions</code>.
     *
     * @return Returns said hash code.
     */
    public final int hashCode() {
        return m_imageType.hashCode() + m_exportOptions.hashCode() + 1;
    }

    /**
     * Sets the auxiliary data that is to be or was used to export an image
     * file.
     *
     * @param data The data to use.
     * @see #getAuxData()
     */
    public final void setAuxData( byte[] data ) {
        m_auxData = data;
    }

    /**
     * Sets the {@link File} that is to be or was used to export an image file.
     *
     * @param file The {@link File} to use.
     * @see #getExportFile()
     */
    public final void setExportFile( File file ) {
        m_exportFile = file;
    }

    /**
     * Sets the integer value of an option assumed to be an
     * {@link IntegerExportOption}.
     *
     * @param className The name of the class of the option to set the value
     * of.
     * @param newValue The new value.
     * @throws ClassCastException if the requested option is not an
     * {@link IntegerExportOption}.
     * @throws IllegalArgumentException if there is no option having the given
     * class name.
     * @see #getIntValueOf(String)
     * @see #setValueOf(String,String)
     */
    public final void setIntValueOf( String className, int newValue ) {
        final IntegerExportOption option =
            (IntegerExportOption)getOption( fixClassName( className ) );
        if ( option == null )
            throw new IllegalArgumentException( className );
        option.setValue( newValue );
    }

    /**
     * Sets the value of an option.
     *
     * @param className The name of the class of the option to set the value
     * of.
     * @param newValue The new value.
     * @throws IllegalArgumentException if there is no option having the given
     * class name.
     * @see #setIntValueOf(String,int)
     */
    public final void setValueOf( String className, String newValue ) {
        final ImageExportOption option = getOption( className );
        if ( option == null )
            throw new IllegalArgumentException( className );
        option.setValue( newValue );
    }

    /**
     * Preserve this ImageExportOptions in XML.
     * @param node An XML context in which to save.
     */
    public void write( XmlNode node ) {
        node = node.addChild( ExportOptionsTag );
        node.setAttribute( TypeTag, m_imageType.getName() );
        save( node );
        if ( m_exportFile != null ) {
            node.setAttribute( FileTag, m_exportFile.getAbsolutePath() );
        }
    }

    /**
     * Instantiate a new ImageExportOptions from a preserved state.
     * @param node An XML context in which state was saved
     * @return The new ImageExportOptions
     * @throws XMLException If the given node has invalid content.
     */
    public static ImageExportOptions read( XmlNode node )
        throws XMLException
    {
        node = node.getChild( ExportOptionsTag );
        final String typeName = node.getAttribute( TypeTag );
        final ImageType type = ImageType.getImageTypeByName( typeName );
        if ( type == null ) {
            throw new XMLException( "Unrecognized image export type" );
        }
        final ImageExportOptions options = type.newExportOptions();
        options.restore( node );
        if ( node.hasAttribute( FileTag ) ) {
            options.m_exportFile = new File( node.getAttribute( FileTag ) );
        }
        return options;
    }

    /**
     * Read the state of this <code>ImageExportOption</code>.
     *
     * @param r The {@link ImageExportOptionReader} to read from.
     */
    public void readFrom( ImageExportOptionReader r ) throws IOException {
        originalWidth.readFrom( r );
        originalHeight.readFrom( r );
    }

    /**
     * Write the state of this <code>ImageExportOption</code>.
     *
     * @param w The {@link ImageExportOptionWriter} to read from.
     */
    public void writeTo( ImageExportOptionWriter w ) throws IOException {
        originalWidth.writeTo( w );
        originalHeight.writeTo( w );
    }

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

    /**
     * Construct an <code>ImageExportOptions</code>.
     *
     * @param imageType The {@link ImageType} these options are for.
     */
    protected ImageExportOptions( ImageType imageType ) {
        originalWidth   = new OriginalWidthOption( 0, this );
        originalHeight  = new OriginalHeightOption( 0, this );

        m_imageType = imageType;
    }

    /**
     * @deprecated
     */
    protected void save( XmlNode node ) {
        originalWidth.save( node );
        originalHeight.save( node );
    }

    /**
     * @deprecated
     */
    protected void restore( XmlNode node ) throws XMLException {
        originalWidth.restore( node );
        originalHeight.restore( node );
    }

    ////////// package ////////////////////////////////////////////////////////

    /**
     * Add an {@link ImageExportOption} to this set of options.
     *
     * @param option The {@link ImageExportOption} to add.
     */
    final void addOption( ImageExportOption option ) {
        m_exportOptions.put( option.getName(), option );
    }

    /**
     * An XML tag constant used in save() and restore().
     */
    final static String ValueTag = "value";

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

    /**
     * "Fix" a class name by ensuring it's both fully-qualified and ends with
     * "Option" since this is how class names are stored in the
     * {@link #m_exportOptions} map.
     *
     * @param className The name of the class to fix.
     * @return Returns the fixed class name.
     */
    private static String fixClassName( String className ) {
        if ( !className.startsWith( "com." ) )
            className = ImageExportOptions.class.getPackage().getName() + '.'
                        + className;
        if ( !className.endsWith( "Option" ) )
            className += "Option";
        return className;
    }

    /**
     * Auxiliary data that is to be or was used to export an image file.
     */
    private byte[] m_auxData;

    /**
     * The {@link File} that is to be or was used to export an image file.
     */
    private File m_exportFile;

    /**
     * All the options comprising this <code>ImageExportOptions</code>.
     * Each key is the fully-qualified name of a class derived from
     * {@link ImageExportOption} and each value is an instance of
     * {@link ImageExportOption}.
     */
    private final Map<String,ImageExportOption> m_exportOptions =
        new HashMap<String,ImageExportOption>();

    /**
     * The {@link ImageType} this instance of <code>ImageExportOptions</code>
     * is for.
     */
    private final ImageType m_imageType;

    /**
     * An XML tag constant used in save() and restore().
     */
    private final static String ExportOptionsTag = "ExportOptions";

    /**
     * An XML tag constant used in save() and restore().
     */
    private final static String TypeTag = "type";

    /**
     * An XML tag constant used in save() and restore().
     */
    private final static String FileTag = "file";
}
/* vim:set et sw=4 ts=4: */
