using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using Gherkin.Ast;

namespace Gherkin
{
    public class GherkinLine : IGherkinLine
    {
        private static char[] inlineWhitespaceChars = new char[] { ' ', '\t', '\u00A0'};

        private readonly string lineText;
        private readonly string trimmedLineText;
        public int LineNumber { get; private set; }

        public GherkinLine(string line, int lineNumber)
        {
            this.LineNumber = lineNumber;

            this.lineText = line;
            this.trimmedLineText = this.lineText.TrimStart();
        }

        public void Detach()
        {
            //nop
        }

        public int Indent
        {
            get { return lineText.Length - trimmedLineText.Length; }
        }

        public bool IsEmpty()
        {
            return trimmedLineText.Length == 0;
        }

        public bool StartsWith(string text)
        {
            return trimmedLineText.StartsWith(text);
        }

        public bool StartsWithTitleKeyword(string text)
        {
            return StringUtils.StartsWith(trimmedLineText, text) &&
                StartsWithFrom(trimmedLineText, text.Length, GherkinLanguageConstants.TITLE_KEYWORD_SEPARATOR);
        }

        private static bool StartsWithFrom(string text, int textIndex, string value)
        {
            return string.CompareOrdinal(text, textIndex, value, 0, value.Length) == 0;
        }

        public string GetLineText(int indentToRemove)
        {
            if (indentToRemove < 0 || indentToRemove > Indent)
                return trimmedLineText;

            return lineText.Substring(indentToRemove);
        }

        public string GetRestTrimmed(int length)
        {
            return trimmedLineText.Substring(length).Trim();
        }

        public IEnumerable<GherkinLineSpan> GetTags()
        {
            var uncommentedLine = Regex.Split(trimmedLineText, @"\s" + GherkinLanguageConstants.COMMENT_PREFIX)[0];
            int position = Indent;
            foreach (string item in uncommentedLine.Split(GherkinLanguageConstants.TAG_PREFIX[0]))
            {
                if (item.Length > 0)
                {
                    var tagName = GherkinLanguageConstants.TAG_PREFIX + item.TrimEnd(inlineWhitespaceChars);
                    if (tagName.Length == 1)
                        continue;

                    if (tagName.IndexOfAny(inlineWhitespaceChars) >= 0)
                        throw new InvalidTagException("A tag may not contain whitespace", new Location(LineNumber, position));

                    yield return new GherkinLineSpan(position, tagName);
                    position += item.Length;
                }
                position++; // separator
            }
        }
        
        public IEnumerable<GherkinLineSpan> GetTableCells()
        {
            var items = SplitCells(trimmedLineText).ToList();
            bool isBeforeFirst = true;
            foreach (var item in items.Take(items.Count - 1)) // skipping the one after last
            {
                if (!isBeforeFirst)
                {
                    int trimmedStart;
                    var cellText = Trim(item.Item1, out trimmedStart);
                    var cellPosition = item.Item2 + trimmedStart;

                    if (cellText.Length == 0)
                        cellPosition = item.Item2;

                    yield return new GherkinLineSpan(Indent + cellPosition + 1, cellText);
                }

                isBeforeFirst = false;
            }
        }

        private IEnumerable<Tuple<string, int>> SplitCells(string row)
        {
            var rowEnum = row.GetEnumerator();            

            string cell = "";
            int pos = 0;
            int startPos = 0;
            while (rowEnum.MoveNext()) {
                pos++;
                char c = rowEnum.Current;
                if (c.ToString() == GherkinLanguageConstants.TABLE_CELL_SEPARATOR) {
                    yield return Tuple.Create(cell, startPos);
                    cell = "";
                    startPos = pos;
                } else if (c == GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR) {
                    rowEnum.MoveNext();
                    pos++;
                    c = rowEnum.Current;
                    if (c == GherkinLanguageConstants.TABLE_CELL_NEWLINE_ESCAPE) {
                        cell += "\n";
                    } else {
                        if (c.ToString() != GherkinLanguageConstants.TABLE_CELL_SEPARATOR && c != GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR) {
                            cell += GherkinLanguageConstants.TABLE_CELL_ESCAPE_CHAR;
                        }
                        cell += c;
                    }
                } else {
                    cell += c;
                }
            }
            yield return Tuple.Create(cell, startPos);
        }

        private string Trim(string s, out int trimmedStart)
        {
            trimmedStart = 0;
            while (trimmedStart < s.Length && inlineWhitespaceChars.Contains(s[trimmedStart]))
                trimmedStart++;

            return s.Trim(inlineWhitespaceChars);
        }
    }
}
