package com.github.kmkt.util.concurrent;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * {@link Gate} の {@link ReentrantLock} による実装。
 * 状態に応じスレッドの実行がブロック/通過するゲート処理を行う実行制御インタフェース。
 * @see Gate
 * 利用サンプル
 * <pre>
 * {@code
 *  public static void main(String[] args) {
 *      try {
 *          AtomicBoolean loopf = new AtomicBoolean(true);
 *          Gate gate = new LockGate(false);
 *          Thread loop = new Thread(new Runnable() {
 *              public void run() {
 *                  System.out.println("Start thread");
 *                  try {
 *                      while (loopf.get()) {
 *                          if (!gate.gate(100, TimeUnit.MILLISECONDS)) {   // Gate がブロック状態の場合はブロックする
 *                              System.out.print("t");  // timeout
 *                              continue;
 *                          }
 *                          System.out.print("s");
 *                          Thread.sleep(200);
 *                      }
 *                  } catch (InterruptedException e) {
 *                      e.printStackTrace();
 *                  }
 *                  System.out.println("Exit thread");
 *              }
 *          });
 *          loop.start();
 *
 *          for (int i = 0; i<5;i++) {
 *              Thread.sleep(1000); // 1sec 走る
 *              gate.setGateState(true);
 *              Thread.sleep(2000); // 2sec 停まる
 *              gate.setGateState(false);
 *          }
 *          loopf.set(false);
 *      } catch (Exception e) {
 *          e.printStackTrace();
 *      }
 *  }
 * }
 * </pre>
 */
public class LockGate implements Gate {
    /** Lock オブジェクト */
    private final Lock lock = new ReentrantLock();
    /** Lock オブジェクト */
    private final Condition condition = lock.newCondition();

    /** true await でブロックする false await でブロックしない */
    private final AtomicBoolean blockState;

    /**
     * 非ブロック状態のインスタンスを生成する。
     */
    public LockGate() {
        this(false);
    }

    /**
     * 初期状態を与えてインスタンスを生成する。
     * @param block 初期状態 true ブロック状態 false 非ブロック状態
     */
    public LockGate(boolean block) {
        this.blockState = new AtomicBoolean(block);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void gate() throws InterruptedException {
        if (blockState.get()) {
            try {
                lock.lock();
                while (blockState.get()) {
                    condition.await();
                }
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long gateNanos(long nanos_timeout) throws InterruptedException {
        if (blockState.get()) {
            try {
                lock.lock();
                while (blockState.get()) {
                    nanos_timeout = condition.awaitNanos(nanos_timeout);
                    if (nanos_timeout <= 0)
                        return nanos_timeout;
                }
            } finally {
                lock.unlock();
            }
        }
        return nanos_timeout;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean gate(long time, TimeUnit unit) throws InterruptedException {
        return gateNanos(unit.toNanos(time)) > 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setGateState(boolean block) {
        if (block == true) {
            this.blockState.set(true);
        } else {
            if (!this.blockState.compareAndSet(true, false))
                return;

            try {
                lock.lock();
                condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean getGateState() {
        return this.blockState.get();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setGate() {
        setGateState(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void releaseGate() {
        setGateState(false);
    }

    @Override
    public String toString() {
        return "LockGate [blockState=" + blockState + "]";
    }
}
