// 
// Copyright (c) 2013-2016 Carsten Sonne Larsen <cs@innolan.dk>
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Text;
using Ntp.Analyzer.Config.Syntax;
using Ntp.Analyzer.Config.Syntax.Option;
using Ntp.Analyzer.Config.Syntax.Setting;
using Ntp.Analyzer.Config.Table;

namespace Ntp.Analyzer.Config.Compiler
{
    /// <summary>
    /// The config parser transforms configuration texts into a syntax tree.
    /// </summary>
    public sealed class ConfigParser : ICompilerStage, IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigParser" /> class.
        /// </summary>
        /// <param name="file">Configuration file.</param>
        /// <param name="root">Syntax root node.</param>
        public ConfigParser(string file, ISyntaxNode root)
        {
            this.root = root;
            tokenizer = new Tokenizer(file);
            errors = new List<string>();
        }

        private readonly List<string> errors;
        private readonly ISyntaxNode root;
        private readonly Tokenizer tokenizer;

        /// <summary>
        /// Gets or sets the symbol table.
        /// </summary>
        /// <value>The symbol table.</value>
        public SymbolTable SymbolTable { get; set; }

        /// <summary>
        /// Gets the errors from parsing.
        /// </summary>
        /// <value>The parser errors.</value>
        public IEnumerable<string> Errors => errors;

        /// <summary>
        /// Execute the parser stage.
        /// </summary>
        public void Execute()
        {
            var node = root;
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordDatabase:
                        ParseDatabaseSection(node);
                        break;
                    case Symbol.KeywordPermission:
                        ParsePermissionSection(node);
                        break;
                    case Symbol.KeywordNotify:
                        ParseNotifySection(node);
                        break;
                    case Symbol.KeywordDaemon:
                    case Symbol.KeywordService:
                        ParseHeartbeatSection(node);
                        break;
                    case Symbol.KeywordReading:
                        ParseReadingSection(node);
                        break;
                    case Symbol.KeywordLog:
                        ParseLogSection(node);
                        break;
                    case Symbol.KeywordListener:
                        ParseListenerSection(node);
                        break;
                    case Symbol.KeywordServer:
                        ParseHostSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            if (errors.Count == 0 && tokenizer.PeekNext() != Token.EndOfFile)
            {
                LogError("Unknown section.");
            }
        }

        public void Dispose()
        {
            tokenizer.Dispose();
        }

        private void AddToSymbolTable(string name, ISyntaxNode node)
        {
            var current = SymbolTable.Lookup(name);

            if (current != null)
            {
                LogError($"section {name} is already defined. Choose another name.");
            }
            else
            {
                SymbolTable.Add(name, node);
            }
        }

        private void LogError(string error)
        {
            string entry = $"Error in line {tokenizer.LineNumber} column {tokenizer.ColumnNumber}: {error}";
            errors.Add(entry);
        }

        private void ParseAboutPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new AboutPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordPageTitle:
                    case Symbol.KeywordServerId:
                    case Symbol.KeywordContentTitle:
                    case Symbol.KeywordLink:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordPageTemplate:
                        ParseOption<PageThemeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordDefault,
                            Symbol.KeywordBootstrap
                        });
                        break;
                    case Symbol.KeywordContent:
                        ParseContentSection(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseFileDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        private void ParseContentSection(ISyntaxNode parent)
        {
            tokenizer.Consume();

            if (tokenizer.GetNext().Symbol != Symbol.OpeningBrace)
                LogError("Content section should begin with an opening brace.");

            tokenizer.Consume();

            Token token;
            var content = new StringBuilder();
            while ((token = tokenizer.GetNext()).Symbol != Symbol.ClosingBrace)
                content.Append(token.Text);

            var node = new StringSettingNode(Keyword.Content, content.ToString(), tokenizer.LineNumber);
            parent.Add(node);
        }

        private void ParseDatabaseSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new DatabaseSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordName:
                    case Symbol.KeywordHost:
                    case Symbol.KeywordIp:
                    case Symbol.KeywordAddress:
                    case Symbol.KeywordHostAddress:
                    case Symbol.KeywordPort:
                    case Symbol.KeywordUser:
                    case Symbol.KeywordPass:
                    case Symbol.KeywordCreate:
                    case Symbol.KeywordUpgrade:
                    case Symbol.KeywordImport:
                    case Symbol.KeywordDatabaseName:
                    case Symbol.KeywordDatabaseUser:
                    case Symbol.KeywordDatabasePass:
                    case Symbol.KeywordConString:
                    case Symbol.KeywordCertFile:
                    case Symbol.KeywordCertPass:
                    case Symbol.KeywordConnectionTimeout:
                    case Symbol.KeywordProtocol:
                    case Symbol.KeywordEnableSsl:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDatabaseProvider:
                        ParseOption<DatabaseProviderNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordDatabaseProviderMySql,
                            Symbol.KeywordDatabaseProviderPostgre
                        });
                        break;
                    case Symbol.KeywordSslMode:
                        ParseOption<DatabaseProviderNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordRequired,
                            Symbol.KeywordPreferred
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseDelimiter()
        {
            if (tokenizer.PeekNext() == Token.NewLine)
                return;

            tokenizer.ConsumeWhiteSpaces();
            if (tokenizer.PeekNext() != Token.NewLine)
                LogError("Expected end of line.");
        }

        private void ParseDirectoryDestinationSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new DirDestinationSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordPrefix:
                    case Symbol.KeywordDirectory:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseFileDestinationSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new FileDestinationSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                if (tokenizer.GetNext().Symbol == Symbol.KeywordFile)
                    ParseValue(node);
                else
                    pass = false;
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseGraphSetSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new GraphSetSyntaxNode(name, parent.Symbol, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordLinkIndex:
                    case Symbol.KeywordGraph:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseHeartbeatSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new HeartbeatSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                if (tokenizer.GetNext().Symbol == Symbol.KeywordHeartbeat)
                    ParseValue(node);
                else
                    pass = false;
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseHostGraphPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new HostGraphPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordGraphPage:
                    case Symbol.KeywordLinkIndex:
                    case Symbol.KeywordLink:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseDirectoryDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        /// <summary>
        /// Parses a host graph configuration section.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private void ParseHostGraphSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new HostGraphSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordWidth:
                    case Symbol.KeywordHeight:
                    case Symbol.KeywordTimespan:
                    case Symbol.KeywordFilterFactor:
                    case Symbol.KeywordJitter:
                    case Symbol.KeywordOffset:
                    case Symbol.KeywordGraphFrequency:
                    case Symbol.KeywordGraphStability:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseFileDestinationSection(node);
                        break;
                    case Symbol.KeywordWebLinks:
                        ParseWebLinkSection(node);
                        break;
                    case Symbol.KeywordGraphTime:
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        /// <summary>
        /// Parses a host page configuration section.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private void ParseHostPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new HostPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordPageTitle:
                    case Symbol.KeywordLink:
                    case Symbol.KeywordPoolMember:
                    case Symbol.KeywordQueryDirect:
                    case Symbol.KeywordPeerPage:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordPageTemplate:
                        ParseOption<PageThemeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordDefault,
                            Symbol.KeywordBootstrap
                        });
                        break;
                    case Symbol.KeywordPageTime:
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    case Symbol.KeywordGraphSet:
                        ParseGraphSetSection(node);
                        break;
                    case Symbol.KeywordSummaries:
                        ParseSummariesSection(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseFileDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        private void ParseHostSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new HostSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordHostId:
                    case Symbol.KeywordIp:
                    case Symbol.KeywordAddress:
                    case Symbol.KeywordHostAddress:
                    case Symbol.KeywordFilePath:
                    case Symbol.KeywordWebPath:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordHostType:
                        ParseOption<HostTypeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordNtpdc,
                            Symbol.KeywordNtpq,
                            Symbol.KeywordNtpctl
                        });
                        break;
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    case Symbol.KeywordAboutPage:
                        ParseAboutPageSection(node);
                        break;
                    case Symbol.KeywordHostPage:
                        ParseHostPageSection(node);
                        break;
                    case Symbol.KeywordPeerPage:
                        ParsePeerPageSection(node);
                        break;
                    case Symbol.KeywordHostGraph:
                        ParseHostGraphSection(node);
                        break;
                    case Symbol.KeywordPeerGraph:
                        ParsePeerGraphSection(node);
                        break;
                    case Symbol.KeywordTrafficGraph:
                        ParseTrafficGraphSection(node);
                        break;
                    case Symbol.KeywordPeerSummaryPage:
                        ParsePeerSummaryPageSection(node);
                        break;
                    case Symbol.KeywordHostGraphPage:
                        ParseHostGraphPageSection(node);
                        break;
                    case Symbol.KeywordPeerGraphPage:
                        ParsePeerGraphPageSection(node);
                        break;
                    case Symbol.KeywordMenu:
                        ParseMenuSection(node);
                        break;
                    case Symbol.KeywordHostStats:
                    case Symbol.KeywordHostIoStats:
                    case Symbol.KeywordPeerStats:
                    case Symbol.KeywordDriftStats:
                        ParseStatSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseIntOrString(ISyntaxNode parent)
        {
            var keyword = ((KeywordToken) tokenizer.Token).Keyword;
            tokenizer.ConsumeWhiteSpaces();
            var token = tokenizer.GetNext();

            switch (token.TokenType)
            {
                case TokenType.IntegerValue:
                    parent.Add(new IntegerSettingNode(keyword, ((IntegerToken) token).Value, tokenizer.LineNumber));
                    break;
                case TokenType.Litteral:
                case TokenType.QuotedIdent:
                    parent.Add(new StringSettingNode(keyword, token.Text, tokenizer.LineNumber));
                    break;
                default:
                    LogError($"{keyword.Name} must be either an integer or a string.");
                    break;
            }
        }

        private void ParseListenerSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new ListenerSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                var token = tokenizer.GetNext();

                switch (token.Symbol)
                {
                    case Symbol.KeywordIp:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordPort:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseLogSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new LogSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordType:
                        ParseOption<LogTypeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordFile,
                            Symbol.KeywordSyslog,
                            Symbol.KeywordConsole
                        });
                        break;
                    case Symbol.KeywordSeverity:
                        ParseOption<SeverityNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordError,
                            Symbol.KeywordWarn,
                            Symbol.KeywordNotice,
                            Symbol.KeywordInfo,
                            Symbol.KeywordDebug,
                            Symbol.KeywordTrace
                        });
                        break;
                    case Symbol.KeywordShowTimestamp:
                    case Symbol.KeywordShowSeverity:
                    case Symbol.KeywordTimeFormat:
                    case Symbol.KeywordFile:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseMenuItem(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new MenuItemSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordPage:
                    case Symbol.KeywordCaption:
                    case Symbol.KeywordLink:
                    case Symbol.KeywordDropdown:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordType:
                        ParseMenuItemType(node);
                        break;
                    case Symbol.KeywordContent:
                        ParseMenuSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseMenuItemType(ISyntaxNode node)
        {
            tokenizer.Consume();
            var token = tokenizer.GetNext();

            switch (token.Symbol)
            {
                case Symbol.KeywordLink:
                case Symbol.KeywordName:
                case Symbol.KeywordPage:
                case Symbol.KeywordSpacer:
                case Symbol.KeywordHeader:
                    node.Add(new MenuItemTypeNode {Value = token.Symbol});
                    break;
                case Symbol.KeywordDropdown:
                    ParseMenuItem(node);
                    break;
                default:
                    node.Add(new MenuItemTypeNode {Value = Symbol.Unknown});
                    break;
            }
        }

        private void ParseMenuSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new MenuSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                if (tokenizer.GetNext().Symbol == Symbol.KeywordItem)
                    ParseMenuItem(node);
                else
                    pass = false;
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseNotifySection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new NotifySyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordUser:
                    case Symbol.KeywordPass:
                    case Symbol.KeywordIp:
                    case Symbol.KeywordHost:
                    case Symbol.KeywordAddress:
                    case Symbol.KeywordHostAddress:
                    case Symbol.KeywordPort:
                    case Symbol.KeywordEnableSsl:
                    case Symbol.KeywordSender:
                    case Symbol.KeywordMail:
                    case Symbol.KeywordRecipient:
                    case Symbol.KeywordSubject:
                    case Symbol.KeywordSmtpHost:
                    case Symbol.KeywordSmtpPort:
                    case Symbol.KeywordSmtpUser:
                    case Symbol.KeywordSmtpPass:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        /// <summary>
        /// Parses the options of a setting.
        /// </summary>
        /// <param name="node">Parent node.</param>
        /// <param name="options">Options.</param>
        private void ParseOption<T>(ISyntaxNode node, List<Symbol> options)
            where T : SymbolSettingNode, new()
        {
            tokenizer.ConsumeWhiteSpaces();
            var token = tokenizer.GetNext();

            if (options.Contains(token.Symbol))
                node.Add(new T {Value = token.Symbol});
            else
            {
                var keyword = ((KeywordToken) tokenizer.Token).Keyword;
                LogError($"Invalid value for option {keyword}");
            }

            ParseDelimiter();
        }

        private void ParsePeerGraphPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new PeerGraphPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordGraphPage:
                    case Symbol.KeywordLinkIndex:
                    case Symbol.KeywordLink:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseDirectoryDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        /// <summary>
        /// Parses a peer graph configuration section.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private void ParsePeerGraphSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new PeerGraphSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordWidth:
                    case Symbol.KeywordHeight:
                    case Symbol.KeywordTimespan:
                    case Symbol.KeywordFilterFactor:
                    case Symbol.KeywordJitter:
                    case Symbol.KeywordOffset:
                    case Symbol.KeywordGraphBalance:
                    case Symbol.KeywordGraphDelay:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseDirectoryDestinationSection(node);
                        break;
                    case Symbol.KeywordWebLinks:
                        ParseWebLinkSection(node);
                        break;
                    case Symbol.KeywordGraphTime:
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        private void ParsePeerPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new PeerPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordPageTitle:
                    case Symbol.KeywordLink:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordPageTemplate:
                        ParseOption<PageThemeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordDefault,
                            Symbol.KeywordBootstrap
                        });
                        break;
                    case Symbol.KeywordTimeStamp:
                    case Symbol.KeywordPageTime:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    case Symbol.KeywordGraphSet:
                        ParseGraphSetSection(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseDirectoryDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        private void ParsePeerSummaryPageSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new PeerSummaryPageSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordPageTitle:
                    case Symbol.KeywordPeerPage:
                    case Symbol.KeywordLink:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordPageTemplate:
                        ParseOption<PageThemeNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordDefault,
                            Symbol.KeywordBootstrap
                        });
                        break;
                    case Symbol.KeywordGraphSet:
                        ParseGraphSetSection(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseFileDestinationSection(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        private void ParsePermissionSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new PermissionSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordUser:
                    case Symbol.KeywordExecUser:
                    case Symbol.KeywordGroup:
                        ParseIntOrString(node);
                        break;
                    case Symbol.KeywordMode:
                        ParseValue(node);
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseReadingSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new ReadingSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordName:
                        StringSettingNode nameNode = ParseValue(node) as StringSettingNode;
                        if (nameNode != null)
                            name = nameNode.Value;
                        break;
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        /// <summary>
        /// Parses the footer of a configuration section.
        /// </summary>
        /// <param name="name">Section name. Shown in error messages.</param>
        private void ParseSectionEnd(string name)
        {
            if (tokenizer.Token.Symbol == Symbol.ClosingBrace)
                return;

            tokenizer.ConsumeWhiteSpaces();
            if (tokenizer.GetNext().Symbol == Symbol.ClosingBrace)
                return;

            string errorName = string.IsNullOrWhiteSpace(name)
                ? string.Empty
                : name + " ";

            LogError($"Configuration section {errorName}has unexpected content.");
        }

        /// <summary>
        /// Get the header of a configuration section.
        /// </summary>
        /// <returns></returns>
        private string ParseSectionStart()
        {
            tokenizer.Consume();
            var token = tokenizer.GetNext();

            string name = null;
            if (token.IsText)
            {
                name = token.Text;
                tokenizer.Consume();
                token = tokenizer.GetNext();
            }

            if (token.Symbol != Symbol.OpeningBrace)
                LogError("Configuration section should begin with an opening brace.");

            return name;
        }

        /// <summary>
        /// Parses the stat configuration section.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private void ParseStatSection(ISyntaxNode parent)
        {
            var symbol = tokenizer.Token.Symbol;
            var name = ParseSectionStart();
            var node = new StatSyntaxNode(symbol, name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordFile:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordFrequency:
                        ParseIntOrString(node);
                        break;
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        private void ParseSummariesSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new SummariesSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                if (tokenizer.GetNext().Symbol == Symbol.KeywordPeerSummaryPage)
                    ParseValue(node);
                else
                    pass = false;
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }

        /// <summary>
        /// Parses a traffic graph configuration section.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private void ParseTrafficGraphSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new TrafficGraphSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                switch (tokenizer.GetNext().Symbol)
                {
                    case Symbol.KeywordFrequency:
                    case Symbol.KeywordInitialRun:
                    case Symbol.KeywordFixedRun:
                    case Symbol.KeywordTitle:
                    case Symbol.KeywordWidth:
                    case Symbol.KeywordHeight:
                    case Symbol.KeywordTimespan:
                    case Symbol.KeywordGraphReceived:
                    case Symbol.KeywordGraphIgnored:
                    case Symbol.KeywordGraphDropped:
                    case Symbol.KeywordGraphSent:
                    case Symbol.KeywordGraphNotSent:
                    case Symbol.KeywordGraphAvgReceived:
                    case Symbol.KeywordGraphAvgIgnored:
                    case Symbol.KeywordGraphAvgDropped:
                    case Symbol.KeywordGraphAvgSent:
                    case Symbol.KeywordGraphAvgNotSent:
                    case Symbol.KeywordGraphPlotInterval:
                    case Symbol.KeywordGraphPacketRate:
                        ParseValue(node);
                        break;
                    case Symbol.KeywordDestinations:
                        ParseFileDestinationSection(node);
                        break;
                    case Symbol.KeywordWebLinks:
                        ParseWebLinkSection(node);
                        break;
                    case Symbol.KeywordGraphTime:
                    case Symbol.KeywordTimeStamp:
                        ParseOption<TimeStampNode>(node, new List<Symbol>
                        {
                            Symbol.KeywordTimeStampLocal,
                            Symbol.KeywordTimeStampUtc
                        });
                        break;
                    default:
                        pass = false;
                        break;
                }
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
            AddToSymbolTable(name, node);
        }

        /// <summary>
        /// Parses the value of a setting.
        /// </summary>
        /// <param name="parent">Parent node.</param>
        private ISyntaxNode ParseValue(ISyntaxNode parent)
        {
            var keyword = ((KeywordToken) tokenizer.Token).Keyword;
            tokenizer.ConsumeWhiteSpaces();
            var token = tokenizer.GetNext();
            ISyntaxNode node;

            if (keyword.ValueType == typeof(string) && token.IsText)
                node = new StringSettingNode(keyword, token.Text, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(string) && token == Token.NewLine)
                node = new StringSettingNode(keyword, string.Empty, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(int) && token.TokenType == TokenType.IntegerValue)
                node = new IntegerSettingNode(keyword, ((IntegerToken) token).Value, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(int) && token.Symbol == Symbol.Zero)
                node = new IntegerSettingNode(keyword, 0, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(int) && token.Symbol == Symbol.One)
                node = new IntegerSettingNode(keyword, 1, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(double) && token.TokenType == TokenType.NumericValue)
                node = new NumericSettingNode(keyword, ((NumericToken) token).Value, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(double) && token.TokenType == TokenType.IntegerValue)
                node = new NumericSettingNode(keyword, ((IntegerToken) token).Value, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(double) && token.Symbol == Symbol.Zero)
                node = new NumericSettingNode(keyword, 0.0, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(double) && token.Symbol == Symbol.One)
                node = new NumericSettingNode(keyword, 1.0, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(bool) &&
                     (token.Symbol == Symbol.BooleanTrue || token.Symbol == Symbol.One))
                node = new BooleanSettingNode(keyword, true, tokenizer.LineNumber);
            else if (keyword.ValueType == typeof(bool) &&
                     (token.Symbol == Symbol.BooleanFalse || token.Symbol == Symbol.Zero))
                node = new BooleanSettingNode(keyword, false, tokenizer.LineNumber);
            else
            {
                LogError($"Invalid value for setting {keyword.Name}");
                ParseDelimiter();
                return null;
            }

            parent.Add(node);
            ParseDelimiter();
            return node;
        }

        private void ParseWebLinkSection(ISyntaxNode parent)
        {
            var name = ParseSectionStart();
            var node = new WebLinkSyntaxNode(name, tokenizer.LineNumber);
            var pass = true;

            do
            {
                tokenizer.Consume();
                if (tokenizer.GetNext().Symbol == Symbol.KeywordLink)
                    ParseValue(node);
                else
                    pass = false;
            } while (pass);

            ParseSectionEnd(name);
            parent.Add(node);
        }
    }
}