/*
 * 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 com.sun.javadoc.RootDoc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Type;
import com.sun.javadoc.Tag;
import java.util.Map;
import java.util.HashMap;
import java.io.IOException;

/**
 * ソースコメントから、属性情報を取り出す Doclet クラスです。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class DocletPlugin {
	private static Map<String,AttKeySet> map = new HashMap<String,AttKeySet>();

	private static final String OG_FOR_SMPL  = "og.formSample";
	private static final String ENCODE = "UTF-8";

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

	/**
	 * Doclet のエントリポイントメソッドです。
	 *
	 * @og.rev 5.7.1.1 (2013/12/13) タグのインデントを止める。
	 *
	 * @param root ドキュメントルートオブジェクト
	 *
	 * @return 正常実行時 true
	 */
	public static boolean start( final RootDoc root ) {
		String version = DocletUtil.getOption( "-version" , root.options() );
		String file    = DocletUtil.getOption( "-outfile" , root.options() );

		mapInit();

		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.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] ;
			if( ! classDoc.isPublic() ) { continue; }

			AttKeySet attSet = getAttGroupName( classDoc ) ;

			if( attSet == null ) { continue; }		// map に登録されていない。

			String attKey   = attSet.getAttKey( classDoc.name() );

			if( attKey == null ) { continue; }		// 対象クラス名が、一致しない。

			String attClass = classDoc.qualifiedName() ;	// Class Full Name
			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);

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<classDoc>" );
			writer.printTag(   "<attClass>"		,attClass				,"</attClass>"		);
			writer.printTag(   "<seq>"			,attSet.getSeq()		,"</seq>"			);
			writer.printTag(   "<attKey>"		,attKey					,"</attKey>"		);
			writer.printTag(   "<valueName>"	,attSet.getValueName()	,"</valueName>"		);
			writer.printTag(   "<description>"	,desc					,"</description>"	);
			writer.printTag(   "<contents>"		,cmnt					,"</contents>"		);
			writer.printTag(   "<formSample>"	,smplTags				,"</formSample>"	);
			writer.printTag( "</classDoc>" );
		}
	}

	/**
	 * 処理する属性クラスのMapを初期化します。
	 * 指定できるのは、親クラスか、直接のインターフェースです。
	 *
	 * @og.rev 4.3.5.0 (2008/02/01) daemonパッケージ追加
	 * @og.rev 5.5.3.5 (2012/06/21) ChartWriter 削除、TransferExec,TransferRead 追加
	 * @og.rev 5.6.3.3 (2013/04/19) DBTableReport,CalendarData,DBConstValue,JspParserFilter,ConnectIF 追加
	 *
	 */
	private static void mapInit() {
		map.put( "org.opengion.hayabusa.db.Query"				, new AttKeySet( "Query"			,0, "queryType"		));
		map.put( "org.opengion.hayabusa.db.CellRenderer"		, new AttKeySet( "Renderer"			,1, "renderer"		));
		map.put( "org.opengion.hayabusa.db.CellEditor"			, new AttKeySet( "Editor"			,2, "editor"		));
		map.put( "org.opengion.hayabusa.db.DBType"				, new AttKeySet( "DBType"			,3, "dbType"		));
		map.put( "org.opengion.hayabusa.db.TableFilter"			, new AttKeySet( "TableFilter"		,4, "tableFilter"	));
		map.put( "org.opengion.hayabusa.db.Selection"			, new AttKeySet( "Selection"		,5, "selection"		));
		map.put( "org.opengion.hayabusa.html.ViewForm"			, new AttKeySet( "ViewForm"			,6, "viewFormType"	));
		map.put( "org.opengion.hayabusa.io.TableWriter"			, new AttKeySet( "TableWriter"		,7, "writerClass"	));
		map.put( "org.opengion.hayabusa.io.TableReader"			, new AttKeySet( "TableReader"		,8, "readerClass"	));
//		map.put( "org.opengion.hayabusa.io.ChartWriter"			, new AttKeySet( "ChartWriter"		,9, "chartClass"	));		// 5.5.3.5 (2012/06/21)
		map.put( "org.opengion.hayabusa.resource.CalendarQuery"	, new AttKeySet( "CalendarQuery"	,10, "calDB"		));
		map.put( "org.opengion.fukurou.process.HybsProcess"		, new AttKeySet( "Process"			,11, "process"		));
		map.put( "org.opengion.fukurou.transfer.TransferExec"	, new AttKeySet( "TransferExec"		,12, "kbExec"		));		// 5.5.3.5 (2012/06/21)
		map.put( "org.opengion.fukurou.transfer.TransferRead"	, new AttKeySet( "TransferRead"		,13, "kbRead"		));		// 5.5.3.5 (2012/06/21)
		map.put( "org.opengion.fukurou.util.HybsTimerTask"		, new AttKeySet( "Daemon"			,14, "daemon"		));		// 4.3.4.4 (2009/01/01)

		map.put( "org.opengion.hayabusa.report.DBTableReport"	, new AttKeySet( "DBTableReport"	,15, "tableReport"	));		// 5.6.3.3 (2013/04/19)
		map.put( "org.opengion.hayabusa.resource.CalendarData"	, new AttKeySet( "CalendarData"		,16, "calData"		));		// 5.6.3.3 (2013/04/19)
		map.put( "org.opengion.hayabusa.db.DBConstValue"		, new AttKeySet( "DBConstValue"		,17, "cnstVal"		));		// 5.6.3.3 (2013/04/19)
		map.put( "org.opengion.fukurou.xml.JspParserFilter"		, new AttKeySet( "JspCreate"		,18, "jspParser"	));		// 5.6.3.3 (2013/04/19)
		map.put( "org.opengion.fukurou.util.ConnectIF	"		, new AttKeySet( "ConnectIF"		,19, "connIF"		));		// 5.6.3.3 (2013/04/19)
	}

	/**
	 * 指定の ClassDoc が、処理する属性クラスのMapに含まれている場合、
	 * その AttKeySet クラスのオブジェクトを返します。
	 * 存在しない場合、null を返します。
	 *
	 * @param	classDoc ClassDocオブジェクト
	 *
	 * @return	ClassDocに対応する AttKeySetオブジェクト
	 */
	private static AttKeySet getAttGroupName( final ClassDoc classDoc ) {
		if( classDoc == null ) { return null; }

		String classFullName = classDoc.qualifiedName() ;
		AttKeySet attKey = map.get( classFullName );
		if( attKey == null ) {
			Type type = classDoc.superclassType();	// 親クラスタイプ
			if( type != null ) {
				attKey = getAttGroupName( type.asClassDoc() );	// 親クラス
			}

			if( attKey == null ) {
				Type[] itface = classDoc.interfaceTypes();		// 直近インターフェース
				for( int i=0; i<itface.length; i++ ) {
					attKey = getAttGroupName( itface[i].asClassDoc() );
					if( attKey != null ) { break; }
				}
			}
		}
		return attKey;
	}

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