package org.codehaus.jackson.map.deser.impl;

import java.util.*;

import org.codehaus.jackson.map.deser.SettableBeanProperty;

/**
 * Helper class used for storing mapping from property name to
 * {@link SettableBeanProperty} instances.
 *<p>
 * Note that this class is used instead of generic {@link java.util.HashMap}
 * is performance: although default implementation is very good for generic
 * use cases, it can still be streamlined a bit for specific use case
 * we have.
 * 
 * @since 1.7
 */
public final class BeanPropertyMap
{
    private final Bucket[] _buckets;
    
    private final int _hashMask;

    private final int _size;
    
    public BeanPropertyMap(Collection<SettableBeanProperty> properties)
    {
        _size = properties.size();
        int bucketCount = findSize(_size);
        _hashMask = bucketCount-1;
        Bucket[] buckets = new Bucket[bucketCount];
        for (SettableBeanProperty property : properties) {
            String key = property.getName();
            int index = key.hashCode() & _hashMask;
            buckets[index] = new Bucket(buckets[index], key, property);
        }
        _buckets = buckets;
    }

    public void assignIndexes()
    {
        // order is arbitrary, but stable:
        int index = 0;
        for (Bucket bucket : _buckets) {
            while (bucket != null) {
                bucket.value.assignIndex(index++);
                bucket = bucket.next;
            }
        }
    }
    
    private final static int findSize(int size)
    {
        // For small enough results (32 or less), we'll require <= 50% fill rate; otherwise 80%
        int needed = (size <= 32) ? (size + size) : (size + (size >> 2));
        int result = 2;
        while (result < needed) {
            result += result;
        }
        return result;
    }
    
    /*
    /**********************************************************
    /* Public API
    /**********************************************************
     */

    public int size() { return _size; }

    /**
     * Accessor for traversing over all contained properties.
     */
    public Iterator<SettableBeanProperty> allProperties() {
        return new IteratorImpl(_buckets);
    }
    
    public SettableBeanProperty find(String key)
    {
        int index = key.hashCode() & _hashMask;
        Bucket bucket = _buckets[index];
        // Let's unroll first lookup since that is null or match in 90+% cases
        if (bucket == null) {
            return null;
        }
        // Primarily we do just identity comparison as keys should be interned
        if (bucket.key == key) {
            return bucket.value;
        }
        while ((bucket = bucket.next) != null) {
            if (bucket.key == key) {
                return bucket.value;
            }
        }
        // Do we need fallback for non-interned Strings?
        return _findWithEquals(key, index);
    }

    /**
     * Specialized method that can be used to replace an existing entry
     * (note: entry MUST exist; otherwise exception is thrown) with
     * specified replacement.
     */
    public void replace(SettableBeanProperty property)
    {
        String name = property.getName();
        int index = name.hashCode() & (_buckets.length-1);

        /* This is bit tricky just because buckets themselves
         * are immutable, so we need to recreate the chain. Fine.
         */
        Bucket tail = null;
        boolean found = false;

        
        for (Bucket bucket = _buckets[index]; bucket != null; bucket = bucket.next) {
            // match to remove?
            if (!found && bucket.key.equals(name)) {
                found = true;
            } else {
                tail = new Bucket(tail, bucket.key, bucket.value);
            }
        }
        // Not finding specified entry is error, so:
        if (!found) {
            throw new NoSuchElementException("No entry '"+property+"' found, can't replace");
        }
        /* So let's attach replacement in front: useful also because
         * it allows replacement even when iterating over entries
         */
        _buckets[index] = new Bucket(tail, name, property);
    }

    /**
     * Specialized method for removing specified existing entry.
     * NOTE: entry MUST exist, otherwise an exception is thrown.
     * 
     * @since 1.9
     */
    public void remove(SettableBeanProperty property)
    {
        // Mostly this is the same as code with 'replace', just bit simpler...
        String name = property.getName();
        int index = name.hashCode() & (_buckets.length-1);
        Bucket tail = null;
        boolean found = false;
        // slightly complex just because chain is immutable, must recreate
        for (Bucket bucket = _buckets[index]; bucket != null; bucket = bucket.next) {
            // match to remove?
            if (!found && bucket.key.equals(name)) {
                found = true;
            } else {
                tail = new Bucket(tail, bucket.key, bucket.value);
            }
        }
        if (!found) { // must be found
            throw new NoSuchElementException("No entry '"+property+"' found, can't remove");
        }
        _buckets[index] = tail;
    }
    
    /*
    /**********************************************************
    /* Helper methods
    /**********************************************************
     */
    
    private SettableBeanProperty _findWithEquals(String key, int index)
    {
        Bucket bucket = _buckets[index];
        while (bucket != null) {
            if (key.equals(bucket.key)) {
                return bucket.value;
            }
            bucket = bucket.next;
        }
        return null;
    }

    /*
    /**********************************************************
    /* Helper beans
    /**********************************************************
     */
    
    private final static class Bucket
    {
        public final Bucket next;
        public final String key;
        public final SettableBeanProperty value;
        
        public Bucket(Bucket next, String key, SettableBeanProperty value)
        {
            this.next = next;
            this.key = key;
            this.value = value;
        }
    }

    private final static class IteratorImpl implements Iterator<SettableBeanProperty>
    {
        /**
         * Buckets of the map
         */
        private final Bucket[] _buckets;

        /**
         * Bucket that contains next value to return (if any); null if nothing more to iterate
         */
        private Bucket _currentBucket;

        /**
         * Index of the next bucket in bucket array to check.
         */
        private int _nextBucketIndex;
        
        public IteratorImpl(Bucket[] buckets) {
            _buckets = buckets;
            // need to initialize to point to first entry...
            int i = 0;
            for (int len = _buckets.length; i < len; ) {
                Bucket b = _buckets[i++];
                if (b != null) {
                    _currentBucket = b;
                    break;
                }
            }
            _nextBucketIndex = i;
        }

        @Override
        public boolean hasNext() {
            return _currentBucket != null;
        }

        @Override
        public SettableBeanProperty next()
        {
            Bucket curr = _currentBucket;
            if (curr == null) { // sanity check
                throw new NoSuchElementException();
            }
            // need to advance, too
            Bucket b = curr.next;
            while (b == null && _nextBucketIndex < _buckets.length) {
                b = _buckets[_nextBucketIndex++];
            }
            _currentBucket = b;
            return curr.value;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}
