/*
 * 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.Iterator;
import java.util.NavigableSet;
import java.util.SortedSet;
import java.util.TreeSet;
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;

/**
 * TreeSet 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 DelayedTreeSetProxy extends TreeSet 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 DelayedTreeSetProxy() {
    }

    public DelayedTreeSetProxy(Comparator paramComparator) {
        super(paramComparator);
    }

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

    public DelayedTreeSetProxy(SortedSet paramSortedSet) {
        super(paramSortedSet);
    }

    @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 OpenJPAStateManager getOwner() {
        return this.sm;
    }

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

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

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

    @Override
    public Object copy(Object paramObject) {
        return new TreeSet((SortedSet) 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) {
        DelayedTreeSetProxy localproxy = new DelayedTreeSetProxy(
                paramComparator);
        localproxy.elementType = paramClass;
        if (paramBoolean1)
            localproxy.changeTracker = new DelayedCollectionChangeTrackerImpl(
                    localproxy, false, false, paramBoolean2);
        return localproxy;
    }

    @Override
    public Object clone() {
        if (_directAccess) {
            return super.clone();
        }
        if (isDelayLoad()) {
            load();
        }
        Proxy localProxy = (Proxy) super.clone();
        localProxy.setOwner(null, 0);
        return localProxy;
    }


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

    ////////////////////////////////////////
    // DelayedProxy methods
    ////////////////////////////////////////
    @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 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);
    }

    ////////////////////////////////////////
    // TreeSet methods
    ////////////////////////////////////////

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

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

    @Override
    public 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 int size() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.size();
    }

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

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

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

    @Override
    public 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 Comparator comparator() {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.comparator();
    }

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

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

    @Override
    public 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 boolean containsAll(Collection c) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.containsAll(c);
    }

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

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

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

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

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

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

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

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

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

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

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

    @Override
    public NavigableSet subSet(Object fromElement, boolean fromInclusive,
            Object toElement, boolean toInclusive) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.subSet(fromElement, fromInclusive, toElement, toInclusive);
    }

    @Override
    public NavigableSet headSet(Object toElement, boolean inclusive) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.headSet(toElement, inclusive);
    }

    @Override
    public NavigableSet tailSet(Object fromElement, boolean inclusive) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.tailSet(fromElement, inclusive);
    }

    @Override
    public SortedSet subSet(Object fromElement, Object toElement) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.subSet(fromElement, toElement);
    }

    @Override
    public SortedSet headSet(Object toElement) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.headSet(toElement);
    }

    @Override
    public SortedSet tailSet(Object fromElement) {
        if (!_directAccess && isDelayLoad()) {
            load();
        }
        return super.tailSet(fromElement);
    }

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

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