using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Ink.Runtime;

namespace Ink.Runtime
{
    public class Path : IEquatable<Path>
	{
        static string parentId = "^";

        // Immutable Component
        public class Component : IEquatable<Component>
		{
			public int index { get; private set; }
			public string name { get; private set; }
			public bool isIndex { get { return index >= 0; } }
            public bool isParent {
                get {
                    return name == Path.parentId;
                }
            }

			public Component(int index)
			{
				Debug.Assert(index >= 0);
				this.index = index;
				this.name = null;
			}

			public Component(string name)
			{
				Debug.Assert(name != null && name.Length > 0);
				this.name = name;
				this.index = -1;
			}

            public static Component ToParent()
            {
                return new Component (parentId);
            }

			public override string ToString ()
			{
				if (isIndex) {
					return index.ToString ();
				} else {
					return name;
				}
			}

            public override bool Equals (object obj)
            {
                return Equals (obj as Component);
            }

            public bool Equals(Component otherComp)
            {
                if (otherComp != null && otherComp.isIndex == this.isIndex) {
                    if (isIndex) {
                        return index == otherComp.index;   
                    } else {
                        return name == otherComp.name;
                    }
                }

                return false;
            }

            public override int GetHashCode ()
            {
                if (isIndex)
                    return this.index;
                else
                    return this.name.GetHashCode ();
            }
		}

		public Component GetComponent(int index)
		{
			return _components[index];
		}

        public bool isRelative { get; private set; }

		public Component head 
		{ 
			get 
			{ 
				if (_components.Count > 0) {
					return _components.First ();
				} else {
					return null;
				}
			} 
		}

		public Path tail 
		{ 
			get 
			{
				if (_components.Count >= 2) {
					List<Component> tailComps = _components.GetRange (1, _components.Count - 1);
					return new Path(tailComps);
				} 

                else {
                    return Path.self;
				}

			}
		}
            
		public int length { get { return _components.Count; } }

		public Component lastComponent 
		{ 
			get 
			{ 
				var lastComponentIdx = _components.Count-1;
				if( lastComponentIdx >= 0 )
					return _components[lastComponentIdx];
				else
					return null;
			} 
		}

        public bool containsNamedComponent {
            get {
                foreach(var comp in _components) {
                    if( !comp.isIndex ) {
                        return true;
                    }
                }
                return false;
            }
        }

		public Path()
		{
			_components = new List<Component> ();
		}

		public Path(Component head, Path tail) : this()
		{
			_components.Add (head);
			_components.AddRange (tail._components);
		}

		public Path(IEnumerable<Component> components, bool relative = false) : this()
		{
			this._components.AddRange (components);
            this.isRelative = relative;
		}

        public Path(string componentsString) : this()
        {
            this.componentsString = componentsString;
        }

        public static Path self {
            get {
                var path = new Path ();
                path.isRelative = true;
                return path;
            }
        }

		public Path PathByAppendingPath(Path pathToAppend)
		{
            Path p = new Path ();

            int upwardMoves = 0;
            for (int i = 0; i < pathToAppend._components.Count; ++i) {
                if (pathToAppend._components [i].isParent) {
                    upwardMoves++;
                } else {
                    break;
                }
            }

            for (int i = 0; i < this._components.Count - upwardMoves; ++i) {
                p._components.Add (this._components [i]);
            }

            for(int i=upwardMoves; i<pathToAppend._components.Count; ++i) {
                p._components.Add (pathToAppend._components [i]);
            }

			return p;
		}

        public Path PathByAppendingComponent (Component c)
        {
            Path p = new Path ();
            p._components.AddRange (_components);
            p._components.Add (c);
            return p;
        }

        public string componentsString {
            get {
				if( _componentsString == null ) {
					_componentsString = StringExt.Join (".", _components);
					if (isRelative) _componentsString = "." + _componentsString;
				}
				return _componentsString;
            }
            private set {
                _components.Clear ();

				_componentsString = value;

                // Empty path, empty components
                // (path is to root, like "/" in file system)
                if (string.IsNullOrEmpty(_componentsString))
                    return;

                // When components start with ".", it indicates a relative path, e.g.
                //   .^.^.hello.5
                // is equivalent to file system style path:
                //  ../../hello/5
                if (_componentsString [0] == '.') {
                    this.isRelative = true;
                    _componentsString = _componentsString.Substring (1);
                } else {
                    this.isRelative = false;
                }

                var componentStrings = _componentsString.Split('.');
                foreach (var str in componentStrings) {
                    int index;
                    if (int.TryParse (str , out index)) {
                        _components.Add (new Component (index));
                    } else {
                        _components.Add (new Component (str));
                    }
                }
            }
        }
		string _componentsString;

		public override string ToString()
		{
            return componentsString;
		}

        public override bool Equals (object obj)
        {
            return Equals (obj as Path);
        }

        public bool Equals (Path otherPath)
        {
            if (otherPath == null)
                return false;

            if (otherPath._components.Count != this._components.Count)
                return false;

            if (otherPath.isRelative != this.isRelative)
                return false;

            return otherPath._components.SequenceEqual (this._components);
        }

        public override int GetHashCode ()
        {
            // TODO: Better way to make a hash code!
            return this.ToString ().GetHashCode ();
        }

		List<Component> _components;
	}
}

