/*
 * 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 のエントリポイントメソッドです。
	 *
	 * @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 );

			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配列として処理させる。
	 *
	 * @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();
//			String cmnt = DocletUtil.htmlFilter( classDoc.commentText() );			// 5.5.4.1 (2012/07/06)
			Tag[]  cmnt = classDoc.inlineTags();									// 5.5.4.1 (2012/07/06)
			Tag[] smplTags	= classDoc.tags(OG_FOR_SMPL);
			Tag[] grpTags	= classDoc.tags(OG_GROUP);

			writer.printTag( "<classDoc>" );
			writer.printTag( "  <tagClass>"		,classFullName	,"</tagClass>" );
			writer.printTag( "  <description>" );
			writer.printTag(         makeGroupTag( grpTags )   );
			writer.printTag(         desc      );
			writer.printTag( "</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 = DocletUtil.htmlFilter( methods[j].commentText() );		// 5.5.4.1 (2012/07/06)
						cmnt = methods[j].inlineTags();									// 5.5.4.1 (2012/07/06)

						writer.printTag( "  <method>" );
						writer.printTag( "    <name>"		,methodName	,"</name>" );
						writer.printTag( "    <htmlExtend>"	,extendFlag	,"</htmlExtend>" );
						writer.printTag( "    <description>",ftag		,"</description>" );
						writer.printTag( "    <contents>" );
						writer.printTag(        cmnt );
						writer.printTag(        tags );
						writer.printTag( "    </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 に変更
	 *
	 * @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 = DocletUtil.htmlFilter( tag[i].text() );
			String data = StringUtil.htmlFilter( tag[i].text() );		// 5.5.4.1 (2012/07/06) DocletUtil → StringUtil に変更
			if( i > 0 ) { but.append( "," ); }
			but.append( data );
		}
		if( but.length() > 0 ) {
			return "【" + but.toString() + "】" ;
		}
		else {
			return "";
		}
	}

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