/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.utils;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * メッセージの埋め込みを解決するプロセッサの正規表現を使用した簡易実装。
 * 本クラスのインスタンスは複数のスレッドで同時に使用できる。
 * @author nakamura
 *
 */
public class EmbeddingResolver implements EmbeddingResolveable {
	private final char abstractChar;
	private final char leftParenthesis;
	private final Pattern pattern;
	
	/**
	 * コンストラクタ。
	 * @param abstractChar テンプレートであることを表すためにメッセージの値の先頭に付ける識別子。
	 * @param leftParenthesis 埋め込みキーの開始を表す左括弧。
	 * @param rightParenthesis 埋め込みキーの開始を表す右括弧。
	 */
	public EmbeddingResolver(final char abstractChar, final char leftParenthesis, final char rightParenthesis){
		// TODO 文字エスケープ対応
		
		this.abstractChar = abstractChar;
		this.leftParenthesis = leftParenthesis;
		pattern = Pattern.compile("(\\" + leftParenthesis + "([^\\}]+)\\" + rightParenthesis + ")");
	}
	
	/**
	 * コンストラクタ。
	 * テンプレート識別子を"#"、左括弧を"{"、右括弧を"}"とする。
	 *
	 */
	public EmbeddingResolver(){
		this('#', '{', '}');
	}
	
	public void resolve(final Map<String,Object> map){
		for(final String key:map.keySet()){
			getValueFromKey(map, key);
		}
	}
	
	private String resolveValueFromAbstractValue(final Map<String,Object> map, final String value){
		final Matcher matcher0 = pattern.matcher(value);
		final Matcher matcher1;
		if(!matcher0.find()){
			return null;
		}else{
			final Object objectValue = map.get(matcher0.group(2));
			if(objectValue == null || !(objectValue instanceof String)){
				return null;
			}
			matcher1 = pattern.matcher((String)objectValue);
		}
		// JPSE5 の java.util.regex は java.lang.Appendable に対応していない。
		final StringBuffer sb = new StringBuffer();
		while(matcher0.find() && matcher1.find()){
			final String newValue = getValueFromKey(map, matcher0.group(2));
			if(newValue == null){
				return null;
			}
			matcher1.appendReplacement(sb, newValue);
		}
		matcher1.appendTail(sb);
		return sb.toString();
	}
	
	private String resolveValueFromDirectValue(final Map<String,Object> map, final String value){
		final StringBuffer sb = new StringBuffer();
		final Matcher matcher = pattern.matcher(value);
		while(matcher.find()){
			final String newValue = getValueFromKey(map, matcher.group(2));
			if(newValue == null){
				return null;
			}
			matcher.appendReplacement(sb, newValue);
		}
		matcher.appendTail(sb);
		return sb.toString();
	}
	
	private String resolveValue(final Map<String,Object> map, final String value){
		if(value.charAt(0) == abstractChar){
			return resolveValueFromAbstractValue(map, value);
		}else if(value.indexOf(leftParenthesis) >= 0){
			return resolveValueFromDirectValue(map, value);
		}else{
			return null;
		}
	}
	
	public String resolve(final Map<String,Object> map, final String value){
		Object value1 = map.get(value);
		if(value1 == null){
			value1 = resolveValue(map, value);
		}
		return (String)value1;
	}
	
	private String getValueFromKey(final Map<String,Object> map, final String key){
		// TODO 循環参照検出に対応
		final Object objectValue = map.get(key);
		if(objectValue == null || !(objectValue instanceof String)){
			return null;
		}
		final String value = (String)objectValue;
		if(value.indexOf(leftParenthesis) < 0){
			return value;
		}else{
			final String newValue = resolveValue(map, value);
			if(newValue == null){
				return null;
			}
			map.put(key, newValue);
			return newValue;
		}
	}
}
