/*
 * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.controller.cluster.raft;

import com.google.common.base.Stopwatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;

public class FollowerLogInformationImpl implements FollowerLogInformation {
    private static final AtomicLongFieldUpdater<FollowerLogInformationImpl> NEXT_INDEX_UPDATER = AtomicLongFieldUpdater.newUpdater(FollowerLogInformationImpl.class, "nextIndex");
    private static final AtomicLongFieldUpdater<FollowerLogInformationImpl> MATCH_INDEX_UPDATER = AtomicLongFieldUpdater.newUpdater(FollowerLogInformationImpl.class, "matchIndex");

    private final String id;

    private final Stopwatch stopwatch = Stopwatch.createUnstarted();

    private final RaftActorContext context;

    private volatile long nextIndex;

    private volatile long matchIndex;

    private long lastReplicatedIndex = -1L;

    private final Stopwatch lastReplicatedStopwatch = Stopwatch.createUnstarted();


    public FollowerLogInformationImpl(String id, long matchIndex, RaftActorContext context) {
        this.id = id;
        this.nextIndex = context.getCommitIndex();
        this.matchIndex = matchIndex;
        this.context = context;
    }

    @Override
    public long incrNextIndex(){
        return NEXT_INDEX_UPDATER.incrementAndGet(this);
    }

    @Override
    public long decrNextIndex() {
        return NEXT_INDEX_UPDATER.decrementAndGet(this);
    }

    @Override
    public boolean setNextIndex(long nextIndex) {
        if(this.nextIndex != nextIndex) {
            this.nextIndex = nextIndex;
            return true;
        }

        return false;
    }

    @Override
    public long incrMatchIndex(){
        return MATCH_INDEX_UPDATER.incrementAndGet(this);
    }

    @Override
    public boolean setMatchIndex(long matchIndex) {
        if(this.matchIndex != matchIndex) {
            this.matchIndex = matchIndex;
            return true;
        }

        return false;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public long getNextIndex() {
        return nextIndex;
    }

    @Override
    public long getMatchIndex() {
        return matchIndex;
    }

    @Override
    public boolean isFollowerActive() {
        long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
        return (stopwatch.isRunning()) &&
                (elapsed <= context.getConfigParams().getElectionTimeOutInterval().toMillis());
    }

    @Override
    public void markFollowerActive() {
        if (stopwatch.isRunning()) {
            stopwatch.reset();
        }
        stopwatch.start();
    }

    @Override
    public void markFollowerInActive() {
        if (stopwatch.isRunning()) {
            stopwatch.stop();
        }
    }

    @Override
    public long timeSinceLastActivity() {
        return stopwatch.elapsed(TimeUnit.MILLISECONDS);
    }

    @Override
    public boolean okToReplicate() {
        // Return false if we are trying to send duplicate data before the heartbeat interval
        if(getNextIndex() == lastReplicatedIndex){
            if(lastReplicatedStopwatch.elapsed(TimeUnit.MILLISECONDS) < context.getConfigParams()
                    .getHeartBeatInterval().toMillis()){
                return false;
            }
        }

        resetLastReplicated();
        return true;
    }

    private void resetLastReplicated(){
        lastReplicatedIndex = getNextIndex();
        if(lastReplicatedStopwatch.isRunning()){
            lastReplicatedStopwatch.reset();
        }
        lastReplicatedStopwatch.start();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("FollowerLogInformationImpl [id=").append(id).append(", nextIndex=").append(nextIndex)
                .append(", matchIndex=").append(matchIndex).append(", stopwatch=")
                .append(stopwatch.elapsed(TimeUnit.MILLISECONDS))
                .append(", followerTimeoutMillis=")
                .append(context.getConfigParams().getElectionTimeOutInterval().toMillis()).append("]");
        return builder.toString();
    }
}

@@ -435,67 +435,8 @@
 nit;
-%0Aimport java.util.concurrent.atomic.AtomicLongFieldUpdater;
 %0A%0Apu
@@ -513,369 +513,8 @@
 n %7B%0A
-    private static final AtomicLongFieldUpdater%3CFollowerLogInformationImpl%3E NEXT_INDEX_UPDATER = AtomicLongFieldUpdater.newUpdater(FollowerLogInformationImpl.class, %22nextIndex%22);%0A    private static final AtomicLongFieldUpdater%3CFollowerLogInformationImpl%3E MATCH_INDEX_UPDATER = AtomicLongFieldUpdater.newUpdater(FollowerLogInformationImpl.class, %22matchIndex%22);%0A%0A
     
@@ -662,33 +662,24 @@
     private 
-volatile 
 long nextInd
@@ -699,17 +699,8 @@
 ate 
-volatile 
 long
@@ -1128,24 +1128,25 @@
 rNextIndex()
+ 
 %7B%0A        re
@@ -1154,48 +1154,19 @@
 urn 
-NEXT_INDEX_UPDATER.incrementAndGet(this)
+nextIndex++
 ;%0A  
@@ -1237,48 +1237,19 @@
 urn 
-NEXT_INDEX_UPDATER.decrementAndGet(this)
+nextIndex--
 ;%0A  
@@ -1531,49 +1531,20 @@
 urn 
-MATCH_INDEX_UPDATER.incrementAndGet(this)
+matchIndex++
 ;%0A  

Make FollowerLogInformationImpl fields non-volatile