using System.Collections.Generic;
using System.Threading;
using ZeroLevel.Services.Serialization;

namespace ZeroLevel.Services.Semantic
{
    public class Trie
        : IBinarySerializable
    {
        internal class TrieNode
            : IBinarySerializable
        {
            public char Key;
            public uint? Value;
            public TrieNode Parent;
            public List<TrieNode> Children;

            public TrieNode() { }
            public TrieNode(TrieNode parent) { Parent = parent; }
            public void Deserialize(IBinaryReader reader)
            {
                this.Key = reader.ReadChar();
                if (reader.ReadBoolean())
                {
                    this.Value = reader.ReadUInt32();
                }
                else
                {
                    this.Value = null;
                }
                this.Children = reader.ReadCollection<TrieNode>();
            }

            public void Serialize(IBinaryWriter writer)
            {
                writer.WriteChar(this.Key);
                if (this.Value.HasValue)
                {
                    writer.WriteBoolean(true);
                    writer.WriteUInt32(this.Value.Value);
                }
                else
                {
                    writer.WriteBoolean(false);
                }
                if (this.Children == null)
                {
                    writer.WriteInt32(0);
                }
                else
                {
                    writer.WriteCollection<TrieNode>(this.Children);
                }
            }
            internal TrieNode Append(string word, int index, bool reverse)
            {
                if (word.Length == index)
                {
                    if (!this.Value.HasValue)
                    {
                        return this;
                    }
                }
                else
                {
                    if (this.Children == null)
                    {
                        this.Children = new List<TrieNode>();
                    }
                    for (int i = 0; i < Children.Count; i++)
                    {
                        if (Children[i].Key == word[index])
                        {
                            return Children[i].Append(word, index + 1, reverse);
                        }
                    }
                    var tn = new TrieNode(reverse ? this : null) { Key = word[index] };
                    Children.Add(tn);
                    return tn.Append(word, index + 1, reverse);
                }
                return null;
            }
            internal uint? GetKey(string word, int index)
            {
                if (word.Length == index + 1)
                {
                    return this.Value;
                }
                else
                {
                    if (this.Children == null)
                    {
                        this.Children = new List<TrieNode>();
                    }
                    for (int i = 0; i < Children.Count; i++)
                    {
                        if (Children[i].Key == word[index])
                        {
                            return Children[i].GetKey(word, index + 1);
                        }
                    }
                }
                return null;
            }

            internal void RebuildReverseIndex(TrieNode parent, Dictionary<uint, TrieNode> index)
            {
                this.Parent = parent;
                if (this.Value.HasValue)
                {
                    index.Add(this.Value.Value, this);
                }
                if (this.Children != null)
                {
                    foreach (var child in this.Children)
                    {
                        child.RebuildReverseIndex(this, index);
                    }
                }
            }

            internal void DestroyReverseIndex()
            {
                this.Parent = null;
                if (this.Children != null)
                {
                    foreach (var child in this.Children)
                    {
                        child.DestroyReverseIndex();
                    }
                }
            }
        }

        internal List<TrieNode> _roots;
        private int _word_index = 0;
        private bool _use_reverse_index;

        private Dictionary<uint, TrieNode> _reverse_index;
        public Trie(bool reverse_index = false)
        {
            _use_reverse_index = reverse_index;
            if (_use_reverse_index)
            {
                _reverse_index = new Dictionary<uint, TrieNode>();
            }
            _roots = new List<TrieNode>();
        }

        public void ToggleReverseIndex(bool enabled)
        {
            if (_use_reverse_index == enabled) return;
            _use_reverse_index = enabled;
            if (_use_reverse_index)
            {
                RebuildReverseIndex();
            }
            else
            {
                DestroyReverseIndex();
            }
        }
        public void Append(string word)
        {
            if (word.Length == 0) return;
            bool found = false;
            for (int i = 0; i < _roots.Count; i++)
            {
                if (_roots[i].Key == word[0])
                {
                    var node = _roots[i].Append(word, 1, _use_reverse_index);
                    Thread.MemoryBarrier();
                    if (node != null)
                    {
                        node.Value = (uint)Interlocked.Increment(ref _word_index);
                        if (_use_reverse_index)
                        {
                            _reverse_index.Add(node.Value.Value, node);
                        }
                    }
                    found = true;
                }
            }
            if (!found)
            {
                var tn = new TrieNode { Key = word[0] };
                _roots.Add(tn);
                var node = tn.Append(word, 1, _use_reverse_index);
                Thread.MemoryBarrier();
                node.Value = (uint)Interlocked.Increment(ref _word_index);
                if (node != null)
                {
                    if (_use_reverse_index)
                    {
                        _reverse_index.Add(node.Value.Value, node);
                    }
                }
            }
        }

        public uint? Key(string word)
        {
            if (word?.Length == 0) return null;
            for (int i = 0; i < _roots.Count; i++)
            {
                if (_roots[i].Key == word[0])
                {
                    if (word.Length == 1)
                    {
                        return _roots[i].Value;
                    }
                    else
                    {
                        return _roots[i].GetKey(word, 1);
                    }
                }
            }
            return null;
        }

        public bool Contains(string word)
        {
            if (word?.Length == 0) return false;
            for (int i = 0; i < _roots.Count; i++)
            {
                if (_roots[i].Key == word[0])
                {
                    if (word.Length == 1)
                    {
                        return _roots[i].Value.HasValue;
                    }
                    else
                    {
                        return _roots[i].GetKey(word, 1).HasValue;
                    }
                }
            }
            return false;
        }

        public void Serialize(IBinaryWriter writer)
        {
            writer.WriteInt32(this._word_index);
            writer.WriteBoolean(this._use_reverse_index);
            writer.WriteCollection<TrieNode>(this._roots);
        }

        public void Deserialize(IBinaryReader reader)
        {
            this._word_index = reader.ReadInt32();
            this._use_reverse_index = reader.ReadBoolean();
            this._roots = reader.ReadCollection<TrieNode>();
            RebuildReverseIndex();
        }

        private void RebuildReverseIndex()
        {
            if (this._use_reverse_index)
            {
                if (_reverse_index == null)
                {
                    _reverse_index = new Dictionary<uint, TrieNode>();
                }
                foreach (var node in _roots)
                {
                    node.RebuildReverseIndex(null, _reverse_index);
                }
            }
        }

        private void DestroyReverseIndex()
        {
            if (_reverse_index != null)
            {
                _reverse_index.Clear();
                _reverse_index = null;
            }
            foreach (var node in _roots)
            {
                node.DestroyReverseIndex();
            }
        }
    }
}