/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package chapters.appenders;

import java.io.FileWriter;
import java.io.IOException;

import org.slf4j.Logger;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.util.StatusPrinter;

public class IOPerformance extends Thread {
  static String MSG = "ABCDEGHIJKLMNOPQRSTUVWXYZabcdeghijklmnopqrstuvwxyz1234567890";
  static String LOG_FILE;
  public static String PARALLEL_FILE;

  static int NUM_THREADS = 1;
  static long l;
  long len;
  boolean immediateFlush;
  Logger logger;
  LoggerContext context;
  double throughput;

  public IOPerformance(boolean _immediateFlush, long _len) {
    this.len = _len;
    this.immediateFlush = _immediateFlush;
    context = new LoggerContext();
    logger = context.getLogger("logger-" + getName());

    // A FileAppender is created according to the buffering and
    // immediate flush setting of this IO instance.
    FileAppender<ILoggingEvent> fa = new FileAppender<ILoggingEvent>();
    fa.setName("FILE");
    PatternLayoutEncoder pa = new PatternLayoutEncoder();
    pa.setPattern("%r %5p %c [%t] - %m%n");
    pa.setContext(context);
    pa.start();
    fa.setEncoder(pa);

    fa.setFile(LOG_FILE);
    fa.setAppend(true);
    fa.setContext(context);
    fa.start();

    ((ch.qos.logback.classic.Logger) logger).addAppender(fa);

    StatusPrinter.print(context);
  }

  public static void main(String[] argv) throws Exception {
    if (argv.length != 3) {
      usage("Wrong number of arguments.");
    }

    l = Integer.parseInt(argv[0]);
    LOG_FILE = argv[1];
    PARALLEL_FILE = argv[2];

    // ----------------------------------------------------
    // first test with immediate flushing
    perfCase(true, l);

    // ----------------------------------------------------
    // Second test with no immediate flushing
    perfCase(false, l);

    // There is no fourth test as buffered IO and immediate flushing
    // do not make sense.
  }

  static void usage(String msg) {
    System.err.println(msg);
    System.err.println("Usage: java " + IOPerformance.class.getName()
        + " runLength logFile otherFile\n"
        + "   runLength (integer) the number of logs to generate perthread\n"
        + "   logFile path to a logFile\n"
        + "   otherFile path to a second file\n");
    System.exit(1);
  }

  static void perfCase(boolean immediateFlush, long len) throws Exception {
    IOPerformance[] threads = new IOPerformance[NUM_THREADS];
    OtherIO otherIOThread = new OtherIO();
    otherIOThread.start();

    // First create the threads
    for (int i = 0; i < NUM_THREADS; i++) {
      threads[i] = new IOPerformance(immediateFlush, len);
    }

    // then start them
    for (int i = 0; i < NUM_THREADS; i++) {
      threads[i].start();
    }

    // wait for them to processPriorToRemoval, compute the average throughput
    double sum = 0;

    for (int i = 0; i < NUM_THREADS; i++) {
      threads[i].join();
      sum += threads[i].throughput;
    }

    // setting the interrupted field will cause counterThread to processPriorToRemoval
    otherIOThread.interrupted = true;
    otherIOThread.join();

    System.out.println("On total throughput of " + (sum)
        + " logs per microsecond.");
    System.out.println("------------------------------------------------");
  }

  public void run() {

    long before = System.nanoTime();

    for (int i = 0; i < len; i++) {
      logger.debug(MSG);
    }

    throughput = (len * 1.0) / ((System.nanoTime() - before) / 1000);
    System.out.println(getName() + ", immediateFlush: " + immediateFlush
        + ", throughput: " + throughput + " logs per microsecond.");
  }
}

class OtherIO extends Thread {
  public boolean interrupted = false;
  public int counter = 0;

  public void run() {
    long before = System.nanoTime();
    try {
      FileWriter fw = new FileWriter(IOPerformance.PARALLEL_FILE, true);

      while (!interrupted) {
        counter++;
        fw.write("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
        fw.flush();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    double tput = (counter * 1.0) / (System.nanoTime() - before);
    System.out.println("Counter thread " + getName()
        + " incremented counter by " + tput + " per nanosecond.");
  }
}
