code: package app;
  import blockchain.data.BCS;
  import com.google.protobuf.ByteString;
  import servers.Top;
  import utils.config.Config;
  import crypto.BlockDigSig;
  import das.ms.BFD;
  import org.apache.commons.cli.*;
  import proto.types.block.*;
  import proto.types.transaction.*;
  import proto.types.meta.*;
  import utils.statistics.Statistics;
  import utils.CSVUtils;
  import java.io.FileWriter;
  import java.io.File;
  import java.io.IOException;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.security.SecureRandom;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.*;
  import java.util.concurrent.*;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.stream.Collectors;
  
  import static crypto.BlockDigSig.hashBlockData;
  import static java.lang.String.format;
  
  class Cli {
      private final static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(Cli.class);
          private Options options = new Options();
          private static String outPath = "/tmp/JToy/res/";
          private AtomicBoolean recorded = new AtomicBoolean(false);
  
          Cli() {
              options.addOption("help", "print this message");
              options.addOption("init", "init a toy server");
              options.addOption("serve", "start the server");
              options.addOption("stop", "terminate the server");
              options.addOption("quit", "exit Toy terminal");
  
              options.addOption(Option.builder("wait")
                      .hasArg()
                      .desc("Usage: wait [sec]\n" +
                              "waits for [sec] second")
                      .build());
  
              Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                  if (!recorded.get()) {
                      Statistics.deactivate();
                      if (Statistics.getSyncs() == 0) {
                          writeSummery(outPath);
                      } else {
                          writeByzSummery(outPath);
                      }
                      writeBlocks();
                  }
  //
              }));
          }
  
  
          void parse(String[] args) throws InterruptedException, IOException {
  
              if (args[0].equals("help")) {
                  help();
                  return;
              }
              if (args[0].equals("init")) {
                  init();
                  System.out.println("Init server... [OK]");
                  return;
              }
  
              if (args[0].equals("up")) {
                  init();
                  serve();
                  while (true) {
                      Thread.sleep(Long.MAX_VALUE);
                  }
              }
  
              if (args[0].equals("serve")) {
                  serve();
                  System.out.println("Serving... [OK]");
                  return;
              }
  
              if (args[0].equals("stop")) {
                  stop();
                  System.out.println("stop server... [OK]");
                  return;
              }
  
              if (args[0].equals("quit")) {
                  if (Statistics.getSyncs() == 0) {
                      writeSummery(outPath);
                  } else {
                      writeByzSummery(outPath);
                  }
  
                  writeBlocks();
                  recorded.set(true);
                  System.exit(0);
              }
  
              if (args[0].equals("wait")) {
                  if (args.length == 2) {
                      int sec = Integer.parseInt(args[1]);
                      if (sec > 0) {
  
                          System.out.println(format("waits for %d seconds ", sec));
                          Thread.sleep(sec * 1000);
                      }
                  }
                  return;
              }
  
              if (args[0].equals("async")) {
                  if (!JToy.type.equals("a") && !JToy.type.equals("b")) {
                      logger.debug("Unable to set async behaviour to a correct server");
                      return;
                  }
                  if (args.length == 3) {
                      int sec = Integer.parseInt(args[1]);
                      int duration = Integer.parseInt(args[2]);
                      asyncPeriod(sec * 1000, duration * 1000);
                  }
                  return;
              }
  
              if (args[0].equals("byz")) {
                  Top.setByzSetting();
                  return;
              }
  
              if (args[0].equals("sigTest")) {
                  logger.info("Accepted sigTest");
                  int time = Integer.parseInt(args[1]);
                  sigTest(outPath, time);
                  return;
  
              }
  
              if (args[0].equals("stStart")) {
                 Statistics.activate(Top.getID());
                  return;
  
              }
  
              if (args[0].equals("stStop")) {
                  Statistics.deactivate();
                  return;
  
              }
  
              if (args[0].equals("status")) {
                  if (args.length == 5) {
                      int channel = Integer.parseInt(args[1]);
                      int pid = Integer.parseInt(args[2]);
                      int bid = Integer.parseInt(args[3]);
                      int tid  = Integer.parseInt(args[4]);
  
                      String stat = txStatus(channel, pid, bid, tid);
                      System.out.println(format("[channel=%d ; pid:%d ; bid=%d ; tid=%d] status=%s", channel, pid,
                              bid, tid, stat));
                      return;
                  }
              }
              logger.error(format("Invalid command %s", Arrays.toString(args)));
  
          }
          private void help() {
              HelpFormatter formatter = new HelpFormatter();
              formatter.printHelp("Optimistic Total Ordering System ", options);
          }
  
          private void init() {
              JToy.init();
              Top.start();
          }
  
          private void serve() {
              if (JToy.type.equals("m")) return;
              logger.debug("start serving");
              Top.serve();
          }
          private void stop() {
              Top.shutdown();
          }
  
  
          private String txStatus(int channel, int pid, int bid, int tid) throws InterruptedException {
              TxID txid = TxID.newBuilder()
                      .setProposerID(pid)
                      .setBid(bid)
                      .setTxNum(tid)
                      .setChannel(channel)
                      .build();
              return Top.status(txid, false).toString();
  
          }
          private void signBlockFromBuilder(Block.Builder b) {
              Block.Builder b1 = b.setHeader(b.getHeader()
                      .toBuilder()
                      .setTransactionHash(ByteString.copyFrom(hashBlockData(b.build()))));
              b1.setHeader(b.getHeader().toBuilder()
                      .setProof(BlockDigSig.sign(b1.getHeader()))).build();
          }
          private Block.Builder createBlock(int txSize) {
              Block.Builder bb = Block.newBuilder();
              for (int i = 0 ; i < Config.getMaxTransactionsInBlock() ; i++) {
                  SecureRandom random = new SecureRandom();
                  byte[] tx = new byte[txSize];
                  random.nextBytes(tx);
                  bb.addData(Transaction.newBuilder()
                          .setId(TxID.newBuilder()
                                  .setTxNum(0)
                                  .setProposerID(0)
                                  .setChannel(0)
                                  .setBid(0))
                          .setClientID(0)
                          .setData(ByteString.copyFrom(tx))
                          .build());
              }
  
              SecureRandom random = new SecureRandom();
              byte[] tx = new byte[32];
              random.nextBytes(tx);
  
              return bb.setHeader(BlockHeader.newBuilder()
                      .setM(Meta.newBuilder()
                              .setChannel(0)
                              .setCidSeries(0)
                              .setCid(0))
                      .setHeight(0)
                      .setBid(BlockID.newBuilder().setBid(0).setPid(0).build())
                      .setPrev(ByteString.copyFrom(tx)));
  
  
          }
          private void sigTest(String pathString, int time) throws IOException, InterruptedException {
              logger.info(format("Starting sigTest [%d, %d]", Config.getTxSize(), Config.getMaxTransactionsInBlock()));
              ExecutorService executor = Executors.newFixedThreadPool(Config.getC());
              CountDownLatch latch1 = new CountDownLatch(Config.getC());
              AtomicInteger avgSig = new AtomicInteger(0);
              AtomicBoolean stop = new AtomicBoolean(false);
              for (int c = 0 ; c < Config.getC() ; c++) {
                  int finalC = c;
                  (executor).submit(() -> {
                      int sigCount = 0;
                      Block.Builder b = createBlock(Config.getTxSize());
                      while (!stop.get()) {
                          signBlockFromBuilder(b);
                          sigCount++;
                      }
                      logger.info(format("finishing verForExec [%d] avg is [%d]", finalC, sigCount));
                      avgSig.addAndGet(sigCount);
                      latch1.countDown();
                  });
              }
              logger.info("Await termination 1");
              Thread.sleep(time * 1000);
              stop.set(true);
  //            executor.awaitTermination(120, TimeUnit.SECONDS);
              latch1.await();
              executor.shutdownNow();
              int sigPerSec = avgSig.get() / time;
              Path path = Paths.get(pathString,   String.valueOf(0), "sig_summary.csv");
              File f = new File(path.toString());
              if (!f.exists()) {
                  f.getParentFile().mkdirs();
                  f.createNewFile();
              }
              logger.info("Collecting results");
              FileWriter writer = new FileWriter(path.toString(), true);
              DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy-HH:mm:ss");
              List<String> row = Arrays.asList(
                      String.valueOf(Config.getC())
                      , String.valueOf(time)
                      , String.valueOf(Config.getTxSize())
                      , String.valueOf(Config.getMaxTransactionsInBlock())
                      , String.valueOf(sigPerSec)
              );
              CSVUtils.writeLine(writer, row);
              writer.flush();
              writer.close();
          }
  
          private void writeBlocks() {
              if (Config.getTxSize() != 512) return;
              logger.info("Starting writeBlocks");
              String pathString = "/tmp/JToy/res/";
              Path path = Paths.get(pathString, String.valueOf(Top.getID()), "bsummary.csv");
              File f = new File(path.toString());
  
              try {
                  if (!f.exists()) {
                      f.getParentFile().mkdirs();
                      f.createNewFile();
                  }
                  FileWriter writer;
                  writer = new FileWriter(path.toString(), true);
                  List<List<String>> rows = new ArrayList<>();
                  int workers = Config.getC();
                  for (int i = Statistics.getH1() ; i < Statistics.getH2() ; i++) {
                      List<Block> rBlocks = new ArrayList<>();
                      for (int  j = 0 ; j < workers ; j++) {
                          rBlocks.add(BCS.nbGetBlock(j, i));
  
                      }
                      long dlt = Collections.max(rBlocks.stream().map(b ->
                              b.getHeader().getHst().getDefiniteTime()).collect(Collectors.toList()));
                      for (int j = 0 ; j < workers ; j++) {
                          Block b = rBlocks.get(j);
                          if (b.getId().getPid() != Top.getID()) continue;
                          long pt = b.getBst().getProposeTime();
                          long tt = b.getHeader().getHst().getTentativeTime();
                          long dt = b.getHeader().getHst().getDefiniteTime();
                          rows.add(Arrays.asList(
                                      String.valueOf(Config.getMaxTransactionsInBlock())
                                      , String.valueOf(Config.getTxSize())
                                      , String.valueOf(workers)
                                      , String.valueOf(j)
                                      , String.valueOf(b.getId().getPid())
                                      , String.valueOf(b.getId().getBid())
                                      , String.valueOf(b.getHeader().getHeight())
                                      , String.valueOf(b.getDataCount())
                                      , String.valueOf(tt - pt)
                                      , String.valueOf(dt - pt)
                                      , String.valueOf(dlt - pt)
                                      , String.valueOf(dt - tt)
                                      , String.valueOf(dlt - dt)
                                  )
  
                          );
                      }
                  }
                  CSVUtils.writeLines(writer, rows);
                  writer.flush();
                  writer.close();
                  logger.info("ended writeBlocks");
              } catch (Exception e) {
                  logger.error(e);
              }
          }
  
          private void writeSummery(String pathString) {
              logger.info("Starting writeSummary");
              Path path = Paths.get(pathString, String.valueOf(Top.getID()), "summary.csv");
              File f = new File(path.toString());
  
              try {
                  if (!f.exists()) {
                      f.getParentFile().mkdirs();
                      f.createNewFile();
                  }
                  FileWriter writer;
                  writer = new FileWriter(path.toString(), true);
  
                  int nob = Statistics.getNob();
                  int noeb = Statistics.getNeb();
                  int txCount = Statistics.getTxCount();
                  int stBlockNum = Statistics.getStBlockNum();
                  long txSize = Config.getTxSize();
                  int txInBlock = Config.getMaxTransactionsInBlock();
                  int avgTxInBlock = 0;
                  double acBP2T = Statistics.getAcBP2T();
                  double acBP2D = Statistics.getAcBP2D();
                  double acBP2DL = Statistics.getAcBP2DL();
  
                  double acHP2T = Statistics.getAcHP2T();
                  double acHP2D = Statistics.getAcHP2D();
                  double acHT2D = Statistics.getAcHT2D();
                  double acHP2DL = Statistics.getAcHP2DL();
                  double acHD2DL = Statistics.getAcHD2DL();
  
                  long time = (Statistics.getStop() - Statistics.getStart()) / 1000;
                  if (nob > 0) {
                      avgTxInBlock = txCount / nob;
  
                  }
  
                  double BP2T = 0;
                  double BP2D = 0;
                  double BP2DL = 0;
  
                  double HP2T = 0;
                  double HP2D = 0;
                  double HT2D = 0;
                  double HP2DL = 0;
                  double HD2DL = 0;
  
                  if (stBlockNum > 0) {
                      BP2T = acBP2T / stBlockNum;
                      BP2D = acBP2D / stBlockNum;
                      BP2DL = acBP2DL / stBlockNum;
  
                      HP2T = acHP2T / stBlockNum;
                      HP2D = acHP2D / stBlockNum;
                      HT2D = acHT2D / stBlockNum;
                      HP2DL = acHP2DL / stBlockNum;
                      HD2DL = acHD2DL / stBlockNum;
                  }
  
                  int tps = 0;
                  int bps = 0;
                  if (time > 0) {
                      tps = ((int) (txCount / time));
                      bps = (int) (nob / time);
                  }
  
                  int pos = Statistics.getPos();
                  int neg = Statistics.getNeg();
                  int opt = Statistics.getOpt();
                  int all = Statistics.getAll();
  
                  double opRate = 0;
                  double posRate = 0;
                  double negRate = 0;
                  double avgNegDecTime = 0;
                  long avgTmo = 0;
                  long avgActTmo = 0;
  
                  if (pos > 0) {
                      opRate = ((double) opt) / ((double) pos);
                  }
                  if (all > 0) {
                      posRate = ((double) pos) / ((double) all);
                      negRate = ((double) neg) / ((double) all);
                      avgTmo = Statistics.getTmo() / all;
                      avgActTmo = Statistics.getActTmo() / all;
                  }
                  if (neg > 0) {
  
                      avgNegDecTime = ((double) Statistics.getNegTime() / (double) neg);
                  }
                  int syncEvents = Statistics.getSyncs();
                  boolean valid = true; //BCS.isValid();
  
                  List<String> row = Arrays.asList(
                          String.valueOf(valid)
                          , String.valueOf(Top.getID())
                          , JToy.type
                          , String.valueOf(Config.getC())
                          , String.valueOf(avgTmo)
                          , String.valueOf(avgActTmo)
                          , String.valueOf(Statistics.getMaxTmo())
                          , String.valueOf(txSize)
                          , String.valueOf(txInBlock)
                          , String.valueOf(txCount)
                          , String.valueOf(time)
                          , String.valueOf(tps)
                          , String.valueOf(nob)
                          , String.valueOf(noeb)
                          , String.valueOf(bps)
                          , String.valueOf(avgTxInBlock)
                          , String.valueOf(opt)
                          , String.valueOf(opRate)
                          , String.valueOf(pos)
                          , String.valueOf(posRate)
                          , String.valueOf(neg)
                          , String.valueOf(negRate)
                          , String.valueOf(avgNegDecTime)
                          , String.valueOf(syncEvents)
                          , String.valueOf(BP2T)
                          , String.valueOf(BP2D)
                          , String.valueOf(BP2DL)
                          , String.valueOf(HP2T)
                          , String.valueOf(HP2D)
                          , String.valueOf(HP2DL)
                          , String.valueOf(HT2D)
                          , String.valueOf(HD2DL)
                          , String.valueOf(BFD.getSuspected())
                  );
                  CSVUtils.writeLine(writer, row);
                  writer.flush();
                  writer.close();
                  logger.info("ended writeSummary");
              } catch (IOException e) {
                  logger.error(e);
              }
  
          }
  
      void writeByzSummery(String pathString) {
          logger.info("Starting writeSummary");
          Path path = Paths.get(pathString, String.valueOf(Top.getID()), "summary.csv");
          File f = new File(path.toString());
  
          try {
              if (!f.exists()) {
                  f.getParentFile().mkdirs();
                  f.createNewFile();
              }
              FileWriter writer;
              writer = new FileWriter(path.toString(), true);
              int workers = Config.getC();
  
              int nob = 0;
              int noeb = 0;
              int txCount = 0;
              for (int i = Statistics.getH1() ; i < Statistics.getH2() ; i++) {
                  for (int j = 0 ; j < workers ; j++) {
                      Block b = BCS.nbGetBlock(j, i);
                          nob++;
                          if (b.getDataCount() == 0) {
                              noeb++;
                          }
                          txCount += b.getDataCount();
  
                  }
              }
  
              long txSize = Config.getTxSize();
              int txInBlock = Config.getMaxTransactionsInBlock();
              int avgTxInBlock = 0;
  
              long time = (Statistics.getStop() - Statistics.getStart()) / 1000;
              if (nob > 0) {
                  avgTxInBlock = txCount / nob;
  
              }
  
              int tps = 0;
              int bps = 0;
              if (time > 0) {
                  tps = ((int) (txCount / time));
                  bps = (int) (nob / time);
              }
  
              int pos = Statistics.getPos();
              int neg = Statistics.getNeg();
              int opt = Statistics.getOpt();
              int all = Statistics.getAll();
  
              double opRate = 0;
              double posRate = 0;
              double negRate = 0;
              double avgNegDecTime = 0;
              long avgTmo = 0;
              long avgActTmo = 0;
  
              if (pos > 0) {
                  opRate = ((double) opt) / ((double) pos);
              }
              if (all > 0) {
                  posRate = ((double) pos) / ((double) all);
                  negRate = ((double) neg) / ((double) all);
                  avgTmo = Statistics.getTmo() / all;
                  avgActTmo = Statistics.getActTmo() / all;
              }
              if (neg > 0) {
  
                  avgNegDecTime = ((double) Statistics.getNegTime() / (double) neg);
              }
              int syncEvents = Statistics.getSyncs();
              boolean valid = true; //BCS.isValid();
  
              List<String> row = Arrays.asList(
                      String.valueOf(valid)
                      , String.valueOf(Top.getID())
                      , JToy.type
                      , String.valueOf(Config.getC())
                      , String.valueOf(avgTmo)
                      , String.valueOf(avgActTmo)
                      , String.valueOf(Statistics.getMaxTmo())
                      , String.valueOf(txSize)
                      , String.valueOf(txInBlock)
                      , String.valueOf(txCount)
                      , String.valueOf(time)
                      , String.valueOf(tps)
                      , String.valueOf(nob)
                      , String.valueOf(noeb)
                      , String.valueOf(bps)
                      , String.valueOf(avgTxInBlock)
                      , String.valueOf(opt)
                      , String.valueOf(opRate)
                      , String.valueOf(pos)
                      , String.valueOf(posRate)
                      , String.valueOf(neg)
                      , String.valueOf(negRate)
                      , String.valueOf(avgNegDecTime)
                      , String.valueOf(syncEvents)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(0)
                      , String.valueOf(BFD.getSuspected())
              );
              CSVUtils.writeLine(writer, row);
              writer.flush();
              writer.close();
              logger.info("ended writeSummary");
          } catch (IOException e) {
              logger.error(e);
          }
  
      }
  
  
      private void asyncPeriod(int sec, int duration) throws InterruptedException {
          System.out.println(format("setting async params [%d] sec delay for [%d] sec", sec/1000, duration/1000));
          Top.setAsyncParam(sec);
          if (duration > 0) {
              Thread.sleep(duration);
          }
          System.out.println("return to normal");
          Top.setAsyncParam(0);
      }
  
  }
  