/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.util;

// import java.awt.color.ColorSpace;
// import java.awt.color.ICC_ColorSpace;
// import java.awt.color.ICC_Profile;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
// import java.awt.image.ColorConvertOp;
// import java.io.File;
// import java.io.IOException;
// import java.io.InputStream;
// import java.util.Locale;
// import java.util.Arrays;
// import javax.media.jai.JAI;

// import javax.imageio.ImageIO;
// import javax.imageio.IIOException;

// import com.sun.media.jai.codec.FileSeekableStream;
// import com.sun.media.jai.util.SimpleCMYKColorSpace;

/**
 * ImageResizer は、画像ファイルのリサイズを行うためのクラスです。
 * ここでの使い方は、初期化時に、オリジナルの画像ファイルを指定し、
 * 変換時に各縮小方法に対応したメソッドを呼び出し、画像を変換します。
 * 変換方法としては、以下の3つがあります。
 * ①最大サイズ(px)指定による変換
 *   縦横の最大サイズ(px)を指定し、変換を行います。
 *   横長の画像については、変換後の横幅=最大サイズとなり、縦幅については、横幅の
 *   縮小率に従って決定されます。
 *   逆に縦長の画像については、変換後の縦幅=最大サイズとなり、横幅については、縦幅の
 *   縮小率に従って決定されます。
 * ②縦横サイズ(px)指定による変換
 *   縦横の変換後のサイズ(px)を個別に指定し、変換を行います。
 * ③縮小率指定による変換
 *   "1"を元サイズとする縮小率を指定し、変換を行います。
 *   縮小率は、縦横で同じ縮小率が適用されます。
 * 入力フォーマットとしてはJPEG/PNG/GIFに、出力フォーマットとしてはJPEG/PNGに対応しています。
 * 出力フォーマットについては、出力ファイル名の拡張子より自動的に決定されますが、一般的には
 * サイズが小さくなるjpegファイルを推奨します。
 * 入出力フォーマットについて、対応していないフォーマットが指定された場合は例外が発生します。
 * また、縦横の出力サイズが入力サイズの縦横よりも両方大きい場合、変換は行われず、入力ファイルが
 * そのままコピーされて出力されます。(拡大変換は行われません)
 *
 * @og.rev 6.0.2.3 (2014/10/10) static 系のメソッドは、ImageUtil に移動
 *
 * @version  4.0
 * @author   Hiroki Nakamura
 * @since    JDK5.0,
 */
public class ImageResizer {
//	private static final String CR = System.getProperty("line.separator");				// 5.5.3.4 (2012/06/19)

//	private static final String ICC_PROFILE = "ISOcoated_v2_eci.icc";		// 5.5.3.4 (2012/06/19)

	private final BufferedImage inputImage; // 入力画像オブジェクト

	private final int inSizeX;				// 入力画像の横サイズ
	private final int inSizeY;				// 入力画像の縦サイズ

//	public static final String READER_SUFFIXES ;	// 5.6.5.3 (2013/06/28) 入力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
//	public static final String WRITER_SUFFIXES ;	// 5.6.5.3 (2013/06/28) 出力画像の形式 [bmp, gif, jpeg, jpg, png, wbmp]
//	// 5.6.5.3 (2013/06/28) 入力画像,出力画像の形式 を ImageIO から取り出します。
//	static {
//		String[] rfn = ImageIO.getReaderFileSuffixes();
//		Arrays.sort( rfn );
//		READER_SUFFIXES = Arrays.toString( rfn );
//
//		String[] wfn = ImageIO.getWriterFileSuffixes();
//		Arrays.sort( wfn );
//		WRITER_SUFFIXES = Arrays.toString( wfn );
//	}

	/**
	 * 入力ファイル名を指定し、画像縮小オブジェクトを初期化します。
	 *
	 * @og.rev 5.4.3.5 (2012/01/17) CMYK対応
	 * @og.rev 5.4.3.7 (2012/01/20) FAIでのファイル取得方法変更
	 * @og.rev 5.4.3.8 (2012/01/24) エラーメッセージ追加
	 * @og.rev 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
	 * @og.rev 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil から取り出します。
	 *
	 * @param fin 入力ファイル名
	 */
	public ImageResizer( final String fin ) {
		inputImage = ImageUtil.readFile( fin );

		inSizeX = inputImage.getWidth();
		inSizeY = inputImage.getHeight();
//
//
//		BufferedImage bi = null;
//		// 5.6.5.3 (2013/06/28) 入力画像の形式 を ImageIO から取り出します。
//		if( !isReaderSuffix( in ) ) {
//		final String errMsg = "入力ファイルは" + READER_SUFFIXES + "のいずれかの形式のみ指定可能です。" + "File=[" + in + "]";
//			throw new RuntimeException( errMsg );
//		}
//
//		File inFile = new File( in );
//		try {
//			// inputImage = ImageIO.read( inFile );
//			bi = ImageIO.read( inFile );
//		}
//		catch ( IIOException ex ) { // 5.4.3.5 (2012/01/17) 決めうち
//			// API的には、IllegalArgumentException と IOException しか記述されていない。
////			FileSeekableStream fsstream = null;
////			try{
////				// 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するように変更
////				// bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null));
////				fsstream = new FileSeekableStream(inFile.getAbsolutePath());
////				bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null));
////			}
////			catch( IOException ioe ){
////			final String errMsg = "イメージファイルの読込(JAI)に失敗しました。" + "File=[" + in + "]";
////				throw new RuntimeException( errMsg,ioe );
////			}
////			catch( Exception oe ){ // 5.4.3.8 (2012/01/23) その他エラーの場合追加
////			final String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れている可能性があります。" + "File=[" + in + "]";
////				throw new RuntimeException( errMsg,oe );
////			}
////			finally{
////				Closer.ioClose(fsstream);
////			}
//		}
//		catch( IOException ex ) {
//		final String errMsg = "イメージファイルの読込に失敗しました。" + "File=[" + in + "]";
//			throw new RuntimeException( errMsg,ex );
//		}
//
//		// 6.0.0.1 (2014/04/25) IIOException の catch ブロックからの例外出力を外に出します。
//		// bi == null は、結果のストリームを読み込みできないような場合、または、IO例外が発生した場合。
//		if( bi == null ) {
//			FileSeekableStream fsstream = null;
//			try{
//				// 5.4.3.7 (2012/01/20) ファイルの開放がGC依存なので、streamで取得するように変更
//				// bi = cmykToSRGB(JAI.create("FileLoad",inFile.toString()).getAsBufferedImage(null,null));
//				fsstream = new FileSeekableStream(inFile.getAbsolutePath());
//				bi = cmykToSRGB(JAI.create("stream",fsstream).getAsBufferedImage(null,null));
//			}
//			catch( IOException ioe ){
//			final String errMsg = "イメージファイルの読込(JAI)に失敗しました。" + "File=[" + in + "]";
//				throw new RuntimeException( errMsg,ioe );
//			}
//			catch( Exception oe ){ // 5.4.3.8 (2012/01/23) その他エラーの場合追加
//			final String errMsg = "イメージファイルの読込(JAI)に失敗しました。ファイルが壊れている可能性があります。" + "File=[" + in + "]";
//				throw new RuntimeException( errMsg,oe );
//			}
//			finally{
//				Closer.ioClose(fsstream);
//			}
//		}
//
//		inputImage = bi;
//		inSizeX = inputImage.getWidth();
//		inSizeY = inputImage.getHeight();
	}

	/**
	 * 縦横の最大サイズ(px)を指定し、変換を行います。
	 * 横長の画像については、変換後の横幅=最大サイズとなり、縦幅については、横幅の
	 * 縮小率に従って決定されます。
	 * 逆に縦長の画像については、変換後の縦幅=最大サイズとなり、横幅については、縦幅の
	 * 縮小率に従って決定されます。
	 *
	 * @param out 出力ファイル名
	 * @param maxSize 変換後の縦横の最大サイズ
	 */
	public void resizeByPixel( final String out, final int maxSize ) {
		int sizeX = 0;
		int sizeY = 0;
		if( inSizeX > inSizeY ) {
			sizeX = maxSize;
			sizeY = inSizeY * maxSize / inSizeX;
		}
		else {
			sizeX = inSizeX * maxSize / inSizeY;
			sizeY = maxSize;
		}
		convert( inputImage, out, sizeX, sizeY );
	}

	/**
	 * 縦横の変換後のサイズ(px)を個別に指定し、変換を行います。
	 *
	 * @param out 出力ファイル名
	 * @param sizeX 変換後の横サイズ(px)
	 * @param sizeY 変換後の縦サイズ(px)
	 */
	public void resizeByPixel( final String out, final int sizeX, final int sizeY ) {
		convert( inputImage, out, sizeX, sizeY );
	}

	/**
	 * "1"を元サイズとする縮小率を指定し、変換を行います。
	 *  縮小率は、縦横で同じ縮小率が適用されます。
	 *
	 * @param out 出力ファイル名
	 * @param ratio 縮小率
	 */
	public void resizeByRatio( final String out, final double ratio ) {
		final int sizeX = (int)( inSizeX * ratio );
		final int sizeY = (int)( inSizeY * ratio );
		convert( inputImage, out, sizeX, sizeY );
	}

	/**
	 * 画像の変換を行うための内部共通メソッドです。
	 *
	 * @og.rev 5.4.1.0 (2011/11/01) 画像によってgetTypeが0を返し、エラーになる不具合を修正
	 * @og.rev 5.6.5.3 (2013/06/28) 出力画像の形式 を ImageIO から取り出します。
	 * @og.rev 5.6.5.3 (2013/06/28) 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする。
	 * @og.rev 5.6.6.1 (2013/07/12) 拡張子の変更があるので、変換しない処理は、ない。
	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil のメソッドを一部使用するように変更します。
	 *
	 * @param inputImage 入力画像オブジェクト
	 * @param out 出力ファイル名
	 * @param sizeX 横サイズ(px)
	 * @param sizeY 縦サイズ(px)
	 */
	private void convert( final BufferedImage inputImage, final String out, final int sizeX, final int sizeY ) {
		// 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする。
		// 5.6.5.3 (2013/06/28) 出力画像の形式 を ImageIO から取り出します。
		// 6.0.2.3 (2014/10/10) ImageUtil のメソッド
//		if( !isWriterSuffix( out ) ) {
//		final String errMsg = "出力ファイルは" + WRITER_SUFFIXES + "のいずれかの形式のみ指定可能です。" + "File=[" + out + "]";
		if( !ImageUtil.isWriterSuffix( out ) ) {
			final String errMsg = "出力ファイルは" + ImageUtil.WRITER_SUFFIXES + "のいずれかの形式のみ指定可能です。" + "File=[" + out + "]";
			throw new RuntimeException( errMsg );
		}

//		File outFile = new File( out );

		// 5.4.1.0 (2011/11/01) 画像によってgetTypeが0を返し、エラーになる不具合を修正
		final int type = inputImage.getType();
		BufferedImage resizeImage = null;
		if( type == 0 ) {
			resizeImage = new BufferedImage( sizeX, sizeY, BufferedImage.TYPE_4BYTE_ABGR_PRE );
		}
		else {
			resizeImage = new BufferedImage( sizeX, sizeY, inputImage.getType() );
		}
		AffineTransformOp ato = null;
		ato = new AffineTransformOp(
				AffineTransform.getScaleInstance(
						(double)sizeX/inSizeX, (double)sizeY/inSizeY ), null );
		ato.filter( inputImage, resizeImage );

		ImageUtil.saveFile( resizeImage,out );		// 6.0.2.3 (2014/10/10) ImageUtil のメソッド

//		try {
//			// 5.6.6.1 (2013/07/12) getSuffix するタイミングを後ろにする。
////			String outSuffix = getSuffix( out );
//			String outSuffix = ImageUtil.getSuffix( out );			// 6.0.2.3 (2014/10/10) ImageUtil のメソッド
//			ImageIO.write( resizeImage, outSuffix, outFile );
//		}
//		catch( IOException ex ) {
//		final String errMsg = "イメージファイルの作成に失敗しました。" + "File=[" + out + "]";
//			throw new RuntimeException( errMsg,ex );
//		}
	}

//	/**
//	 * ファイル名から拡張子(小文字)を求めます。
//	 * 拡張子 が存在しない場合は、null を返します。
//	 *
//	 * @og.rev 5.6.5.3 (2013/06/28) private ⇒ public へ変更
//	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil に移動
//	 *
//	 * @param fileName ファイル名
//	 *
//	 * @return 拡張子(小文字)。なければ、null
//	 */
//	public static String getSuffix( final String fileName ) {
//		String suffix = null;
//		if( fileName != null ) {
//			int sufIdx = fileName.lastIndexOf( '.' );
//			if( sufIdx >= 0 ) {
//				suffix = fileName.substring( sufIdx + 1 ).toLowerCase( Locale.JAPAN );
//			}
//		}
//		return suffix;
//	}

//	/**
//	 * ファイル名から入力画像になりうるかどうかを判定します。
//	 * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
//	 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
//	 *
//	 * @og.rev 5.6.5.3 (2013/06/28) 新規追加
//	 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
//	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil に移動
//	 *
//	 * @param fileName ファイル名
//	 *
//	 * @return 入力画像として使用できるかどうか。できる場合は、true
//	 */
//	public static final boolean isReaderSuffix( final String fileName ) {
//		String suffix = getSuffix( fileName );
//
//		return suffix != null && READER_SUFFIXES.indexOf( suffix ) >= 0 ;
//	}

//	/**
//	 * ファイル名から出力画像になりうるかどうかを判定します。
//	 * コンストラクターの引数（入力画像）や、実際の処理の中（出力画像）で
//	 * 、変換対象となるかどうかをチェックしていますが、それを事前に確認できるようにします。
//	 *
//	 * @og.rev 5.6.5.3 (2013/06/28) 新規追加
//	 * @og.rev 5.6.6.1 (2013/07/12) getSuffix が null を返すケースへの対応
//	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil に移動
//	 *
//	 * @param fileName ファイル名
//	 *
//	 * @return 出力画像として使用できるかどうか。できる場合は、true
//	 */
//	public static final boolean isWriterSuffix( final String fileName ) {
//		String suffix = getSuffix( fileName );
//
//		return suffix != null && WRITER_SUFFIXES.indexOf( suffix ) >= 0 ;
//	}

//	/**
//	 * BufferedImageをISOCoatedのICCプロファイルで読み込み、RGBにした結果を返します。
//	 * (CMYKからRBGへの変換、ビット反転)
//	 * なお、ここでは、外部の ICC_PROFILE(ISOcoated_v2_eci.icc) を利用して、処理速度アップを図りますが、
//	 * 存在しない場合、標準の、com.sun.media.jai.util.SimpleCMYKColorSpace を利用しますので、エラーは出ません。
//	 * ただし、ものすごく遅いため、実用的ではありません。
//	 * ISOcoated_v2_eci.icc ファイルは、zip圧縮して、拡張子をjar に変更後、(ISOcoated_v2_eci.jar)
//	 * javaエクステンション((JAVA_HOME\)jre\lib\ext) にコピーするか、実行時に、CLASSPATHに設定します。
//	 *
//	 * @og.rev 5.4.3.5 (2012/01/17)
//	 * @og.rev 5.5.3.4 (2012/06/19) ICC_PROFILE の取得先を、ISOcoated_v2_eci.icc に変更
//	 * @og.rev 6.0.2.3 (2014/10/10) ImageUtil に移動
//	 *
//	 * @param readImage BufferedImageオブジェクト
//	 *
//	 * @return 変換後のBufferedImage
//	 * @throws IOException 入出力エラーが発生したとき
//	 */
//	public BufferedImage cmykToSRGB( final BufferedImage readImage ) throws IOException {
//		ClassLoader loader = Thread.currentThread().getContextClassLoader();
//		InputStream icc_stream = loader.getResourceAsStream( ICC_PROFILE );
//
//		// 5.5.3.4 (2012/06/19) ICC_PROFILE が存在しない場合は、標準のSimpleCMYKColorSpace を使用。
//		ColorSpace cmykCS = null;
//		if( icc_stream != null ) {
//			ICC_Profile prof =	ICC_Profile.getInstance(icc_stream);	//変換プロファイル
//			cmykCS = new ICC_ColorSpace(prof);
//		}
//		else {
//			// 遅いので標準のスペースは使えない
//		final String errMsg = ICC_PROFILE + " が見つかりません。" + CR
//							+ " CLASSPATHの設定されている場所に配備してください。"	+	CR
//							+ " 標準のSimpleCMYKColorSpaceを使用しますのでエラーにはなりませんが、非常に遅いです。" ;
//			System.out.println( errMsg );
//			cmykCS = SimpleCMYKColorSpace.getInstance();
//		}
//		BufferedImage rgbImage = new BufferedImage(readImage.getWidth(),
//				readImage.getHeight(), BufferedImage.TYPE_INT_RGB);
//		ColorSpace rgbCS = rgbImage.getColorModel().getColorSpace();
//		ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
//		cmykToRgb.filter(readImage, rgbImage);
//
//		int width  = rgbImage.getWidth();
//		int height = rgbImage.getHeight();
//		// 反転が必要
//		for (int i=0;i<width;i++) {
//			for (int j=0;j<height;j++) {
//				int rgb = rgbImage.getRGB(i, j);
//				int rr = (rgb & 0xff0000) >> 16;
//				int gg = (rgb & 0x00ff00) >> 8;
//				int bb = (rgb & 0x0000ff);
//				rgb = (Math.abs(rr - 255) << 16) + (Math.abs(gg - 255) << 8) + (Math.abs(bb - 255));
//				rgbImage.setRGB(i, j, rgb);
//			}
//		}
//
//		return rgbImage;
//	}

	/**
	 * メイン処理です。
	 * Usage: java org.opengion.fukurou.util.ImageResizer [Input Filename] [OutputFilename] [MaxResize]
	 *
	 * @param  args  引数文字列配列 入力ファイル、出力ファイル、縦横最大サイズ
	 */
	public static void main( final String[] args ) {
		if( args.length < 3 ) {
			LogWriter.log( "Usage: java org.opengion.fukurou.util.ImageResizer [Input Filename] [OutputFilename] [MaxResize]" );
			return ;
		}

		final ImageResizer ir = new ImageResizer( args[0] );
		ir.resizeByPixel( args[1], Integer.parseInt( args[2] ) );
	}
}
