package anastore.store;

import anastore.util.TimeRange;

import java.io.IOException;
import java.lang.ref.SoftReference;

/**
 * A single version of a datum.  The datum is guaranteed to be on
 * disk, and may also be in memory.  If it is in memory, it is
 * always retrieved from there, so there can never be more than
 * one reachable instance of a version of a datum.
 */
public class Version<D extends Storable>
{
    private final StorableFactory<D> _sf;
    private final Disk.BlockHandle _bh;
    private volatile SoftReference<D> _ref;

    Version(StorableFactory<D> sf, Disk.BlockHandle bh, D datum)
    {
        _sf = sf;
        _bh = bh;
        _ref = new SoftReference<D>(datum);
    }

    Version(StorableFactory<D> sf, Disk.BlockHandle bh)
    {
        _sf = sf;
        _bh = bh;
        _ref = new SoftReference<D>(null);
    }

    /**
     * Get the time range for which this version is valid.  This
     * <i>must not</i> be modified.
     */
    public TimeRange getRange()
    {
        return _bh.getRange();
    }

    /**
     * Set the upper bound of the timestamp.  This must be called very
     * carefully in order to ensure correct invocation of existing
     * deprecation handlers and registrations of new deprecation
     * handlers.
     */
    void deprecate(long timestamp)
    {
        _bh.getRange().setUpperBound(timestamp);
    }

    /**
     * Retrieve the data stored in this version.  If there is a
     * reachable copy of this datum, it will be returned in order to
     * guarantee singleton semantics.  Otherwise, the datum will be
     * reconstituted from disk.
     */
    public D get()
        throws IOException
    {
        D datum;

        // Retrieve from memory
        datum = _ref.get();
        if (datum != null)
            return datum;

        // Retrieve from disk.  To avoid lock contention, we
        // didn't lock when trying to retrieve from memory, but
        // now we do in order to prevent multiple concurrent runs
        // to disk.
        synchronized (this) {
            // We were operating optimistically before, so verify
            // that the reference hasn't been filled in the mean
            // time
            datum = _ref.get();
            if (datum != null)
                return datum;

            // Go to disk
            byte[] data = _bh.read();
            datum = _sf.fromData(data);
            _ref = new SoftReference<D>(datum);
            return datum;
        }
    }

    @Override
    public String toString()
    {
        StringBuilder b = new StringBuilder("Version<" + _bh.getID() + "," +
                                            getRange());
        D datum = _ref.get();
        if (datum != null) {
            b.append("=>");
            b.append(datum.toString());
        }
        b.append('>');
        return b.toString();
    }

    /**
     * A helper for printing version sets.  This adds a short summary
     * of the version to the string builder that would be appropriate
     * for a string representation of the version in the context of a
     * version set.
     */
    void toShortString(StringBuilder b)
    {
        b.append(getRange().toString());
        D datum = _ref.get();
        if (datum != null) {
            b.append(":");
            b.append(datum.toString());
        }
    }

    /**
     * Forcibly clear this datum's memoizer in order to simulate
     * running out of heap.  Only for testing.
     */
    void testForgetData()
    {
        _ref.clear();
    }
}
