/*
 * 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.apache.openjpa.util.proxy;

import java.io.ObjectStreamException;
import java.util.Collection;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;
import java.util.function.IntFunction;

import org.apache.openjpa.kernel.AutoDetach;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.BrokerFactory;
import org.apache.openjpa.kernel.DetachedStateManager;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.util.ChangeTracker;
import org.apache.openjpa.util.CollectionChangeTracker;
import org.apache.openjpa.util.DelayedCollectionChangeTrackerImpl;
import org.apache.openjpa.util.Proxies;
import org.apache.openjpa.util.Proxy;

/**
 * Vector proxy with delay loading capability.  Allows non-indexed
 * add and remove operations to occur on an unloaded collection.  Operations
 * that require a load will trigger a load.
 */
@SuppressWarnings({"rawtypes","unchecked"})
public class DelayedVectorProxy extends Vector implements ProxyCollection, DelayedProxy {
    private transient OpenJPAStateManager sm;
    private transient int field;
    private transient CollectionChangeTracker changeTracker;
    private transient Class elementType;

    private transient OpenJPAStateManager _ownerSm;
    private transient boolean _directAccess = false;
    private transient BrokerFactory _brokerFactory = null;
    private transient Broker _broker = null;
    private transient OpenJPAStateManager _delayedSm;
    private transient int _delayedField;
    private transient boolean _detached = false;

    public DelayedVectorProxy(int paramInt) {
        super(paramInt);
    }

    public DelayedVectorProxy() {
    }

    public DelayedVectorProxy(Collection paramCollection) {
        super(paramCollection);
    }

    public DelayedVectorProxy(int paramInt1, int paramInt2) {
        super(paramInt1, paramInt2);
    }

    @Override
    public void setOwner(OpenJPAStateManager paramOpenJPAStateManager,
            int paramInt) {
        // If clearing the owner of this proxy, store away what is necessary for
        // delayed loading
        if (sm != null && detaching(paramOpenJPAStateManager, paramInt)) {
            _detached = true;
            _delayedSm = sm;
            _delayedField = field;
        } else {
            _detached = false;
        }

        this.sm = paramOpenJPAStateManager;
        if (sm != null && sm.getPersistenceCapable() != null) {
            _ownerSm = (OpenJPAStateManager) sm.getPersistenceCapable()
                    .pcGetStateManager();
        }
        this.field = paramInt;
        if (sm != null && sm.getContext() != null) {
            _brokerFactory = sm.getContext().getBroker().getBrokerFactory();
        }
    }

    private boolean detaching(OpenJPAStateManager paramOpenJPAStateManager,
            int paramInt) {
        if ((paramOpenJPAStateManager == null && paramInt == -1)
                || (paramOpenJPAStateManager != null && paramOpenJPAStateManager instanceof DetachedStateManager)) {
            return true;
        }
        return false;
    }

    @Override
    public int getDelayedField() {
        if (field == -1 || _detached) {
            return _delayedField;
        }
        return field;
    }

    @Override
    public OpenJPAStateManager getDelayedOwner() {
        if (sm == null || _detached) {
            return _delayedSm;
        }
        return sm;
    }

    @Override
    public OpenJPAStateManager getOwner() {
        return this.sm;
    }

    @Override
    public int getOwnerField() {
        return this.field;
    }

    @Override
    public boolean isDirectAccess() {
        return _directAccess;
    }

    @Override
    public void setDirectAccess(boolean direct) {
        _directAccess = direct;
    }

    public BrokerFactory getBrokerFactory() {
        return _brokerFactory;
    }

    @Override
    public void load() {
        ProxyCollections.loadCollection(this);
    }

    @Override
    public Broker getBroker() {
        if (_broker == null || _broker.isClosed()) {
            if (_brokerFactory != null) {
                _broker = _brokerFactory.newBroker();
            }
        }
        return _broker;
    }

    @Override
    public void closeBroker() {
        if (_broker != null && !_broker.isClosed()) {
            _broker.setAutoDetach(AutoDetach.DETACH_CLOSE);
            _broker.close();
            _broker = null;
        }
    }

    @Override
    public OpenJPAStateManager getOwnerStateManager() {
        return _ownerSm;
    }

    @Override
    public boolean isDetached() {
        return _detached;
    }

    public boolean isDelayLoad() {
        return ProxyCollections.isDelayed(this);
    }

    @Override
    public synchronized Object clone() {
        if (isDirectAccess()) {
            return super.clone();
        }
        if (isDelayLoad()) {
            load();
        }

        Proxy localProxy = (Proxy) super.clone();
        localProxy.setOwner(null, 0);
        return localProxy;
    }

    @Override
    public ChangeTracker getChangeTracker() {
        return this.changeTracker;
    }

    protected void setChangeTracker(CollectionChangeTracker ct) {
        changeTracker = ct;
    }

    @Override
    public Object copy(Object paramObject) {
        if (isDelayLoad()) {
            load();
        }
        return new Vector((Collection) paramObject);
    }

    @Override
    public Class getElementType() {
        return this.elementType;
    }

    protected void setElementType(Class<?> elemType) {
        elementType = elemType;
    }

    @Override
    public ProxyCollection newInstance(Class paramClass,
            Comparator paramComparator, boolean paramBoolean1,
            boolean paramBoolean2) {
        DelayedVectorProxy localproxy = new DelayedVectorProxy();
        localproxy.elementType = paramClass;
        if (paramBoolean1) {
            localproxy.changeTracker = new DelayedCollectionChangeTrackerImpl(
                    localproxy, true, true, paramBoolean2);
        }
        return localproxy;
    }

    @Override
    public synchronized boolean add(Object paramObject) {
        if (_directAccess) {
            return super.add(paramObject);
        }

        ProxyCollections.beforeAdd(this, paramObject);
        boolean bool = super.add(paramObject);
        return ProxyCollections.afterAdd(this, paramObject, bool);
    }

    @Override
    public synchronized void add(int paramInt, Object paramObject) {
        if (!_directAccess) {
            if (isDelayLoad()) {
                load();
            }
        }
        ProxyCollections.beforeAdd(this, paramInt, paramObject);
        super.add(paramInt, paramObject);
    }

    @Override
    public void clear() {
        if (!_directAccess) {
            if (isDelayLoad()) {
                load();
            }
            ProxyCollections.beforeClear(this);
        }
        super.clear();
    }

    @Override
    public synchronized boolean addAll(int paramInt, Collection paramCollection) {
        if (_directAccess) {
            return super.addAll(paramInt, paramCollection);
        }
        if (isDelayLoad()) {
            load();
        }
        return ProxyCollections.addAll(this, paramInt, paramCollection);
    }

    @Override
    public synchronized boolean addAll(Collection paramCollection) {
        if (_directAccess) {
            return super.addAll(paramCollection);
        }
        return ProxyCollections.addAll(this, paramCollection);
    }

    @Override
    public synchronized void addElement(Object paramObject) {
        if (_directAccess) {
            super.addElement(paramObject);
            return;
        }

        ProxyCollections.beforeAddElement(this, paramObject);
        super.addElement(paramObject);
        ProxyCollections.afterAddElement(this, paramObject);
    }

    @Override
    public synchronized Object remove(int paramInt) {
        if (_directAccess) {
            return super.remove(paramInt);
        }
        if (isDelayLoad()) {
            load();
        }

        ProxyCollections.beforeRemove(this, paramInt);
        Object localObject = super.remove(paramInt);
        return ProxyCollections.afterRemove(this, paramInt, localObject);
    }

    @Override
    public synchronized boolean remove(Object paramObject) {
        if (_directAccess) {
            return super.remove(paramObject);
        }
        ProxyCollections.beforeRemove(this, paramObject);
        setDirectAccess(true);
        boolean bool = super.remove(paramObject);
        setDirectAccess(false);
        return ProxyCollections.afterRemove(this, paramObject, bool);
    }

    @Override
    public synchronized Object set(int paramInt, Object paramObject) {
        if (_directAccess) {
            return super.set(paramInt, paramObject);
        }
        if (isDelayLoad()) {
            load();
        }
        ProxyCollections.beforeSet(this, paramInt, paramObject);
        Object localObject = super.set(paramInt, paramObject);
        return ProxyCollections.afterSet(this, paramInt, paramObject,
                localObject);
    }

    @Override
    public synchronized boolean removeAll(Collection paramCollection) {
        if (_directAccess) {
            return super.removeAll(paramCollection);
        }
        return ProxyCollections.removeAll(this, paramCollection);
    }

    @Override
    public synchronized boolean retainAll(Collection paramCollection) {
        if (_directAccess) {
            return super.retainAll(paramCollection);
        }
        if (isDelayLoad()) {
            load();
        }
        return ProxyCollections.retainAll(this, paramCollection);
    }

    @Override
    public synchronized void insertElementAt(Object paramObject, int paramInt) {
        if (_directAccess) {
            super.insertElementAt(paramObject, paramInt);
            return;
        }
        if (isDelayLoad()) {
            load();
        }

        ProxyCollections.beforeInsertElementAt(this, paramObject, paramInt);
        super.insertElementAt(paramObject, paramInt);
    }

    @Override
    public synchronized void removeAllElements() {
        if (_directAccess) {
            super.removeAllElements();
            return;
        }
        if (isDelayLoad()) {
            load();
        }
        ProxyCollections.beforeRemoveAllElements(this);
        super.removeAllElements();
    }

    @Override
    public synchronized boolean removeElement(Object paramObject) {
        if (_directAccess) {
            return super.removeElement(paramObject);
        }
        ProxyCollections.beforeRemoveElement(this, paramObject);
        setDirectAccess(true);
        boolean bool = super.removeElement(paramObject);
        setDirectAccess(false);
        return ProxyCollections.afterRemoveElement(this, paramObject, bool);
    }

    @Override
    public synchronized void removeElementAt(int paramInt) {
        if (_directAccess) {
            super.removeElementAt(paramInt);
            return;
        }
        if (isDelayLoad()) {
            load();
        }
        ProxyCollections.beforeRemoveElementAt(this, paramInt);
        super.removeElementAt(paramInt);
    }

    @Override
    public synchronized void setElementAt(Object paramObject, int paramInt) {
        if (_directAccess) {
            super.setElementAt(paramObject, paramInt);
            return;
        }
        if (isDelayLoad()) {
            load();
        }
        ProxyCollections.beforeSetElementAt(this, paramObject, paramInt);
        super.setElementAt(paramObject, paramInt);
    }

    @Override
    public Iterator iterator() {
        if (_directAccess) {
            return super.iterator();
        }
        if (isDelayLoad()) {
            load();
        }
        Iterator localIterator = super.iterator();
        return ProxyCollections.afterIterator(this, localIterator);
    }

    @Override
    public ListIterator listIterator(int paramInt) {
        if (_directAccess) {
            return super.listIterator(paramInt);
        }
        if (isDelayLoad()) {
            load();
        }
        ListIterator localListIterator = super.listIterator(paramInt);
        return ProxyCollections.afterListIterator(this, paramInt,
                localListIterator);
    }

    @Override
    public ListIterator listIterator() {
        if (_directAccess) {
            return super.listIterator();
        }
        if (isDelayLoad()) {
            load();
        }
        ListIterator localListIterator = super.listIterator();
        return ProxyCollections.afterListIterator(this, localListIterator);
    }

    @Override
    public synchronized void setSize(int paramInt) {
        if (_directAccess) {
            super.setSize(paramInt);
            return;
        }
        if (isDelayLoad()) {
            load();
        }
        Proxies.dirty(this, true);
        super.setSize(paramInt);
    }

    protected synchronized Object writeReplace() throws ObjectStreamException {
        if (isDelayLoad()) {
            load();
        }
        return Proxies.writeReplace(this, true);
    }

    @Override
    public synchronized boolean contains(Object object) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.contains(object);
    }

    @Override
    public synchronized boolean containsAll(Collection collection) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.containsAll(collection);
    }

    @Override
    public synchronized boolean isEmpty() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.isEmpty();
    }

    @Override
    public synchronized int size() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.size();
    }

    @Override
    public synchronized Object[] toArray() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.toArray();
    }

    @Override
    public synchronized Object[] toArray(Object[] array) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.toArray(array);
    }

    @Override
    public Object[] toArray(IntFunction generator) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.toArray(generator);
    }

    @Override
    public synchronized boolean equals(Object paramObject) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.equals(paramObject);
    }

    @Override
    public synchronized int hashCode() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.hashCode();
    }

    @Override
    public synchronized int lastIndexOf(Object object) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.lastIndexOf(object);
    }

    @Override
    public synchronized List subList(int start, int end) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.subList(start, end);
    }

    @Override
    public synchronized Object get(int location) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.get(location);
    }

    @Override
    public synchronized int indexOf(Object object) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.indexOf(object);
    }

    @Override
    public synchronized int indexOf(Object object, int index) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.indexOf(object, index);
    }

    @Override
    public synchronized void copyInto(Object[] anArray) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        super.copyInto(anArray);
    }

    @Override
    public synchronized void trimToSize() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        super.trimToSize();
    }

    @Override
    public synchronized void ensureCapacity(int minCapacity) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        super.ensureCapacity(minCapacity);
    }

    @Override
    public synchronized int capacity() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.capacity();
    }

    @Override
    public Enumeration elements() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.elements();
    }

    @Override
    public synchronized int lastIndexOf(Object o, int index) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.lastIndexOf(o, index);
    }

    @Override
    public synchronized Object elementAt(int index) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.elementAt(index);
    }

    @Override
    public synchronized Object firstElement() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.firstElement();
    }

    @Override
    public synchronized Object lastElement() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.lastElement();
    }

    @Override
    public synchronized String toString() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.toString();
    }

    @Override
    protected synchronized void removeRange(int fromIndex, int toIndex) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        super.removeRange(fromIndex, toIndex);
    }
}
