using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Blueprinter.Core.Parsing;

namespace Blueprinter.Core
{
    public static class BlueprintParser
    {
        private static IEnumerable<char> _invalidChars;

        static BlueprintParser()
        {
            _invalidChars = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars());
        }

        public static Blueprint Parse(string blueprint)
        {
            // TODO: Split better
            return Parse(blueprint.Split(new[] { Environment.NewLine }, StringSplitOptions.None));
        }

        public static Blueprint Parse(IEnumerable<string> blueprintLines)
        {
            var exceptions = new[] { string.Empty, null };
            var structure = blueprintLines.Except(exceptions).ToArray();

            var blueprint = new Blueprint();

            BlueprintNode currentNode = blueprint;

            for (int ii = 0, length = structure.Length; ii < length; ii++)
            {
                string originalLine = structure[ii],
                    line = originalLine;

                line = ScrubComments(line)
                    .Replace("\t", string.Empty) // remove tabs
                    .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); // replace alternate slashes with correct slashes

                try
                {
                    // if line doesn't start with slash, it's in root
                    if (!StartsWithSlash(line))
                    {
                        // normalize
                        line = string.Format("{0}{1}", Path.DirectorySeparatorChar, line);
                        currentNode = blueprint;
                    }

                    // split the path into folder/file parts
                    currentNode = ParseLine(line, currentNode);
                }
                catch (BlueprintParseException ex)
                {
                    ex.ProblemLine = originalLine;
                    ex.Line = ii;
                    throw ex;
                }
            }

            return blueprint;
        }

        private static BlueprintNode ParseLine(string line, BlueprintNode currentNode)
        {
            var segments = GetSegmentDescriptors(line);

            foreach (var segment in segments)
            {
                var node = segment.Type == BlueprintNodeType.Project
                    ? currentNode
                    : new BlueprintNode(segment.Name);

                node.Shortcut = segment.Shortcut;

                if (segment.Type == BlueprintNodeType.Project)
                {
                    continue;
                }

                node.OwnerBlueprint = currentNode.OwnerBlueprint;
                node.ParentNode = currentNode;
                node.Type = segment.Type;
                currentNode.Children.Add(node);

                if (segment.Type == BlueprintNodeType.Directory)
                {
                    currentNode = node;
                }
            }

            return currentNode;
        }

        private static IEnumerable<SegmentDescriptor> GetSegmentDescriptors(string line)
        {
            var descriptors = new List<SegmentDescriptor>();
            var tokens = line.Split(new[] { Path.DirectorySeparatorChar });

            for (int ii = 1, ll = tokens.Length; ii < ll; ii++)
            {
                SegmentDescriptor segment;
                var token = tokens[ii];
                var trimmedToken = token.Trim();

                // this segment may begin with a colon;
                // signifying the prior segment is either
                // a directory or the root
                if (trimmedToken.StartsWith(":"))
                {
                    var shortcut = trimmedToken.Substring(1); // get shortcut without ':'

                    if (descriptors.Any())
                    {
                        segment = descriptors.Last();
                    }
                    else
                    {
                        segment = new SegmentDescriptor { Type = BlueprintNodeType.Project };
                        descriptors.Add(segment);
                    }

                    segment.Shortcut = shortcut;

                    continue;
                }

                var isLastItem = ii == ll - 1;

                segment = new SegmentDescriptor();

                var parts = token.Split(new[] { ':' }).ToList();             

                segment.Name = parts[0];
                segment.Type = isLastItem ? BlueprintNodeType.File : BlueprintNodeType.Directory;
                
                // ensure there are at least two elements in the list
                var extension = segment.Type == BlueprintNodeType.File
                    ? Path.GetExtension(segment.Name).Replace(".", string.Empty)
                    : "";

                parts.Add(extension);
                
                segment.Shortcut = parts[1];
                descriptors.Add(segment);
            }

            return descriptors.ToArray();
        }

        private static string ScrubComments(string line)
        {
            if (line.Contains('*'))
            {
                line = line.Remove(line.IndexOf("*")).Trim();
            }
            return line;
        }

        private static bool StartsWithSlash(string str)
        {
            return (str.StartsWith(Path.DirectorySeparatorChar.ToString()));
        }
    }
}
