// Copyright (C) 2003, 2005 Daisuke Arai <darai@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra 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 3 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, see <http://www.gnu.org/licenses/>.
// 
// $Id: FunctionNode.cs,v 1.3 2007-12-27 08:34:39 panacoran Exp $

using System;
using System.Collections;

namespace Protra.Lib.Lang
{
	/// <summary>
	/// 関数のノードを表すクラスです。
	/// </summary>
	public class FunctionNode : Node
	{
		/// <summary>
		/// 関数のタイプ
		/// </summary>
		private FunctionType ft;
		/// <summary>
		/// 引数のノードのリスト
		/// </summary>
		private ArrayList nodeList = new ArrayList();

		/// <summary>
		/// 文脈を解析します。
		/// </summary>
		/// <exception cref="Protra.Lib.Lang.ParseException">
		/// 文脈にエラーがある場合にthrowされます。
		/// </exception>
		/// <param name="context">コンテキスト</param>
		public override void Parse(Context context)
		{
			this.ContextToken = (ContextToken)context.Current;

			// 関数名の取得
			ContextToken ct = (ContextToken)context.Current;
			string name = ct.ToString();

			// 引数のチェック
			context.MoveNext();
			ct = (ContextToken)context.Current;
			string token = ct.ToString();
			if(token != "(")
			{
				ft = new FunctionType(name, nodeList.Count);
				return;
			}
			context.MoveNext();
			ct = (ContextToken)context.Current;
			token = ct.ToString();
			if(token == ")")
			{
				ft = new FunctionType(name, nodeList.Count);
				context.MoveNext();
				return;
			}
			do
			{
				Node node = new ExpressionNode();
				node.Parse(context);
				nodeList.Add(node);
				ct = (ContextToken)context.Current;
				token = ct.ToString();
				if(token == ")")
				{
					ft = new FunctionType(name, nodeList.Count);
					context.MoveNext();
					return;
				}
				else if(token != ",")
					throw new ParseException("',' or ')' expected", this.ContextToken);
			}while(context.MoveNext());
		}

		/// <summary>
		/// 構文木を簡略化します。
		/// </summary>
		/// <returns>簡略化されたノード</returns>
		public override Node Simplify()
		{
			for(int i = 0; i < nodeList.Count; i++)
			{
				Node node = (Node)nodeList[i];
				nodeList[i] = node.Simplify();
			}
			return this;
		}

		/// <summary>
		/// プログラムを実行します。
		/// </summary>
		/// <exception cref="Protra.Lib.Lang.RuntimeException">
		/// プログラム実行中にエラーが発生した場合にthrowされます。
		/// </exception>
		/// <param name="resource">リソース</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>実行結果</returns>
		public override Value Execute(Resource resource, int at)
		{
			// 関数が定義されていれば実行する
			if(resource.FunctionTable.ContainsFunctionType(ft))
			{	
				// 関数定義ノードの取得
				FunctionDefinitionNode fdn = resource.FunctionTable[ft];

				// 引数のローカル変数を用意
				VariableTable lvt = new VariableTable();
				lvt["at"] = new Value(at);
				string[] names = fdn.ArgumentNames;
				for(int i = 0; i < nodeList.Count; i++)
				{
					Node node = (Node)nodeList[i];
					Value val = node.Execute(resource, at);
					lvt[names[i]] = val;
				}
				// ローカル変数のテーブルをプッシュ
				resource.Stack.Push(lvt);

				// 文の実行
				Node[] nodes = fdn.Nodes;
				try
				{
					foreach(Node node in nodes)
						node.Execute(resource, at);

					// ローカル変数のテーブルをポップ
					resource.Stack.Pop();
					return null;
				}
				catch(ReturnStatementExecutedException exc)
				{
					// ローカル変数のテーブルをポップ
					resource.Stack.Pop();
					return exc.Value;
				}
			}

			// 関数がIExecuterFactoryによって提供されるならば実行する
			try
			{
				IExecuter executer = resource.ExecuterFactory.CreateExecuter(ft);
				Value[] args = new Value[nodeList.Count];
				for(int i = 0; i < args.Length; i++)
				{
					Node node = (Node)nodeList[i];
					Value val = node.Execute(resource, at);
					args[i] = val;
				}

				return executer.Execute(args, at);
			}
			catch(NoSuchExecuterException)
			{
				throw new RuntimeException("undefined function --- " + ft.ToString(), this.ContextToken);
			}
		}
	}
}
