using Lucene.Net.Util;
using System;

namespace Lucene.Net.Support
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    // LUCENENET specific

    /// <summary> Simple lockless and memory barrier free String intern cache that is guaranteed
    /// to return the same String instance as String.intern() does.
    /// </summary>
    public class SimpleStringInterner : StringInterner
    {
        
        internal /*private*/ class Entry
        {
            internal /*private*/ System.String str;
            internal /*private*/ int hash;
            internal /*private*/ Entry next;
            internal Entry(System.String str, int hash, Entry next)
            {
                this.str = str;
                this.hash = hash;
                this.next = next;
            }
        }
        
        private Entry[] cache;
        private int maxChainLength;
        
        /// <param name="tableSize"> Size of the hash table, should be a power of two.
        /// </param>
        /// <param name="maxChainLength"> Maximum length of each bucket, after which the oldest item inserted is dropped.
        /// </param>
        public SimpleStringInterner(int tableSize, int maxChainLength)
        {
            cache = new Entry[System.Math.Max(1, BitUtil.NextHighestPowerOfTwo(tableSize))];
            this.maxChainLength = System.Math.Max(2, maxChainLength);
        }
        
        // @Override
        public override System.String Intern(System.String s)
        {
            int h = s.GetHashCode();
            // In the future, it may be worth augmenting the string hash
            // if the lower bits need better distribution.
            int slot = h & (cache.Length - 1);
            
            Entry first = this.cache[slot];
            Entry nextToLast = null;
            
            int chainLength = 0;
            
            for (Entry e = first; e != null; e = e.next)
            {
                if (e.hash == h && (ReferenceEquals(e.str, s) || String.CompareOrdinal(e.str, s) == 0))
                {
                    // if (e.str == s || (e.hash == h && e.str.compareTo(s)==0)) {
                    return e.str;
                }
                
                chainLength++;
                if (e.next != null)
                {
                    nextToLast = e;
                }
            }

            // insertion-order cache: add new entry at head

#if !NETSTANDARD
            s = String.Intern(s);
#endif

            this.cache[slot] = new Entry(s, h, first);
            if (chainLength >= maxChainLength)
            {
                // prune last entry
                nextToLast.next = null;
            }
            return s;
        }
    }
}