using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Irony.Parsing;
using Irony.Ast;
using Irony.Interpreter;
using Irony.Interpreter.Ast;

namespace myScriptableBrowser
{

    // A ready-to-use evaluator implementation.

    // This grammar describes programs that consist of simple expressions and assignments
    // for ex:
    // x = 3
    // y = -x + 5
    //  the result of calculation is the result of last expression or assignment.
    //  Irony's default  runtime provides expression evaluation. 
    //  supports inc/dec operators (++,--), both prefix and postfix, and combined assignment operators like +=, -=, etc.
    //  supports bool operators &, |, and short-circuit versions &&, ||
    //  supports ternary ?: operator

    [Language("myScriptGrammar" , "1.0" , "myScriptGrammar")]
    public class myScriptGrammar : InterpretedLanguageGrammar
    {

        public myScriptGrammar()
            : base(caseSensitive: false)
        {

            this.GrammarComments = @"VBSCRIPTっぽい言語";

            #region src
            // 1. Terminals

            var comma = ToTerm(",");
            var colon = ToTerm(":");

            //数値は
            //表記方法　C#
            //利用できる形式　元の計算機＋UINT32.64（C#+BIGINTEGER）
            var number = TerminalFactory.CreateCSharpNumber("number");
            //Let's allow big integers (with unlimited number of digits):
            number.DefaultIntTypes = new TypeCode[] { TypeCode.Int32 , TypeCode.UInt32 , TypeCode.Int64 , TypeCode.UInt64 , NumberLiteral.TypeCodeBigInt };

            #region CreateCSharpNumber
            //public static NumberLiteral CreateCSharpNumber(string name) {
            //  NumberLiteral term = new NumberLiteral(name);
            //  term.DefaultIntTypes = new TypeCode[] { TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64 };
            //  term.DefaultFloatType = TypeCode.Double;
            //  term.AddPrefix("0x", NumberOptions.Hex);
            //  term.AddSuffix("u", TypeCode.UInt32, TypeCode.UInt64);
            //  term.AddSuffix("l", TypeCode.Int64, TypeCode.UInt64);
            //  term.AddSuffix("ul", TypeCode.UInt64);
            //  term.AddSuffix("f", TypeCode.Single);
            //        term.AddSuffix("d", TypeCode.Double);
            //  term.AddSuffix("m", TypeCode.Decimal);
            //  return term;
            //}
            #endregion


            var identifier = new IdentifierTerminal("identifier");
            var comment = new CommentTerminal("comment" , "#" , "\n" , "\r");
            //comment must be added to NonGrammarTerminals list; it is not used directly in grammar rules,
            // so we add it to this list to let Scanner know that it is also a valid terminal. 
            base.NonGrammarTerminals.Add(comment);


            //////String literal with embedded expressions  ------------------------------------------------------------------
            ////var stringLit = new StringLiteral("string" , "\"" , StringOptions.AllowsAllEscapes | StringOptions.IsTemplate);
            ////stringLit.AddStartEnd("'" , StringOptions.AllowsAllEscapes | StringOptions.IsTemplate);
            ////stringLit.AstConfig.NodeType = typeof(StringTemplateNode);
            ////var Expr = new NonTerminal("Expr"); //declare it here to use in template definition 
            ////var templateSettings = new StringTemplateSettings(); //by default set to Ruby-style settings 
            ////templateSettings.ExpressionRoot = Expr; //this defines how to evaluate expressions inside template
            ////this.SnippetRoots.Add(Expr);
            ////stringLit.AstConfig.Data = templateSettings;
            //////--------------------------------------------------------------------------------------------------------

            //C#のstringliteralをつかう（変数の展開なし）
            var stringLit = TerminalFactory.CreateCSharpString("stringLit");


            // 2. Non-terminals
            #region original
            //var Term = new NonTerminal("Term");
            //var BinExpr = new NonTerminal("BinExpr" , typeof(BinaryOperationNode));
            //var ParExpr = new NonTerminal("ParExpr");
            //var UnExpr = new NonTerminal("UnExpr" , typeof(UnaryOperationNode));
            //var TernaryIfExpr = new NonTerminal("TernaryIf" , typeof(IfNode));
            //var ArgList = new NonTerminal("ArgList" , typeof(ExpressionListNode));
            //var FunctionCall = new NonTerminal("FunctionCall" , typeof(FunctionCallNode));
            //var MemberAccess = new NonTerminal("MemberAccess" , typeof(MemberAccessNode));
            //var IndexedAccess = new NonTerminal("IndexedAccess" , typeof(IndexedAccessNode));
            //var ObjectRef = new NonTerminal("ObjectRef"); // foo, foo.bar or f['bar']
            //var UnOp = new NonTerminal("UnOp");
            //var BinOp = new NonTerminal("BinOp" , "operator");
            //var PrefixIncDec = new NonTerminal("PrefixIncDec" , typeof(IncDecNode));
            //var PostfixIncDec = new NonTerminal("PostfixIncDec" , typeof(IncDecNode));
            //var IncDecOp = new NonTerminal("IncDecOp");
            //var AssignmentStmt = new NonTerminal("AssignmentStmt" , typeof(AssignmentNode));
            //var AssignmentOp = new NonTerminal("AssignmentOp" , "assignment operator");
            //var Statement = new NonTerminal("Statement");
            //var Program = new NonTerminal("Program" , typeof(StatementListNode));
            #endregion

            var Expr = new NonTerminal("Expr");
            var Term = new NonTerminal("Term");
            var BinExpr = new NonTerminal("BinExpr" , typeof(BinaryOperationNode));
            var ParExpr = new NonTerminal("ParExpr");
            var UnExpr = new NonTerminal("UnExpr" , typeof(UnaryOperationNode));
            var TernaryIfExpr = new NonTerminal("TernaryIf" , typeof(IfNode));
            var ArgList = new NonTerminal("ArgList" , typeof(ExpressionListNode));
            var FunctionCall = new NonTerminal("FunctionCall" , typeof(FunctionCallNode));
            var MemberAccess = new NonTerminal("MemberAccess" , typeof(MemberAccessNode));
            var IndexedAccess = new NonTerminal("IndexedAccess" , typeof(IndexedAccessNode));
            var ObjectRef = new NonTerminal("ObjectRef"); // foo, foo.bar or f['bar']
            var UnOp = new NonTerminal("UnOp");
            var BinOp = new NonTerminal("BinOp" , "operator");
            var PrefixIncDec = new NonTerminal("PrefixIncDec" , typeof(IncDecNode));
            var PostfixIncDec = new NonTerminal("PostfixIncDec" , typeof(IncDecNode));
            var IncDecOp = new NonTerminal("IncDecOp");
            var AssignmentStmt = new NonTerminal("AssignmentStmt" , typeof(AssignmentNode));
            var AssignmentOp = new NonTerminal("AssignmentOp" , "assignment operator");

            //追加
            var FunctionDef = new NonTerminal("FunctionDef" , typeof(FunctionDefNode));
            var ParamList = new NonTerminal("ParamList" , typeof(ParamListNode));

            var Block = new NonTerminal("Block" , typeof(ExtStatementListNode));
            var embedded_statement = new NonTerminal("Embedded_statement");
            var Line_statement = new NonTerminal("Line_statement");
            var FunctionDefs = new NonTerminal("FunctionDefs" , typeof(ExtStatementListNode));
            var Program = new NonTerminal("Program" , typeof(ExtStatementListNode));

            //

            //    protected internal void OnAstNodeCreated(ParseTreeNode parseNode) {
            //new EventHandler<AstNodeEventArgs>(
            //Program.AstNodeCreated += (
            //    (object s , AstNodeEventArgs e) =>
            //    {
            //        System.Diagnostics.Debug.WriteLine("Program " + e.AstNode.ToString());
            //    });


            //制御構文
            var ReturnStmt = new NonTerminal("ReturnStmt" , typeof(ReturnNode));
            var ForStmt = new NonTerminal("ForStmt" , typeof(ForNode));
            var BreakStmt = new NonTerminal("BreakStmt" , typeof(BreakNode));
            var ContinueStmt = new NonTerminal("ContinueStmt" , typeof(ContinueNode));

            //セレクタ
            var IfStmt = new NonTerminal("IfStmt" , typeof(IfNode));
            var WhileStmt = new NonTerminal("WhileStmt" , typeof(WhileNode));

            //////これ大事。。
            ////this.UsesNewLine = false;


            // 3. BNF rules
            #region Original
            //Expr.Rule = Term | UnExpr | BinExpr | PrefixIncDec | PostfixIncDec | TernaryIfExpr;
            //Term.Rule = number | ParExpr | stringLit | FunctionCall | identifier | MemberAccess | IndexedAccess;
            //ParExpr.Rule = "(" + Expr + ")";
            //UnExpr.Rule = UnOp + Term + ReduceHere();
            //UnOp.Rule = ToTerm("+") | "-";
            //BinExpr.Rule = Expr + BinOp + Expr;
            //BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**" | "==" | "<" | "<=" | ">" | ">=" | "!=" | "&&" | "||" | "&" | "|";
            //PrefixIncDec.Rule = IncDecOp + identifier;
            //PostfixIncDec.Rule = identifier + PreferShiftHere() + IncDecOp;
            //IncDecOp.Rule = ToTerm("++") | "--";
            //TernaryIfExpr.Rule = Expr + "?" + Expr + ":" + Expr;
            //MemberAccess.Rule = Expr + PreferShiftHere() + "." + identifier;
            //AssignmentStmt.Rule = ObjectRef + AssignmentOp + Expr;
            //AssignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=";
            //Statement.Rule = AssignmentStmt | Expr | Empty;
            //ArgList.Rule = MakeStarRule(ArgList , comma , Expr);
            //FunctionCall.Rule = Expr + PreferShiftHere() + "(" + ArgList + ")";
            //FunctionCall.NodeCaptionTemplate = "call #{0}(...)";
            //ObjectRef.Rule = identifier | MemberAccess | IndexedAccess;
            //IndexedAccess.Rule = Expr + PreferShiftHere() + "[" + Expr + "]";

            //Program.Rule = MakePlusRule(Program , NewLine , Statement);

            //this.Root = Program;       // Set grammar root


            #endregion

            //式
            Expr.Rule = Term | UnExpr | BinExpr | PrefixIncDec | PostfixIncDec | TernaryIfExpr;
            //単項式
            Term.Rule = number | ParExpr | stringLit | FunctionCall | ObjectRef;

            //単項式バリエーション
            ParExpr.Rule = "(" + Expr + ")";
            UnExpr.Rule = UnOp + Term + ReduceHere();
            UnOp.Rule = ToTerm("+") | "-";
            PrefixIncDec.Rule = IncDecOp + identifier;
            PostfixIncDec.Rule = identifier + PreferShiftHere() + IncDecOp;
            IncDecOp.Rule = ToTerm("++") | "--";

            //二項式
            BinExpr.Rule = Expr + BinOp + Expr;
            BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**" | "==" | "<" | "<=" | ">" | ">=" | "!=" | "&&" | "||" | "&" | "|";

            //三項式
            TernaryIfExpr.Rule = Expr + "?" + Expr + ":" + Expr;

            //代入
            AssignmentStmt.Rule = ObjectRef + AssignmentOp + Expr;
            AssignmentOp.Rule = ToTerm("=") | "+=" | "-=" | "*=" | "/=";

            //変数参照
            ObjectRef.Rule = identifier | MemberAccess | IndexedAccess;
            IndexedAccess.Rule = Expr + PreferShiftHere() + "[" + Expr + "]";
            MemberAccess.Rule = Expr + PreferShiftHere() + "." + identifier;



            //関数呼び出しを変更
            //MemberAccessで、メソッドへのポインタを得る
            //Objectrefに還元されるので、それを捕まえる。
            //関数呼び出し  //identifier
            FunctionCall.Rule = ObjectRef + PreferShiftHere() + "(" + ArgList + ")";
            FunctionCall.NodeCaptionTemplate = "call #{0}(...)";
            ArgList.Rule = MakeStarRule(ArgList , comma , Expr);




            //********************制御文
            ReturnStmt.Rule = "return" + Expr | "return";
            BreakStmt.Rule = "break";
            ContinueStmt.Rule = "continue";


            //ForStmt.Rule = "for" + AssignmentStmt + "to" + Expr + NewLine + Block + "next";

            //var ForStmt_init = new NonTerminal("ForStmt_init", typeof(ExtStatementListNode));
            //var ForStmt_endvalue = new NonTerminal("ForStmt_endvalue", typeof(ExtStatementListNode));
            var ForStmt_opt_step = new NonTerminal("ForStmt_opt_step" , typeof(ExtStatementListNode));
            var ForStmt_body = new NonTerminal("ForStmt_body" , typeof(ExtStatementListNode));

            ForStmt.Rule = "for" + AssignmentStmt + "to" + Expr + ForStmt_opt_step + NewLine + ForStmt_body;

            //ForStmt_init.Rule = "for" + AssignmentStmt;
            //ForStmt_endvalue.Rule = ToTerm("to") +  Expr;
            ForStmt_opt_step.Rule = "step" + Expr | Empty;
            ForStmt_body.Rule = Block + "next";


            //#Transient non-terminal must have zero or one non-punctuation child nodes; 
            //non-terminals: Condition true_Clause false_Clause.

            //if
            var If_Condition = new NonTerminal("Condition_IF" , typeof(ExtStatementListNode));
            var If_True_Clause = new NonTerminal("True_Clause" , typeof(ExtStatementListNode));
            var If_OptElse_Clause = new NonTerminal("OptElse_Clause" , typeof(ExtStatementListNode));

            IfStmt.Rule = If_Condition + If_True_Clause + If_OptElse_Clause;

            If_Condition.Rule = "if" + Expr;
            If_True_Clause.Rule = "then" + NewLine + Block;
            If_OptElse_Clause.Rule = "endif" | "else" + NewLine + Block + "endif";

            //while
            var While_Condition = new NonTerminal("Condition_While" , typeof(ExtStatementListNode));
            var While_Body = new NonTerminal("Body_While" , typeof(ExtStatementListNode));
            While_Condition.Rule = "while" + Expr + NewLine;
            While_Body.Rule = Block + "wend" + NewLine;
            WhileStmt.Rule = While_Condition + While_Body;

            ////foreach
            //var ForEachStmt = new NonTerminal("ForEachStmt", typeof(ForeachNode));
            //var ForEach_body = new NonTerminal("ForEach_body", typeof(ExtStatementListNode));


            //debug用
            var Suspend = new NonTerminal("Suspend" , typeof(SuspendNode));
            var getIDs = new NonTerminal("getIDs" , typeof(getIDsNode));
            Suspend.Rule = "Suspend";
            getIDs.Rule = "getIDsNode";

            //関数定義
            //            FunctionDef.Rule = "function" + identifier + PreferShiftHere() + "(" + ParamList + ")" + NewLine + Block + "fend" + NewLinePlus;
            FunctionDef.Rule = "function" + identifier + PreferShiftHere() + "(" + ParamList + ")"
                               + NewLine + Block + "fend" + NewLineStar;

            FunctionDef.NodeCaptionTemplate = "function #{1}(...)";
            ParamList.Rule = MakeStarRule(ParamList , comma , identifier);


            //関数定義群
            FunctionDefs.Rule = MakeStarRule(FunctionDefs , FunctionDef);



            //文　関数定義（FunctionDef）を含まないこと
            embedded_statement.Rule = AssignmentStmt | Expr | Empty | ReturnStmt | BreakStmt | ContinueStmt | IfStmt | ForStmt | WhileStmt | Suspend;

            //NewLineを付加する工程をまとめて行うために、追加
            Line_statement.Rule = embedded_statement + NewLinePlus;


            //ブロック　メイン、関数のボディー（関数定義のネストを許さない）
            Block.Rule = MakeStarRule(Block , Line_statement);



            //ルート　メインブロック＋関数定義（群）
            Program.Rule = Block + FunctionDefs + Eof;
            //term.AstPartsMap = new int[] {1, 4, 2}; 

            this.Root = Program;       // Set grammar root

            //**********************************************************************
            //Programのchildnodeの順番を変える（関数定義を先に読み込むようにする）
            //これを入れとかないと、unknownSymbol
            //イメージとしては、パースツリーはそのままASTの順番が変わる感じ。
            //Program.Rule = Block + FunctionDefs + Eof;　>>>　Program.Rule = FunctionDefs ＋　Block;
            Program.AstConfig.PartsMap = new int[] { 1 , 0 };

            //予約語？？
            //MarkReservedWords(new string[] { "function" , "fend" , "if"});

            // 4. Operators precedence
            RegisterOperators(10 , "?");
            RegisterOperators(15 , "&" , "&&" , "|" , "||");
            RegisterOperators(20 , "==" , "<" , "<=" , ">" , ">=" , "!=");
            RegisterOperators(30 , "+" , "-");
            RegisterOperators(40 , "*" , "/");
            RegisterOperators(50 , Associativity.Right , "**");
            // For precedence to work, we need to take care of one more thing: BinOp. 
            //For BinOp which is or-combination of binary operators, we need to either 
            // 1) mark it transient or 2) set flag TermFlags.InheritPrecedence
            // We use first option, making it Transient.  

            // 5. Punctuation and transient terms
            MarkPunctuation("(" , ")" , "?" , ":" , "[" , "]");
            RegisterBracePair("(" , ")");
            RegisterBracePair("[" , "]");
            //MarkTransient(Term , Expr , Stmt , BinOp , UnOp , IncDecOp , AssignmentOp , ParExpr , ObjectRef);

            //ここで指定しなかった場合、ASTノードクリエーターが必要。
            //予め準備されたASTノードクリエータを使うこともでき、その場合はASTノードタイプをセットする
            MarkTransient(Term , Expr , BinOp , UnOp , IncDecOp , AssignmentOp , ParExpr , ObjectRef ,
                embedded_statement , Line_statement);

            // 7. Syntax error reporting
            MarkNotReported("++" , "--");
            AddToNoReportGroup("(" , "++" , "--");
            AddToNoReportGroup(NewLine);
            AddOperatorReportGroup("operator");
            AddTermsReportGroup("assignment operator" , "=" , "+=" , "-=" , "*=" , "/=");

            //8. Console
            ConsoleTitle = "Irony Expression Evaluator";
            ConsoleGreeting =
      @"Irony Expression Evaluator 

  Supports variable assignments, arithmetic operators (+, -, *, /),
    augmented assignments (+=, -=, etc), prefix/postfix operators ++,--, string operations. 
  Supports big integer arithmetics, string operations.
  Supports strings with embedded expressions : ""name: #{name}""

Press Ctrl-C to exit the program at any time.
";
            ConsolePrompt = "?";
            ConsolePromptMoreInput = "?";

            //9. Language flags. 
            // Automatically add NewLine before EOF so that our BNF rules work correctly when there's no final line break in source
            this.LanguageFlags = LanguageFlags.NewLineBeforeEOF | LanguageFlags.CreateAst | LanguageFlags.SupportsBigInt;
        }


        public override LanguageRuntime CreateRuntime(LanguageData language)
        {
            return new MyScriptEvaluatorRuntime(language);
        }
        


        #region Running in Grammar Explorer
        private static myScriptEvaluator _evaluator;
        public override string RunSample(RunSampleArgs args)
        {
            if (_evaluator == null)
            {
                _evaluator = new myScriptEvaluator(this);
                _evaluator.Globals.Add("null" , _evaluator.Runtime.NoneValue);
                _evaluator.Globals.Add("true" , true);
                _evaluator.Globals.Add("false" , false);

            }
            _evaluator.ClearOutput();
            //for (int i = 0; i < 1000; i++)  //for perf measurements, to execute 1000 times
            _evaluator.Evaluate(args.ParsedSample);
            return _evaluator.GetOutput();
        }
        #endregion


        //public override void CreateTokenFilters(LanguageData language , TokenFilterList filters)
        //{
        //    var outlineFilter = new CodeOutlineFilter(language.GrammarData ,
        //      OutlineOptions.ProduceIndents | OutlineOptions.CheckBraces , ToTerm(@" _")); // "\" is continuation symbol
        //    filters.Add(outlineFilter);
        //}


        //public override void CreateTokenFilters(LanguageData language , TokenFilterList filters)
        //{
        //    var outlineFilter = new CodeOutlineFilter(language.GrammarData ,
        //      OutlineOptions.None , ToTerm(@" _")); // "\" is continuation symbol
        //    filters.Add(outlineFilter);
        //}

            #endregion



    }//class
}
