#region License
/* **********************************************************************************
 * Copyright (c) Roman Ivantsov
 * This source code is subject to terms and conditions of the MIT License
 * for Irony. A copy of the license can be found in the License.txt file
 * at the root of this distribution. 
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the 
 * MIT License.
 * You must not remove this notice from this software.
 * **********************************************************************************/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Irony.Ast;
using Irony.Parsing;

namespace Irony.Interpreter.Ast
{



    public class ExtStatementListNode : AstNode
    {
        AstNode _singleChild; //stores a single child when child count == 1, for fast access

        public override void Init(AstContext context, ParseTreeNode treeNode)
        {
            base.Init(context, treeNode);
            var nodes = treeNode.GetMappedChildNodes();
            foreach(var child in nodes)
            {
                //don't add if it is null; it can happen that "statement" is a comment line and statement's node is null.
                // So to make life easier for language creator, we just skip if it is null
                if(child.AstNode != null)
                    AddChild(string.Empty, child);
            }
            AsString = "Statement List";
            if(ChildNodes.Count == 0)
            {
                AsString += " (Empty)";
            }
            else
                ChildNodes[ChildNodes.Count - 1].Flags |= AstNodeFlags.IsTail;
        }

        protected override object DoEvaluate(ScriptThread thread)
        {
            thread.CurrentNode = this;  //standard prolog
            lock(LockObject)
            {
                switch(ChildNodes.Count)
                {
                    case 0:
                        Evaluate = EvaluateEmpty;
                        break;
                    case 1:
                        _singleChild = ChildNodes[0];
                        Evaluate = EvaluateOne;
                       break;
                    default:
                        Evaluate = EvaluateMultiple;
                        break;
                }//switch
            }//lock
            var result = Evaluate(thread);
            thread.CurrentNode = Parent; //standard epilog
            return result;
        }

        private object EvaluateEmpty(ScriptThread thread)
        {
            return null;
        }

        private object EvaluateOne(ScriptThread thread)
        {
            thread.CurrentNode = this;  //standard prolog

            //EDIT-START **************************************************
            CheckSuspend(thread);
            //EDIT-END   **************************************************

            object result = _singleChild.Evaluate(thread);

            //EDIT-START **************************************************
            if (HasReturnType(result))
                result = SetReturnType(result);
            //EDIT-END   **************************************************

            thread.CurrentNode = Parent; //standard epilog
            return result;
        }


        private object EvaluateMultiple(ScriptThread thread)
        {
            thread.CurrentNode = this;  //standard prolog
            object result = null;

            for(int i = 0; i < ChildNodes.Count; i++)
            {
                //EDIT-START **************************************************
                CheckSuspend(thread);
                //EDIT-END   **************************************************

                result = ChildNodes[i].Evaluate(thread);

                //EDIT-START **************************************************
                if (HasReturnType(result))
                {
                    result = SetReturnType(result);
                    break;
                }
                //EDIT-END   **************************************************
            }

            thread.CurrentNode = Parent; //standard epilog
            return result; //return result of last statement
        }

        public override void SetIsTail()
        {
            base.SetIsTail();
            if(ChildNodes.Count > 0)
                ChildNodes[ChildNodes.Count - 1].SetIsTail();
        }

        private bool HasReturnType(object result)
        {
            var resWithRetType = result as resultWithReturnType;
            return resWithRetType != null;
        }

        private object SetReturnType(object result)
        {
            //return情報がなかったら、何もしない
            if (!HasReturnType(result))
                return result;

            //ネストしているケースを考慮して、
            //脱出目的のノード（タイプ）に到達したらもとに戻す（それをreturn）。
            //課題：ソース記述ミスで、到達できないノード種類を指定された時の対処
            //呼び出し元で例外投げる？
            var resWithRetType = result as resultWithReturnType;

            switch (resWithRetType.retType)
            {
                case ReturnType.EXIT_FUNCTION:
                    if (Parent is LambdaNode)
                        result = resWithRetType.resultObj;
                    break;

                case ReturnType.EXIT_LOOP:
                    if (Parent is WhileNode || Parent is ForNode)
                        result = resWithRetType.resultObj;
                    break;

                case ReturnType.CONTINUE_LOOP:
                    break;
                case ReturnType.NONE:
                    break;
                default:
                    break;
            }
            return result;
        }



        private void CheckSuspend(ScriptThread thread)
        {
            //メッセージ処理する時間を与える
            System.Windows.Forms.Application.DoEvents();

            //ScriptAppクラスに増やしたフィールドをチェックし、
            //フラグがたっていたら、
            //遷移するデリゲートを呼び出す
            if (thread.App.SuspendRequested && thread.App.OnSuspend != null)
                thread.App.OnSuspend(thread);
        }



    }//class

}//namespace
