/*******************************************************************************
 * Copyright (c) 1998, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.testing.tests.returning.model;

import java.util.Enumeration;

import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.queries.ReadObjectQuery;
import org.eclipse.persistence.mappings.*;
import org.eclipse.persistence.mappings.DatabaseMapping.WriteType;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.testing.tests.returning.ProjectAndDatabaseAdapter;

public abstract class AdapterWithReturnObjectControl implements ProjectAndDatabaseAdapter, ReturnObjectControl {

    public boolean isOriginalSetupRequired() {
        return false;
    }

    public abstract void updateProject(Project project, Session session);

    public abstract void updateDatabase(Session session);

    public Object getObjectForInsert(Session session, Object objectToInsert) {
        ClassDescriptor desc = session.getClassDescriptor(objectToInsert);
        Record rowToInsert = desc.getObjectBuilder().buildRow(objectToInsert, (AbstractSession)session, WriteType.INSERT);
        Record rowReturn = getRowForInsert(rowToInsert);
        if (rowReturn != null && !rowReturn.isEmpty()) {
            Record row = new DatabaseRecord(rowToInsert.size());
            row.putAll(rowToInsert);
            row.putAll(rowReturn);
            return readObjectFromRow(session, desc, row);
        } else {
            return objectToInsert;
        }
    }

    public Object getObjectForUpdate(Session session, Object objectToUpdateBeforeChange, Object objectToUpdateAfterChange, boolean useUOW) {
        ClassDescriptor desc = session.getClassDescriptor(objectToUpdateBeforeChange);
        Record rowBeforeChange = desc.getObjectBuilder().buildRow(objectToUpdateBeforeChange, (AbstractSession)session, WriteType.UPDATE);
        Record rowAfterChange = desc.getObjectBuilder().buildRow(objectToUpdateAfterChange, (AbstractSession)session, WriteType.UPDATE);
        Record rowChange = new DatabaseRecord();
        getChange(rowChange, session, objectToUpdateBeforeChange, objectToUpdateAfterChange, desc, useUOW, WriteType.UPDATE);
        Record rowReturn = getRowForUpdate(rowChange);
        if (rowReturn != null && !rowReturn.isEmpty()) {
            Record row = new DatabaseRecord(rowAfterChange.size());
            row.putAll(rowAfterChange);
            row.putAll(rowReturn);
            return readObjectFromRow(session, desc, row);
        } else {
            return objectToUpdateAfterChange;
        }
    }

    public void getChange(Record row, Session session, Object object1, Object object2, ClassDescriptor desc, boolean useUOW, WriteType writeType) {
        for (Enumeration mappings = desc.getMappings().elements(); mappings.hasMoreElements(); ) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();
            if (!mapping.isReadOnly()) {
                getChange(row, mapping, session, object1, object2, useUOW, writeType);
            }
        }
    }

    public void getChange(Record row, DatabaseMapping mapping, Session session, Object object1, Object object2, boolean useUOW, WriteType writeType) {
        if (mapping.isAggregateObjectMapping()) {
            Object aggregate1 = mapping.getAttributeValueFromObject(object1);
            Object aggregate2 = mapping.getAttributeValueFromObject(object2);
            if (aggregate1 == null && aggregate2 == null) {
                if (!useUOW) {
                    mapping.writeFromObjectIntoRow(object2, (DatabaseRecord)row, (AbstractSession)session, writeType);
                }
            } else if (aggregate1 != null && aggregate2 != null && aggregate1.getClass().equals(aggregate2.getClass())) {
                ClassDescriptor desc = ((AggregateObjectMapping)mapping).getReferenceDescriptor();
                getChange(row, session, aggregate1, aggregate2, desc, useUOW, writeType);
            } else {
                mapping.writeFromObjectIntoRow(object2, (DatabaseRecord)row, (AbstractSession)session, writeType);
            }
        } else {
            Record row1 = new DatabaseRecord();
            Record row2 = new DatabaseRecord();
            mapping.writeFromObjectIntoRow(object1, (DatabaseRecord)row1, (AbstractSession)session, writeType);
            mapping.writeFromObjectIntoRow(object2, (DatabaseRecord)row2, (AbstractSession)session, writeType);

            for (int i = 0; i < row1.size(); i++) {
                DatabaseField field = (DatabaseField)((DatabaseRecord)row1).getFields().elementAt(i);
                Object valueBefore = ((DatabaseRecord)row1).getValues().elementAt(i);
                Object valueAfter = row2.get(field);
                boolean changed;
                if (valueAfter == null) {
                    changed = valueBefore != null;
                } else {
                    changed = !valueAfter.equals(valueBefore);
                }
                if (changed) {
                    row.put(field, valueAfter);
                }
            }
        }
    }

    protected Object readObjectFromRow(Session session, ClassDescriptor desc, Record row) {
        if (desc.hasInheritance()) {
            Class newClass = desc.getInheritancePolicy().classFromRow((DatabaseRecord)row, (AbstractSession)session);
            desc = session.getClassDescriptor(newClass);
        }
        Object object = desc.getObjectBuilder().buildNewInstance();
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSession((AbstractSession)session);
        for (Enumeration mappings = desc.getMappings().elements(); mappings.hasMoreElements(); ) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();
            mapping.readFromRowIntoObject((DatabaseRecord)row, query.getJoinedAttributeManager(), object, null, query, query.getSession(), true);
        }
        return object;
    }

    protected Record getRowForInsert(Record rowToInsert) {
        return null;
    }

    protected Record getRowForUpdate(Record rowChange) {
        return null;
    }
}
