/*
 * 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.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

import com.swetake.util.Qrcode;

/**
 * QrcodeImage は、３次元バーコードの QRコードイメージを生成する
 * 独立したコンポーネントです。
 * ここでの使い方は、初期化時に、エンコードする文字列(120Byte以内)と、
 * 出力ファイル名を指定して、Graphics に描画したQRコードイメージを
 * JPEG 変換し、指定のファイルに上書き保存しています。
 * QRコード作成に、http://www.swetake.com/ の Qrcode クラスを使用しています。
 * これが、2004/11/7  ver.0.50beta9 とのことなので、動作チェック、および、
 * 製品としての保証という意味では、まだ使えるレベルではありませんが、
 * コード計算さえバグっていなければ使えうる為、試験的導入致します。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class QrcodeImage extends Component {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.7.1.1 (2013/12/13)" ;

	private static final long serialVersionUID = 571120131213L ;

	// version  0 ～ 39 => 1 ～ 40
	// errCo    H,M    0:H 1:M
	// encMd    N,A,B  0:N 1:A 2:B
	private static final int[][][] QC_DATA = new int[][][] {
		{{   17 ,   10 ,   4 } , {   34 ,   20 ,    8 }} ,
		{{   34 ,   20 ,   8 } , {   63 ,   38 ,   16 }} ,
		{{   58 ,   35 ,  15 } , {  101 ,   61 ,   26 }} ,
		{{   82 ,   50 ,  21 } , {  149 ,   90 ,   38 }} ,
		{{  106 ,   64 ,  27 } , {  202 ,  122 ,   52 }} ,
		{{  139 ,   84 ,  36 } , {  255 ,  154 ,   65 }} ,
		{{  154 ,   93 ,  39 } , {  293 ,  178 ,   75 }} ,
		{{  202 ,  122 ,  52 } , {  365 ,  221 ,   93 }} ,
		{{  235 ,  143 ,  60 } , {  432 ,  262 ,  111 }} ,
		{{  288 ,  174 ,  74 } , {  513 ,  311 ,  131 }} ,
		{{  331 ,  200 ,  85 } , {  604 ,  366 ,  155 }} ,
		{{  374 ,  227 ,  96 } , {  691 ,  419 ,  177 }} ,
		{{  427 ,  259 , 109 } , {  796 ,  483 ,  204 }} ,
		{{  468 ,  283 , 120 } , {  871 ,  528 ,  223 }} ,
		{{  530 ,  321 , 136 } , {  991 ,  600 ,  254 }} ,
		{{  602 ,  365 , 154 } , { 1082 ,  656 ,  277 }} ,
		{{  674 ,  408 , 173 } , { 1212 ,  734 ,  310 }} ,
		{{  746 ,  452 , 191 } , { 1346 ,  816 ,  345 }} ,
		{{  813 ,  493 , 208 } , { 1500 ,  909 ,  384 }} ,
		{{  919 ,  557 , 235 } , { 1600 ,  970 ,  410 }} ,
		{{  969 ,  587 , 248 } , { 1708 , 1035 ,  438 }} ,
		{{ 1056 ,  640 , 270 } , { 1872 , 1134 ,  480 }} ,
		{{ 1108 ,  672 , 284 } , { 2059 , 1248 ,  528 }} ,
		{{ 1228 ,  744 , 315 } , { 2188 , 1326 ,  561 }} ,
		{{ 1286 ,  779 , 330 } , { 2395 , 1451 ,  614 }} ,
		{{ 1425 ,  864 , 365 } , { 2544 , 1542 ,  652 }} ,
		{{ 1501 ,  910 , 385 } , { 2701 , 1637 ,  692 }} ,
		{{ 1581 ,  958 , 405 } , { 2857 , 1732 ,  732 }} ,
		{{ 1677 , 1016 , 430 } , { 3035 , 1839 ,  778 }} ,
		{{ 1782 , 1080 , 457 } , { 3289 , 1994 ,  843 }} ,
		{{ 1897 , 1150 , 486 } , { 3486 , 2113 ,  894 }} ,
		{{ 2022 , 1226 , 518 } , { 3693 , 2238 ,  947 }} ,
		{{ 2157 , 1307 , 553 } , { 3909 , 2369 , 1002 }} ,
		{{ 2301 , 1394 , 590 } , { 4134 , 2506 , 1060 }} ,
		{{ 2361 , 1431 , 605 } , { 4343 , 2632 , 1113 }} ,
		{{ 2524 , 1530 , 647 } , { 4588 , 2780 , 1176 }} ,
		{{ 2625 , 1591 , 673 } , { 4775 , 2894 , 1224 }} ,
		{{ 2735 , 1658 , 701 } , { 5039 , 3054 , 1292 }} ,
		{{ 2927 , 1774 , 750 } , { 5313 , 3220 , 1362 }} ,
		{{ 3057 , 1852 , 784 } , { 5596 , 3391 , 1435 }} } ;

	/** エラー訂正レベル ('L','M','Q','H')  H とM のみサポート	*/
	public static enum ErrCrct {
					H(0),M(1);
					private int no;
					public static final ErrCrct DEF = M;
					ErrCrct(int no) { this.no = no; }
					public int getNo() { return no; }
					public char getCh() {
						final char ch ;
						switch(this) {
							case H:  ch = 'H'; break;
							case M:
							default: ch = 'M'; break;
						}
						return ch ;
					}
					public static ErrCrct get( final char ch ) {
						final ErrCrct rtn ;
						switch(ch) {
							case 'H': rtn = H; break;
							case 'M':
							default:  rtn = M; break;
						}
						return rtn ;
					}
	};

	/** エンコードモード ('N':数字モード 'A':英数字モード　'B':8bit byteモード) */
	public static enum EncMode {
					N(0),A(1),B(1);
					private int no;
					public static final EncMode DEF = B;
					EncMode(int no) { this.no = no; }
					public int getNo() { return no; }
					public char getCh() {
						final char ch ;
						switch(this) {
							case N:  ch = 'N'; break;
							case A:  ch = 'A'; break;
							case B:
							default: ch = 'B'; break;
						}
						return ch ;
					}
					public static EncMode get( final char ch ) {
						final EncMode rtn ;
						switch(ch) {
							case 'N': rtn = N; break;
							case 'A': rtn = A; break;
							case 'B':
							default:  rtn = B; break;
						}
						return rtn ;
					}
	};

	/** バージョン (1から40の整数。0を設定すると自動設定になります。) 初期値:{@value}	*/
	public static final int DEF_VERSION = 5;		// 5.7.1.1 (2013/12/13) VERSION チェックのために、IDを変更します。

	/** セルのマージン 初期値:{@value}	*/
	public static final int MARGIN = 4;

	/** １セル辺りの塗りつぶしピクセル 初期値:{@value}	*/
	public static final int PIXEL = 3;

	/** 出力イメージのタイプ(PNG/JPEG) 初期値:{@value}	*/
	public static final String IMAGE_TYPE = "PNG";

	private String qrData;
	private String saveFile;
	private String imgType	= IMAGE_TYPE ;
	private ErrCrct	errCo	= ErrCrct.DEF ;
	private EncMode	encMd	= EncMode.DEF ;
	private int		version	= DEF_VERSION ;			// 5.7.1.1 (2013/12/13) VERSION チェックのために、IDを変更します。
	private int		pixel	= PIXEL ;

	private int imageSize ;

	/**
	 * 初期化メソッド
	 *
	 * エラー訂正レベル:M , マージン:4(セル分) , 塗りつぶしピクセル:3
	 * エンコードモード:B(バイナリ) 、バージョン:5 , イメージのタイプ:PNG
	 * に初期化されます。
	 *
	 * @og.rev 5.7.1.1 (2013/12/13) VERSION チェックのために、VERSION ⇒ DEF_VERSION に変更します。
	 *
	 * @param   qrData  エンコードする文字列(120Byte 以内)
	 * @param   saveFile 出力ファイル名
	 */
	public void init( final String qrData,final String saveFile ) {
		init( qrData,saveFile,DEF_VERSION,EncMode.DEF,ErrCrct.DEF,IMAGE_TYPE,PIXEL );	// 5.7.1.1 (2013/12/13)
	}

	/**
	 * 初期化メソッド
	 *
	 * エラー訂正レベル:M , マージン:4(セル分) , 塗りつぶしピクセル:3
	 * イメージのタイプ:PNG に初期化されます。
	 *
	 * @param   qrData   エンコードする文字列(120Byte 以内)
	 * @param   saveFile 出力ファイル名
	 * @param   version  バージョン (1から40の整数。0を設定すると自動設定になります。)
	 * @param   encMd    エンコードモード ('N':数字モード 'A':英数字モード　'B':8bit byteモード)
	 */
	public void init( final String qrData,final String saveFile,final int version,final EncMode encMd ) {
		init( qrData,saveFile,version,encMd,ErrCrct.DEF,IMAGE_TYPE,PIXEL );
	}

	/**
	 * 初期化メソッド
	 *
	 * エラー訂正レベル:M , マージン:4(セル分) , 塗りつぶしピクセル:3
	 *
	 * @param   qrData   エンコードする文字列(120Byte 以内)
	 * @param   saveFile 出力ファイル名
	 * @param   version  バージョン (1から40の整数。0を設定すると自動設定になります。)
	 * @param   encMd    エンコードモード('N':数字モード 'A':英数字モード　'B':8bit byteモード)
	 * @param   errCo    エラー訂正レベル ('L','M','Q','H')
	 * @param   imgType  イメージファイル形式(PNG/JPEG)
	 * @param   pixel    １セル辺りの塗りつぶしピクセル
	 */
	public void init( final String qrData,final String saveFile,final int version,final EncMode encMd,
						final ErrCrct errCo ,final String imgType,final int pixel ) {

		this.qrData   = qrData;
		this.saveFile = saveFile;
		this.imgType  = imgType;
		this.errCo    = errCo;
		this.encMd    = encMd;
		this.version  = version;
		this.pixel    = pixel;

		imageSize = ( MARGIN*2 + 17 + version*4 )*pixel;
	}

	/**
	 * 描画処理を行います。
	 *
	 * @param   gpx  Graphicsオブジェクト
	 */
	@Override
	public void paint( final Graphics gpx ) {
		Qrcode qrc =new Qrcode();
		qrc.setQrcodeErrorCorrect(errCo.getCh());
		qrc.setQrcodeEncodeMode(encMd.getCh());
		qrc.setQrcodeVersion(version);

		byte[] dt =qrData.getBytes( StringUtil.DEFAULT_CHARSET );	// 5.5.2.6 (2012/05/25) findbugs対応
		boolean[][] sfg = qrc.calQrcode( dt );

		int size = sfg.length;
		int mgn  = MARGIN*pixel ;
		for( int i=0; i<size; i++ ) {
			for( int j=0; j<size; j++ ) {
				if( sfg[j][i] ) {
					gpx.fillRect( mgn+j*pixel,mgn+i*pixel,pixel,pixel );
				}
			}
		}
	}

	/**
	 * 描画処理を行います。
	 *
	 */
	public void saveImage() {
		// 出力用イメージの生成
		BufferedImage image = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_BGR );

		// イメージからグラフィックコンテキストを取得
		Graphics grph = image.getGraphics();
		grph.setColor( java.awt.Color.WHITE );
		grph.fillRect( 0,0,imageSize, imageSize );
		grph.setColor( java.awt.Color.BLACK );

		// JEditorPane をイメージに書き込む
		// paintComponent は proteced なので使用できない
		paint( grph );

		// 使い終わったグラフィックコンテキストを開放
		grph.dispose();

		try {
			// イメージの出力 Image I/O を使用
			ImageIO.write( image, imgType, new File( saveFile ) );
		} catch ( IOException ex ) {
			String errMsg = "イメージファイルの出力に失敗しました。"
					+ "File=[" + saveFile + "]" ;
			throw new RuntimeException( errMsg,ex );
		}
	}

	/**
	 * メイン処理です。
	 * Usage: java org.opengion.fukurou.util.QrcodeImage Encode [SaevFile]
	 *
	 * @param  args  引数文字列配列
	 */
	public static void main( final String[] args ) {
		if( args.length == 0 ) {
			LogWriter.log( "Usage: java org.opengion.fukurou.util.QrcodeImage Encode [SaevFile]" );
			return ;
		}

		String qrcode = args[0];
		String file   = (args.length > 1) ? args[1] : "img.png";

		QrcodeImage qrImage = new QrcodeImage();
		qrImage.init( qrcode,file );
		qrImage.saveImage();
	}

	/**
	 * 内部データを標準出力へ出力します。
	 *
	 */
	public static void printQcData() {
		char[] strJ = new char[] { 'H','M' };
		char[] strK = new char[] { 'N','A','B' };

		for( int i=0; i<QC_DATA.length; i++ ) {
			System.out.print( "version=[" + (i+1) + "] " );
			for( int j=0; j<QC_DATA[i].length; j++ ) {
				char errCo = strJ[j];
				for( int k=0; k<QC_DATA[i][j].length; k++ ) {
					System.out.print( errCo + strK[k] + "=[" + QC_DATA[i][j][k] + "] " );
				}
			}
			System.out.println();
		}
	}

	/**
	 * バージョン情報を取得します。
	 *
	 * @param   errCo	エラー訂正レベル ('L','M','Q','H')
	 * @param	encMd	エンコードモード ('N':数字モード 'A':英数字モード　'B':8bit byteモード)
	 * @param	len		対象範囲
	 *
	 * @return	バージョン情報
	 */
	public static int getVersion( final ErrCrct errCo, final EncMode encMd, final int len ) {
		int errCoInt = errCo.getNo() ;
		int encMdInt = encMd.getNo() ;

		int rtn = -1;
		for( int i=0; i<QC_DATA.length; i++ ) {
			if( QC_DATA[i][errCoInt][encMdInt] >= len ) {
				rtn = i;
				break;
			}
		}

		if( rtn < 0 ) {
			String errMsg = "データ量が対象範囲を超えています。エラーレベルや、モードを調整してください。"
						+ "ErrCo:" + errCo + " EncMd:" + encMd + " len=[" + len + "]"
						+ " MaxLen=[" + QC_DATA[QC_DATA.length-1][errCoInt][encMdInt] + "]" ;
			throw new RuntimeException( errMsg );
		}

		return rtn + 1 ;
	}

	/**
	 * 最大サイズを取得します。
	 *
	 * @param	version	バージョン情報
	 * @param   errCo	エラー訂正レベル ('L','M','Q','H')
	 * @param	encMd	エンコードモード ('N':数字モード 'A':英数字モード　'B':8bit byteモード)
	 *
	 * @return	最大サイズ
	 */
	public static int getMaxSize( final int version, final ErrCrct errCo, final EncMode encMd ) {
		int errCoInt = errCo.getNo() ;
		int encMdInt = encMd.getNo() ;

		return QC_DATA[version][errCoInt][encMdInt] ;
	}
}
