// Copyright (C) 2003, 2005 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2008, 2010 panacoran <panacoran@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 274 2010-02-28 11:21:56Z panacoran $

using System;
using System.Collections.Generic;

namespace Protra.Lib.Lang
{
	/// <summary>
	/// 関数のノードを表すクラス。
	/// </summary>
	public class FunctionNode : Node
	{
		/// <summary>
		/// 関数のタイプ
		/// </summary>
		private FunctionType ft;
		/// <summary>
		/// 引数のノードのリスト
		/// </summary>
		private List<Node> nodeList = new List<Node>();
        
		/// <summary>
		/// 構文解析する。
		/// </summary>
		/// <exception cref="Protra.Lib.Lang.ParseException">
		/// 構文解析中にエラーが発生したときにthrowされる。
		/// </exception>
        /// <return>
        /// 解析した結果のノード。
        /// </return>
		public override Node Parse()
		{
			Token = Scanner.Token;

			// 関数名の取得
			string name = Scanner.Token.Value;

			// 引数のチェック
            Scanner.Scan();
			if (Scanner.Token.Value != "(")
			{
				ft = new FunctionType(name, 0);
				return this;
			}
            Scanner.Scan();
			if (Scanner.Token.Value == ")")
			{
				ft = new FunctionType(name, 0);
                Scanner.Scan();
				return this;
			}
			do
			{
				nodeList.Add(new ExpressionNode().Parse());
				if (Scanner.Token.Value == ")")
				{
					ft = new FunctionType(name, nodeList.Count);
                    Scanner.Scan();
					return this;
				}
				else if (Scanner.Token.Value != ",")
					throw new ParseException("',' or ')' expected", Token);
			}
            while (Scanner.Scan());
            throw new ParseException("unexpected EOF", Token);
		}

		/// <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.ContainsKey(ft))
			{	
				// 関数定義ノードの取得
				FunctionDefinitionNode fdn = resource.FunctionTable[ft];

				// スタックフレームを用意
                Value[] frame = new Value[fdn.FrameSize];
				frame[0] = new Value(at);
				for(int i = 0; i < nodeList.Count; i++)
                    frame[i + 1] = nodeList[i].Execute(resource, at);
				
				resource.Stack.Push(frame);

				// 文の実行
				List<Node> nodes = fdn.Nodes;
                try
                {
                    foreach (Node node in nodes)
                        node.Execute(resource, at);
                    return null;
                }
                catch (ReturnStatementExecutedException e)
                {
                    return e.Value;
                }
                finally
                {
                    resource.Stack.Pop();
                }
			}

			// 組み込み関数を実行する。
            try
            {
                Value[] args = new Value[nodeList.Count];
                for (int i = 0; i < nodeList.Count; i++)
                {
                    Node node = (Node)nodeList[i];
                    args[i] = node.Execute(resource, at);
                }
                return resource.Builtins.Invoke(ft.Name, args, at);
            }
            catch (NoSuchBuiltinException)
            {
                throw new RuntimeException("undefined function --- " + ft.ToString(), Token);
            }
            catch (Exception e)
            {
                throw new RuntimeException(e.Message, Token);
            }
		}
	}
}
