// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;

namespace Microsoft.CSharp.RuntimeBinder.Syntax
{
    internal sealed class NameTable
    {
        private sealed class Entry
        {
            public readonly Name Name;
            public readonly int HashCode;
            public Entry Next;

            public Entry(Name name, int hashCode, Entry next)
            {
                Name = name;
                HashCode = hashCode;
                Next = next;
            }
        }

        private Entry[] _entries;
        private int _count;
        private int _mask;

        internal NameTable()
        {
            _mask = 31;
            _entries = new Entry[_mask + 1];
        }

        public Name Add(string key)
        {
            int hashCode = ComputeHashCode(key);
            for (Entry e = _entries[hashCode & _mask]; e != null; e = e.Next)
            {
                if (e.HashCode == hashCode && e.Name.Text.Equals(key))
                {
                    return e.Name;
                }
            }

            return AddEntry(new Name(key), hashCode);
        }

        public Name Add(string key, int length)
        {
            int hashCode = ComputeHashCode(key, length);
            for (Entry e = _entries[hashCode & _mask]; e != null; e = e.Next)
            {
                if (e.HashCode == hashCode && Equals(e.Name.Text, key, length))
                {
                    return e.Name;
                }
            }

            return AddEntry(new Name(key.Substring(0, length)), hashCode);
        }

        internal void Add(Name name)
        {
            int hashCode = ComputeHashCode(name.Text);
            // make sure it doesn't already exist
            Debug.Assert(Array.TrueForAll(_entries, e => e?.Name.Text != name.Text));

            AddEntry(name, hashCode);
        }

        private static int ComputeHashCode(string key)
        {
            unchecked
            {
                int hashCode = key.Length;
                // use key.Length to eliminate the range check
                for (int i = 0; i < key.Length; i++)
                {
                    hashCode += (hashCode << 7) ^ key[i];
                }

                // mix it a bit more
                hashCode -= hashCode >> 17;
                hashCode -= hashCode >> 11;
                hashCode -= hashCode >> 5;

                return hashCode;
            }
        }

        private static int ComputeHashCode(string key, int length)
        {
            Debug.Assert(key != null);
            Debug.Assert(length <= key.Length);
            unchecked
            {
                int hashCode = length;
                for (int i = 0; i < length; i++)
                {
                    hashCode += (hashCode << 7) ^ key[i];
                }

                // mix it a bit more
                hashCode -= hashCode >> 17;
                hashCode -= hashCode >> 11;
                hashCode -= hashCode >> 5;

                return hashCode;
            }
        }

        private static bool Equals(string candidate, string key, int length)
        {
            Debug.Assert(candidate != null);
            Debug.Assert(key != null);
            Debug.Assert(length <= key.Length);
            if (candidate.Length != length)
            {
                return false;
            }

            for (int i = 0; i < candidate.Length; i++)
            {
                if (candidate[i] != key[i])
                {
                    return false;
                }
            }

            return true;
        }

        private Name AddEntry(Name name, int hashCode)
        {
            int index = hashCode & _mask;
            Entry e = new Entry(name, hashCode, _entries[index]);
            _entries[index] = e;
            if (_count++ == _mask)
            {
                Grow();
            }

            return e.Name;
        }

        private void Grow()
        {
            int newMask = _mask * 2 + 1;
            Entry[] oldEntries = _entries;
            Entry[] newEntries = new Entry[newMask + 1];

            // use oldEntries.Length to eliminate the range check
            for (int i = 0; i < oldEntries.Length; i++)
            {
                Entry e = oldEntries[i];
                while (e != null)
                {
                    int newIndex = e.HashCode & newMask;
                    Entry tmp = e.Next;
                    e.Next = newEntries[newIndex];
                    newEntries[newIndex] = e;
                    e = tmp;
                }
            }

            _entries = newEntries;
            _mask = newMask;
        }
    }
}
