/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.examples.report;

import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.measure.StandardComponents.marketDataFactory;

import java.io.File;
import java.time.LocalDate;
import java.util.List;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.google.common.base.Strings;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.calc.CalculationRules;
import com.opengamma.strata.calc.CalculationRunner;
import com.opengamma.strata.calc.Column;
import com.opengamma.strata.calc.Results;
import com.opengamma.strata.calc.marketdata.MarketDataConfig;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.calc.runner.CalculationFunctions;
import com.opengamma.strata.calc.runner.CalculationTasks;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.examples.marketdata.ExampleMarketData;
import com.opengamma.strata.examples.marketdata.ExampleMarketDataBuilder;
import com.opengamma.strata.measure.StandardComponents;
import com.opengamma.strata.measure.rate.RatesMarketDataLookup;
import com.opengamma.strata.product.Trade;
import com.opengamma.strata.report.Report;
import com.opengamma.strata.report.ReportCalculationResults;
import com.opengamma.strata.report.ReportRequirements;
import com.opengamma.strata.report.ReportRunner;
import com.opengamma.strata.report.ReportTemplate;
import com.opengamma.strata.report.cashflow.CashFlowReportRunner;
import com.opengamma.strata.report.cashflow.CashFlowReportTemplate;
import com.opengamma.strata.report.framework.format.ReportOutputFormat;
import com.opengamma.strata.report.trade.TradeReportRunner;
import com.opengamma.strata.report.trade.TradeReportTemplate;

/**
 * Tool for running a report from the command line.
 */
public final class ReportRunnerTool implements AutoCloseable {

  /**
   * The calculation runner.
   */
  private final CalculationRunner runner;

  @Parameter(
      names = {"-t", "--template"},
      description = "Report template input file",
      required = true,
      converter = ReportTemplateParameterConverter.class)
  private ReportTemplate template;

  @Parameter(
      names = {"-m", "--marketdata"},
      description = "Market data root directory",
      validateValueWith = MarketDataRootValidator.class)
  private File marketDataRoot;

  @Parameter(
      names = {"-p", "--portfolio"},
      description = "Portfolio input file",
      required = true,
      converter = TradeListParameterConverter.class)
  private TradeList tradeList;

  @Parameter(
      names = {"-d", "--date"},
      description = "Valuation date, YYYY-MM-DD",
      required = true,
      converter = LocalDateParameterConverter.class)
  private LocalDate valuationDate;

  @Parameter(
      names = {"-f", "--format"},
      description = "Report output format, ascii or csv",
      converter = ReportOutputFormatParameterConverter.class)
  private ReportOutputFormat format = ReportOutputFormat.ASCII_TABLE;

  @Parameter(
      names = {"-i", "--id"},
      description = "An ID by which to select a single trade")
  private String idSearch;

  @Parameter(
      names = {"-h", "--help"},
      description = "Displays this message",
      help = true)
  private boolean help;

  @Parameter(
      names = {"-v", "--version"},
      description = "Prints the version of this tool",
      help = true)
  private boolean version;

  /**
   * Runs the tool.
   * 
   * @param args  the command-line arguments
   */
  public static void main(String[] args) {
    try (ReportRunnerTool reportRunner = new ReportRunnerTool(CalculationRunner.ofMultiThreaded())) {
      JCommander commander = new JCommander(reportRunner);
      commander.setProgramName(ReportRunnerTool.class.getSimpleName());
      try {
        commander.parse(args);
      } catch (ParameterException e) {
        System.err.println("Error: " + e.getMessage());
        System.err.println();
        commander.usage();
        return;
      }
      if (reportRunner.help) {
        commander.usage();
      } else if (reportRunner.version) {
        String versionName = ReportRunnerTool.class.getPackage().getImplementationVersion();
        if (versionName == null) {
          versionName = "unknown";
        }
        System.out.println("Strata Report Runner Tool, version " + versionName);
      } else {
        try {
          reportRunner.run();
        } catch (Exception e) {
          System.err.println(Messages.format("Error: {}\n", e.getMessage()));
          commander.usage();
        }
      }
    }
  }

  //-------------------------------------------------------------------------
  // creates an instance
  private ReportRunnerTool(CalculationRunner runner) {
    this.runner = ArgChecker.notNull(runner, "runner");
  }

  //-------------------------------------------------------------------------
  private void run() {
    ReportRunner<ReportTemplate> reportRunner = getReportRunner(template);
    ReportRequirements requirements = reportRunner.requirements(template);
    ReportCalculationResults calculationResults = runCalculationRequirements(requirements);

    Report report = reportRunner.runReport(calculationResults, template);

    switch (format) {
      case CSV:
        report.writeCsv(System.out);
        break;
      case ASCII_TABLE:
      default:
        report.writeAsciiTable(System.out);
        break;
    }
  }

  private ReportCalculationResults runCalculationRequirements(ReportRequirements requirements) {
    List<Column> columns = requirements.getTradeMeasureRequirements();

    ExampleMarketDataBuilder marketDataBuilder =
        marketDataRoot == null ? ExampleMarketData.builder() : ExampleMarketDataBuilder.ofPath(marketDataRoot.toPath());

    CalculationFunctions functions = StandardComponents.calculationFunctions();
    RatesMarketDataLookup ratesLookup = marketDataBuilder.ratesLookup(valuationDate);
    CalculationRules rules = CalculationRules.of(functions, ratesLookup);

    MarketData marketData = marketDataBuilder.buildSnapshot(valuationDate);

    List<Trade> trades;

    if (Strings.nullToEmpty(idSearch).trim().isEmpty()) {
      trades = tradeList.getTrades();
    } else {
      trades = tradeList.getTrades().stream()
          .filter(t -> t.getInfo().getId().isPresent())
          .filter(t -> t.getInfo().getId().get().getValue().equals(idSearch))
          .collect(toImmutableList());
      if (trades.size() > 1) {
        throw new IllegalArgumentException(
            Messages.format("More than one trade found matching ID: '{}'", idSearch));
      }
    }
    if (trades.isEmpty()) {
      throw new IllegalArgumentException("No trades found. Please check the input portfolio or trade ID filter.");
    }

    // the reference data, such as holidays and securities
    ReferenceData refData = ReferenceData.standard();

    // calculate the results
    CalculationTasks tasks = CalculationTasks.of(rules, trades, columns, refData);
    MarketDataRequirements reqs = tasks.requirements(refData);
    MarketData calibratedMarketData = marketDataFactory().create(reqs, MarketDataConfig.empty(), marketData, refData);
    Results results = runner.getTaskRunner().calculate(tasks, calibratedMarketData, refData);

    return ReportCalculationResults.of(
        valuationDate, trades, requirements.getTradeMeasureRequirements(), results, functions, refData);
  }

  @SuppressWarnings({"unchecked", "rawtypes"})
  private ReportRunner<ReportTemplate> getReportRunner(ReportTemplate reportTemplate) {
    // double-casts to achieve result type, allowing report runner to be used without external knowledge of template type
    if (reportTemplate instanceof TradeReportTemplate) {
      return (ReportRunner) TradeReportRunner.INSTANCE;
    } else if (reportTemplate instanceof CashFlowReportTemplate) {
      return (ReportRunner) CashFlowReportRunner.INSTANCE;
    }
    throw new IllegalArgumentException(Messages.format("Unsupported report type: {}", reportTemplate.getClass().getSimpleName()));
  }

  @Override
  public void close() {
    runner.close();
  }

}
