/*****************************************************************
 *   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.cayenne.access;

import java.util.Collection;
import java.util.Map;

import org.apache.cayenne.Persistent;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.QueryMetadata;

/**
 * A paginated list that implements a strategy for retrieval of entities with a single PK
 * column. It is much more memory-efficient compared to the superclass.
 * 
 * @since 3.0
 */
class SimpleIdIncrementalFaultList<E> extends IncrementalFaultList<E> {

    protected DbAttribute pk;

    SimpleIdIncrementalFaultList(DataContext dataContext, Query query) {
        super(dataContext, query);

        Collection<DbAttribute> pks = rootEntity.getDbEntity().getPrimaryKeys();
        if (pks.size() != 1) {
            throw new IllegalArgumentException(
                    "Expected a single column primary key, instead got "
                            + pks.size()
                            + ". ObjEntity: "
                            + rootEntity.getName());
        }

        pk = pks.iterator().next();
    }

    @Override
    IncrementalFaultList<E>.IncrementalListHelper createHelper(QueryMetadata metadata) {
        if (metadata.isFetchingDataRows()) {
            return new SingleIdDataRowListHelper();
        }
        else {
            return new SingleIdPersistentListHelper();
        }
    }

    @Override
    protected Expression buildIdQualifier(Object id) {
        return ExpressionFactory.matchDbExp(pk.getName(), id);
    }

    class SingleIdPersistentListHelper extends
            IncrementalFaultList<E>.PersistentListHelper {

        @Override
        boolean objectsAreEqual(Object object, Object objectInTheList) {

            if (objectInTheList instanceof Persistent) {
                // due to object uniquing this should be sufficient
                return object == objectInTheList;
            }
            else {
                Persistent persistent = (Persistent) object;
                Map<String, Object> idSnapshot = persistent.getObjectId().getIdSnapshot();

                return idSnapshot.size() == 1
                        && objectInTheList.equals(idSnapshot.get(pk.getName()));
            }
        }

        @Override
        boolean replacesObject(Object object, Object objectInTheList) {
            if (objectInTheList instanceof Persistent) {
                return false;
            }

            Persistent persistent = (Persistent) object;
            Map<String, Object> idSnapshot = persistent.getObjectId().getIdSnapshot();

            return idSnapshot.size() == 1
                    && objectInTheList.equals(idSnapshot.get(pk.getName()));
        }
    }

    class SingleIdDataRowListHelper extends IncrementalFaultList<E>.DataRowListHelper {

        @Override
        boolean objectsAreEqual(Object object, Object objectInTheList) {

            if (objectInTheList instanceof Map) {
                return super.objectsAreEqual(object, objectInTheList);
            }

            if (object == null && objectInTheList == null) {
                return true;
            }

            if (object != null && objectInTheList != null) {

                Map<?, ?> map = (Map<?, ?>) object;

                return objectInTheList.equals(map.get(pk.getName()));
            }

            return false;
        }

        @Override
        boolean replacesObject(Object object, Object objectInTheList) {

            if (objectInTheList instanceof Map) {
                return false;
            }

            Map<?, ?> map = (Map<?, ?>) object;
            return objectInTheList.equals(map.get(pk.getName()));
        }
    }
}
