/*
 * Copyright 2021, TeamDev. All rights reserved.
 *
 * Licensed 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
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.core;

import com.google.protobuf.Timestamp;
import io.spine.time.InstantConverter;
import io.spine.time.TimestampTemporal;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Objects.requireNonNull;

/**
 * An object with associated point in time.
 */
public interface WithTime {

    /**
     * Obtains the point in time associated with the object.
     */
    Timestamp timestamp();

    /**
     * Verifies if associated time is after the passed point in time.
     */
    default boolean isAfter(Timestamp bound) {
        checkNotNull(bound);
        TimestampTemporal timeTemporal = TimestampTemporal.from(timestamp());
        TimestampTemporal boundTemporal = TimestampTemporal.from(bound);
        return timeTemporal.isAfter(boundTemporal);
    }

    /**
     * Verifies if the associated time is before the passed point in time.
     */
    default boolean isBefore(Timestamp bound) {
        checkNotNull(bound);
        TimestampTemporal timeTemporal = TimestampTemporal.from(timestamp());
        TimestampTemporal boundTemporal = TimestampTemporal.from(bound);
        return timeTemporal.isBefore(boundTemporal);
    }

    /**
     * Verifies if the associated time point is within the passed period of time.
     *
     * @param periodStart
     *         lower bound, exclusive
     * @param periodEnd
     *         higher bound, inclusive
     * @return {@code true} if the time point of the command creation lies in between the given two
     */
    default boolean isBetween(Timestamp periodStart, Timestamp periodEnd) {
        checkNotNull(periodStart);
        checkNotNull(periodEnd);
        TimestampTemporal thisTime = TimestampTemporal.from(timestamp());
        TimestampTemporal start = TimestampTemporal.from(periodStart);
        TimestampTemporal end = TimestampTemporal.from(periodEnd);
        return thisTime.isBetween(start, end);
    }

    /**
     * Obtains the associated time point as {@link Instant}.
     *
     * @see #timestamp()
     */
    default Instant instant() {
        Timestamp timestamp = timestamp();
        Instant result = InstantConverter.reversed()
                                         .convert(timestamp);
        return requireNonNull(result);
    }

    /**
     * Obtains the local date of the {@linkplain #instant()} associated time point}.
     */
    default LocalDate localDate() {
        @SuppressWarnings("FromTemporalAccessor") // `Instant` does have date info.
        LocalDate result = LocalDate.from(instant());
        return result;
    }

    /**
     * Obtains the local date and time of the {@linkplain #instant()} associated time point}.
     */
    default LocalDateTime localDateTime() {
        @SuppressWarnings("FromTemporalAccessor") // `Instant` does have date/time info.
        LocalDateTime result = LocalDateTime.from(instant());
        return result;
    }
}

@@ -1323,64 +1323,27 @@
 ime.
-InstantConverter;%0Aimport io.spine.time.TimestampTemporal
+TimestampExtensions
 ;%0A%0Ai
@@ -1496,56 +1496,8 @@
 ull;
-%0Aimport static java.util.Objects.requireNonNull;
 %0A%0A/*
@@ -1796,37 +1796,37 @@
 After(Timestamp 
-bound
+other
 ) %7B%0A        chec
@@ -1826,37 +1826,37 @@
    checkNotNull(
-bound
+other
 );%0A        Times
@@ -1854,200 +1854,61 @@
     
-TimestampTemporal timeTemporal = TimestampTemporal.from(timestamp());%0A        TimestampTemporal boundTemporal = TimestampTemporal.from(bound);%0A        return timeTemporal.isAfter(boundTemporal
+return TimestampExtensions.isAfter(timestamp(), other
 );%0A 
@@ -2043,21 +2043,21 @@
 mestamp 
-bound
+other
 ) %7B%0A    
@@ -2073,21 +2073,21 @@
 NotNull(
-bound
+other
 );%0A     
@@ -2093,201 +2093,62 @@
     
-TimestampTemporal timeTemporal = TimestampTemporal.from(timestamp());%0A        TimestampTemporal boundTemporal = TimestampTemporal.from(bound);%0A        return timeTemporal.isBefore(boundTemporal
+return TimestampExtensions.isBefore(timestamp(), other
 );%0A 
@@ -2641,601 +2641,287 @@
     
-TimestampTemporal thisTime = TimestampTemporal.from(timestamp());%0A        TimestampTemporal start = TimestampTemporal.from(periodStart);%0A        TimestampTemporal end = TimestampTemporal.from(periodEnd);%0A        return thisTime.isBetween(start, end);%0A    %7D%0A%0A    /**%0A     * Obtains the associated time point as %7B@link Instant%7D.%0A     *%0A     * @see #timestamp()%0A     */%0A    default Instant instant() %7B%0A        Timestamp timestamp = timestamp();%0A        Instant result = InstantConverter.reversed()%0A                                         .convert(timestamp);%0A        return requireNonNull(result
+return TimestampExtensions.isBetween(timestamp(), periodStart, periodEnd);%0A    %7D%0A%0A    /**%0A     * Obtains the associated time point as %7B@link Instant%7D.%0A     *%0A     * @see #timestamp()%0A     */%0A    default Instant instant() %7B%0A        return TimestampExtensions.toInstant(timestamp()
 );%0A 

Simplify comparisons using `Timestamp` extensions