/* Simple argument-parsing class
 * Copyright (C) 2001 Patrick E. Pelletier <ppelleti@users.sourceforge.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/**
 * Copyright (C) 2006-2007  NTT DATA CORPORATION
 * 
 * Version: 1.0.0 2007/04/01
 *  
 */
package net.cellcomputing.himawari.argparse;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import net.cellcomputing.himawari.accessory.Pair;
import net.cellcomputing.himawari.accessory.STLVector;
import net.cellcomputing.himawari.accessory.primitive.p_String;
import net.cellcomputing.himawari.accessory.primitive.p_boolean;
import net.cellcomputing.himawari.accessory.primitive.p_double;
import net.cellcomputing.himawari.accessory.primitive.p_int;
import net.cellcomputing.himawari.exception.XqException;

/**
 * 引数の解析を行うクラス
 * @author NTT DATA Corporation
 */
public strictfp class ArgParse {

	/**
	 * オプションが一個以上あることを示す。
	 */
	public static int SEP_NONE = -1;//(例："--foo　one --foo two") 
	/**
	 * オプションに対するパラメータが複数個必要であることを示す。
	 */
	public static int SEP_ARGV = -2;//(例：、"--foo one two")

//			ASCII文字を使用する場合、argv(String配列)を１要素だけ使用。
//			文字で分けることにより複数の要素にします。 (例:"--foo one,two")


	
	/**
	 * コンストラクタ
	 */
	public ArgParse()
	{
		d = new ArgParseInternalData();
		d.allowOneCharOptionsToBeCombined = false;
		d.allowUnrecognizedOptions = false;
	}
	
	/**
	 * デストラクタ
	 */
	public void destruct()
	{
		d.options.clear();
		d.options = null;
	}
	
	/**
	 * 複数オプションが連続入力されているかを判断。
	 */
	public void allowOneCharOptionsToBeCombined() // long but descriptive :)
	{
//		ダブルハイフンで指定。
//		ただし、このメソッドが呼ばれない場合は、 ハイフンがシングルでもダブルでも同じ意味になる。
//		例："--bar" とすることで、"-bar" を "-b -a -r"　として扱う。(前提：エイリアス機能を使用していること)
		d.allowOneCharOptionsToBeCombined = true;
	}

	/**
	 * 認識されていないオプションかどうかを判断。 
	 */
	public void allowUnrecognizedOptions()
	{
//		Normally, parse() will return an error if there are any
//		unrecognized options.  But if you call this method before
//		calling parse(), then unrecognized options will go into
//		the leftovers, without causing an error.
//		オプションの場合は、parse()はエラーを繰り返す。
//		parse()の前に、この関数を呼ぶと、認識されていないオプションは、
//		エラーを出さずにleftoverメソッドへ
		 
		d.allowUnrecognizedOptions = true;
	}
	
//	以下に宣言されている関数について	
//	"--foo" will set *value to true.  If allow_negation is true,
//	then "--nofoo" will set *value to false.
//	"--option_name"　のときは　value = true
//	allow_negation がtrueであり、"--nooption_name" のときは value = false
	/**
	 *	オプション情報の追加をする。<br>
	 * 　呼び出し条件：	オプションの詳細パラメータがboolean型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param value	オプションの詳細パラメータ
	 */
	public void argFlag(String name, String usage, p_boolean value)
	{		
		argFlag(name, usage, value, true);
	}
		
	/**
	 * オプション情報の追加をする。<br>
	 * 呼び出し条件：	allow_negationに初期値が与えられている場合。<br>
	 *　				オプションの詳細パラメータがboolean型の場合に呼ばれる。
	 * @param name	オプション名	
	 * @param usage	用法メッセージ
	 * @param value	オプションの詳細パラメータ
	 * @param allow_negation	否定状態	
	 */
	public void argFlag(String name, String usage, p_boolean value, 
			boolean allow_negation)
	{		 
		d.addOption(name, new FlagHandler(usage, value, allow_negation));
	}
	
	/**
	 * オプション情報の追加をする。<br>
	 * 呼び出し条件：	オプションの詳細パラメータがint型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param value	オプションの詳細パラメータ
	 */
	public void argInt(String name, String usage, p_int value)
	{
		d.addOption(name, new IntHandler(usage, value));
	}
	
	/**
	 * オプション情報の追加をする。<br>
	 * 呼び出し条件：	オプションの詳細パラメータ(複数)がint型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 */
	public void argInts(String name, String usage, STLVector<Integer> values)
	{
		argInts(name, usage, values, SEP_NONE, -1);
	}
	/**
	 * オプション情報の追加をする。<br>
	 *　呼び出し条件：	separatorの初期値が与えられている場合。<br>
	 * 				オプションの詳細パラメータ(複数)がint型の場合に呼ばれる。
	 * @param name	オプション名	
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator	オプション入力状態
	 */
	public void argInts(String name, String usage, STLVector<Integer> values, int separator)
	{
		argInts(name, usage, values, separator, -1);
	}
	/**
	 *　オプション情報を追加する。<br>
	 *　呼び出し条件：	separator, countの初期値が与えられいる場合<br>
	 *　				オプションの詳細パラメータ(複数)がint型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator	オプション入力状態
	 * @param count	詳細パラメータの数
	 */
	public void argInts(String name, String usage,
			STLVector<Integer> values, int separator, int count)
	{
		d.addOption(name, new IntsHandler(usage, values, separator, count));
	}
	
	/**
	 * オプション情報を追加する。<br>
	 * 呼び出し条件：	オプションの詳細パラメータがfloat型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param value	オプションの詳細パラメータ
	 */
	public void argFloat(String name, String usage, p_double value)
	{
		d.addOption(name, new FloatHandler(usage, value));
	}
	
	/**
	 * オプション情報を追加する。<br>
	 * 呼び出し条件：	オプションの詳細パラメータ(複数)がfloat型の場合に呼ばれる。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 */
	public void argFloats(String name, String usage, STLVector<Double> values)
	{
		argFloats(name, usage, values, SEP_NONE, -1);
	}
	
	/**
	 * オプション情報を追加する。<br>
	 * 呼び出し条件：	separatorの初期値が与えられている場合。<br>
	 * 				オプションの詳細パラメータ(複数)がfloat型の場合。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator	オプション入力状態
	 */
	public void argFloats(String name, String usage, STLVector<Double> values, int separator)
	{
		argFloats(name, usage, values, separator, -1);
	}
	/**
	 * オプション情報を追加する。<br>
	 * 呼び出し条件：	saparator, countの初期値が与えられている場合。 <br>
	 * 				オプションの詳細パラメータ(複数)がfloat型の場合。
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator	オプション入力状態
	 * @param count	詳細パラメータの数
	 */
	public void argFloats(String name, String usage,
			STLVector<Double> values, int separator, int count)
	{
		d.addOption(name, new FloatsHandler(usage, values, separator, count));
	}
	
	/**
	 * オプション情報を追加する。<br>
	 * 呼び出し条件：	オプションの詳細パラメータがString型の場合
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param value	オプションの詳細パラメータ
	 */
	public void argString(String name, String usage, p_String value)
	{
		d.addOption(name, new StringHandler(usage, value));
	}
	
	/**
	 *　オプション情報を追加する。 <br>
	 * 呼び出し条件：	オプションの詳細パラメータ(複数)がString型の場合
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 */
	public void argStrings(String name, String usage, STLVector<String> values)
	{
		argStrings(name, usage, values, SEP_NONE, -1);
	}
	
	/**
	 * オプション情報を追加する。<br>
	 *　呼び出し条件：	オプションの詳細パラメータ(複数)がString型の場合<br>
	 *				saparatorの初期値が与えられている場合 
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator	オプション入力状態
	 */
	public void argStrings(String name, String usage, STLVector<String> values, int separator)
	{
		argStrings(name, usage, values, separator, -1);
	}
	
	/**
	 * オプション情報を追加する。<br>
	 *　呼び出し条件：	オプションの詳細パラメータ(複数)がString型の場合<br>
	 *				saparator, countの初期値が与えられている場合 
	 * @param name	オプション名
	 * @param usage	用法メッセージ
	 * @param values	オプションの詳細パラメータ
	 * @param separator　オプション入力状態
	 * @param count	詳細パラメータの数
	 */
	public void argStrings(String name, String usage,
			STLVector<String> values, int separator, int count)
	{
		d.addOption(name, new StringsHandler(usage, values, separator, count));
	}
	
	/**
	 * オプション名を置き換える。
	 * @param realname	実際のオプション名	
	 * @param aliasname	置き換えるオプション名
	 */
	public void alias(String realname, String aliasname)
	{
//		Makes "aliasname" work just like "realname".  Note that
//		"realname" can be a negated flag name (if the flag allows
//		negation), so you can make "--fooless" mean "--nofoo", for
//		exanple.
		
//		=> "realname"が無効な名のときは注意！
//		=> 例："--nofoo" を "--fooless"　という意味にできる。。
		
		if(d.aliases.get(aliasname) == null)
		{
			d.aliases.put(aliasname, realname);
			OptionHandler e = d.options.get(realname);
			
			if(e != null){
				e.aliases.add(aliasname);
			}
		}
	}
	
	/**
	 * 入力テキストを、そのまま用法メッセージリストへ挿入する。
	 * @param text	入力テキスト
	 */
	public void usageHeader(String text)
	{
//		 This inserts literal text into the usage message.  The order
//	     is significant with respect to calls to argFlag, argInt, etc.
//	     The most common use of this would be to add the
//	     "Usage: blech [options] files" at the top, but it can also be
//	     used to make different "sections" of options (give the --help
//	     option to GNU tar to see what I mean) or to make a footer at the
//	     end of the usage.  Your string can contain newlines, but it
//	     shouldn't end in a newline unless you want an extra blank line.
//	     The "indent" value specifies how to line up
//	     columns of individual usage messages in the follow section.
//	     (Again, refer to the GNU tar usage message to see what I mean.)
		
		usageHeader(text, 25);
	}
	
	/**
	 *　入力テキストを、そのまま用法メッセージリストへ挿入する<br>
	 *　入力テキストを示唆する初期値が与えられている場合
	 * @param text	入力テキスト
	 * @param indent	入力テキストを示唆する数値 
	 */
	//Java化実装段階では使用されていない
	public void usageHeader(String text, int indent)
	{
		Pair<Integer, String> tmppair = new Pair<Integer, String>(indent, text);
		d.usage.add(tmppair);
	}
	
	/**
	 * オプションとして入力された文字の解析を行う。
	 * @param argc	引数の数
	 * @param argv	引数の文字列
	 * @return (d.errmsg.length()== 0); 
	 */
	public boolean parse(int argc, String[] argv)
	{
		OptionHandler argeater = null;	//解析済みのオプションデータ
		OptionHandler oneshotargeater = null;	//パラメータを一つだけもつ
		boolean endofoptions = false;
		String eatername = "";	//解析済みのオプションデータ

//		These statments are to reset things so we can call
//		parse() more than once.  This probably isn't a very
//		useful feature, though, so maybe it should be taken
//		out to simplify things.
		
		//各オプションを未解析の状態にする
		d.leftovers.clear();
		Iterator<OptionHandler> e = d.options.values().iterator();
		while(e.hasNext()){
			OptionHandler value = e.next();
			value.reset();
		}	 
		d.errmsg = "";
		
		//オプション名の解析を行う
		try
		{
			for (int i = 0; i < argc; i++) 
			{
				if (d.errmsg.length() > 0){	//エラーメッセージがある場合、リターンへ
					break;
				}
				if (endofoptions) {	//オプションの解析が終了
					d.leftovers.add(argv[i]);
					continue;
				}
				if (oneshotargeater != null) {
					d.errmsg = oneshotargeater.handleargsplit(argv[i], eatername);
					oneshotargeater = null;
					continue;
				}
				
//				The argv[i][1] == '\0' part below is so we treat "-"
//				by itself as a regular argument, not as an option,
//				since it's a common abbreviation for stdin, and isn't
//				really meaningful as an option.	　

//				オプションのパラメータを解析
				if (( argv[i].charAt(0) != '-' || argv[i].length() <= 1 ) && 
						( argeater == null || ( argeater.count > 0 || argeater.count == -1 ) ) ) 
				{
					if (argeater == null){	//どこのオプションにも属さないパラメータとして判断する
						d.leftovers.add(argv[i]);
					}
					else{	//文字列の有効性を調べる
						d.errmsg = argeater.handleargsplit(argv[i], eatername);
					}
					continue;
				}

//				argv[i]の一文字目は'-'であることは明らか(仮定)なため省略する。argv[]の'-'を除く部分をコピー
				argeater = null;
				String rest = argv[i].substring(1);
				
				int point = 0; //"-"の数をカウント
				point = 0;
				
//				"-"が2つの場合⇒"--foo" 
//				if (rest.charAt(point) == '-') {
				if( rest.startsWith("-") ){
					point++;
					
					//"--"だけの場合
					if (rest.length() <= point) { 
						endofoptions = true; //オプション解析終了フラグを立てる。
						continue;
					}
				}
				
				int ndashes = point + 1;//"-"　の数
				int l = 0;
				while(l < rest.length() && rest.charAt(l) != ':' && rest.charAt(l) != '=')
					l++;	//オプション名の文字数だけをカウント
								
//				Do all the bundled options except the last one.
//				We will let the last one fall through to the regular
//				logic.  This is because the last option could take
//				an argument, but the others can't. 
				
//				"-"が一つ、かつオプション連続入力されている場合　(-a, -b, -c => -abc)
				if (d.allowOneCharOptionsToBeCombined && ndashes == 1 && l > 1)
				{
					for (int j = 0; j < l - 1; j++) 
					{
						String onechar = String.valueOf(rest.charAt(j));
						p_boolean no = new p_boolean();
						OptionHandler oh = d.findOption(onechar, no);
						
//						オプションデータが見つからなかった場合
						if (oh == null) {
							d.errmsg = argv[i] + ": '" + onechar + 
										"' is an unrecognized option";
							/* if only this was Shading Language and I could
							 * say "break 2"... */
							throw new XqException();
						}
//						オプションのパラメータが入力されていない場合
						if (oh.takesarg()) {
							d.errmsg = argv[i] + ": '" + onechar +	"' requires an argument";
							throw new XqException();
						}

						d.errmsg = oh.handleargsplit(no.value ? "no" : "", argv[i]);
//						オプションが無効な場合
						if (d.errmsg.length() > 0){
							throw new XqException();
						}
					}
					point += l - 1;
					l = 1;
				}

				p_boolean no = new p_boolean();
			    String option_name = rest.substring(point, (l+point > rest.length())?rest.length():l+point );
				OptionHandler oh = d.findOption(option_name, no);
				
				//オプションに対応する情報(OptionHandlerオブジェクト)がない場合
				if (oh == null) {
					
					//See if there is an option which matches the first character and takes and
					//argument, if so, match it as an option/argument pair.
					String onechar = rest.substring(point, (1+point > rest.length()) ? rest.length():1+point);;
					p_boolean no2 = new p_boolean();
					oh = d.findOption(onechar, no2); 
					
					//エイリアスされている&&パラメータがない
					if( oh != null && oh.takesarg()) {
						l = 0;//Setup the lenght to point to the remainder.
					}
					else {
						//オプション名が認識されている⇒一時保存　、されていない⇒エラー
						if (d.allowUnrecognizedOptions) {
							d.leftovers.add(argv[i]);
							continue;
						}
						else {
							d.errmsg = argv[i] + ": unrecognized option";
							break;
						}
					}
				}
			
				//パラメータを持つオプションの場合
				if (oh.takesarg()){
					if (l < rest.length()) { 
						d.errmsg = oh.handleargsplit(rest.substring(point + l + 1), argv[i]);
					}
					else if (oh.separator != SEP_ARGV) {
						oneshotargeater = oh;
					}
					if (oh.separator == SEP_ARGV) {
						argeater = oh;
					}
					eatername = argv[i];
				} 
				else {
					if (l < rest.length()) {
						d.errmsg = argv[i] + ": doesn't take an argument";
					}
					else {
						d.errmsg = oh.handleargsplit (no.value ? "no" : "", argv[i]);
					}
				}
			}
		}catch( XqException err){
		}finally{	
			if (d.errmsg.length() == 0 && oneshotargeater != null){
				d.errmsg = "missing an argument at end of command line";
			}
		}
		return (d.errmsg.length() == 0);
	}
	
	
	// If parse() returns false, this method will give you an
	// error message that describes what's wrong.  Note that
	// although it's legal to call parse() more than once on the
	// same ArgParse object, only the most recent error message is
	// retained.
	/**
	 *　エラーメッセージを返す。 
	 * @return　d.errmsg
	 */
	public String errmsg()
	{
		return d.errmsg;
	}
	
//	 Returns a usage string made up of the usage messages of
//	 the individual arguments, with things from usageHeader()
//	 interspersed at the appropriate places.  The string returned
//	 contains embedded newlines, and also a trailing newline.
//	そのメッセージは、埋め込まれていたもの、つくられたものを含んでいる。
	/** 
	 * ヘルプメッセージ(＝各オプションの用法メッセージ)を返す。
	 * @return　ret
	 */
	public String usagemsg()
	{
		String ret = new String();
		int indent = 25;
		
		Iterator<Pair<Integer, String>> ite = d.usage.iterator();
		while(ite.hasNext())
		{
			Pair<Integer, String> e = ite.next();
			if(e.first.intValue() >= 0)
			{
				indent = e.first;
				ret += e.second;
				ret += '\n';
			}
			else
			{
				OptionHandler foundoption = d.options.get(e.second);
				if(foundoption == null)
					continue;
				OptionHandler oh = foundoption; 
				ArrayList<String> names = oh.aliases;
				names.add(e.second);
				
				Collections.sort( names, new CompareByLength() );
				
				boolean first = true;
				String line = "  ";
				int it = names.size();
				
//				オプションのヘルプメッセージを作成
				for(int k = 0; k < it ; k++) 
				{
					if(first){
						if(d.allowOneCharOptionsToBeCombined && it != 1)
							line += "    ";
					}
					else{
						line += ", ";
					}
					first = false;
					if(d.allowOneCharOptionsToBeCombined && it != 1)
						line += '-';
					line += '-';
					line += names.get(k);
				}
				
				int it2 = oh.usage.length();
				for (int k = 0; k < it2; k++) {
					if (oh.usage.charAt(k) == '\n') {
						ret += line;
						ret += '\n';
						line = "";
					}
					else if (oh.usage.charAt(k) == '\t' ) {
						int spaces = indent - line.length();
						if (spaces < 1) {
							/* break onto a new line if we're already past
							 * the indent */
							ret += line;
							ret += '\n';
							line = "";
							spaces = indent;
						}
						for(int n = 0; n < spaces; n++){
							line += " ";
						}
					} 
					else {
						line += oh.usage.charAt(k);
					}
				}
				ret += line;
				ret += '\n';
			}
		}
		return ret;
	}
	
	
	/**
	 * どのオプションにも属さないパラメータ情報を持つクラス
	 * @return　d.leftovers
	 */
	public STLVector<String> leftovers()
	{
		return d.leftovers;
		
	}
	
	/**
	 * オプションに関する情報を持つオブジェクト
	 */
	private ArgParseInternalData d;
}
