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


import jdk.javadoc.doclet.DocletEnvironment	 ;
// import jdk.javadoc.doclet.Doclet  ;
// import jdk.javadoc.doclet.Reporter ;
// import javax.lang.model.element.Element	;
import javax.lang.model.element.Modifier ;
import javax.lang.model.element.TypeElement;
// import javax.lang.model.element.ElementKind	;
// import javax.lang.model.element.VariableElement;
// import javax.lang.model.SourceVersion	 ;
import javax.lang.model.type.TypeMirror;
// import javax.lang.model.type.PrimitiveType;
import javax.lang.model.util.ElementFilter	 ;
import javax.lang.model.util.Elements ;
// import javax.lang.model.util.Types	 ;
import javax.tools.Diagnostic.Kind	 ;
import com.sun.source.doctree.DocCommentTree  ;
import com.sun.source.util.DocTrees  ;
import com.sun.source.doctree.DocTree  ;

// import java.util.Locale ;
import java.util.Set;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;

// import java.io.IOException;
// import java.io.File;
// import java.io.PrintWriter;

// import org.opengion.fukurou.util.FileUtil;
// import org.opengion.fukurou.util.StringUtil;

/**
 * ソースコメントから、パラメータ情報を取り出す Doclet クラスです。
 * og.paramLevel タグと og.cryptography タグを切り出します。
 * これらは、ｼｽﾃﾑパラメータとしてGE12ﾃｰﾌﾞﾙに設定される値をクラスより抽出する
 * のに使用します。
 *
 * @version  7.3
 * @author	Kazuhiko Hasegawa
 * @since	 JDK11.0,
 */
public class DocTreePlugin extends AbstractDocTree {
	private static final String OG_FOR_SMPL  = "og.formSample";

	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	// 6.4.3.3 (2016/03/04) 匿名クラスとインスタンス初期化子による、Mapの初期化処理。
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,AttKeySet> ATT_KEY_MAP = new HashMap<>();
	static {
		int no = 0;				// 7.2.2.0 (2020/03/27) 連番の自動生成
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Query"					, new AttKeySet( "Query"			,no++, "queryType"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellRenderer"			, new AttKeySet( "Renderer"			,no++, "renderer"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.CellEditor"				, new AttKeySet( "Editor"			,no++, "editor"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBType"					, new AttKeySet( "DBType"			,no++, "dbType"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.TableFilter"				, new AttKeySet( "TableFilter"		,no++, "tableFilter"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.Selection"				, new AttKeySet( "Selection"		,no++, "selection"		));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.db.DBConstValue"			, new AttKeySet( "DBConstValue"		,no++, "cnstVal"		));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.hayabusa.html.ViewForm"				, new AttKeySet( "ViewForm"			,no++, "viewFormType"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableWriter"				, new AttKeySet( "TableWriter"		,no++, "writerClass"	));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.io.TableReader"				, new AttKeySet( "TableReader"		,no++, "readerClass"	));
//		ATT_KEY_MAP.put( "org.opengion.hayabusa.report.DBTableReport"		, new AttKeySet( "DBTableReport"	,no++, "tableReport"	));		// 7.0.1.5 (2018/12/10) 削除
		ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarQuery"		, new AttKeySet( "CalendarQuery"	,no++, "calDB"			));
		ATT_KEY_MAP.put( "org.opengion.hayabusa.resource.CalendarData"		, new AttKeySet( "CalendarData"		,no++, "calData"		));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.fukurou.process.HybsProcess"			, new AttKeySet( "Process"			,no++, "process"		));
//		ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferExec"		, new AttKeySet( "TransferExec"		,no++, "kbExec"			));		// 5.5.3.5 (2012/06/21)
//		ATT_KEY_MAP.put( "org.opengion.fukurou.transfer.TransferRead"		, new AttKeySet( "TransferRead"		,no++, "kbRead"			));		// 5.5.3.5 (2012/06/21)
		ATT_KEY_MAP.put( "org.opengion.fukurou.util.HybsTimerTask"			, new AttKeySet( "Daemon"			,no++, "daemon"			));		// 4.3.4.4 (2009/01/01)
		ATT_KEY_MAP.put( "org.opengion.fukurou.util.ConnectIF	"			, new AttKeySet( "ConnectIF"		,no++, "connIF"			));		// 5.6.3.3 (2013/04/19)
		ATT_KEY_MAP.put( "org.opengion.fukurou.xml.JspParserFilter"			, new AttKeySet( "JspCreate"		,no++, "jspParser"		));		// 5.6.3.3 (2013/04/19)
	}

	private String version	;
	private String outfile	;

//	private DocTrees docUtil;
//	private Elements eleUtil ;

	/**
	 * Doclet のエントリポイントメソッドです(昔の startメソッド)。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param docEnv ドックレットを1回呼び出す操作環境
	 *
	 * @return 正常実行時 true
	 */
	@Override
	public boolean run( final DocletEnvironment docEnv ) {
		try( DocTreeWriter writer = new DocTreeWriter( outfile,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( docEnv,writer );
			writer.printTag( "</javadoc>" );
		}
		catch( final Throwable th ) {
			reporter.print(Kind.ERROR, th.getMessage());
		}

		return true;
	}

	/**
	 * DocletEnvironmentよりコンテンツを作成します。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param docEnv	ドックレットの最上位
	 * @param writer	DocTreeWriterオブジェクト
	 */
	private void writeContents( final DocletEnvironment docEnv, final DocTreeWriter writer ) {
//		docUtil = docEnv.getDocTrees();
//		eleUtil = docEnv.getElementUtils();

//		// get the DocTrees utility class to access document comments
		final DocTrees docUtil = docEnv.getDocTrees();
		final Elements eleUtil  = docEnv.getElementUtils();

		// クラス単位にループする。
		for( final TypeElement typEle : ElementFilter.typesIn(docEnv.getIncludedElements())) {
			if( !typEle.getModifiers().contains( Modifier.PUBLIC ) ) { continue; }

			final String fullName	= String.valueOf( typEle.getQualifiedName() ) ;
//			final String fullName = String.valueOf(typEle);
	//		System.out.println(typEle.getKind() + ":" + fullName);
			writer.setClassName( fullName );

			final AttKeySet attSet = getAttGroupName( typEle,eleUtil ) ;
			if( attSet == null ) { continue; }		// map に登録されていない。

			final int ed = fullName.lastIndexOf( '.' );
			String attKey = null;
			if( ed > 0 ) {
				final String name = fullName.substring( ed+1 );
				attKey = attSet.getAttKey( name );
			}
			if( attKey == null ) { continue; }		// 対象クラス名が、一致しない。

			final DocCommentTree docTree = docUtil.getDocCommentTree(typEle);		// ﾄﾞｷｭﾒﾝﾃｰｼｮﾝ･ｺﾒﾝﾄが見つからない場合、null が返る。

			final List<? extends DocTree> desc	= docTree == null ? EMPTY_LIST : docTree.getFirstSentence();
			final List<? extends DocTree> cmnt	= docTree == null ? EMPTY_LIST : docTree.getFullBody();

			final Map<String,List<String>> blkTagMap = blockTagsMap(docTree);
			final String smplTags	= getBlockTag( OG_FOR_SMPL, blkTagMap, "" );

//			String smplTags = "";
//			if( docTree != null ) {
//				for( final DocTree dt : docTree.getBlockTags() ) {
//					final String tag = String.valueOf(dt);
//					if( tag.contains( OG_FOR_SMPL ) ) {
//						smplTags = tag.substring( OG_FOR_SMPL.length() + 1 ).trim();
//					}
//				}
//			}

			// 5.7.1.1 (2013/12/13) タグのインデントを止める。
			writer.printTag( "<classDoc>" );
			writer.printTag( "  <attClass>"		,fullName				,"</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>" );
		}
	}

	/**
	 * 指定の ClassDoc が、処理する属性クラスのMapに含まれている場合、
	 * その AttKeySet クラスのオブジェクトを返します。
	 * 存在しない場合、null を返します。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @param	typEle TypeElementオブジェクト
	 * @param	eleUtil Elementsユーティリティ
	 *
	 * @return	typEleに対応する AttKeySetオブジェクト
	 */
	private static AttKeySet getAttGroupName( final TypeElement typEle,final Elements eleUtil ) {
		if( typEle == null ) { return null; }

		final String fullName = String.valueOf(typEle);
		AttKeySet attKey = ATT_KEY_MAP.get( fullName );
		if( attKey == null ) {
			for( final TypeElement tp : eleUtil.getAllTypeElements(fullName) ) {
				if( typEle.equals( tp ) ) { continue; }
				attKey = getAttGroupName( tp,eleUtil );
				if( attKey != null ) { return attKey; }
			}

			final TypeMirror stype = typEle.getSuperclass();			// 親クラスタイプ
			if( stype != null ) {
				final String suCls = String.valueOf( stype );
				attKey = ATT_KEY_MAP.get( suCls );	// 親クラス
				if( attKey == null ) {
					for( final TypeElement tp : eleUtil.getAllTypeElements(suCls) ) {
						if( typEle.equals( tp ) ) { continue; }
						attKey = getAttGroupName( tp,eleUtil );
						if( attKey != null ) { return attKey; }
					}
				}
			}

			if( attKey == null ) {
				for( final TypeMirror itype : typEle.getInterfaces() ) {		// 直近インターフェース
					final String intFce = String.valueOf( itype );
					attKey = ATT_KEY_MAP.get( intFce );
					if( attKey != null ) { return attKey; }
					else {
						for( final TypeElement tp : eleUtil.getAllTypeElements(intFce) ) {
							if( typEle.equals( tp ) ) { continue; }
							attKey = getAttGroupName( tp,eleUtil );
							if( attKey != null ) { return attKey; }
						}
					}
				}
			}
		}

		return attKey;
	}

	/**
	 * 属性情報を管理する、AttKeySet クラスです。
	 *
	 * @og.rev 7.3.0.0 (2021/01/06) 新しいJavaDoc対応
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class AttKeySet {
		private final String searchKey ;
		private final int    len ;
		private final String seq ;
		private final String valueName ;

		/**
		 * コンストラクター
		 *
		 * @param searchKey  検索キー
		 * @param seq        シーケンス番号
		 * @param valueName  属性名
		 *
		 */
		/* default */ AttKeySet( final String searchKey,final int seq,final String valueName ) {
			this.searchKey		= searchKey ;
			this.seq	        = String.valueOf( seq );
			this.valueName		= valueName ;

			len = searchKey.length();
		}

		/**
		 * シーケンス番号を返します。
		 *
		 * @return シーケンス番号
		 *
		 */
		/* default */ String getSeq() {
			return seq;
		}

		/**
		 * 属性名を返します。
		 *
		 * @return 属性名
		 *
		 */
		/* default */ String getValueName() {
			return valueName;
		}

		/**
		 * クラス名の先頭一致の場合の、**** 部分を返します。
		 * インターフェースも扱えるように修正しましたので、先頭が _ の場合は、
		 * _ を削除して返します。
		 *
		 * @param name クラスの名称 (例：DBCellEditor_**** , ViewForm_****)
		 * @return クラス名の**** 部分
		 */
		/* default */ String getAttKey( final String name ) {
			// 6.1.0.0 (2014/12/26) refactoring
			String rtn = null;								// 一致しなかった。

			if( name.equals( searchKey ) ) {				// 完全一致：インターフェース
				rtn =  "(Interface)" + name ;
			}
			else if( name.indexOf( searchKey ) == 0 ) {		// 先頭一致した。
				rtn = name.substring( len );
				if( rtn.charAt(0) == '_' ) { rtn = rtn.substring( 1 ); }	// 先頭が _ の場合は、_ を削除
			}

			return rtn ;
		}
	}

	/**
	 * サポートされているすべてのオプションを返します。
	 *
	 * @return サポートされているすべてのオプションを含むセット、存在しない場合は空のセット
	 */
	@Override
	public Set<? extends Option> getSupportedOptions() {
		final Option[] options = {
			new AbstractOption( "-outfile", "-version" ) {

				/**
				 * 必要に応じてオプションと引数を処理します。
				 *
				 * @param  opt オプション名
				 * @param  arguments 引数をカプセル化したリスト
				 * @return 操作が成功した場合はtrue、そうでない場合はfalse
				 */
				@Override
				public boolean process(final String opt, final List<String> arguments) {
					if( "-outfile".equalsIgnoreCase(opt) ) {
						outfile = arguments.get(0);
					}
					else if( "-version".equalsIgnoreCase(opt) ) {
						version = arguments.get(0);
					}
					return true;
				}
			}
		};
		return new HashSet<>(Arrays.asList(options));
	}
}
