/*
 * 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.List;
import java.util.Map;

/**
 * 単一リクエストまたは単一スレッドごとの状態を保持するコンテキスト。 本クラスはリクエストごとに生成され単一のスレッドのみからアクセスされる必要がある。
 * 適用しているパターン：Interpreter(Context 役)。
 * 
 * @author nakamura
 * 
 */
public class ValidatorContext {
	private String id;

	private Map<String, List<Object>> inputMap;

	private MessageCollectable collectable;

	private String tmpKey;

	private int tmpIndex;

	/**
	 * コンストラクタ。
	 * 
	 */
	public ValidatorContext() {
	}

	/**
	 * 浅いコピーコンストラクタ。 サブクラスだけでなく{@link ValidatorExpression}からも呼び出されることを想定しているため
	 * public にしている。
	 * 
	 * @param base
	 *            コピー元。
	 */
	public ValidatorContext(final ValidatorContext base) {
		this.id = base.id;
		this.inputMap = base.inputMap;
		this.collectable = base.collectable;
		this.tmpKey = base.tmpKey;
		this.tmpIndex = base.tmpIndex;
	}

	/**
	 * 浅いコピーを行う。{@link java.lang.Cloneable}は実装していない。
	 * 
	 * @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, "collectable", collectable);
		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) {
		collectable.add(message, this);
	}

	/**
	 * 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(final 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);
		}
	}

	/**
	 * {@link MessageCollectable}を返す。
	 * 
	 * @return {@link MessageCollectable}。
	 */
	public MessageCollectable getCollectable() {
		return collectable;
	}

	/**
	 * {@link MessageCollectable}を設定する。
	 * {@link #add(String)}からここで設定した
	 * {@link MessageCollectable#add(String, ValidatorContext)}を呼び出す。
	 * 
	 * @param collectable
	 *            {@link MessageCollectable}。
	 */
	public void setCollectable(final MessageCollectable collectable) {
		this.collectable = collectable;
	}
}
