/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

/*
 * The org.opensourcephysics.media.gif package provides GIF services
 * including implementations of the Video and VideoRecorder interfaces.
 *
 * Copyright (c) 2004  Douglas Brown and Wolfgang Christian.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * For additional information and documentation on Open Source Physics,
 * please see <http://www.opensourcephysics.org/>.
 */
package org.opensourcephysics.media.core;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;
import javax.swing.filechooser.FileFilter;

import org.opensourcephysics.controls.XML;
import org.opensourcephysics.tools.ResourceLoader;

/**
 * This is an image video recorder that uses scratch files.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class ImageVideoRecorder extends ScratchVideoRecorder {
  // instance fields
  protected int frameCount;
	private String tempFileBasePath;
	private String tempFileType = "png"; //$NON-NLS-1$
	private String[] savedFilePaths;

  /**
   * Constructs a default ImageVideoRecorder object.
   */
  public ImageVideoRecorder() {
    super(new ImageVideoType());
  }

  /**
   * Constructs a ImageVideoRecorder object for a specific image type.
   */
  public ImageVideoRecorder(ImageVideoType type) {
    super(type);
    String ext = type.getDefaultExtension();
    if (ext!=null)
    	tempFileType = ext;
  }

  /**
   * Gets the video.
   *
   * @return the video
   * @throws IOException
   */
  public Video getVideo() throws IOException {
    if(isSaved && saveFile!=null) {
      return videoType.getVideo(saveFile.getAbsolutePath());
    }
    if (saveFile!=null) {
    	saveScratch();
    	if (savedFilePaths!=null && savedFilePaths.length>0) {
      	return new ImageVideo(savedFilePaths[0], savedFilePaths.length>1);    		
    	}
    }
    return null;
  }

  /**
   * Saves all video images to a numbered sequence of files.
   *
   * @param fileName the file name basis for images
   * @return the full path of the first image in the sequence
   * @throws IOException
   */
  public String saveVideo(String fileName) throws IOException {
    if(fileName==null) {
      return saveVideoAs();
    }
    setFileName(fileName);
    if(saveFile==null) {
      throw new IOException("Read-only file"); //$NON-NLS-1$
    }
    saveScratch();
    isSaved = true;
    return (savedFilePaths==null || savedFilePaths.length==0)? 
    		null : savedFilePaths[0];
  }
  
  /**
   * Sets the expected frame count.
   *
   * @param n the expected frame count
   */
  public void setExpectedFrameCount(int n) {
  	frameCount = n;
  }

  /**
   * Discards the current video and resets the recorder to a ready state.
   */
  @Override
  public void reset() {
  	frameCount = 0;
    deleteTempFiles();
    super.reset();
  }

  /**
   * Called by the garbage collector when this recorder is no longer in use.
   */
	@Override
  protected void finalize() {
  	reset();
  }
  

  //________________________________ protected methods _________________________________

  /**
   * Required by ScratchVideoRecorder, but unused.
   */
  protected void saveScratch() throws IOException {
		// get the FileFilter selected by the user
    FileFilter fileFilter = chooser.getFileFilter();
		if (!hasContent || !(fileFilter instanceof VideoFileFilter))
			return;
		
		// copy temp files or open and re-encode if needed
		synchronized (tempFiles) {
			String fileName = saveFile.getAbsolutePath();
			savedFilePaths = getFileNames(fileName, tempFiles.size());
			for (int i = 0; i < tempFiles.size(); i++) {
				String path = savedFilePaths[i];
				File tempFile = tempFiles.get(i);
				if (!tempFile.exists()) {
					savedFilePaths = null;
					throw new IOException("temp image file not found"); //$NON-NLS-1$
				}
				if (ext==tempFileType) {
					// copy images
					File targetFile = new File(path);
					VideoIO.copyFile(tempFile, targetFile);
				}
				else {
					// open and encode images in desired format
					BufferedImage image = ResourceLoader.getBufferedImage(tempFile.getAbsolutePath());
					if (image==null) {
						throw new IOException("unable to load temp image file"); //$NON-NLS-1$
					}
		      javax.imageio.ImageIO.write(image, ext, 
		      		new BufferedOutputStream(new FileOutputStream(path)));
				}
			}
		}
		deleteTempFiles();
		hasContent = false;
		canRecord = false;
  }

  /**
   * Starts the video recording process.
   *
   * @return true if video recording successfully started
   */
  protected boolean startRecording() {
    if(dim==null) {
      if(frameImage!=null) {
        dim = new Dimension(frameImage.getWidth(null), frameImage.getHeight(null));
      } else {
        return false;
      }
    }
		try {
			tempFileBasePath = XML.stripExtension(scratchFile.getAbsolutePath());
		} catch (Exception e) {
			return false;
		}
    return true;
  }

//  /**
//   * Appends a frame to the current video. Note: this creates a new
//   * BufferedImage each time a frame is appended and can use lots of
//   * memory in a hurry.
//   *
//   * @param image the image to append
//   * @return true if image successfully appended
//   */
//  protected boolean append(Image image) {
//    int w = image.getWidth(null);
//    int h = image.getHeight(null);
//    BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
//    Graphics2D g = bi.createGraphics();
//    g.drawImage(image, 0, 0, null);
//    images.add(bi);
//    return true;
//  }
  /**
   * Appends a frame to the current video by saving the image in a tempFile.
   *
   * @param image the image to append
   * @return true if image successfully appended
   */
	@Override
	protected boolean append(Image image) {
		int w = image.getWidth(null);
		int h = image.getHeight(null);
		if (dim==null) {
			dim = new Dimension(w, h);
		}
		// can't append images that are different size than first
		if (dim.width!=w || dim.height!=h)
			return false;
		// convert to BufferedImage if needed
		if (!(image instanceof BufferedImage)) {
			BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);			
			img.getGraphics().drawImage(image, 0, 0, null);
			image = img;
		}
		BufferedImage source = (BufferedImage)image;
		String fileName = tempFileBasePath+"_"+tempFiles.size()+".tmp"; //$NON-NLS-1$ //$NON-NLS-2$
    try {
			ImageIO.write(source, tempFileType, new BufferedOutputStream(
			    new FileOutputStream(fileName)));
		} catch (Exception e) {
			return false;
		}
		File imageFile = new File(fileName);
		if (imageFile.exists()) {
			synchronized (tempFiles) {
				tempFiles.add(imageFile);
			}
			imageFile.deleteOnExit();
		}
		return true;
	}

  /**
   * Return the file that will be saved if the specified file is selected.
   * This is needed by ImageVideoRecorder since it strips and/or appends digits
   * to the selected file name.
   *
   * @param file the file selected with the chooser
   * @return the file (or first file) to be saved
   */
  protected File getFileToBeSaved(File file) {
    // determine number of digits to append to file names
    int n = frameCount>0? frameCount: tempFiles.size();
    // if single or no image, return file itself
    if(n<=1) {
      return file;
    }
    String fileName = file.getAbsolutePath();
    // get appended number
    int i = getAppendedNumber(fileName);
    // get base
    String base = getBase(fileName);
    if(i>0) {
      fileName = base+((n+i<10) ? String.valueOf(i) : ((n+i<100)&&(i<10)) ? "0"+i : ( //$NON-NLS-1$
        n+i<100) ? String.valueOf(i) : ((n+i<1000)&&(i<10)) ? "00"+i : ((             //$NON-NLS-1$
          n+i<1000)&&(i<100)) ? "0"+i : (                                             //$NON-NLS-1$
            n+i<1000) ? String.valueOf(i) : (i<10) ? "000"+i : (                      //$NON-NLS-1$
              i<100) ? "00"+i : (                                                     //$NON-NLS-1$
                i<1000) ? "0"+i :                                                     //$NON-NLS-1$
                  String.valueOf(i));
    } else {
      fileName = base+((n<10) ? "0" : (                                               //$NON-NLS-1$
        n<100) ? "00" : (                                                             //$NON-NLS-1$
          n<1000) ? "000" :                                                           //$NON-NLS-1$
            "0000");                                                                  //$NON-NLS-1$
    }
    if (ext!=null)
    	fileName += "."+ext; //$NON-NLS-1$
    return new File(fileName);
  }

  /**
   * Saves images to a numbered sequence of jpg files.
   *
   * @param fileName the file name basis for images
   * @param images the images to save
   * @return the paths of the saved images
   * @throws IOException
   */
  protected static String[] saveImages(String fileName, BufferedImage[] images) throws IOException {    
  	String[] fileNames = getFileNames(fileName, images.length);
    for(int i = 0; i<images.length; i++) {
    	String next = fileNames[i];
      javax.imageio.ImageIO.write(images[i], ext, 
      		new BufferedOutputStream(new FileOutputStream(next)));
    }
    return fileNames;
  }
  
  protected static String[] getFileNames(String fileName, int length) {
    if(length==1) return new String[] {fileName};
    // determine number of digits to append to file names
    int k = getAppendedNumber(fileName);
    ArrayList<String> paths = new ArrayList<String>();
    int digits = (length+k<10) ? 1 : (length+k<100) ? 2 : (length+k<1000) ? 3 : 4;
    // get base
    String base = getBase(fileName);
    for(int i = 0; i<length; i++) {
      // append numbers and save images
      String num = String.valueOf(i+k);
      if((digits==2)&&(i+k<10)) {
        num = "0"+num;             //$NON-NLS-1$
      } else if((digits==3)&&(i+k<10)) {
        num = "00"+num;            //$NON-NLS-1$
      } else if((digits==3)&&(i+k<100)) {
        num = "0"+num;             //$NON-NLS-1$
      } else if((digits==4)&&(i+k<10)) {
        num = "000"+num;           //$NON-NLS-1$
      } else if((digits==4)&&(i+k<100)) {
        num = "00"+num;            //$NON-NLS-1$
      } else if((digits==4)&&(i+k<1000)) {
        num = "0"+num;             //$NON-NLS-1$
      }
      fileName = base+num+"."+ext; //$NON-NLS-1$
      paths.add(fileName);
    }
    return paths.toArray(new String[0]);  	
  }

  protected static String getBase(String path) {
    String base = XML.stripExtension(path);
    // strip off digits at end, if any
    int len = base.length();
    int digits = 1;
    for(; digits<len; digits++) {
      try {
        Integer.parseInt(base.substring(len-digits));
      } catch(NumberFormatException ex) {
        break;
      }
    }
    digits--; // failed at digits, so go back one
    if(digits==0) { // no number found
      return base;
    }
    return base.substring(0, len-digits);
  }

  protected static int getAppendedNumber(String path) {
    String base = XML.stripExtension(path);
    // look for appended number at end, if any
    int len = base.length();
    int digits = 1;
    int n = 0;
    for(; digits<len; digits++) {
      try {
        n = Integer.parseInt(base.substring(len-digits));
      } catch(NumberFormatException ex) {
        break;
      }
    }
    return n;
  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
