﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace CommandParser
{
    #region Data Structure

    public class Token : Object
    {
        public string type;

        public Token(string type)
        {
            this.type = type;
        }

        public virtual object GetValue()
        {
            return null;
        }

        public override string ToString()
        {
            return "Type: " + type;
        }
    }

    public class TKTag : Token
    {
        public char value;

        public TKTag(char value)
            : base(Lexer.TAG)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value;
        }
    }

    public class TKInt : Token
    {
        public int value;

        public TKInt(int value)
            : base(Lexer.INT)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value;
        }
    }

    public class TKFloat : Token
    {
        public float value;

        public TKFloat(float value)
            : base(Lexer.FLOAT)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value;
        }
    }

    public class TKWord : Token
    {
        public string value;

        public TKWord(string value)
            : base(Lexer.WORD)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value;
        }
    }

    public class TKString : Token
    {
        public string value;

        public TKString(string value)
            : base(Lexer.STRING)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value;
        }
    }

    public class TKBool : Token
    {
        public bool value;

        public TKBool(bool value)
            : base(Lexer.BOOL)
        {
            this.value = value;
        }

        public override object GetValue()
        {
            return value;
        }

        public override string ToString()
        {
            return "Type: " + type + " Value: " + value.ToString();
        }
    }

    #endregion

    public class Lexer
    {
        public static string WORD = "WORD";
        public static string TAG = "TAG";
        public static string STRING = "STRING";
        public static string INT = "INT";
        public static string FLOAT = "FLOAT";
        public static string BOOL = "BOOL";

        public static char DOT = '.';
        public static char COMMA = ',';
        public static char BRACKET_LEFT = '(';
        public static char BRACKET_RIGHT = ')';
        public static char DB_QUOTES = '"';
        public static char SEMICOION = ';';
        public static char SPACE = ' ';

        public static List<Token> Analysis(string inputString)
        {
            List<Token> outputTokens = new List<Token>();
            char[] inputChars = inputString.ToCharArray();
            int charsLength = inputChars.Length;

            int index = 0;
            char peek = SPACE;

            #region lambda
            Func<bool> readch = () =>
            {
                if (index < charsLength)
                {
                    peek = inputChars[index];
                    index++;
                    return true;
                }
                else
                {
                    peek = SPACE;
                    return false;
                }
            };

            Func<bool> revertch = () =>
            {
                if (index - 1 >= 0 && index < charsLength)
                {
                    index--;
                    peek = inputChars[index];
                    return true;
                }
                else
                {
                    peek = SPACE;
                    return false;
                }
            };
            #endregion

            while (index < charsLength)
            {
                #region 跳过空白字符
                while (readch())
                {
                    if (Char.IsWhiteSpace(peek))
                        continue;
                    else
                        break;
                }
                #endregion

                #region 判断int/float
                if (Char.IsDigit(peek))
                {
                    // int
                    int tmp = 0;
                    do
                    {
                        tmp = tmp * 10 + (int)Char.GetNumericValue(peek);
                    }
                    while (readch() && Char.IsDigit(peek));
                    if (peek != DOT)
                    {
                        // find token, to next loop
                        outputTokens.Add(new TKInt(tmp));
                        revertch();
                        continue;
                    }
                    // float
                    float tmp1 = tmp;
                    float tmp2 = 10f;
                    while (readch())
                    {
                        if (!Char.IsDigit(peek))
                            break;
                        tmp1 += (int)Char.GetNumericValue(peek) / tmp2;
                        tmp2 *= 10f;
                    }
                    // find token, to next loop
                    outputTokens.Add(new TKFloat(tmp1));
                    revertch();
                    continue;
                }
                #endregion

                #region 判断Word
                if (Char.IsLetter(peek))
                {
                    StringBuilder builder = new StringBuilder();
                    do
                    {
                        builder.Append(peek);
                    }
                    while (readch() && Char.IsLetterOrDigit(peek));
                    // find word, to next loop
                    outputTokens.Add(new TKWord(builder.ToString()));
                    revertch();
                    continue;
                }
                #endregion

                #region 判断string
                if (peek == DB_QUOTES)
                {
                    StringBuilder builder = new StringBuilder();
                    while (readch())
                    {
                        if (peek == DB_QUOTES)
                            break;
                        else
                            builder.Append(peek);
                    }
                    // find string, to next loop
                    outputTokens.Add(new TKString(builder.ToString()));
                    continue;
                }
                #endregion

                #region 判断Tag
                if (peek == SEMICOION)
                {
                    // find ';'
                    break;
                }
                else if (peek == COMMA)
                {
                    // find ','
                    outputTokens.Add(new TKTag(COMMA));
                    continue;
                }
                else if (peek == DOT)
                {
                    // find '.'
                    outputTokens.Add(new TKTag(DOT));
                    continue;
                }
                else if (peek == BRACKET_LEFT)
                {
                    // find '('
                    outputTokens.Add(new TKTag(BRACKET_LEFT));
                    continue;
                }
                else if (peek == BRACKET_RIGHT)
                {
                    // find ')'
                    outputTokens.Add(new TKTag(BRACKET_RIGHT));
                    continue;
                }
                else
                {
                    throw new ArgumentException("Lexer error, undefined character '" + peek + "'.");
                }
                #endregion
            }

            // 将word.value == true/false的Token:word，转换为Token:bool

            for (int i = 0; i < outputTokens.Count; i++)
            {
                var tk = outputTokens[i];
                if (tk is TKWord)
                {
                    TKWord word = tk as TKWord;
                    if (word.value == "true" || word.value == "false")
                    {
                        outputTokens[i] = new TKBool(word.value == "true" ? true : false);
                    }
                }
            }

            return outputTokens;
        }
    }
}
