package nxm.sys.libg;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import javax.imageio.ImageIO;

import nxm.sys.lib.MidasException;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;

/** This class provides utility methods designed to help in exporting graphical component to an image.

    @since   NeXtMidas 1.7.5
    @author  J. Czechowski IV
*/
public class ExportImage extends Image {
  private static final String[] SUGGESTED_TYPES = { "png", "jpeg", "jpg", "gif" }; // Suggested types (in order)
  private static final String   DEFAULT_TYPE    = SUGGESTED_TYPES[0];

  /** This is the underlying image that is being created. */
  protected BufferedImage image;

  /** This will create a new imager to use to create an image.
      This will use the default image type of {@link BufferedImage#TYPE_INT_RGB}.
    @param width  The width of the image.
    @param height The height of the image.
  */
  public ExportImage(int width, int height) {
    this(width, height, BufferedImage.TYPE_INT_RGB);
  }

  /** This will create a new imager to use to create an image.
    @param width     The width of the image.
    @param height    The height of the image.
    @param imageType The type of image to create (see {@link BufferedImage#BufferedImage}).
  */
  public ExportImage(int width, int height, int imageType) {
    this.image = new BufferedImage(width, height, imageType);
  }

  /** Gets the underlying {@link BufferedImage} that is represented by this class.
      @return The image that is the core image of this class.
  */
  public BufferedImage getBufferedImage() {
    return this.image;
  }

  /** This will get the list of the file types that can be written to. Any of the elements of this
      array would be a valid <code>fileType</code> to pass to {@link #writeToFile(String,String)}.
      @return The file types that can be written to.
      @see #writeToFile(String,String)
  */
  public static String[] getFileTypes() {
    return getFileTypes(false);
  }

  /** This will get the list of the file types that can be written to. Any of
      the elements of this array would be a valid <code>fileType</code> to
      pass to {@link #writeToFile(String,String)}.
      @param lowercaseOnly Only include lower-case versions of names. When this is false, it is
                           common to see many names that differ only in case (e.g. "JPG" vs "jpg").
      @return The file types that can be written to.
      @see #writeToFile(String,String)
      @since NeXtMidas 2.5.5
  */
  public static String[] getFileTypes (boolean lowercaseOnly) {
    String[] types = ImageIO.getWriterFormatNames();
    if (lowercaseOnly) {
      ArrayList<String> lower = new ArrayList<String>();
      for (String ty : types) {
        String lc = ty.toLowerCase();

        if (ty.equals(lc)) lower.add(ty);
      }
      types = lower.toArray(StringUtil.EMPTY_STRING_ARRAY);
    }
    return types;
  }

  /** This will get a suggest a type to use.
      @return A suggested type from {@link #getFileTypes()} or null if none found.
      @since NeXtMidas 2.5.5
   */
  public static String getSuggestedType() {
    String[] types = getFileTypes();
    Arrays.sort(types);

    for (String suggestedType : SUGGESTED_TYPES) {
      if (Arrays.binarySearch(types, suggestedType) >= 0) return suggestedType;
    }
    return (types.length == 0)? null : types[0];
  }


  /** This will write the image to a file. The extension of the file is taken from the filename.
      If there is no extension on the filename, the default is used from {@link #DEFAULT_TYPE}.
      @param fileName The name of the output file.
      @throws IOException If there is an error in writing the file.
      @see #getFileTypes()
   */
  public void writeToFile (String fileName) throws IOException {

    if (fileName.indexOf(".") < 0) {
      Shell.warning("ExportImage: No file type or extension provided, assuming default of "+DEFAULT_TYPE);
    }

    writeToFile(fileName, DEFAULT_TYPE);
  }

  /** This will write the image to a file.<br>
      Note 1: If an extension is desired on the file, that extension must be already added to the file name given.<br>
      Note 2: If the given file type does not match the extension provided on the file name,
              the file type will override the extension on the name, since it is explicit.
      @param fileName The name of the output file.
      @param fileType The file type to use for the image file (this is usually
                      the same as the extension of the fileName).
      @throws IOException If there is an error in writing the file.
      @see #getFileTypes()
  */
  public void writeToFile(String fileName, String fileType) throws IOException {

    if (fileName.indexOf(".") < 0) {} // do nothing (allow for intentional "blank" extension
    else {
      String ext = fileName.substring(fileName.indexOf("."));
      if (!ext.substring(1).equalsIgnoreCase(fileType)) {
        // remove extension from filename and use passed file type
        Shell.warning("Extension in file name ("+ext+") does not match given file type ("
            +fileType+"). Using "+fileType);
      }
    }

    if (!ImageIO.write(this.image, fileType, new File(fileName))) {
      throw new MidasException("Could not find image writer for " + "file type (" + fileType + ").");
    }
  }

  /** This will export a Component to an image file. For example, to create a
      <code>.jpeg</code> file from an existing {@link javax.swing.JButton},
      named <code>okButton</code>, the following would be used:
      <pre>
          exportToFile(okButton, "button.jpeg", "jpeg")
      </pre>
      Note: <i>This method may fail to export the entire image if the
            {@link Component} uses complex paint methods.</i>

      @param src      The component to make an image of.
      @param fileName The name of the output file.
      @param fileType The file type to use for the image file (this is usually
                      the same as the extension of the fileName).

      @throws IOException If there is an error in writing the file.
      @see #writeToFile(String,String)
      @see #getFileTypes()
      @see #export(Component)
  */
  public static void exportToFile (Component src, String fileName, String fileType) throws IOException {
    export(src).writeToFile(fileName, fileType);
  }

  /** This will export a Component to an image. For example, to create an image from an existing
      {@link javax.swing.JButton}, named <code>okButton</code>, the following would be used:
      <pre>
        ExportImage image = ExportImage.export(okButton);
      </pre>
      Note: <i>This method may fail to export the entire image if the {@link Component} uses complex paint methods.</i>
    @param src The component to make an image of.
    @return the exported ExportImage
  */
  public static ExportImage export (Component src) {
    ExportImage compImgr = new ExportImage(src.getWidth(), src.getHeight());
    src.print(compImgr.getGraphics());
    return compImgr;
  }

  public void flush() {
    this.image.flush();
  }

  public Graphics getGraphics() {
    return this.image.getGraphics();
  }

  public int getHeight(ImageObserver observer) {
    return this.image.getHeight(observer);
  }

  public Object getProperty(String name, ImageObserver observer) {
    return this.image.getProperty(name, observer);
  }

  public ImageProducer getSource() {
    return this.image.getSource();
  }

  public int getWidth(ImageObserver observer) {
    return this.image.getWidth(observer);
  }

}
