/*
 * 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.brooklyn.core.mgmt.persist;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReadWriteLock;

import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode;
import org.apache.brooklyn.util.time.Duration;

import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.io.ByteSource;

/**
 * Interface for working with persistence targets, including file system and jclouds object stores.
 * @author Andrea Turli
 */
public interface PersistenceObjectStore {

    /** accessor to an object/item in a {@link PersistenceObjectStore} */
    public interface StoreObjectAccessor {
        /** gets the object, or null if not found */
        String get();
        byte[] getBytes();
        boolean exists();
        void put(String contentsToReplaceOrCreate);
        void put(ByteSource bytes);
        void append(String contentsToAppendOrCreate);
        void delete();
        // NB: creation date is available for many blobstores but 
        // not on java.io.File and filesystems, so it is not included here 
        /** last modified date, null if not supported or does not exist */
        Date getLastModifiedDate();
    }
    public interface StoreObjectAccessorWithLock extends StoreObjectAccessor {
        /** waits for all currently scheduled write lock operations (puts, appends, and deletes) to complete;
         * but does not wait on or prevent subsequent modifications.
         * this is suitable for a model where the caller is managing synchronization.
         * <p> 
         * for more complex uses, readers should <code>getLockObject().readLock().lockInterruptibly()</code> 
         * and ensure they subsequently <code>unlock()</code> it of course. see {@link #getLockObject()}. */
        void waitForCurrentWrites(Duration timeout) throws InterruptedException, TimeoutException;
        
        /**
         * Whether there are any scheduled write lock operations, either queued or executing.
         */
        @VisibleForTesting
        boolean isWriting();
        
        /** returns the underlying lock in case callers need more complex synchronization control */ 
        ReadWriteLock getLockObject();
    }

    /** human-readable name of this object store */
    public String getSummaryName();
    
    /**
     * Allows a way for an object store to be created ahead of time, and a mgmt context injected.
     * Currently subsequent changes are not permitted.
     * <p>
     * A {@link ManagementContext} must be supplied via constructor or this method before invoking other methods.
     */
    @Beta
    public void injectManagementContext(ManagementContext managementContext);
    
    /**
     * Prepares the persistence store for read use and non-contentious write use,
     * in particular detecting whether we should clean or register a need for backup etc.
     * Typically called early in the setup lifecycle, after {@link #injectManagementContext(ManagementContext)},
     * but before {@link #prepareForMasterUse()}.
     * <p>
     * See {@link #prepareForMasterUse()} for discussion of "contentious writes".
     */
    @Beta
    public void prepareForSharedUse(PersistMode persistMode, HighAvailabilityMode highAvailabilityMode);

    /** 
     * Prepares the persistence store for "contentious writes".
     * These are defined as those writes which might overwrite important information.
     * Implementations usually perform backup/versioning of the store if required.
     * <p>
     * Caller must call {@link #prepareForSharedUse(PersistMode, HighAvailabilityMode)} first
     * (and {@link #injectManagementContext(ManagementContext)} before that).
     * <p>
     * This is typically invoked "at the last moment" e.g. before the any such write,
     * mainly in order to prevent backups being made unnecessarily (e.g. if a node is standby,
     * or if it tries to become master but is not capable),
     * but also to prevent simultaneous backups which can cause problems with some stores
     * (only a mgmt who knows he is the master should invoke this).
     **/
    @Beta
    public void prepareForMasterUse();
    
    /**
     * For reading/writing data to the item at the given path.
     * Note that the accessor is not generally thread safe, usually does not support blocking,
     * and multiple instances may conflict with each other.
     * <p>
     * Clients should wrap in a dedicated {@link StoreObjectAccessorLocking} and share
     * if multiple threads may be accessing the store.
     * This method may be changed in future to allow access to a shared locking accessor.
     */
    @Beta
    // TODO requiring clients to wrap and cache accessors is not very nice API, 
    // better would be to do caching here probably,
    // but we've already been doing it this way above for now (Jun 2014)
    StoreObjectAccessor newAccessor(String path);

    /** create the directory at the given subPath relative to the base of this store */
    void createSubPath(String subPath);

    /**
     * Lists the paths of objects contained at the given path, including the subpath.
     * For example, if a file-based ObjectStore is configured to write to file://path/to/root/
     * then parentSubPath=entities would return the contents of /path/to/root/entities/, such as
     * [entities/e1, entities/e2, entities/e3].
     * The returned paths values are usable in calls to {@link #newAccessor(String)}.
     */
    List<String> listContentsWithSubPath(String subPath);

    /** Entirely delete the contents of this persistence location.
     * Use with care, primarily in tests. This will recursively wipe the indicated location. */ 
    public void deleteCompletely();
    
    /**
     * Closes all resources used by this ObjectStore. No subsequent calls should be made to the ObjectStore;
     * behaviour of such calls is undefined but likely to throw exceptions.
     */
    void close();

}