package jp.co.sint.tools;

import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.imageio.ImageIO;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
//import com.sun.image.codec.jpeg.JPEGImageDecoder;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class SIBGPhotoTool {
  
  private File changeImageFile;
  private File outputImageFile;
  private double width;
  private double height;
  private double max_bytesize;
  
  /**
   * <b>SIBGPhotoTool</b>
   * コンストラクタ
   * @param なし
   * @return なし
   * @throws なし
   */
  public SIBGPhotoTool(){}
  
  public SIBGPhotoTool(File changeImageFile, File outputImageFile,double width, double height, double max_bytesize){
    this.changeImageFile = changeImageFile;
    this.outputImageFile = outputImageFile;
    this.width = width;
    this.height = height;
    this.max_bytesize = max_bytesize;
  }
  
  public void execute() throws FileNotFoundException {
    InputStream inputStream = new FileInputStream(changeImageFile);
    
    byte[] byteData = getBytes(inputStream);
    if (byteData == null) return;
    
    double scale = getScale(byteData, width, height);
    if (scale == 0) return;
    
    byteData = recreateImage(byteData, scale);
    try {inputStream.close();} catch (IOException e) {e.printStackTrace();}
    if (byteData == null) return;
    
    if (max_bytesize != 0) {
      byteData = miniImage(byteData, max_bytesize);
    }
    inputStream = new ByteArrayInputStream(byteData);
    OutputStream os = null;
    try {
      os = new BufferedOutputStream(new FileOutputStream(outputImageFile));
      int c;
      while ((c = inputStream.read()) != -1) os.write(c);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (os != null) {
        try {
          os.flush();
          os.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      
      if (inputStream != null) {
        try {inputStream.close();} catch (IOException e) {e.printStackTrace();}
      }
    }
  }
  
  /**
   * ファイルサイズを小さくする
   * 
   * @param byteData
   * @param max_bytesize
   * @return ファイルサイズが小さくなった画像
   */
  private byte[] miniImage(byte[] byteData,double max_bytesize) {
    if (byteData == null)return null;
    
    ByteArrayInputStream input = new ByteArrayInputStream(byteData);
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    try {
      BufferedImage image = ImageIO.read(input);
      JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
      
      JPEGEncodeParam encodeParam = encoder.getDefaultJPEGEncodeParam(image);
      
      //boolean widthCheck = false;
      //boolean heightCheck = false;
      //JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(input);
      
      float sizeValue = new Float(max_bytesize).floatValue() / byteData.length;
      
      encodeParam.setQuality(sizeValue, false);
      encoder.encode(image, encodeParam);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        input.close();
        output.flush();
        output.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    
    byte[] byteDataResult = null;
    if (output.size() > 0) byteDataResult = output.toByteArray();
    
    return byteDataResult;
  }
  
  /**
   * 倍率にそって、画像を変換する
   * 
   * @param byteData
   * @param scale
   * @return 変換後の画像
   */
  private byte[] recreateImage(byte[] byteData, double scale) {
    ByteArrayInputStream inputStream = new ByteArrayInputStream(byteData);
    try {
      BufferedImage image = ImageIO.read(inputStream);
      
      //AffineTransformOp atOp = new AffineTransformOp(AffineTransform.getScaleInstance(scale, scale), null);
      
      BufferedImage imageNew = new BufferedImage((int) (image.getWidth() * scale),(int) (image.getHeight() * scale), image.getType());
      
      //atOp.filter(image, imageNew);
      
      imageNew.getGraphics().drawImage(image.getScaledInstance((int) (image.getWidth() * scale), (int) (image.getHeight() * scale), java.awt.Image.SCALE_AREA_AVERAGING)
          ,0, 0, (int) (image.getWidth() * scale), (int) (image.getHeight() * scale), null);
      
      ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
      
      try {
        ImageIO.write(imageNew, "jpg", byteOutput);
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        try {
          byteOutput.flush();
          byteOutput.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      
      if (byteOutput.size() > 0) byteData = byteOutput.toByteArray();
    } catch (IOException e) {
      e.printStackTrace();
      byteData = null;
    }
    
    return byteData;
  }
  
  /**
   * InputStreamをバイト配列に変換する
   * 
   * @param is
   * @return バイト配列
   */
  private byte[] getBytes(InputStream is) {
    ByteArrayOutputStream b = new ByteArrayOutputStream();
    OutputStream os = new BufferedOutputStream(b);
    int c;
    try {
      while ((c = is.read()) != -1) {
        os.write(c);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (os != null) {
        try {
          os.flush();
          os.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    return b.toByteArray();
  }
  
  /**
   * 元画像と出力画像の倍率を取得
   * 200X200の画像を100X100に変換しようとする場合、
   * 倍率は「0.5」。
   * 
   * @param byteData
   * @param width
   * @param height
   * @param max_bytesize
   * @return 変換前と変換後の倍率
   */
  private double getScale(byte[] byteData, double width, double height) {
    double doubleValueWidth = 0;
    double doubleValueHeight = 0;
    
    ByteArrayInputStream inputStream = new ByteArrayInputStream(byteData);
    try {
      BufferedImage image = ImageIO.read(inputStream);
      
      doubleValueWidth = width / image.getWidth();
      if (doubleValueWidth > 1) doubleValueWidth = 1;
      
      doubleValueHeight = height / image.getHeight();
      if (doubleValueHeight > 1) doubleValueHeight = 1;
    } catch (IOException e) {
      e.printStackTrace();
    }
    
    if (doubleValueHeight < doubleValueWidth) return doubleValueHeight;
    else return doubleValueWidth;
  }
}