// Copyright (c) allan_walpy. All Rights Reserved. Licensed under the Unlicense licence. See LICENSE in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Threading.Tasks;

using Alist.Error;
using Alist.Common;

namespace Alist.Tree
{

    /// <summary>
    /// adress of object in tree
    /// </summary>
    public partial class Adress
        : IEquatable<Adress>, IEquatable<string>, IComparable
    {

        /// <summary>
        /// separator of node names in adress
        /// </summary>
        public const char Separator = '.';

        /// <summary>
        /// node names of adress
        /// </summary>
        public List<string> NodeName { get; private set; }

        /// <summary>
        /// returns true if adress points at root
        /// </summary>
        public bool IsRoot
            => this.NodeName.Count == 0;

        /// <summary>
        /// create tree adress by list of node names
        /// </summary>
        /// <param name="nodeName">list of node names</param>
        protected internal Adress(List<string> nodeName)
        {
            this.NodeName = nodeName ?? new List<string>();
        }

        /// <summary>
        /// create tree adress pointing at root
        /// </summary>
        public Adress()
            : this(nodeName: new List<string>{ })
        {   }

        /// <summary>
        /// create tree adress by it's string form
        /// </summary>
        /// <param name="adress">string form of adress</param>
        public Adress(string adress)
            : this(nodeName: Adress.ToNodeName(adress))
        {   }

        private static string UnstringifyName(string name)
            => HtmlEntity.ToNormalString(name);

        private static string StringifyName(string name)
            => HtmlEntity.ToHtmlEntity(name, Adress.Separator);

        /// <summary>
        /// return list of node names by string form of adress
        /// </summary>
        /// <param name="adress">string form of adress</param>
        public static List<string> ToNodeName(string adress)
        {
            if (adress == null)
            {
                return new List<string>();
            }
            List<string> levelName = adress.Split(Adress.Separator).ToList();
            return levelName.Select<string, string>((x) => Adress.UnstringifyName(x))
                .ToList();
        }

        /// <summary>
        /// returns a copy of current adress
        /// </summary>
        public Adress Clone()
            => new Adress(this.ToString());

        /// <summary>
        /// add new node name to current adress
        /// </summary>
        public void GoDown(string name)
            => this.NodeName.Add(UnstringifyName(name));

        /// <summary>
        /// remove one node name of current adress
        /// </summary>
        public void GoUp()
        {
            if (this.IsRoot)
            {
                throw new ArgumentOutOfRangeException(
                    paramName: nameof(this.NodeName),
                    message: Helper.ErrorMessage.Node_NoParentNodeOnRoot
                );
            }
            this.NodeName.RemoveAt(this.NodeName.Count - 1);
        }

        /// <summary>
        /// add range of node names
        /// </summary>
        /// <param name="nodeName">node names</param>
        public void GoDowner(List<string> nodeName)
            => this.NodeName.AddRange(nodeName ?? new List<string>());

        /// <summary>
        /// add range of node names
        /// </summary>
        /// <param name="adress">adress with node names</param>
        public void GoDowner(Adress adress)
            => this.GoDowner(adress.NodeName);

        /// <summary>
        ///add range of node names
        /// </summary>
        /// <param name="adress">string form of adress with node names</param>
        public void GoDowner(string adress)
            => this.GoDowner(new Adress(adress));

        /// <summary>
        /// remove count node names of current adress
        /// </summary>
        public void GoUpper(int count)
        {
            if ((this.NodeName.Count < count) || (count < 0))
            {
                throw new ArgumentOutOfRangeException(
                    paramName: nameof(count),
                    actualValue: count,
                    message: Helper.ErrorMessage.Node_NotAccessableNodeCount
                );
            }
            this.NodeName.Resize(this.NodeName.Count - count);
        }

    }

}