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

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Interpreter デザインパターンの Context 役。
 * 本クラスはスレッドセーフではない。本クラスはリクエストごとに生成することを想定している。
 * @author nakamura
 *
 */
public class ValidatorContext {
	private String id;
	private Map<String,List<Object>> inputMap;
	private final List<String> messageList;
	private final Map<String,List<String>> messageMap;
	
	private String tmpKey;
	private int tmpIndex;

	/**
	 * コンストラクタ。
	 *
	 */
	public ValidatorContext(){
		messageList = new ArrayList<String>();
		messageMap = new LinkedHashMap<String,List<String>>();
	}
	
	/**
	 * 浅いコピーコンストラクタ。
	 * @param base コピー元。
	 */
	public ValidatorContext(final ValidatorContext base){
		this.id = base.id;
		this.inputMap = base.inputMap;
		this.messageList = base.messageList;
		this.messageMap = base.messageMap;
		this.tmpKey = base.tmpKey;
		this.tmpIndex = base.tmpIndex;
	}
	
	/**
	 * 浅いコピー。{@link java.lang.Clonerable}は実装していない。
	 * @return このオブジェクトの浅いコピー。
	 */
	public ValidatorContext copy(){
		return new ValidatorContext(this);
	}
	
	/**
	 * このインスタンスの内容を出力する。
	 * テスト・デバッグ用。
	 * @param sb 出力先。
	 * @throws IOException {@link Appendable}が例外を投げた場合。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public void appendTo(final Appendable sb) throws IOException{
		sb.append("ValidatorContext dump information:");
		appendTo(sb, "id", id);
		appendTo(sb, "inputMap", inputMap);
		appendTo(sb, "messageList", messageList);
		appendTo(sb, "messageMap", messageMap);
		appendTo(sb, "tmpKey", tmpKey);
		appendTo(sb, "tmpIndex", tmpIndex);
	}
	
	private static void appendTo(final Appendable sb, final String label, final Object o) throws IOException{
		sb.append('\n');
		sb.append(',');
		sb.append(label);
		sb.append(':');
		if(o != null){
			sb.append(o.toString());
		}
	}
	
	/**
	 * メッセージを追加する。
	 * @param message メッセージ。
	 */
	public void add(final String message){
		List<String> list = messageMap.get(tmpKey);
		if(list == null){
			list = new ArrayList<String>();
			messageMap.put(tmpKey, list);
		}
		list.add(message);
		
		messageList.add(message);
	}
	
	/**
	 * キーとメッセージ一覧の{@link Map}を返す。
	 * 本メソッドは内部のインスタンスをそのまま返す。
	 * @return キーとメッセージ一覧の{@link Map}。
	 */
	public Map<String,List<String>> getMessageMap(){
		return messageMap;
	}

	/**
	 * メッセージ一覧の{@link List}を返す。
	 * 本メソッドは内部のインスタンスをそのまま返す。
	 * @return メッセージ一覧の{@link List}。
	 */
	public List<String> getMessageList() {
		return messageList;
	}

	/**
	 * idを返す。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @return id。
	 */
	public String getId() {
		return id;
	}

	/**
	 * id を設定する。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @param id id。
	 */
	public void setId(final String id) {
		this.id = id;
	}

	public Map<String,List<Object>> getInputMap() {
		return inputMap;
	}

	public void setInputMap(final Map<String,List<Object>> inputMap) {
		this.inputMap = inputMap;
	}

	/**
	 * {@link #getInputMap()}を検索するためのキーを返す。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @return 一時的に保持された単一のプロパティのキーを返す。
	 */
	public String getTmpKey() {
		return tmpKey;
	}

	/**
	 * {@link #getInputMap()}を検索するためのキーを設定する。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @param tmpKey 一時的に保持する単一のプロパティのキー。
	 */
	public void setTmpKey(final String tmpKey) {
		this.tmpKey = tmpKey;
	}

	/**
	 * {@link #getInputMap()}を検索するためのインデックスを返す。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @return 一時的に保持された単一のプロパティのインデックス。
	 */
	public int getTmpIndex() {
		return tmpIndex;
	}

	/**
	 * {@link #getInputMap()}を検索するためのインデックスを設定する。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @param tmpIndex 一時的に保持する単一のプロパティのインデックス。
	 */
	public void setTmpIndex(int tmpIndex) {
		this.tmpIndex = tmpIndex;
	}
	
	/**
	 * {@link #getTmpKey()}と{@link #getTmpIndex()}で{@link #getInputMap()}を検索し、
	 * その位置の値を返す。
	 * @return 一時的に保持された単一のプロパティの値。
	 */
	public Object getTmpValue() {
		final List<Object> list = inputMap.get(tmpKey);
		if(list == null || list.size() <= tmpIndex){
			return null;
		}else{
			return list.get(tmpIndex);
		}
	}

	/**
	 * {@link #getTmpKey()}と{@link #getTmpIndex()}で{@link #getInputMap()}を検索し、
	 * その位置に値を設定する。
	 * @param value 一時的に保持する単一のプロパティの値。
	 */
	public void setTmpValue(final Object value) {
		List<Object> list = inputMap.get(tmpKey);
		if(list == null){
			list = new ArrayList<Object>();
			inputMap.put(tmpKey, list);
		}
		if(list.size() == 0 && tmpIndex == 0){
			list.add(value);
		}else{
			list.set(tmpIndex, value);
		}
	}
}
