/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.coherence.jcache.localcache;

import com.tangosol.coherence.jcache.common.JCacheEntryMetaInf;

import javax.cache.expiry.ExpiryPolicy;

/**
 * Represents the internal Cache Entry Value with in an {@link LocalCache}.
 * <p>
 * The actual value passed to the Cache is represented in an internal format,
 * generated by passing the value through an {@link com.tangosol.coherence.jcache.common.InternalConverter}.
 * <p>
 * {@link LocalCacheValue}s additionally store and provide meta information about
 * Cache Entry Values, including information for dealing with expiry.
 *
 * @author bo  2013.12.19
 * @since Coherence 12.1.3
 */
public class LocalCacheValue
    {
    // ----- constructors ---------------------------------------------------

    /**
     * Constructs an {@link LocalCacheValue} with the creation, access and
     * modification times being the current time.
     *
     * @param internalValue the internal representation of the value
     * @param ldtCreation  the time when the cache entry was created
     * @param policy       {@link ExpiryPolicy} to use for this created value
     * @param kind         a JCacheSynthetic marker for special handling for this entry.
     */
    private LocalCacheValue(Object internalValue, long ldtCreation, ExpiryPolicy policy, JCacheSyntheticKind kind)
        {
        m_internalValue = internalValue;
        m_metaInf       = new JCacheEntryMetaInf(ldtCreation, policy);
        m_syntheticKind = kind;
        }

    /**
     * Copy constructor
     *
     * @param oldValue  the old local cache value
     */
    public LocalCacheValue(LocalCacheValue oldValue)
        {
        this.m_internalValue = oldValue.m_internalValue;
        this.m_metaInf       = new JCacheEntryMetaInf(oldValue.m_metaInf);
        this.m_syntheticKind = oldValue.m_syntheticKind;
        }

    // ----- LocalCacheValue methods ----------------------------------------

    /**
     * Gets the time (since the Epoc) in milliseconds since the internal value
     * was created.
     *
     * @return time in milliseconds (since the Epoc)
     */
    public long getCreationTime()
        {
        return m_metaInf.getCreationTime();
        }

    /**
     * Gets the time (since the Epoc) in milliseconds since the internal value
     * was last accessed.
     *
     * @return time in milliseconds (since the Epoc)
     */
    public long getAccessTime()
        {
        return m_metaInf.getAccessTime();
        }

    /**
     * Gets the number of times the internal value has been accessed.
     *
     * @return the access count
     */
    public long getAccessCount()
        {
        return m_metaInf.getAccessCount();
        }

    /**
     * Gets the time (since the Epoc) in milliseconds since the internal value
     * was last modified.
     *
     * @return time in milliseconds (since the Epoc)
     */
    public long getModificationTime()
        {
        return m_metaInf.getModificationTime();
        }

    /**
     * Gets the number of times the internal value has been modified (set)
     *
     * @return the modification count
     */
    public long getModificationCount()
        {
        return m_metaInf.getModificationCount();
        }

    /**
     * Gets the time (since the Epoc) in milliseconds when the Cache Entry
     * associated with this value should be considered expired.
     *
     * @return time in milliseconds (since the Epoc)
     */
    public long getExpiryTime()
        {
        return m_metaInf.getExpiryTime();
        }

    /**
     * Sets the time (since the Epoc) in milliseconds when the Cache Entry
     * associated with this value should be considered expired.
     *
     * @param ldtExpiry time in milliseconds (since the Epoc)
     */
    public void setExpiryTime(long ldtExpiry)
        {
        m_metaInf.setExpiryTime(ldtExpiry);
        }

    /**
     * Determines if the Cache Entry associated with this value would be expired
     * at the specified time
     *
     * @param ldtNow time in milliseconds (since the Epoc)
     * @return true if the value would be expired at the specified time
     */
    public boolean isExpiredAt(long ldtNow)
        {
        return m_metaInf.isExpiredAt(ldtNow);
        }

    /**
     * Gets the internal value (without updating the access time).
     *
     * @return the internal value
     */
    public Object get()
        {
        return m_internalValue;
        }

    /**
     * Sets the internal value (without updating the modification time)
     *
     * @param internalValue the new internal value
     */
    public void set(Object internalValue)
        {
        this.m_internalValue = internalValue;
        }

    /**
     * Gets the internal value with the side-effect of updating the access time
     * to that which is specified and incrementing the access count.
     *
     * @param ldtAccess the time when the internal value was accessed
     * @return the internal value
     */
    public Object getInternalValue(long ldtAccess)
        {
        m_metaInf.setAccessTime(ldtAccess);
        m_syntheticKind = JCacheSyntheticKind.JCACHE_SYNTHETIC_UPDATE;

        return m_internalValue;
        }

    /**
     * Access internal value without updating access expiry.
     *
     * Added this method for accessing value to construct a CacheEntryEvent.
     * This access should not be considered for resetting expiry for an access.
     * With StoreByReference configured to true, this access for CacheEntryEvent
     * could end up modifying the LocalCachedValue in the LocalCache.
     *
     * @return internal value
     */
    public Object getInternalValue()
        {
        return m_internalValue;
        }

    /**
     * Sets the internal value with the additional side-effect of updating the
     * modification time to that which is specified and incrementing the
     * modification count.
     *
     * @param internalValue    the new internal value
     * @param ldtModification  the time when the value was modified
     * @param policy           the expiry policy
     *
     * @return this LocalCacheValue
     */
    public LocalCacheValue updateInternalValue(Object internalValue, long ldtModification, ExpiryPolicy policy)
        {
        m_metaInf.modified(ldtModification, policy);
        m_syntheticKind = JCacheSyntheticKind.JCACHE_SYNTHETIC_NONE;
        m_internalValue = internalValue;
        return this;
        }

    /**
     * Access value
     *
     * @param ldtAccess  the time when the value was accessed
     * @param policy     the expiry policy
     */
    public void accessInternalValue(long ldtAccess, ExpiryPolicy policy)
        {
        m_metaInf.accessed(ldtAccess, policy);
        m_syntheticKind = JCacheSyntheticKind.JCACHE_SYNTHETIC_UPDATE;
        }

    /**
     * Create a LocalCacheValue marked with Synthetic marker that it was loaded.
     * This distinction is necessary to ensure that a just read-through entry from an external resource
     * is not immediately written back to external resource via write-through.
     *
     * @param internalValue internal format of value of a LocalCache entry
     * @param ldtCreation   creation time
     * @param policy        expiry policy to use
     *
     * @return a {@link LocalCacheValue} marked as loaded via read-through
     */
    public static LocalCacheValue createLoadedLocalCacheValue(Object internalValue, long ldtCreation,
        ExpiryPolicy policy)
        {
        return LocalCacheValue.createLocalCacheValue(internalValue, ldtCreation, policy,
            JCacheSyntheticKind.JCACHE_SYNTHETHIC_LOADED);
        }

    /**
     * Create a LocalCacheValue for an LocalCache entry.
     *
     * @param internalValue  internal format of value
     * @param ldtCreation    entry creation time
     * @param policy         expiry policy to use to generate expiry for the new LocalCache entry.
     *
     * @return {@link LocalCache} with appropriate JCache MetaInfo.
     */
    public static LocalCacheValue createLocalCacheValue(Object internalValue, long ldtCreation, ExpiryPolicy policy)
        {
        return LocalCacheValue.createLocalCacheValue(internalValue, ldtCreation, policy,
            JCacheSyntheticKind.JCACHE_SYNTHETIC_NONE);
        }

    /**
     * determine if last update was considered synthetic
     *
     * @return synthetic update state
     */
    public boolean isSyntheticUpdate()
        {
        boolean fResult = false;

        switch (m_syntheticKind)
            {
            case JCACHE_SYNTHETIC_CLEAR:
            case JCACHE_SYNTHETIC_UPDATE:
                fResult = true;
                break;

            case JCACHE_SYNTHETIC_NONE:
            case JCACHE_SYNTHETHIC_LOADED:
                fResult = false;
                break;
            }

        return fResult;
        }

    /**
     * Determine if last update to this LocalValue was a load from an external resource.
     *
     * @return true iff this value has not been updated since it was last loaded.
     */
    public boolean isLoaded()
        {
        return m_syntheticKind == JCacheSyntheticKind.JCACHE_SYNTHETHIC_LOADED;
        }

    // ----- helpers --------------------------------------------------------

    /**
     * Create a LocalCacheValue with JCache MetaINfo
     *
     * @param internalValue internal format of value
     * @param ldtCreation   creation time (for JCache MetaInfo)
     * @param policy        expiryPolicy for entry that contains this value
     * @param kind          synthetic update kind
     *
     * @return LocalCacheValue with appropriate JCache MetaInfo
     */
    private static LocalCacheValue createLocalCacheValue(Object internalValue, long ldtCreation, ExpiryPolicy policy,
        JCacheSyntheticKind kind)
        {
        return new LocalCacheValue(internalValue, ldtCreation, policy, kind);
        }

    /**
     * Synthetic Update
     */
    public static enum JCacheSyntheticKind
        {
        JCACHE_SYNTHETIC_NONE,
        JCACHE_SYNTHETIC_CLEAR,
        JCACHE_SYNTHETIC_UPDATE,
        JCACHE_SYNTHETHIC_LOADED
        }

    // ----- data members ---------------------------------------------------

    /**
     * The internal representation of the Cache Entry value.
     *
     * @see com.tangosol.coherence.jcache.common.InternalConverter
     */
    private Object m_internalValue;

    /**
     * JCache Meta Information for this {@link LocalCacheValue}
     */
    private JCacheEntryMetaInf m_metaInf;

    /**
     * Record what kind of synthetic update was made to entry containing this {@link LocalCacheValue}.
     */
    private JCacheSyntheticKind m_syntheticKind = JCacheSyntheticKind.JCACHE_SYNTHETIC_NONE;
    }
