package com.android.volley.toolbox;

import androidx.annotation.Nullable;
import com.android.volley.Cache;
import com.android.volley.Header;
import com.android.volley.VolleyLog;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;

/** Handles holding onto the cache headers for an entry. */
class CacheHeader {
    /** Magic number for current version of cache file format. */
    private static final int CACHE_MAGIC = 0x20150306;

    /**
     * The size of the data identified by this CacheHeader on disk (both header and data).
     *
     * <p>Must be set by the caller after it has been calculated.
     *
     * <p>This is not serialized to disk.
     */
    long size;

    /** The key that identifies the cache entry. */
    final String key;

    /** ETag for cache coherence. */
    @Nullable final String etag;

    /** Date of this response as reported by the server. */
    final long serverDate;

    /** The last modified date for the requested object. */
    final long lastModified;

    /** TTL for this record. */
    final long ttl;

    /** Soft TTL for this record. */
    final long softTtl;

    /** Headers from the response resulting in this cache entry. */
    final List<Header> allResponseHeaders;

    private CacheHeader(
            String key,
            String etag,
            long serverDate,
            long lastModified,
            long ttl,
            long softTtl,
            List<Header> allResponseHeaders) {
        this.key = key;
        this.etag = "".equals(etag) ? null : etag;
        this.serverDate = serverDate;
        this.lastModified = lastModified;
        this.ttl = ttl;
        this.softTtl = softTtl;
        this.allResponseHeaders = allResponseHeaders;
    }

    /**
     * Instantiates a new CacheHeader object.
     *
     * @param key The key that identifies the cache entry
     * @param entry The cache entry.
     */
    CacheHeader(String key, Cache.Entry entry) {
        this(
                key,
                entry.etag,
                entry.serverDate,
                entry.lastModified,
                entry.ttl,
                entry.softTtl,
                getAllResponseHeaders(entry));
    }

    private static List<Header> getAllResponseHeaders(Cache.Entry entry) {
        // If the entry contains all the response headers, use that field directly.
        if (entry.allResponseHeaders != null) {
            return entry.allResponseHeaders;
        }

        // Legacy fallback - copy headers from the map.
        return HttpHeaderParser.toAllHeaderList(entry.responseHeaders);
    }

    /**
     * Reads the header from a CountingInputStream and returns a CacheHeader object.
     *
     * @param is The InputStream to read from.
     * @throws IOException if fails to read header
     */
    static CacheHeader readHeader(DiskBasedCache.CountingInputStream is) throws IOException {
        int magic = DiskBasedCacheUtility.readInt(is);
        if (magic != CACHE_MAGIC) {
            // don't bother deleting, it'll get pruned eventually
            throw new IOException();
        }
        String key = DiskBasedCacheUtility.readString(is);
        String etag = DiskBasedCacheUtility.readString(is);
        long serverDate = DiskBasedCacheUtility.readLong(is);
        long lastModified = DiskBasedCacheUtility.readLong(is);
        long ttl = DiskBasedCacheUtility.readLong(is);
        long softTtl = DiskBasedCacheUtility.readLong(is);
        List<Header> allResponseHeaders = DiskBasedCacheUtility.readHeaderList(is);
        return new CacheHeader(
                key, etag, serverDate, lastModified, ttl, softTtl, allResponseHeaders);
    }

    /** Creates a cache entry for the specified data. */
    Cache.Entry toCacheEntry(byte[] data) {
        Cache.Entry e = new Cache.Entry();
        e.data = data;
        e.etag = etag;
        e.serverDate = serverDate;
        e.lastModified = lastModified;
        e.ttl = ttl;
        e.softTtl = softTtl;
        e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders);
        e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders);
        return e;
    }

    /** Writes the contents of this CacheHeader to the specified OutputStream. */
    boolean writeHeader(OutputStream os) {
        try {
            DiskBasedCacheUtility.writeInt(os, CACHE_MAGIC);
            DiskBasedCacheUtility.writeString(os, key);
            DiskBasedCacheUtility.writeString(os, etag == null ? "" : etag);
            DiskBasedCacheUtility.writeLong(os, serverDate);
            DiskBasedCacheUtility.writeLong(os, lastModified);
            DiskBasedCacheUtility.writeLong(os, ttl);
            DiskBasedCacheUtility.writeLong(os, softTtl);
            DiskBasedCacheUtility.writeHeaderList(allResponseHeaders, os);
            os.flush();
            return true;
        } catch (IOException e) {
            VolleyLog.d("%s", e.toString());
            return false;
        }
    }
}