/*
 *  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.isis.applib.services.repository;

import java.util.List;
import java.util.function.Predicate;

import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.query.Query;

public interface RepositoryService {

    /**
     * Normally any queries are automatically preceded by flushing pending executions.
     *
     * <p>
     * This key allows this behaviour to be disabled.
     *
     * <p>
     *     Originally introduced as part of ISIS-1134 (fixing memory leaks in the objectstore)
     *     where it was found that the autoflush behaviour was causing a (now unrepeatable)
     *     data integrity error (see <a href="https://issues.apache.org/jira/browse/ISIS-1134?focusedCommentId=14500638&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14500638">ISIS-1134 comment</a>, in the isis-module-security.
     *     However, that this could be circumvented by removing the call to flush().
     *     We don't want to break existing apps that might rely on this behaviour, on the
     *     other hand we want to fix the memory leak.  Adding this configuration property
     *     seems the most prudent way forward.
     * </p>
     */
    String KEY_DISABLE_AUTOFLUSH = "isis.services.container.disableAutoFlush";

    /**
     * Same as {@link org.apache.isis.applib.services.factory.FactoryService#instantiate(Class)}; provided as a
     * convenience because instantiating and {@link #persist(Object) persisting} are often done together.
     */
    @Programmatic
    <T> T instantiate(final Class<T> ofType);

    /**
     * Determines if the specified object is persistent (that it is stored permanently outside of the virtual machine
     * in the object store).
     *
     * <p>
     *     This method can also return <code>true</code> if the object has been {@link #isDeleted(Object) deleted}
     *     from the object store.
     * </p>
     */
    @Programmatic
    boolean isPersistent(Object domainObject);

    /**
     * Determines if the specified object has been deleted from the object store.
     */
    @Programmatic
    boolean isDeleted(Object domainObject);

    /**
     * Persist the specified object (or do nothing if already persistent).
     *
     * <p>
     * It is recommended that the object be initially instantiated using
     * {@link org.apache.isis.applib.DomainObjectContainer#newTransientInstance(Class)}.  However, the framework will also
     * handle the case when the object is simply <i>new()</i>ed up.
     *
     * @see org.apache.isis.applib.DomainObjectContainer#newTransientInstance(Class)
     * @see #isPersistent(Object)
     */
    @Programmatic
    <T> T persist(T domainObject);

    /**
     * Persist the specified object (or do nothing if already persistent) and flushes changes to the database.
     *
     * @see #persist(Object)
     */
    @Programmatic
    <T> T persistAndFlush(T domainObject);

    /**
     * Deletes the domain object but only if is persistent.
     *
     * @param domainObject
     */
    @Programmatic
    void remove(Object domainObject);

    /**
     * Deletes the domain object but only if is persistent, and flushes changes to the database.
     *
     * @param domainObject
     */
    @Programmatic
    void removeAndFlush(Object domainObject);

    /**
     * Returns all the instances of the specified type (including subtypes).
     * If the optional range parameters are used, the dataset returned starts
     * from (0 based) index, and consists of only up to count items.
     *
     * <p>
     * If there are no instances the list will be empty. This method creates a
     * new {@link List} object each time it is called so the caller is free to
     * use or modify the returned {@link List}, but the changes will not be
     * reflected back to the repository.
     * </p>
     *
     * <p>
     * This method should only be called where the number of instances is known
     * to be relatively low, unless the optional range parameters (2 longs) are
     * specified. The range parameters are "start" and "count".
     * </p>
     *
     * @param range 2 longs, specifying 0-based start and count.
     */
    @Programmatic
    <T> List<T> allInstances(Class<T> ofType, long... range);

    /**
     * Returns all the instances of the specified type (including subtypes) that
     * the predicate object accepts. If the optional range parameters are used, the
     * dataset returned starts from (0 based) index, and consists of only up to
     * count items.
     *
     * <p>
     * If there are no instances the list will be empty. This method creates a
     * new {@link List} object each time it is called so the caller is free to
     * use or modify the returned {@link List}, but the changes will not be
     * reflected back to the repository.
     * </p>
     *
     * <p>
     * This method is useful during exploration/prototyping, but - because the filtering is performed client-side -
     * this method is only really suitable for initial development/prototyping, or for classes with very few
     * instances.  Use {@link #allMatches(Query)} for production code.
     * </p>
     *
     * @see #allMatches(Class, Predicate, long...)
     *
     * @param range 2 longs, specifying 0-based start and count.
     */
    @Programmatic
    <T> List<T> allMatches(final Class<T> ofType, final Predicate<? super T> predicate, long... range);

    /**
     * Returns all the instances that match the given {@link Query}.
     *
     * <p>
     * If there are no instances the list will be empty. This method creates a
     * new {@link List} object each time it is called so the caller is free to
     * use or modify the returned {@link List}, but the changes will not be
     * reflected back to the repository.
     * </p>
     *
     * <p>
     *     This method is the recommended way of querying for multiple instances.
     * </p>
     */
    @Programmatic
    <T> List<T> allMatches(Query<T> query);

    /**
     * Find the only instance of the specified type (including subtypes) that
     * has the specified title.
     *
     * <p>
     * If no instance is found then <tt>null</tt> will be return, while if there
     * is more that one instances a run-time exception will be thrown.
     *
     * <p>
     * This method is useful during exploration/prototyping, but - because the filtering is performed client-side -
     * this method is only really suitable for initial development/prototyping, or for classes with very few
     * instances.  Use {@link #uniqueMatch(Query)} for production code.
     * </p>
     */
    @Programmatic
    <T> T uniqueMatch(final Class<T> ofType, final Predicate<T> predicate);

    /**
     * Find the only instance that matches the provided query.
     *
     * <p>
     * If no instance is found then null will be return, while if there is more
     * that one instances a run-time exception will be thrown.
     * </p>
     *
     * <p>
     *     This method is the recommended way of querying for (precisely) one instance.  See also {@link #allMatches(Query)}
     * </p>
     *
     * @see #firstMatch(Query)
     */
    @Programmatic
    <T> T uniqueMatch(Query<T> query);


}
