/*
 * 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.taglet;

import org.opengion.fukurou.util.LogWriter;

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;

import com.sun.javadoc.RootDoc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.ConstructorDoc;
import com.sun.javadoc.ExecutableMemberDoc;
import com.sun.javadoc.Type;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.Tag;
import com.sun.javadoc.SourcePosition;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.AnnotationTypeDoc;

/**
 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
 * パラメータの version に一致する og.rev タグの 書かれたメソッドを、
 * ピックアップします。
 * og.rev タグ で、まとめて表示します。
 * このクラスは、RELEASE-NOTES.txt を作成する場合に、変更箇所をピックアップするのに使用します。
 *
 * @version  6.0.2.3 (2014/10/10)
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class DocletVerCheck {
	private static final String  SELECT_PACKAGE	= "org.opengion" ;
	private static final boolean NOT_PRIVATE	= false ;
	private static final String  ENCODE			= "UTF-8";

	private static final String	OG_REV			= "og.rev";

	private static final Map<String,List<String[]>> verInfo = new HashMap<String,List<String[]>>();

	/**
	 * すべてが staticメソッドなので、コンストラクタを呼び出さなくしておきます。
	 *
	 */
	private DocletVerCheck() {}

	/**
	 * Doclet のエントリポイントメソッドです。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	root	エントリポイントのRootDocオブジェクト
	 *
	 * @return 正常実行時 true
	 */
	public static boolean start( final RootDoc root ) {
		String version	= DocletUtil.getOption( "-version" , root.options() );
		String file		= DocletUtil.getOption( "-outfile" , root.options() );

		DocletTagWriter writer = null;
		try {
			writer = new DocletTagWriter( file,ENCODE );

			writeContents( root.classes(),writer,version );
		}
		catch( IOException ex ) {
			LogWriter.log( ex );
		}
		finally {
			if( writer != null ) { writer.close(); }
		}
		return true;
	}

	/**
	 * ClassDoc 配列よりコンテンツを作成します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param classes	ClassDoc配列
	 * @param writer	Tagを書き出すWriterオブジェクト
	 * @param version	チェックする対象のバージョン
	 */
	private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer,final String version ) {
		for(int x=0; x< classes.length; x++) {
			ClassDoc classDoc	= classes[x] ;
			String fullName		= classDoc.qualifiedName();

			fullName = fullName.substring( SELECT_PACKAGE.length()+1 );		// パッケージ名を簡略化しておきます。

			// コンストラクタ、メソッドから、対象の @og.rev の値を取得します。
			ExecutableMemberDoc[][] docs = new ExecutableMemberDoc[2][];
			docs[0] = classDoc.constructors( false ) ;
			docs[1] = classDoc.methods( false ) ;

			for( int i=0; i<docs.length; i++ ) {
				for( int j=0; j < docs[i].length; j++ ) {
					ExecutableMemberDoc doc = docs[i][j];
					Tag[] revTags = doc.tags(OG_REV);
					for( int k=0 ; k<revTags.length; k++ ) {
						String rev = revTags[k].text();
						if( rev.startsWith( version ) ) {
							List<String[]> list = verInfo.get( rev );		// rev コメントでまとめます。
							if( list == null ) { list = new ArrayList<String[]>(); }
							list.add( menberCheck( doc,fullName ) );
							verInfo.put( rev,list );

							break;
						}
					}
				}
			}
		}

		for( String key : verInfo.keySet() ) {
			writer.printTag( key );
			for( String[] vals : verInfo.get( key ) ) {
				writer.printTag( "\t" , vals[0] , "#" , vals[1] );
	//			writer.printTag( "\t" , vals[2] );						// ポジションはデバッグ情報
			}
		}
	}

	/**
	 * メンバークラス(コンストラクタ、メソッド)から、メソッド名、シグネチャを取得します。
	 *
	 * @og.rev 6.0.2.3 (2014/10/10) 新規作成
	 *
	 * @param	menber		ExecutableMemberDocオブジェクト
	 * @return	配列にセットされた情報( fullName,signature,position )
	 */
	private static String[] menberCheck( final ExecutableMemberDoc menber,final String fullName ) {

		String menberName = menber.name();

		StringBuilder sigBuf = new StringBuilder( 200 );
		sigBuf.append( menberName ).append( "(" ) ;
		Parameter[] prm = menber.parameters();

		for( int k=0; k<prm.length; k++ ) {
			Type ptyp = prm[k].type();
			String prmNm =prm[k].name();

			sigBuf.append( ptyp.typeName() ).append( ptyp.dimension() ).append( " " )
					.append( prmNm ).append( "," );
		}

		if( prm.length > 0 ) { sigBuf.deleteCharAt( sigBuf.length()-1 ); }
		sigBuf.append( ")" );
		String signature = sigBuf.toString();

		String	position = menber.position().toString();
		System.out.println( position );		// デバッグ情報

		return new String[] { fullName , signature , position };
	}

	/**
	 * カスタムオプションを使用するドックレットの必須メソッド optionLength(String) です。
	 *
	 * ドックレットに認識させる各カスタムオプションに、 optionLength がその
	 * オプションを構成する要素 (トークン) の数を返さなければなりません。
	 * このカスタムオプションでは、 -tag オプションそのものと
	 * その値の 2 つの要素で構成されるので、作成するドックレットの
	 * optionLengthメソッドは、 -tag オプションに対して 2 を返さなくては
	 * なりません。また、認識できないオプションに対しては、0 を返します。
	 *
	 * @param	option	オプション文字列
	 *
	 * @return	要素(トークン) の数
	 */
	public static int optionLength( final String option ) {
		if("-version".equalsIgnoreCase(option)) {
			return 2;
		}
		else if("-outfile".equalsIgnoreCase(option)) {
			return 2;
		}
		return 0;
	}
}
