/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.hadoop.fs.slive;

import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class which provides a report for the given operation output
 */
class ReportWriter {

  // simple measurement types
  // expect long values
  // these will be reported on + rates by this reporter
  static final String OK_TIME_TAKEN = "milliseconds_taken";
  static final String FAILURES = "failures";
  static final String SUCCESSES = "successes";
  static final String BYTES_WRITTEN = "bytes_written";
  static final String FILES_CREATED = "files_created";
  static final String DIR_ENTRIES = "dir_entries";
  static final String OP_COUNT = "op_count";
  static final String CHUNKS_VERIFIED = "chunks_verified";
  static final String CHUNKS_UNVERIFIED = "chunks_unverified";
  static final String BYTES_READ = "bytes_read";
  static final String NOT_FOUND = "files_not_found";
  static final String BAD_FILES = "bad_files";

  private static final Logger LOG = LoggerFactory.getLogger(ReportWriter.class);

  private static final String SECTION_DELIM = "-------------";

  /**
   * @return String to be used for as a section delimiter
   */
  private String getSectionDelimiter() {
    return SECTION_DELIM;
  }

  /**
   * Writes a message the the logging library and the given print writer (if it
   * is not null)
   * 
   * @param msg
   *          the message to write
   * @param os
   *          the print writer if specified to also write to
   */
  private void writeMessage(String msg, PrintWriter os) {
    LOG.info(msg);
    if (os != null) {
      os.println(msg);
    }
  }

  /**
   * Provides a simple report showing only the input size, and for each
   * operation the operation type, measurement type and its values.
   * 
   * @param input
   *          the list of operations to report on
   * @param os
   *          any print writer for which output should be written to (along with
   *          the logging library)
   */
  void basicReport(List<OperationOutput> input, PrintWriter os) {
    writeMessage("Default report for " + input.size() + " operations ", os);
    writeMessage(getSectionDelimiter(), os);
    for (OperationOutput data : input) {
      writeMessage("Operation \"" + data.getOperationType() + "\" measuring \""
          + data.getMeasurementType() + "\" = " + data.getValue(), os);
    }
    writeMessage(getSectionDelimiter(), os);
  }

  /**
   * Provides a more detailed report for a given operation. This will output the
   * keys and values for all input and then sort based on measurement type and
   * attempt to show rates for various metrics which have expected types to be
   * able to measure there rate. Currently this will show rates for bytes
   * written, success count, files created, directory entries, op count and
   * bytes read if the variable for time taken is available for each measurement
   * type.
   * 
   * @param operation
   *          the operation that is being reported on.
   * @param input
   *          the set of data for that that operation.
   * @param os
   *          any print writer for which output should be written to (along with
   *          the logging library)
   */
  void opReport(String operation, List<OperationOutput> input,
      PrintWriter os) {
    writeMessage("Basic report for operation type " + operation, os);
    writeMessage(getSectionDelimiter(), os);
    for (OperationOutput data : input) {
      writeMessage("Measurement \"" + data.getMeasurementType() + "\" = "
          + data.getValue(), os);
    }
    // split up into measurement types for rates...
    Map<String, OperationOutput> combined = new TreeMap<String, OperationOutput>();
    for (OperationOutput data : input) {
      if (combined.containsKey(data.getMeasurementType())) {
        OperationOutput curr = combined.get(data.getMeasurementType());
        combined.put(data.getMeasurementType(), OperationOutput.merge(curr,
            data));
      } else {
        combined.put(data.getMeasurementType(), data);
      }
    }
    // handle the known types
    OperationOutput timeTaken = combined.get(OK_TIME_TAKEN);
    if (timeTaken != null) {
      Long mTaken = Long.parseLong(timeTaken.getValue().toString());
      if (mTaken > 0) {
        NumberFormat formatter = Formatter.getDecimalFormatter();
        for (String measurementType : combined.keySet()) {
          Double rate = null;
          String rateType = "";
          if (measurementType.equals(BYTES_WRITTEN)) {
            Long mbWritten = Long.parseLong(combined.get(measurementType)
                .getValue().toString())
                / (Constants.MEGABYTES);
            rate = (double) mbWritten / (double) (mTaken / 1000.0d);
            rateType = "MB/sec";
          } else if (measurementType.equals(SUCCESSES)) {
            Long succ = Long.parseLong(combined.get(measurementType).getValue()
                .toString());
            rate = (double) succ / (double) (mTaken / 1000.0d);
            rateType = "successes/sec";
          } else if (measurementType.equals(FILES_CREATED)) {
            Long filesCreated = Long.parseLong(combined.get(measurementType)
                .getValue().toString());
            rate = (double) filesCreated / (double) (mTaken / 1000.0d);
            rateType = "files created/sec";
          } else if (measurementType.equals(DIR_ENTRIES)) {
            Long entries = Long.parseLong(combined.get(measurementType)
                .getValue().toString());
            rate = (double) entries / (double) (mTaken / 1000.0d);
            rateType = "directory entries/sec";
          } else if (measurementType.equals(OP_COUNT)) {
            Long opCount = Long.parseLong(combined.get(measurementType)
                .getValue().toString());
            rate = (double) opCount / (double) (mTaken / 1000.0d);
            rateType = "operations/sec";
          } else if (measurementType.equals(BYTES_READ)) {
            Long mbRead = Long.parseLong(combined.get(measurementType)
                .getValue().toString())
                / (Constants.MEGABYTES);
            rate = (double) mbRead / (double) (mTaken / 1000.0d);
            rateType = "MB/sec";
          }
          if (rate != null) {
            writeMessage("Rate for measurement \"" + measurementType + "\" = "
                + formatter.format(rate) + " " + rateType, os);
          }
        }
      }
    }
    writeMessage(getSectionDelimiter(), os);
  }

}
