/*
 * Copyright (c) 2010-2019. Axon Framework
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.axonframework.eventhandling;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.axonframework.common.Assert;

import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.util.Objects;
import java.util.OptionalLong;

/**
 * Tracking token based on the global sequence number of an event.
 *
 * @author Rene de Waele
 * @since 3.0
 */
public class GlobalSequenceTrackingToken implements TrackingToken, Comparable<GlobalSequenceTrackingToken>,
                                                    Serializable {

    private static final long serialVersionUID = -3658606104934080049L;

    private final long globalIndex;

    /**
     * Initializes a {@link GlobalSequenceTrackingToken} from the given {@code globalIndex} of the event.
     *
     * @param globalIndex the global sequence number of the event
     */
    @JsonCreator
    @ConstructorProperties({"globalIndex"})
    public GlobalSequenceTrackingToken(@JsonProperty("globalIndex") long globalIndex) {
        this.globalIndex = globalIndex;
    }

    /**
     * Get the global sequence number of the event
     *
     * @return the global sequence number of the event
     */
    public long getGlobalIndex() {
        return globalIndex;
    }

    /**
     * Returns a new {@link GlobalSequenceTrackingToken} instance that is the sum of this token's sequence number and
     * the given {@code offset}.
     *
     * @param offset the offset between this token's sequence number of that of the returned instance
     * @return a new tracking token with global sequence increased with the given offset
     */
    public GlobalSequenceTrackingToken offsetBy(int offset) {
        return new GlobalSequenceTrackingToken(globalIndex + offset);
    }

    /**
     * Returns a new {@link GlobalSequenceTrackingToken} instance with sequence number incremented by 1.
     *
     * @return a new tracking token with sequence number incremented by 1
     */
    public GlobalSequenceTrackingToken next() {
        return offsetBy(1);
    }

    @Override
    public TrackingToken lowerBound(TrackingToken other) {
        Assert.isTrue(other instanceof GlobalSequenceTrackingToken, () -> "Incompatible token type provided.");
        GlobalSequenceTrackingToken otherToken = (GlobalSequenceTrackingToken) other;

        if (otherToken.globalIndex < this.globalIndex) {
            return otherToken;
        } else {
            return this;
        }
    }

    @Override
    public TrackingToken upperBound(TrackingToken other) {
        Assert.isTrue(other instanceof GlobalSequenceTrackingToken, () -> "Incompatible token type provided.");
        if (((GlobalSequenceTrackingToken) other).globalIndex > this.globalIndex) {
            return other;
        }
        return this;
    }

    @Override
    public boolean covers(TrackingToken other) {
        Assert.isTrue(other == null || other instanceof GlobalSequenceTrackingToken,
                      () -> "Incompatible token type provided:" + other.getClass().getSimpleName());
        GlobalSequenceTrackingToken otherToken = (GlobalSequenceTrackingToken) other;

        return otherToken == null || otherToken.globalIndex <= this.globalIndex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        GlobalSequenceTrackingToken that = (GlobalSequenceTrackingToken) o;
        return globalIndex == that.globalIndex;
    }

    @Override
    public int hashCode() {
        return Objects.hash(globalIndex);
    }

    @Override
    public String toString() {
        return "IndexTrackingToken{" + "globalIndex=" + globalIndex + '}';
    }

    @Override
    public int compareTo(GlobalSequenceTrackingToken o) {
        return Long.compare(globalIndex, o.globalIndex);
    }

    @Override
    public OptionalLong position() {
        return OptionalLong.of(globalIndex);
    }
}

@@ -24,10 +24,10 @@
 0-20
+2
 1
-9
 . Ax
@@ -229,17 +229,16 @@
 *%0A *    
- 
 http://w
@@ -753,48 +753,8 @@
 rty;
-%0Aimport org.axonframework.common.Assert;
 %0A%0Aim
@@ -879,16 +879,71 @@
 lLong;%0A%0A
+import static org.axonframework.common.Assert.isTrue;%0A%0A
 /**%0A * T
@@ -2789,39 +2789,32 @@
 ther) %7B%0A        
-Assert.
 isTrue(other ins
@@ -2841,32 +2841,47 @@
 ceTrackingToken,
+%0A              
  () -%3E %22Incompat
@@ -2896,37 +2896,73 @@
 en type provided
-.%22
+:%22 + other.getClass().getSimpleName()
 );%0A
+%0A
         GlobalSe
@@ -3023,33 +3023,32 @@
 ngToken) other;%0A
-%0A
         if (othe
@@ -3251,39 +3251,32 @@
 ther) %7B%0A        
-Assert.
 isTrue(other ins
@@ -3311,16 +3311,31 @@
 ngToken,
+%0A              
  () -%3E %22
@@ -3370,13 +3370,49 @@
 ided
-.%22
+:%22 + other.getClass().getSimpleName()
 );%0A
+%0A
     
@@ -3626,23 +3626,16 @@
         
-Assert.
 isTrue(o
@@ -3703,39 +3703,32 @@
 %0A               
-       
 () -%3E %22Incompati
@@ -3746,32 +3746,49 @@
 ype provided:%22 +
+ (other != null ?
  other.getClass(
@@ -3804,16 +3804,26 @@
 leName()
+ : %22null%22)
 );%0A     

Clarify exception message