/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt VS Tools.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

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

namespace QtVsTest.Macros
{
    using System.Collections;
    using static QtVsTools.SyntaxAnalysis.RegExpr;

    class MacroLines : IEnumerable<MacroLine>
    {
        List<MacroLine> Lines = new List<MacroLine>();

        public void Add(MacroLine line) { Lines.Add(line); }

        public IEnumerator<MacroLine> GetEnumerator() { return Lines.GetEnumerator(); }

        IEnumerator IEnumerable.GetEnumerator() { return Lines.GetEnumerator(); }
    }

    public class MacroLine
    { }

    public enum StatementType
    {
        Unknown,

        // Define reusable macro
        //# macro <macro name>
        Macro,

        // Switch to thread
        //# thread <ui | default>
        Thread,

        // Add reference to assembly and (opt.) namespaces in that assembly
        //# ref <assembly name> [namespace] [namespace] ...
        Reference,
        Ref = Reference,

        // Add reference to namespace
        //# using <namespace>
        Using,

        // Declare global variable, shared by called/calling macros
        //# var <type> <name> [ => <initial value> ]
        Var,

        // Get Visual Studio SDK service and assign it to local variable
        //# service <var name> <service interface> [service type]
        Service,

        // Call macro
        //# call <macro>
        Call,

        // Wait until expression evaluation returns non-default value
        // Optionally assign evaluated value to variable
        //# wait [timeout] [ <var type> <var name> ] => <expr>
        Wait,

        // UI automation command
        //
        // Set context based on UI element name path
        //# ui context [ VS ] => _string_ [, _string_, ... ]
        //
        // Set context based on window handle
        //# ui context HWND => _int_
        //
        // Get reference to UI element pattern. By default, the current context is used as source.
        // A name path relative to the current context allows using a child element as source.
        //# ui pattern <_TypeName_> <_VarName_> [ => _string_ [, _string_, ... ] ]
        //
        // Get reference to UI element Invoke pattern and immediately call the Invoke() method.
        //# ui pattern Invoke [ => _string_ [, _string_, ... ] ]
        //
        // Get reference to UI element Toggle pattern and immediately call the Toggle() method.
        //# ui pattern Toggle [ => _string_ [, _string_, ... ] ]
        Ui,

        // Close Visual Studio
        //# quit
        Quit
    }

    public class Statement : MacroLine
    {
        public StatementType Type { get; set; }
        public List<string> Args { get; set; }
        public string Code { get; set; }
    }

    public class CodeLine : MacroLine
    {
        public string Code;
        public CodeLine(string code)
        {
            Code = code;
        }
    }

    class MacroParser
    {
        Parser MacroTextParser;
        Token TokenMacro;

        public static MacroParser Get()
        {
            var _this = new MacroParser();
            return _this.Initialize() ? _this : null;
        }

        enum TokenId
        {
            Macro,
            Code,
            Statement,
            StatementType,
            StatementArg,
            StatementArgValue,
            StatementCode,
            StatementCodeValue,
        };

        bool Initialize()
        {
            var charEsc = Char['\\'];
            var charQuot = Char['\"'];
            var stmtBegin = new Token("//#");
            var codeBegin = new Token("=>");

            var stmtType = new Token(TokenId.StatementType,
                CharWord.Repeat(atLeast: 1));

            var quotedArgValue = new Token(TokenId.StatementArgValue, SkipWs_Disable,
                (charEsc & charQuot | CharSet[~(charQuot + CharCr + CharLf)]).Repeat(atLeast: 1));

            var quotedArg = new Token(TokenId.StatementArg,
                charQuot & quotedArgValue & charQuot.Optional())
            {
                new Rule<string>
                {
                    Create(TokenId.StatementArgValue, (string value) => value )
                }
            };

            var unquotedArg = new Token(TokenId.StatementArg,
                !LookAhead[charQuot] & CharSet[~CharSpace].Repeat(atLeast: 1));

            var stmtArg = !LookAhead[codeBegin] & (quotedArg | unquotedArg);

            var stmtCodeValue = new Token(TokenId.StatementCodeValue,
                CharSet[~(CharCr + CharLf)].Repeat());

            var stmtCode = new Token(TokenId.StatementCode,
                codeBegin & stmtCodeValue)
            {
                new Rule<string>
                {
                    Create(TokenId.StatementCodeValue, (string value) => value )
                }
            };

            var stmtLine = StartOfLine &
                new Token(TokenId.Statement, SkipWs_Disable,
                    //  '//#'    <type>     [ <arg>... ]   [  '=>'       <code>  ]
                    stmtBegin & stmtType & stmtArg.Repeat() & stmtCode.Optional())
                {
                    new Rule<Statement>
                    {
                        Capture(value => new Statement { Args = new List<string>() }),
                        Update(TokenId.StatementType, (Statement s, string typeStr) =>
                        {
                            StatementType type;
                            if (Enum.TryParse(typeStr, ignoreCase: true, result: out type))
                                s.Type = type;
                            else
                                s.Type = StatementType.Unknown;
                        }),
                        Update(TokenId.StatementArg, (Statement s, string arg) => s.Args.Add(arg)),
                        Update(TokenId.StatementCode, (Statement s, string code) => s.Code = code)
                    }
                }
                & SkipWs & (LineBreak | EndOfFile);

            var codeLine = StartOfLine &
                new Token(TokenId.Code, SkipWs_Disable,
                    !LookAhead[stmtBegin & stmtType] & CharSet[~(CharCr + CharLf)].Repeat())
                {
                    new Rule<CodeLine>
                    {
                        Capture((string value) => new CodeLine(value))
                    }
                }
                & (LineBreak | EndOfFile);

            TokenMacro = new Token(TokenId.Macro, SkipWs_Disable,
                (stmtLine | codeLine).Repeat() & EndOfFile)
            {
                new Rule<MacroLines>
                {
                    Update(TokenId.Statement, (MacroLines m, Statement s) => m.Add(s)),
                    Update(TokenId.Code,      (MacroLines m, CodeLine l)  => m.Add(l))
                }
            };

            var parser = TokenMacro.Render(defaultTokenWs: CharHorizSpace.Repeat());
            if (parser == null)
                return false;

            MacroTextParser = parser;
            return true;
        }

        public MacroLines Parse(string macroText)
        {
            return MacroTextParser.Parse(macroText)
                .GetValues<MacroLines>(TokenMacro)
                .FirstOrDefault();
        }
    }
}