/*
 * 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 org.opengion.fukurou.util.StringUtil;

import com.sun.javadoc.RootDoc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.Type;
import com.sun.javadoc.Tag;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;

/**
 * ソースコメントから、タグ情報を取り出す Doclet クラスです。
 * この Doclet は、":org.opengion.hayabusa.taglib" のみ対象として処理します。
 * og.formSample , og.tag , og.group タグを切り出します。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class DocletTaglib {
	private static Map<String,String> map = new HashMap<String,String>();

	private static final String OG_FOR_SMPL	= "og.formSample";
	private static final String OG_TAG_NAME	= "og.tag";
	private static final String OG_GROUP	= "og.group";

	private static final String OG_TAG_CLASS = "org.opengion.hayabusa.taglib";
	private static final String ENCODE = "UTF-8";

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

	/**
	 * Doclet のエントリポイントメソッドです。
	 *
	 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
	 *
	 * @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 );

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<?xml version=\"1.0\" encoding=\"", ENCODE, "\" ?>" );
			writer.printTag( "<javadoc>" );
			writer.printTag(   "<version>",version,"</version>" );
			writer.printTag(   "<description></description>" );
			writeContents( root.classes(),writer );
			writer.printTag( "</javadoc>" );
		}
		catch( IOException ex ) {
			LogWriter.log( ex );
		}
		finally {
			if( writer != null ) { writer.close(); }
		}
		return true;
	}

	/**
	 * ClassDoc 配列よりコンテンツを作成します。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) コメントは文字列でなく、Tag配列として処理させる。
	 * @og.rev 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
	 * @og.rev 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
	 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
	 *
	 * @param classes	ClassDoc配列
	 * @param writer	DocletTagWriterオブジェクト
	 */
	private static void writeContents( final ClassDoc[] classes,final DocletTagWriter writer ) {
		for(int i=0; i< classes.length; i++) {
			ClassDoc classDoc      = classes[i] ;
			String   classFullName = classDoc.qualifiedName() ;

			if( ! classDoc.isPublic() ||
				classFullName.indexOf( OG_TAG_CLASS ) < 0 ) { continue; }

			Tag[]  desc = classDoc.firstSentenceTags();
			Tag[]  cmnt = classDoc.inlineTags();									// 5.5.4.1 (2012/07/06)
			Tag[] smplTags	= classDoc.tags(OG_FOR_SMPL);
			Tag[] grpTags	= classDoc.tags(OG_GROUP);

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<classDoc>" );
			writer.printTag(   "<tagClass>"		,classFullName	,"</tagClass>" );
			// 5.6.6.1 (2013/07/12) og.group を、tagGroup として独立させる。
			writer.printTag(   "<tagGroup>"		,makeGroupTag( grpTags )	,"</tagGroup>" );
			writer.printTag(   "<description>"	,desc						,"</description>" );
			writer.printTag(   "<contents>"		,cmnt			,"</contents>" );
			writer.printTag(   "<formSample>"	,smplTags		,"</formSample>" );

			map.clear();
			String className = classDoc.name();
			while( 	! "BodyTagSupport".equals( className ) &&
					! "TagSupport".equals( className ) ) {
				String extendFlag = "false";
				if( "HTMLTagSupport".equals( className ) ) {
					extendFlag = "true" ;
				}
				MethodDoc[] methods = classDoc.methods();
				for(int j=0; j < methods.length; j++) {
					if( ! methods[j].isPublic() ) { continue; }
					Tag[] tags = methods[j].tags(OG_TAG_NAME);
					if(tags.length > 0) {
						String methodName = DocletUtil.removeSetter( methods[j].name() );
						if( map.containsKey( methodName ) ) { continue; }
						map.put( methodName,className );
						Tag[] ftag = methods[j].firstSentenceTags();
						cmnt = methods[j].inlineTags();									// 5.5.4.1 (2012/07/06)

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
						writer.printTag(   "<method>" );
						writer.printTag(     "<name>"		,methodName	,"</name>" );
						writer.printTag(     "<htmlExtend>"	,extendFlag	,"</htmlExtend>" );
						writer.printTag(     "<description>",ftag		,"</description>" );
						// 5.7.1.1 (2013/12/13) cmnt と tags の間に改行をセット
						writer.printTag(     "<contents>"	,cmnt		,"" );
						writer.printTag( ""					,tags		,"</contents>" );
						writer.printTag(   "</method>");
					}
				}
				Type type = classDoc.superclassType();
				if( type == null ) { break; }
				classDoc  = type.asClassDoc() ;
				className = classDoc.name();
			}
			writer.printTag( "  </classDoc>" );
		}
	}

	/**
	 * タグ配列を受け取り、タグ出力します。
	 * 複数のタグを出力する場合に、カンマ区切り文字で連結します。
	 *
	 * @og.rev 5.5.4.1 (2012/07/06) DocletUtil.htmlFilter → StringUtil.htmlFilter に変更
	 * @og.rev 5.6.6.1 (2013/07/12) og.group の表示方法を変更する。
	 *
	 * @param	tag タグ配列
	 *
	 * @return	タグ出力文字列
	 */
	private static String makeGroupTag( final Tag[] tag ) {
		StringBuilder but = new StringBuilder( 200 );
		for( int i=0; i<tag.length; i++ ) {
			String data = StringUtil.htmlFilter( tag[i].text() );		// 5.5.4.1 (2012/07/06) DocletUtil → StringUtil に変更
			if( i > 0 ) { but.append( "," ); }
			but.append( "【" ).append( data ).append( "】" );			// 5.6.6.1 (2013/07/12) og.group の表示方法を変更
		}
		return but.toString() ;											// 5.6.6.1 (2013/07/12)
	}

	/**
	 * カスタムオプションを使用するドックレットの必須メソッド 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;
	}
}
