/* $Id: PackageValidator.java,v 1.2 2007/12/11 09:54:58 nito Exp $
 * 
 * Copyright (c)ARGO 21, Corporation. 2005, 2006.  All rights reserved.
 * 
 * This file is part of Nautica Workflow.
 * 
 *  Nautica Workflow 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.
 * 
 *  Nautica Workflow 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 Nautica Workflow; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 */
package jp.co.argo21.nautica.tool.wfd;

import java.util.ArrayList;
import java.util.List;

import jp.co.argo21.nautica.commons.util.ResourceManager;
import jp.co.argo21.nautica.commons.util.StringUtils;
import jp.co.argo21.nautica.tool.wfd.element.ActivityElement;
import jp.co.argo21.nautica.tool.wfd.element.ApplicationElement;
import jp.co.argo21.nautica.tool.wfd.element.Element;
import jp.co.argo21.nautica.tool.wfd.element.PackageElement;
import jp.co.argo21.nautica.tool.wfd.element.ParameterElement;
import jp.co.argo21.nautica.tool.wfd.element.ParticipantElement;
import jp.co.argo21.nautica.tool.wfd.element.ProcessElement;
import jp.co.argo21.nautica.tool.wfd.element.TransitionElement;
import jp.co.argo21.nautica.tool.wfd.element.VariableElement;


/**
 * パッケージ情報の検証を行う。
 * 
 * @author  Norihiro Itoh(ARGO 21 Corp.)
 * @version $Revision: 1.2 $
 * @since   Nautica Workflow 1.0
 */
public class PackageValidator
{
	/** エラー */
	private static final int E = ValidationResult.ERROR; 
	/** 警告 */
	private static final int W = ValidationResult.WARNING; 
	
	/** 検証器 */
	private static final PackageValidator singleton = new PackageValidator();

	/**
	 * 検証器を返す。
	 * 
	 * @return	検証器
	 */
	public static PackageValidator getInstance()
	{
		return singleton;
	}

	/**
	 * 検証器を生成する。
	 */
	private PackageValidator()
	{
	}

	/**
	 * 検証結果を１件追加する。
	 * 
	 * @param results	結果一覧
	 * @param kind	結果種別
	 * @param elem	対象要素
	 * @param msgid	メッセージ
	 */	
	private void addResult(List<ValidationResult> results, int kind, Element elem, String msgid)
	{
		ResourceManager rm = WorkflowDesignerManager.getResourceManager();

		ValidationResult r = new ValidationResult(kind, elem, rm.getResource(msgid));
		results.add(r);
	}

	/**
	 * 指定されたパッケージを検証する。
	 * 
	 * @param elem	パッケージ
	 * @return	検証結果一覧
	 */
	public List<ValidationResult> validate(PackageElement elem)
	{
		List<ValidationResult> results = new ArrayList<ValidationResult>();

		if (elem == null) {
			addResult(results, E, elem, "message.error.0101");
			return results;
		} 
		
		validatePackageElement(elem, results);
		
		if (results.size() == 0) return null;
		else return results;
	}

	/**
	 * 指定されたパッケージを検証する。
	 * 
	 * @param elem	パッケージ
	 * @param results	検証結果一覧
	 */
	private void validatePackageElement(
	PackageElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0102");
		
		List<ApplicationElement> apps = elem.getApplications();
		if (apps.size() > 0) {
			for (ApplicationElement e : apps) {
				validateApplicationElement(e, results);
				validateSameName(e, apps, results);			}
		}
		
		List<ParticipantElement> parts = elem.getParticipants();
		if (parts.size() > 0) {
			for (ParticipantElement e : parts) {
				validateParticipantElement(e, results);
				validateSameName(e, parts, results);			}
		}
		
		List<VariableElement> vars = elem.getVariables();
		if (vars.size() > 0) {
			for (VariableElement e : vars) {
				validateVariableElement(e, results);
				validateSameName(e, vars, results);
			}
		}
		
		List<ProcessElement> procs = elem.getProcesses();
		if (procs.size() > 0) {
			for (ProcessElement e : procs) {
				validateProcessElement(elem, e, results);
				validateSameName(e, procs, results);			}
		}
	}

	/**
	 * 指定されたアプリケーションを検証する。
	 * 
	 * @param elem	アプリケーション
	 * @param results	検証結果一覧
	 */
	private void validateApplicationElement(
	ApplicationElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0103");
		
		List<ParameterElement> params = elem.getParameters();
		if (params.size() > 0) {
			for (ParameterElement e : params) {
				validateParameterElement(e, results);
			}
		}
	}

	/**
	 * 指定されたワークフロー参加者を検証する。
	 * 
	 * @param elem	ワークフロー参加者
	 * @param results	検証結果一覧
	 */
	private void validateParticipantElement(
	ParticipantElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0104");
	}

	/**
	 * 指定されたプロセス変数を検証する。
	 * 
	 * @param elem	プロセス変数
	 * @param results	検証結果一覧
	 */
	private void validateVariableElement(
	VariableElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0105");
		if (StringUtils.checkNull(elem.getInitialValue())) 
			addResult(results, W, elem, "message.warn.0101");
	}

	/**
	 * 指定されたプロセスを検証する。
	 * 
	 * @param pkg	パッケージ
	 * @param elem	プロセス
	 * @param results	検証結果一覧
	 */
	private void validateProcessElement(
	PackageElement pkg,
	ProcessElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0106");

		List<String> rsps = elem.getResponsibles();
		if (rsps.size() == 0)
			addResult(results, W, elem, "message.warn.0102");

		List<ParameterElement> params = elem.getParameters();
		if (params.size() > 0) {
			for (ParameterElement e : params) {
				validateParameterElement(e, results);
			}
		}
		
		List<ActivityElement> acts = elem.getActivities();
		List<TransitionElement> trs = elem.getTransitions();
		if (acts.size() > 0) {
			for (ActivityElement e : acts) {
				validateTransition(e, trs, results);
				validateActivityElement(pkg, e, results);
			}
		}
		
		if (trs.size() > 0) {
			for (TransitionElement e : trs) {
				validateTransitionElement(e, results);
			}
		}
	}

	/**
	 * 指定されたパラメータを検証する。
	 * 
	 * @param elem	パラメータ
	 * @param results	検証結果一覧
	 */
	private void validateParameterElement(
	ParameterElement elem,
	List<ValidationResult> results)
	{
		//NOP
	}

	/**
	 * 指定されたアクティビティを検証する。
	 * 
	 * @param pkg	パッケージ
	 * @param elem	アクティビティ
	 * @param results	検証結果一覧
	 */
	private void validateActivityElement(
	PackageElement pkg,
	ActivityElement elem,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(elem.getName())) 
			addResult(results, E, elem, "message.error.0107");

		String type = elem.getType();

		if (type.equals(ActivityElement.INTERACTIVE)) {
			if (StringUtils.checkNull(elem.getPerformerID()))
				addResult(results, W, elem, "message.error.0119");
		} else if (type.equals(ActivityElement.AUTOMATIC)) {
			if (StringUtils.checkNull(elem.getPerformerID()))
				addResult(results, W, elem, "message.error.0119" +
						"");
			if (StringUtils.checkNull(elem.getApplicationID())) {
				addResult(results, E, elem, "message.error.0108");
				validateApplication(elem, pkg.getApplications(), results);

				List<String> params = elem.getApplicationParameters();
				if (params.size() > 0) {
					for (String aid : params) {
						validateActualParams(elem, aid, pkg.getVariables(), results);
					}
				}
				
			}
		} else if (type.equals(ActivityElement.SUBPROCESS)) {
			if (StringUtils.checkNull(elem.getSubprocessID())) {
				addResult(results, E, elem, "message.error.0109");
				validateSubprocess(elem, pkg.getProcesses(), results);

				List<String> params = elem.getSubprocessParameters();
				if (params.size() > 0) {
					for (String pid : params) {
						validateActualParams(elem, pid, pkg.getVariables(), results);
					}
				}
			}
		}
	}

	/**
	 * 指定された遷移を検証する。
	 * 
	 * @param elem	遷移
	 * @param results	検証結果一覧
	 */
	private void validateTransitionElement(
	TransitionElement elem,
	List<ValidationResult> results)
	{
		switch (elem.getType()) {
			case TransitionElement.CONDITION:
				if (elem.getCondition() == null) 
					addResult(results, E, elem, "message.error.0110");
				break;
		}
	}

	/**
	 * 指定されたアクティビティの遷移を検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param trs	遷移一覧
	 * @param results	検証結果一覧
	 */	
	private void validateTransition(
	ActivityElement elem,
	List<TransitionElement> trs,
	List<ValidationResult> results)
	{
		int in = 0;
		int out = 0;
		for (TransitionElement e : trs) {
			if (elem == e.getSource()) out++;
			else if (elem == e.getDestination()) in++;
		}
		if (elem.getType().equals(ActivityElement.START)) {
			if (out == 0)
				addResult(results, E, elem, "message.error.0111");
		} else if (elem.getType().equals(ActivityElement.END)) {
			if (in == 0)
				addResult(results, E, elem, "message.error.0112");
		} else {
			if (in == 0)
				addResult(results, E, elem, "message.error.0112");
			if (out == 0)
				addResult(results, E, elem, "message.error.0111");
		}
	}

	/**
	 * 指定されたアクティビティのプロセス変数を検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param vars	プロセス変数一覧
	 * @param results	検証結果一覧
	 */	
	private void validateSameName(
	Element elem,
	List<? extends Element> vars,
	List<ValidationResult> results)
	{
		for (Element e : vars) {
			if (elem.getName() == null || e.getName() == null) {
			} else if (elem == e) {
			} else if (elem.getName().equals(e.getName())) {
				addResult(results, E, elem, "message.error.0113");
			}
		}
	}

	/**
	 * 指定されたアクティビティのアプリケーションを検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param apps	アプリケーション一覧
	 * @param results	検証結果一覧
	 */	
	private void validateApplication(
	ActivityElement elem,
	List<ApplicationElement> apps,
	List<ValidationResult> results)
	{
		String appID = elem.getApplicationID();
		if (appID == null) return;
		
		boolean found = false;
		for (ApplicationElement e : apps) {
			if (appID.equals(e.getID())) {
				found = true;
				break;
			}
		}
		if (found == false)
			addResult(results, E, elem, "message.error.0114");
	}

	/**
	 * 指定されたアクティビティのサブプロセスを検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param procs	プロセス一覧
	 * @param results	検証結果一覧
	 */	
	private void validateSubprocess(
	ActivityElement elem,
	List<ProcessElement> procs,
	List<ValidationResult> results)
	{
		String procID = elem.getSubprocessID();
		if (procID == null) return;
		
		boolean found = false;
		for (ProcessElement e : procs) {
			if (procID.equals(e.getID())) {
				found = true;
				break;
			}
		}
		if (found == false)
			addResult(results, E, elem, "message.error.0115");
	}

	/**
	 * 指定されたアクティビティに割り当てられた実行権限を検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param parts	ワークフロー参加者一覧
	 * @param results	検証結果一覧
	 */	
	@SuppressWarnings("unused")
	private void validateParticipant(
	ActivityElement elem,
	List<ParticipantElement> parts,
	List<ValidationResult> results)
	{
		String partID = elem.getPerformerID();
		if (partID == null) return;
		
		boolean found = false;
		for (ParticipantElement e : parts) {
			if (partID.equals(e.getID())) {
				found = true;
				break;
			}
		}
		if (found == false)
			addResult(results, E, elem, "message.error.0116");
	}

	/**
	 * 指定されたアクティビティに割り当てられたパラメータを検証する。
	 * 
	 * @param elem	アクティビティ
	 * @param id	パラメータID
	 * @param params	パラメータ一覧
	 * @param results	検証結果一覧
	 */	
	private void validateActualParams(
	ActivityElement elem,
	String id,
	List<VariableElement> params,
	List<ValidationResult> results)
	{
		if (StringUtils.checkNull(id)) {
			addResult(results, E, elem, "message.error.0117");
			return;
		} 
		
		boolean found = false;
		for (VariableElement e : params) {
			if (id.equals(e.getID())) {
				found = true;
				break;
			}
		}
		if (found == false)
			addResult(results, E, elem, "message.error.0118");
	}
}
