/**
 * Copyright 2013 David Rusek <dave dot rusek at gmail dot com>
 *
 * 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.robotninjas.barge;

import com.google.common.io.Files;
import com.google.common.util.concurrent.AbstractService;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.inject.Guice;
import com.google.protobuf.Service;
import org.robotninjas.barge.proto.RaftProto;
import org.robotninjas.barge.state.Raft;
import org.robotninjas.barge.state.StateTransitionListener;
import org.robotninjas.protobuf.netty.server.RpcServer;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import java.io.File;
import java.util.concurrent.ExecutionException;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.propagateIfInstanceOf;
import static org.robotninjas.barge.state.Raft.StateType.START;

@ThreadSafe
@Immutable
public class NettyRaftService extends AbstractService implements RaftService {

  private final RpcServer rpcServer;
  private final Raft ctx;

  @Inject
  NettyRaftService(@Nonnull RpcServer rpcServer, @Nonnull Raft ctx) {

    this.rpcServer = checkNotNull(rpcServer);
    this.ctx = checkNotNull(ctx);

  }

  @Override
  protected void doStart() {

    try {

      ctx.setState(null, START);

      configureRpcServer();
      rpcServer.startAsync().awaitRunning();

      notifyStarted();

    } catch (Exception e) {
      notifyFailed(e);
    }

  }

  private void configureRpcServer() {
    RaftServiceEndpoint endpoint = new RaftServiceEndpoint(ctx);
    Service replicaService = RaftProto.RaftService.newReflectiveService(endpoint);
    rpcServer.registerService(replicaService);
  }

  @Override
  protected void doStop() {

    try {
      rpcServer.stopAsync().awaitTerminated();
      notifyStopped();
    } catch (Exception e) {
      notifyFailed(e);
    }

  }

  @Override
  public ListenableFuture<Object> commitAsync(final byte[] operation) throws RaftException {

    return ctx.commitOperation(operation);

  }

  @Override
  public Object commit(final byte[] operation) throws RaftException, InterruptedException {
    try {
      return commitAsync(operation).get();
    } catch (ExecutionException e) {
      propagateIfInstanceOf(e.getCause(), NotLeaderException.class);
      propagateIfInstanceOf(e.getCause(), NoLeaderException.class);
      throw propagate(e.getCause());
    }
  }

  public static Builder newBuilder(NettyClusterConfig config) {
    return new Builder(config);
  }

  private void addTransitionListener(StateTransitionListener listener) {
    ctx.addTransitionListener(listener);
  }

  public static class Builder {

    private static long TIMEOUT = 150;

    private final NettyClusterConfig config;
    private File logDir = Files.createTempDir();
    private long timeout = TIMEOUT;
    private StateTransitionListener listener;

    protected Builder(NettyClusterConfig config) {
      this.config = config;
    }

    public Builder timeout(long timeout) {
      this.timeout = timeout;
      return this;
    }

    public Builder logDir(File logDir) {
      this.logDir = logDir;
      return this;
    }

    public NettyRaftService build(StateMachine stateMachine) {
      NettyRaftService nettyRaftService = Guice.createInjector(
        new NettyRaftModule(config, logDir, stateMachine, timeout))
        .getInstance(NettyRaftService.class);

      nettyRaftService.addTransitionListener(listener);

      return nettyRaftService;
    }

    public Builder transitionListener(StateTransitionListener listener) {
      this.listener = listener;
      return this;
    }
  }

}

@@ -959,16 +959,67 @@
 e.Raft;%0A
+import org.robotninjas.barge.state.Raft.StateType;%0A
 import o
@@ -3145,24 +3145,102 @@
 %0A    %7D%0A  %7D%0A%0A
+  public boolean isLeader() %7B%0A    return StateType.LEADER == ctx.type();%0A  %7D%0A%0A
   public sta

Add isLeader function to NettyRaftService