// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2005  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.util.settings;

import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import java.util.prefs.Preferences;

/**
 *  The Java printing interface is cluttered and fails to fulfill all wishes.
 *  This is a try at a class which stores the print settings in the
 *  preferences so they are restored on the next read.
 *  <p>
 *  Because of the uninspired interface of the <tt>javax.print.attribute.EnumSyntax</tt> class
 *  it is necessary that several inner classes extend specific <tt>EnumSyntax</tt>
 *  classes from the <tt>javax.print.attribute.standard</tt> package here to get
 *  access to their enum values. This allows to access all values and be save for
 *  future changes. But some of the <tt>EnumSyntax</tt> are declared final,
 *  in which case we have to copy their values to access them and hope that
 *  they will never be extended.
 *
 *  @author <a href="mailto:rammi@caff.de">Rammi</a>
 *  @version $Revision$
 */
public class PrintRequestProperties
  extends HashPrintRequestAttributeSet
{
  /** The extension used for the class of an attribute. */
  public static final String PREF_KEY_EXT_CLASS = ".class";
  /** Preferences key for media. */
  public static final String PREF_KEY_MEDIA = "print.media";
  /** Accessor classes for media. */
  public static final EnumAttributeFinder[] MEDIA_FINDER = {
          new MediaSizeNameHelper(),
          new MediaNameHelper(),
          new MediaTrayHelper()
  };

  /** Preferences key for media size x. */
  public static final String PREF_KEY_MEDIA_PRINTABLE_AREA_X      = "print.area.x";
  /** Preferences key for media size y. */
  public static final String PREF_KEY_MEDIA_PRINTABLE_AREA_Y      = "print.area.y";
  /** Preferences key for media size width. */
  public static final String PREF_KEY_MEDIA_PRINTABLE_AREA_WIDTH  = "print.area.w";
  /** Preferences key for media size height. */
  public static final String PREF_KEY_MEDIA_PRINTABLE_AREA_HEIGHT = "print.area.h";

  /** Preference key for job sheets. */
  public static final String PREF_KEY_JOB_SHEETS = "print.jobsheets";
  /** Accessor class for job sheets. */
  public static final EnumAttributeFinder JOB_SHEETS_FINDER = new JobSheetsHelper();

  /** Preference key for chromaticity. */
  public static final String PREF_KEY_CHROMATICITY = "print.chromaticity";
  /**
   * Possible values for chromaticity because some clever guy made the class final
   * so we cannot use our standard method on accessing enum values.
   */
  public static final Chromaticity[] CHROMATICITY_VALUES = {
    Chromaticity.MONOCHROME,
    Chromaticity.COLOR
  };

  /** Preference key for orientation. */
  public static final String PREF_KEY_ORIENTATION = "print.orientation";
  /**
   * Possible values for orientation because some clever guy made the class final
   * so we cannot use our standard method on accessing enum values.
   */
  public static final OrientationRequested[] ORIENTATION_VALUES = {
          OrientationRequested.PORTRAIT,
          OrientationRequested.LANDSCAPE,
          OrientationRequested.REVERSE_LANDSCAPE,
          OrientationRequested.REVERSE_PORTRAIT
  };

  /**
   * Construct a new, empty print request attribute set.
   */
  public PrintRequestProperties()
  {
  }

  /**
   *  Construct a new print request attribute set,
   *  trying to restore it from the preferences.
   *  @param preferences preferences object to restore from
   *  @see #loadFrom(java.util.prefs.Preferences)
   */
  public PrintRequestProperties(Preferences preferences)
  {
    loadFrom(preferences);
  }

  /**
   * Construct a new print request attribute set,
   * initially populated with the given value.
   *
   * @param attribute Attribute value to add to the set.
   * @throws NullPointerException (unchecked exception) Thrown if <CODE>attribute</CODE> is null.
   */
  public PrintRequestProperties(PrintRequestAttribute attribute)
  {
    super(attribute);
  }

  /**
   * Construct a new print request attribute set, initially populated with
   * the values from the given array. The new attribute set is populated
   * by adding the elements of <CODE>attributes</CODE> array to the set in
   * sequence, starting at index 0. Thus, later array elements may replace
   * earlier array elements if the array contains duplicate attribute
   * values or attribute categories.
   *
   * @param attributes Array of attribute values to add to the set.
   *                   If null, an empty attribute set is constructed.
   * @throws NullPointerException (unchecked exception)
   *                              Thrown if any element of <CODE>attributes</CODE> is null.
   */
  public PrintRequestProperties(PrintRequestAttribute[] attributes)
  {
    super(attributes);
  }

  /**
   * Construct a new attribute set, initially populated with the
   * values from the  given set where the members of the attribute set
   * are restricted to the <code>(PrintRequestAttributeSe</code> interface.
   *
   * @param attributes set of attribute values to initialise the set. If
   *                   null, an empty attribute set is constructed.
   * @throws ClassCastException (unchecked exception) Thrown if any element of
   *                            <CODE>attributes</CODE> is not an instance of
   *                            <CODE>(PrintRequestAttributeSe</CODE>.
   */
  public PrintRequestProperties(PrintRequestAttributeSet attributes)
  {
    super(attributes);
  }

  /**
   *  Save some of the preferences to the preferences.
   *  The interface is too cluttered to store all
   *  settings without getting headaches.
   *  @param preferences preferences to store the settings
   *  @see  #loadFrom(java.util.prefs.Preferences)
   */
  public void storeTo(Preferences preferences)
  {
    storeEnumValue(preferences, PREF_KEY_MEDIA, Media.class);
    storeEnumValue(preferences, PREF_KEY_JOB_SHEETS, JobSheets.class);
    storeEnumValue(preferences, PREF_KEY_ORIENTATION, OrientationRequested.class);

    MediaPrintableArea area = (MediaPrintableArea)get(MediaPrintableArea.class);
    if (area != null) {
      preferences.putFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_X,
                           area.getX(MediaPrintableArea.MM));
      preferences.putFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_Y,
                           area.getY(MediaPrintableArea.MM));
      preferences.putFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_WIDTH,
                           area.getWidth(MediaPrintableArea.MM));
      preferences.putFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_HEIGHT,
                           area.getHeight(MediaPrintableArea.MM));
    }
    else {
      preferences.remove(PREF_KEY_MEDIA_PRINTABLE_AREA_X);
      preferences.remove(PREF_KEY_MEDIA_PRINTABLE_AREA_Y);
      preferences.remove(PREF_KEY_MEDIA_PRINTABLE_AREA_WIDTH);
      preferences.remove(PREF_KEY_MEDIA_PRINTABLE_AREA_HEIGHT);
    }

    Chromaticity chroma = (Chromaticity)get(Chromaticity.class);
    if (chroma != null) {
      preferences.putInt(PREF_KEY_CHROMATICITY, chroma.getValue());
    }
    else {
      preferences.remove(PREF_KEY_CHROMATICITY);
    }
  }

  /**
   *  Load some of the preferences from the preferences.
   *  The interface is too cluttered to store all
   *  settings without getting headaches.
   *  @param preferences preferences to load the settings
   *  @see #storeTo(java.util.prefs.Preferences)
   */
  public void loadFrom(Preferences preferences)
  {
    loadEnumValue(preferences, PREF_KEY_MEDIA, MEDIA_FINDER);
    loadEnumValue(preferences, PREF_KEY_JOB_SHEETS, JOB_SHEETS_FINDER);

    loadFinalValue(preferences, PREF_KEY_ORIENTATION, ORIENTATION_VALUES, ORIENTATION_VALUES);
    loadFinalValue(preferences, PREF_KEY_CHROMATICITY, CHROMATICITY_VALUES, CHROMATICITY_VALUES);

    float x = preferences.getFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_X,
                                   Float.NEGATIVE_INFINITY);
    float y = preferences.getFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_Y,
                                   Float.NEGATIVE_INFINITY);
    float w = preferences.getFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_WIDTH,
                                   Float.NEGATIVE_INFINITY);
    float h = preferences.getFloat(PREF_KEY_MEDIA_PRINTABLE_AREA_HEIGHT,
                                   Float.NEGATIVE_INFINITY);
    if (x != Float.NEGATIVE_INFINITY  &&
            y != Float.NEGATIVE_INFINITY  &&
            w != Float.NEGATIVE_INFINITY  &&
            h != Float.NEGATIVE_INFINITY) {
      add(new MediaPrintableArea(x, y, w, h, MediaPrintableArea.MM));
    }
  }

  /**
   *  Load a value from a fixed set of values.
   *  @param preferences  preferences to load the settings
   *  @param key          preference key
   *  @param values       possible values
   *  @param attributes   possible attributes, sorted the same way as the values, usually exactly the same array
   */
  private void loadFinalValue(Preferences preferences, String key, EnumSyntax[] values, PrintRequestAttribute[] attributes)
  {
    int v = preferences.getInt(key, Integer.MIN_VALUE);
    if (v != Integer.MIN_VALUE) {
      for (int c = 0;   c < values.length;  ++c) {
        if (values[c].getValue() == v) {
          add(attributes[c]);
          break;
        }
      }
    }
  }

  /**
   *  Store an enum value in the preferences.
   *  @param preferences preferences to store to
   *  @param prefkey     preferences key
   *  @param key         attribute key
   */
  private void storeEnumValue(Preferences preferences,
                              String prefkey,
                              Class key)
  {
    EnumSyntax enumSyntax = (EnumSyntax)get(key);
    if (enumSyntax != null) {
      preferences.put(prefkey+PREF_KEY_EXT_CLASS, enumSyntax.getClass().toString());
      preferences.putInt(prefkey, enumSyntax.getValue());
    }
    else {
      preferences.remove(prefkey+PREF_KEY_EXT_CLASS);
      preferences.remove(prefkey);
    }
  }

  /**
   *  Load an enum value from the preferences.
   *  @param preferences preferences to read from
   *  @param key         preferences key
   *  @param finder      finder class to get the enum value
   *  @return <code>true</code> if the attribute was restored,
   *          <code>false</code> otherwise
   *  @see #loadEnumValue(java.util.prefs.Preferences, String, de.caff.util.settings.PrintRequestProperties.EnumAttributeFinder[])
   */
  private boolean loadEnumValue(Preferences preferences,
                                String key,
                                EnumAttributeFinder finder)
  {
    return loadEnumValue(preferences, key, new EnumAttributeFinder[] { finder });
  }

  /**
   *  Load an enum value from the preferences.
   *  @param preferences preferences to read from
   *  @param key         preferences key
   *  @param finder      finder classes to get the enum value
   *  @return <code>true</code> if the attribute was restored,
   *          <code>false</code> otherwise
   */
  private boolean loadEnumValue(Preferences preferences,
                                String key,
                                EnumAttributeFinder[] finder)
  {
    String classname = preferences.get(key+PREF_KEY_EXT_CLASS, null);
    if (classname != null) {
      for (int f = 0;  f < finder.length;  ++f) {
        if (classname.equals(finder[f].getBasicClassname())) {
          int value = preferences.getInt(key, Integer.MIN_VALUE);
          if (value != Integer.MIN_VALUE) {
            Attribute enumValue = finder[f].getEnumValue(value);
            if (enumValue != null) {
              add(enumValue);
              return true;
            }
          }
        }
      }
    }
    return false;
  }

  /**
   *  Interface for finders of enum values.
   */
  private static interface EnumAttributeFinder
          extends Attribute
  {
    /**
     *  Get the class name of the overridden class.
     *  @return class name
     */
    public String getBasicClassname();

    /**
     *  Get the enum value corresponding to a given integer value.
     *  @param value integer value
     *  @return the enum value with this given integer value
     *          or <code>null</code> if no match is found
     */
    public Attribute getEnumValue(int value);
  }

  /**
   *  Ugly stuff to access the media size name attribute.
   */
  private static class MediaSizeNameHelper
    extends MediaSizeName
    implements EnumAttributeFinder
  {
    /**
     *  Constructor.
     */
    public MediaSizeNameHelper()
    {
      super(Integer.MIN_VALUE);
    }

    /**
     * Get the class name of the overridden class.
     *
     * @return class name
     */
    public String getBasicClassname()
    {
      return MediaSizeName.class.toString();
    }

    /**
     * Get the enum value corresponding to a given integer value.
     *
     * @param value integer value
     * @return the enum value with this given integer value
     *         or <code>null</code> if no match is found
     */
    public Attribute getEnumValue(int value)
    {
      return getMedia(value);
    }

    /**
     *  Get the media corresponding to a given enum value.
     *  @param value enum value
     *  @return the media with this value or <code>null</code> if no match is found
     */
    public Media getMedia(int value)
    {
      EnumSyntax[] table = getEnumValueTable();
      for (int t = 0;  t < table.length;  ++t) {
        if (table[t].getValue() == value) {
          return (Media)table[t];
        }
      }
      return null;
    }

    /**
     * Returns a clone of this enumeration value, which to preserve the
     * semantics of enumeration values is the same object as this enumeration
     * value.
     */
    public Object clone()
    {
      return this;
    }
  }
  /**
   *  Ugly stuff to access the media name attribute.
   */
  private static class MediaNameHelper
    extends MediaName
    implements EnumAttributeFinder
  {
    /**
     *  Constructor.
     */
    public MediaNameHelper()
    {
      super(Integer.MIN_VALUE);
    }

    /**
     * Get the class name of the overridden class.
     *
     * @return class name
     */
    public String getBasicClassname()
    {
      return MediaName.class.toString();
    }

    /**
     * Get the enum value corresponding to a given integer value.
     *
     * @param value integer value
     * @return the enum value with this given integer value
     *         or <code>null</code> if no match is found
     */
    public Attribute getEnumValue(int value)
    {
      return getMedia(value);
    }

    /**
     *  Get the media corresponding to a given enum value.
     *  @param value enum value
     *  @return the media with this value or <code>null</code> if no match is found
     */
    public Media getMedia(int value)
    {
      EnumSyntax[] table = getEnumValueTable();
      for (int t = 0;  t < table.length;  ++t) {
        if (table[t].getValue() == value) {
          return (Media)table[t];
        }
      }
      return null;
    }

    /**
     * Returns a clone of this enumeration value, which to preserve the
     * semantics of enumeration values is the same object as this enumeration
     * value.
     */
    public Object clone()
    {
      return this;
    }
  }
  /**
   *  Ugly stuff to access the media tray attribute.
   */
  private static class MediaTrayHelper
    extends MediaTray
    implements EnumAttributeFinder
  {
    /**
     *  Constructor.
     */
    public MediaTrayHelper()
    {
      super(Integer.MIN_VALUE);
    }

    /**
     * Get the class name of the overridden class.
     *
     * @return class name
     */
    public String getBasicClassname()
    {
      return MediaTray.class.toString();
    }

    /**
     * Get the enum value corresponding to a given integer value.
     *
     * @param value integer value
     * @return the enum value with this given integer value
     *         or <code>null</code> if no match is found
     */
    public Attribute getEnumValue(int value)
    {
      return getMedia(value);
    }

    /**
     *  Get the media corresponding to a given enum value.
     *  @param value enum value
     *  @return the media with this value or <code>null</code> if no match is found
     */
    public Media getMedia(int value)
    {
      EnumSyntax[] table = getEnumValueTable();
      for (int t = 0;  t < table.length;  ++t) {
        if (table[t].getValue() == value) {
          return (Media)table[t];
        }
      }
      return null;
    }

    /**
     * Returns a clone of this enumeration value, which to preserve the
     * semantics of enumeration values is the same object as this enumeration
     * value.
     */
    public Object clone()
    {
      return this;
    }
  }

  /**
   *  Ugly stuff to access the job sheets attribute.
   */
  private static class JobSheetsHelper
    extends JobSheets
    implements EnumAttributeFinder
  {
    /**
     *  Constructor.
     */
    public JobSheetsHelper()
    {
      super(Integer.MIN_VALUE);
    }

    /**
     * Get the class name of the overridden class.
     *
     * @return class name
     */
    public String getBasicClassname()
    {
      return JobSheets.class.toString();
    }

    /**
     * Get the enum value corresponding to a given integer value.
     *
     * @param value integer value
     * @return the enum value with this given integer value
     *         or <code>null</code> if no match is found
     */
    public Attribute getEnumValue(int value)
    {
      EnumSyntax[] table = getEnumValueTable();
      for (int t = 0;  t < table.length;  ++t) {
        if (table[t].getValue() == value) {
          return (Attribute)table[t];
        }
      }
      return null;
    }

    /**
     * Returns a clone of this enumeration value, which to preserve the
     * semantics of enumeration values is the same object as this enumeration
     * value.
     */
    public Object clone()
    {
      return this;
    }
  }

}
