/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.sessions.remote;

import java.util.*;

import org.eclipse.persistence.indirection.*;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.internal.descriptors.*;
import org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder;

/**
 * Helper class for RemoteSessionController.
 * This descriptor iterator will replace all the normal
 * value holders in an object with remote value holders
 * that can be serialized to the client.
 */
public class ReplaceValueHoldersIterator extends DescriptorIterator {
    RemoteSessionController controller;

    /**
     * default constructor
     */
    private ReplaceValueHoldersIterator() {
        super();
    }

    /**
     * public constructor
     */
    public ReplaceValueHoldersIterator(RemoteSessionController controller) {
        this();
        this.initialize(controller);
    }

    /**
     * build and return an object descriptor for the specified object
     */
    protected ObjectDescriptor buildObjectDescriptor(Object object) {
        return controller.buildObjectDescriptor(object);
    }

    /**
     * build a properly initialized remote value holder
     */
    protected RemoteValueHolder buildRemoteValueHolderFor(ValueHolderInterface valueHolder) {
        RemoteValueHolder remoteValueHolder = null;

        if (valueHolder instanceof RemoteValueHolder) {
            remoteValueHolder = (RemoteValueHolder)valueHolder;
        } else if ((valueHolder instanceof UnitOfWorkValueHolder) && ((UnitOfWorkValueHolder)valueHolder).getWrappedValueHolder() instanceof RemoteValueHolder) {
            return (RemoteValueHolder)((UnitOfWorkValueHolder)valueHolder).getWrappedValueHolder();
        } else {
            remoteValueHolder = new RemoteValueHolder();
            remoteValueHolder.setWrappedServerValueHolder(valueHolder);
            remoteValueHolder.setMapping(this.getCurrentMapping());
        }
        saveRemoteValueHolder(remoteValueHolder);
        return remoteValueHolder;
    }

    /**
     * initialize instance
     */
    protected void initialize(RemoteSessionController controller) {
        this.controller = controller;
    }

    /**
     * Iterate an indirect container.
     */
    protected void internalIterateIndirectContainer(IndirectContainer container) {
        ValueHolderInterface containedValueHolder = container.getValueHolder();

        if (containedValueHolder instanceof RemoteValueHolder) {
            containedValueHolder = ((RemoteValueHolder)containedValueHolder).getWrappedServerValueHolder();
        }

        synchronized (containedValueHolder) {
            // extract VH within sync block to ensure correct one is used.
            ValueHolderInterface valueHolder = container.getValueHolder();
            RemoteValueHolder remoteValueHolder = buildRemoteValueHolderFor(valueHolder);

            container.setValueHolder(remoteValueHolder);
            remoteValueHolder.setServerIndirectionObject(container);
        }
    }

    /**
     * Synchronously create a remote value holder. The value holder passed in is ignored
     * so that we can ensure that the value holder being held by the object is used
     * instead of the one that was passed in.
     */
    protected void internalIterateValueHolder(ValueHolderInterface originalValueHolder) {
        ValueHolderInterface rootValueHolder = originalValueHolder;

        // Ensure we have the base server side value holder. There will only be one of these per server.
        if (rootValueHolder instanceof RemoteValueHolder) {
            rootValueHolder = ((RemoteValueHolder)rootValueHolder).getWrappedServerValueHolder();
        }
        synchronized (rootValueHolder) {
            ValueHolderInterface valueHolder = (ValueHolderInterface)getCurrentMapping().getAttributeValueFromObject(getVisitedParent());
            RemoteValueHolder remoteValueHolder = buildRemoteValueHolderFor(valueHolder);

            if (valueHolder != remoteValueHolder) {
                if (this.getCurrentMapping().isOneToOneMapping()) {
                    this.setOneToOneMappingSettingsIn(remoteValueHolder);
                }
                this.getCurrentMapping().setAttributeValueInObject(this.getVisitedParent(), remoteValueHolder);
            }
        }
    }

    /**
     * if we get here, it is a domain object
     */
    protected void iterate(Object object) {
        ((Map)getResult()).put(object, this.buildObjectDescriptor(object));
    }

    /**
     * save the remote value holder for later use
     */
    protected void saveRemoteValueHolder(RemoteValueHolder remoteValueHolder) {
        controller.saveRemoteValueHolder(remoteValueHolder);
    }

    /**
     * Set one-to-one mapping information.
     * This allows us to look for a cache hit
     * before going to the database.
     */
    protected void setOneToOneMappingSettingsIn(RemoteValueHolder remoteValueHolder) {
        ObjectReferenceMapping oneToOneMapping = (ObjectReferenceMapping)this.getCurrentMapping();// cast it
        if (oneToOneMapping.getDescriptor().getObjectBuilder().isPrimaryKeyMapping(oneToOneMapping)) {
            remoteValueHolder.setRow(oneToOneMapping.extractPrimaryKeyRowForSourceObject(this.getVisitedParent(), this.getSession()));
        }
        remoteValueHolder.setTargetObjectPrimaryKeys(oneToOneMapping.extractPrimaryKeysForReferenceObject(this.getVisitedParent(), this.getSession()));
    }
}
