/*
 * @(#)CachedArrayList.java 10/5/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Map;

/**
 * This is a fast access ArrayList that sacrifices memory for speed. It will reduce the speed of indexOf method from
 * O(n) to O(1). However it will at least double the memory used by ArrayList. So use it appropriately. <p><strong>Just
 * like ArrayList, this implementation is not synchronized.</strong> If you want a thread safe implementation, you can
 * use {@link com.jidesoft.utils.CachedArrayList}.
 */
public class CachedArrayList<E> extends ArrayList<E> {
    private static final long serialVersionUID = 3835017332487313880L;
    private Map<Object, IntegerWrapper> _indexCache;
    private boolean _lazyCaching = false;

    public CachedArrayList() {
    }

    public CachedArrayList(Collection<? extends E> c) {
        super(c);
        if (!isLazyCaching()) {
            cacheAll();
        }
    }

    public CachedArrayList(int initialCapacity) {
        super(initialCapacity);
    }

    @Override
    public int indexOf(Object elem) {
        initializeCache();
        IntegerWrapper o = _indexCache.get(elem);
        if (o != null) {
            return o.integer;
        }
        else if (isLazyCaching()) {
            int i = super.indexOf(elem);
            if (i == -1) {
                uncacheIt(elem);
            }
            else {
                cacheIt(elem, i);
            }
            return i;
        }
        else {
            return -1;
        }
    }

    /**
     * Adjusts the cache so that all values that are greater than index will increase by the value specified by the
     * increase parameter.
     *
     * @param index    the index. All values above this index will be changed.
     * @param increase a positive number to increase or a negative number to decrease.
     */
    protected synchronized void adjustCache(int index, int increase) {
        if (_indexCache != null) {
            if (size() - index <= size() >> 2) {
                Collection<IntegerWrapper> values = _indexCache.values();
                for (IntegerWrapper value : values) {
                    if (value.integer >= index) {
                        value.integer += increase;
                    }
                }
            }
            else {
                uncacheAll();
                if (!isLazyCaching()) {
                    cacheAll();
                }
            }
        }
    }

    protected Map<Object, IntegerWrapper> createCache() {
        return new IdentityHashMap<Object, IntegerWrapper>();
    }

    /**
     * Caches the index of the element.
     *
     * @param o     the element
     * @param index the index.
     */
    public synchronized void cacheIt(Object o, int index) {
        if (_indexCache != null && (_indexCache.get(o) == null || index < _indexCache.get(o).integer)) {
            _indexCache.put(o, new IntegerWrapper(index));
        }
    }

    /**
     * Uncaches the index of the element.
     *
     * @param o the element
     */
    public synchronized void uncacheIt(Object o) {
        if (_indexCache != null) {
            _indexCache.remove(o);
        }
    }

    @Override
    public boolean add(E element) {
        boolean added = super.add(element);
        if (!isLazyCaching() && added) {
            initializeCache();
            cacheIt(element, size() - 1);
        }
        return added;
    }

    @Override
    public void add(int index, E element) {
        super.add(index, element);
        if (!isLazyCaching()) {
            initializeCache();
            adjustCache(index, 1);
            cacheIt(element, index);
        }
        else if (_indexCache != null) {
            adjustCache(index, 1);
            cacheIt(element, index);
        }
    }

    private void initializeCache() {
        if (_indexCache == null) {
            _indexCache = createCache();
        }
    }

    @Override
    public E remove(int index) {
        E element = super.remove(index);
        if (element != null) {
            uncacheIt(element);
            adjustCache(index, -1);
        }
        return element;
    }

    @Override
    public boolean remove(Object o) {
        int oldIndex = indexOf(o);
        boolean removed = super.remove(o);
        if (removed) {
            uncacheIt(o);
            adjustCache(oldIndex, -1);
        }
        return removed;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        uncacheAll();
        return super.removeAll(c);
    }


    @Override
    public void clear() {
        uncacheAll();
        super.clear();
    }


    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean added = super.addAll(c);
        if (added) {
            cacheAll();
        }
        return added;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        boolean added = super.addAll(index, c);
        initializeCache();
        adjustCache(index, c.size());
        for (E e : c) {
            cacheIt(e, index++);
        }
        return added;
    }

    @Override
    public E set(int index, E element) {
        if (!isLazyCaching()) {
            initializeCache();
            E e = super.set(index, element);
            uncacheIt(e);
            cacheIt(element, index);
            return e;
        }
        else {
            return super.set(index, element);
        }
    }

    /**
     * Invalidated the whole cache.
     */
    public void invalidateCache() {
        uncacheAll();
    }

    /**
     * Uncache the whole cache. It is the same as {@link #invalidateCache()}.
     */
    public synchronized void uncacheAll() {
        if (_indexCache != null) {
            _indexCache.clear();
            _indexCache = null;
        }
    }

    /**
     * Cache all the element index.
     */
    public synchronized void cacheAll() {
        _indexCache = createCache();
        Integer i = 0;
        for (Object elem : this) {
            if (_indexCache.get(elem) == null) {
                _indexCache.put(elem, new IntegerWrapper(i));
            }
            i++;
        }
    }

    public boolean isLazyCaching() {
        return _lazyCaching;
    }

    public void setLazyCaching(boolean lazyCaching) {
        _lazyCaching = lazyCaching;
    }

    @Override
    protected void removeRange(int fromIndex, int toIndex) {
        if (fromIndex == toIndex) {
            remove(fromIndex);
        }
        else {
            super.removeRange(fromIndex, toIndex);
            uncacheAll();
            if (!isLazyCaching()) {
                cacheAll();
            }
        }
    }

    public static class IntegerWrapper {
        int integer;

        private IntegerWrapper(int integer) {
            this.integer = integer;
        }
    }
}
