/*
 *  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.
 */
package org.codehaus.groovy.util;

import java.util.Collection;
import java.util.LinkedList;

public abstract class AbstractConcurrentMapBase {
    protected static final int MAXIMUM_CAPACITY = 1 << 30;
    static final int MAX_SEGMENTS = 1 << 16;
    static final int RETRIES_BEFORE_LOCK = 2;
    final int segmentMask;
    final int segmentShift;
    protected final Segment[] segments;

    public AbstractConcurrentMapBase(Object segmentInfo) {
        int sshift = 0;
        int ssize = 1;
        while (ssize < 16) {
            ++sshift;
            ssize <<= 1;
        }
        segmentShift = 32 - sshift;
        segmentMask = ssize - 1;
        this.segments = new Segment[ssize];

        int c = 512 / ssize;
        if (c * ssize < 512)
            ++c;
        int cap = 1;
        while (cap < c)
            cap <<= 1;

        for (int i = 0; i < this.segments.length; ++i)
            this.segments[i] = createSegment(segmentInfo, cap);
    }

    protected abstract Segment createSegment(Object segmentInfo, int cap);

    protected static <K> int hash(K key) {
        int h = System.identityHashCode(key);
        h += ~(h << 9);
        h ^=  (h >>> 14);
        h +=  (h << 4);
        h ^=  (h >>> 10);
        return h;
    }

    public Segment segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }

    public int fullSize() {
        int count = 0;
        for (Segment segment : segments) {
            segment.lock();
            try {
                for (int j = 0; j < segment.table.length; j++) {
                    Object o = segment.table[j];
                    if (o != null) {
                        if (o instanceof Entry) {
                            count++;
                        } else {
                            Object[] arr = (Object[]) o;
                            count += arr.length;
                        }
                    }
                }
            } finally {
                segment.unlock();
            }
        }
        return count;
    }

    public int size() {
        int count = 0;
        for (Segment segment : segments) {
            segment.lock();
            try {
                for (int j = 0; j < segment.table.length; j++) {
                    Object o = segment.table[j];
                    if (o != null) {
                        if (o instanceof Entry) {
                            Entry e = (Entry) o;
                            if (e.isValid())
                                count++;
                        } else {
                            Object[] arr = (Object[]) o;
                            for (Object value : arr) {
                                Entry info = (Entry) value;
                                if (info != null && info.isValid())
                                    count++;
                            }
                        }
                    }
                }
            } finally {
                segment.unlock();
            }
        }
        return count;
    }

    public Collection values() {
        Collection result = new LinkedList();
        for (Segment segment : segments) {
            segment.lock();
            try {
                for (int j = 0; j < segment.table.length; j++) {
                    Object o = segment.table[j];
                    if (o != null) {
                        if (o instanceof Entry) {
                            Entry e = (Entry) o;
                            if (e.isValid())
                                result.add(e);
                        } else {
                            Object[] arr = (Object[]) o;
                            for (Object value : arr) {
                                Entry info = (Entry) value;
                                if (info != null && info.isValid())
                                    result.add(info);
                            }
                        }
                    }
                }
            } finally {
                segment.unlock();
            }
        }
        return result;
    }

    public static class Segment extends LockableObject {
        private static final long serialVersionUID = -4128828550135386431L;
        volatile int count;

        int threshold;

        protected volatile Object[] table;

        protected Segment(int initialCapacity) {
            setTable(new Object[initialCapacity]);
        }

        void setTable(Object[] newTable) {
            threshold = (int) (newTable.length * 0.75f);
            table = newTable;
        }

        void removeEntry (Entry e) {
            lock ();
            int newCount = count;
            try {
                Object [] tab = table;
                int index = e.getHash() & (tab.length-1);
                Object o = tab[index];
                if (o != null) {
                    if (o instanceof Entry) {
                        if (o == e) {
                            tab [index] = null;
                            newCount--;
                        }
                    }
                    else {
                        Object[] arr = (Object[]) o;
                        Object res = null;
                        for (Object value : arr) {
                            Entry info = (Entry) value;
                            if (info != null) {
                                if (info != e) {
                                    if (info.isValid()) {
                                        res = put(info, res);
                                    } else {
                                        newCount--;
                                    }
                                } else {
                                    newCount--;
                                }
                            }
                        }
                        tab [index] = res;
                    }
                    count = newCount;
                }
            }
            finally {
                unlock();
            }
        }

        void rehashIfThresholdExceeded() {
            if(count > threshold) {
                rehash();
            }
        }

        void rehash() {
            Object[] oldTable = table;
            int oldCapacity = oldTable.length;
            if (oldCapacity >= MAXIMUM_CAPACITY)
                return;

            int newCount = 0;
            for (int i = 0; i < oldCapacity ; i++) {
                Object o = oldTable [i];
                if (o != null) {
                    if (o instanceof Entry) {
                        Entry e = (Entry) o;
                        if (e.isValid()) {
                            newCount++;
                        }
                        else {
                            oldTable[i] = null;
                        }
                    }
                    else {
                        Object[] arr = (Object[]) o;
                        int localCount = 0;
                        for (int index = 0; index < arr.length; index++) {
                            Entry e = (Entry) arr[index];
                            if (e != null && e.isValid()) {
                                localCount++;
                            }
                            else {
                                arr [index] = null;
                            }
                        }
                        if (localCount == 0)
                          oldTable[i] = null;
                        else
                          newCount += localCount;
                    }
                }
            }

            Object[] newTable = new Object[newCount+1 < threshold ? oldCapacity : oldCapacity << 1];
            int sizeMask = newTable.length - 1;
            newCount = 0;
            for (Object o : oldTable) {
                if (o != null) {
                    if (o instanceof Entry) {
                        Entry e = (Entry) o;
                        if (e.isValid()) {
                            int index = e.getHash() & sizeMask;
                            put(e, index, newTable);
                            newCount++;
                        }
                    } else {
                        Object[] arr = (Object[]) o;
                        for (Object value : arr) {
                            Entry e = (Entry) value;
                            if (e != null && e.isValid()) {
                                int index = e.getHash() & sizeMask;
                                put(e, index, newTable);
                                newCount++;
                            }
                        }
                    }
                }
            }

            threshold = (int)(newTable.length * 0.75f);

            table = newTable;
            count = newCount;
        }

        private static void put(Entry ee, int index, Object[] tab) {
            Object o = tab[index];
            if (o != null) {
                if (o instanceof Entry) {
                    Object[] arr = new Object [2];
                    arr [0] = ee;
                    arr [1] = o;
                    tab[index] = arr;
                    return;
                }
                else {
                    Object[] arr = (Object[]) o;
                    Object[] newArr = new Object[arr.length+1];
                    newArr [0] = ee;
                    System.arraycopy(arr, 0, newArr, 1, arr.length);
                    tab [index] = newArr;
                    return;
                }
            }
            tab[index] = ee;
        }

        private static Object put(Entry ee, Object o) {
            if (o != null) {
                if (o instanceof Entry) {
                    Object[] arr = new Object [2];
                    arr [0] = ee;
                    arr [1] = o;
                    return arr;
                }
                else {
                    Object[] arr = (Object[]) o;
                    Object[] newArr = new Object[arr.length+1];
                    newArr [0] = ee;
                    System.arraycopy(arr, 0, newArr, 1, arr.length);
                    return newArr;
                }
            }
            return ee;
        }
    }

    public interface Entry<V> {
        V getValue();

        void setValue(V value);

        int getHash();

        boolean isValid();
    }
}
