/*
 * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.descriptors.invalidation;

import java.util.*;
import org.eclipse.persistence.internal.identitymaps.CacheKey;
import org.eclipse.persistence.descriptors.invalidation.CacheInvalidationPolicy;

/**
 * PUBLIC:
 * A CacheInvalidationPolicy that allows objects to expire every day at a specific time.
 * A daily cache invalidation policy is created with an hour, minute, second and millisecond
 * when objects will expire.  Objects will expire in the cache every day at that time.
 * @see CacheInvalidationPolicy
 */
public class DailyCacheInvalidationPolicy extends CacheInvalidationPolicy {
    protected Calendar expiryTime = null;
    protected Calendar previousExpiry = null;// previous expiry is stored for efficiency

    /**
     * INTERNAL:
     * Default constructor for Project XML
     * if setters are not called to set expiry times, expiry time will be the time of
     * day at which this object is instantiated.
     */
    public DailyCacheInvalidationPolicy() {
        expiryTime = Calendar.getInstance();
        previousExpiry = (Calendar)expiryTime.clone();
        previousExpiry.add(Calendar.DAY_OF_YEAR, -1);
    }

    /**
     * PUBLIC:
     * Construct a daily policy that will allow objects to expire at a specific time of day.
     * Provide the hour, minute, second and millisecond.  Objects that make use of this policy
     * will be set to expire at that exact time every day.
     */
    public DailyCacheInvalidationPolicy(int hour, int minute, int second, int millisecond) {
        setExpiryTime(hour, minute, second, millisecond);
    }

    /**
     * INTERNAL:
     * Return the next expiry time.
     */
    public long getExpiryTimeInMillis(CacheKey key) {
        incrementExpiry();
        return this.expiryTime.getTimeInMillis();
    }

    /**
     * INTERNAL:
     * Get the expiry time as a Calendar.  Used for setting the expiry time in deployment XML
     */
    public Calendar getExpiryTime() {
        return expiryTime;
    }

    /**
     * INTERNAL:
     * Return true if this object has expire or is invalid
     */
    public boolean isInvalidated(CacheKey key, long currentTimeMillis) {
        if (key.getInvalidationState() == CacheKey.CACHE_KEY_INVALID) {
            return true;
        }

        long expiryMillis = expiryTime.getTimeInMillis();
        long readTime = key.getReadTime();

        if (currentTimeMillis < expiryMillis) {
            long previousExpiryMillis = previousExpiry.getTimeInMillis();
            if (readTime >= previousExpiryMillis) {
                // both current time and read time are between expiry yesterday and expiry today - not expired
                return false;
            } else if (currentTimeMillis >= previousExpiryMillis) {
                // read time is less than previous expiry and current time is after it. - expired and need update of expiry
                return true;
            } else {
                // read time is before previous expiry and so is current time - strange case, but not expired
                return false;
            }
        } else {
            if (readTime < expiryMillis) {
                // current time is greater than expiry and read time is before - expire and update expiry
                incrementExpiry();
                return true;
            } else {
                // current time and read time are greater than expiry - no expire and update expiry
                incrementExpiry();
                return false;
            }
        }
    }

    /**
     * INTERNAL:
     * Update the expiry time to be the day after the current day.
     */
    public void incrementExpiry() {
        long currentTimeMillis = System.currentTimeMillis();
        long expiryInMillis = expiryTime.getTimeInMillis();
        if (currentTimeMillis <= expiryInMillis) {
            // no updated needed.  Return for efficiency
            return;
        }
        while (currentTimeMillis > expiryTime.getTimeInMillis()) {
            // increment the expiry time until it is after the current time
            previousExpiry.add(Calendar.DAY_OF_YEAR, 1);
            expiryTime.add(Calendar.DAY_OF_YEAR, 1);
        }
    }

    /**
     * PUBLIC:
     * Set a new expiry time for this object
    * Provide the hour, minute, second and millisecond.  Objects which make use of this policy
     * will be set to expire at that exact time every day.
     */
    public void setExpiryTime(int hour, int minute, int second, int millisecond) {
        expiryTime = Calendar.getInstance();
        expiryTime.set(Calendar.HOUR_OF_DAY, hour);
        expiryTime.set(Calendar.MINUTE, minute);
        expiryTime.set(Calendar.SECOND, second);
        expiryTime.set(Calendar.MILLISECOND, millisecond);
        previousExpiry = (Calendar)expiryTime.clone();
        previousExpiry.add(Calendar.DAY_OF_YEAR, -1);
        incrementExpiry();
    }

    /**
     * INTERNAL:
     * Set the expiry time based on a Calendar. Used for setting the expiry time from
     * deployment XML.
     */
    public void setExpiryTime(Calendar calendar) {
        setExpiryTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND));
    }

    public Object clone() {
        DailyCacheInvalidationPolicy clone = null;

        try {
            clone = (DailyCacheInvalidationPolicy)super.clone();
            if (this.expiryTime != null) {
                clone.setExpiryTime((Calendar)this.expiryTime.clone());
            }
            if (this.previousExpiry != null) {
                clone.previousExpiry = (Calendar)this.previousExpiry.clone();
            }
        } catch (Exception exception) {
            throw new InternalError("clone failed");
        }

        return clone;
    }
}
