/*
 * 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.phoenix.pherf.result;

import java.io.File;
import java.io.IOException;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.phoenix.pherf.PherfConstants;
import org.apache.phoenix.pherf.result.file.ResultFileDetails;
import org.apache.phoenix.pherf.result.impl.CSVFileResultHandler;
import org.apache.phoenix.pherf.result.impl.CSVResultHandler;
import org.apache.phoenix.pherf.rules.RulesApplier;
import org.apache.phoenix.pherf.util.PhoenixUtil;

public class ResultUtil {

  /* This variable needs to be static - Otherwise multiple result files will be generated */
  private static String FILE_SUFFIX = null;

  /**
   * Write data load time details
   * @param dataLoadThreadTime {@link DataLoadThreadTime}
   */
  public synchronized void write(DataLoadThreadTime dataLoadThreadTime) throws IOException {
    ensureBaseResultDirExists();

    CSVResultHandler writer = null;
    try {
      if (!dataLoadThreadTime.getThreadTime().isEmpty()) {
        writer = new CSVFileResultHandler();
        writer.setResultFileDetails(ResultFileDetails.CSV);
        writer.setResultFileName("Data_Load_Details");

        for (WriteThreadTime writeThreadTime : dataLoadThreadTime.getThreadTime()) {
          List<ResultValue> rowValues = new ArrayList<>();
          rowValues.add(new ResultValue(PhoenixUtil.getZookeeper()));
          rowValues.addAll(writeThreadTime.getCsvRepresentation(this));
          Result result = new Result(ResultFileDetails.CSV_DETAILED_PERFORMANCE,
            "ZK," + dataLoadThreadTime.getCsvTitle(), rowValues);
          writer.write(result);
        }
      }
    } finally {
      if (writer != null) {
        writer.flush();
        writer.close();
      }
    }
  }

  /**
   * Write data load time summary
   */
  public synchronized void write(DataLoadTimeSummary dataLoadTime) throws IOException {
    ensureBaseResultDirExists();

    CSVResultHandler writer = null;
    ResultFileDetails resultFileDetails;
    if (PhoenixUtil.isThinDriver()) {
      resultFileDetails = ResultFileDetails.CSV_THIN_AGGREGATE_DATA_LOAD;
    } else {
      resultFileDetails = ResultFileDetails.CSV_AGGREGATE_DATA_LOAD;
    }
    try {
      writer = new CSVFileResultHandler();
      writer.setResultFileDetails(resultFileDetails);
      writer.setResultFileName("Data_Load_Summary");

      for (TableLoadTime loadTime : dataLoadTime.getTableLoadTime()) {
        List<ResultValue> rowValues = new ArrayList<>();
        if (PhoenixUtil.isThinDriver()) {
          rowValues.add(new ResultValue(PhoenixUtil.getQueryServerUrl()));
        } else {
          rowValues.add(new ResultValue(PhoenixUtil.getZookeeper()));
        }
        rowValues.addAll(loadTime.getCsvRepresentation(this));
        Result result =
          new Result(resultFileDetails, resultFileDetails.getHeader().toString(), rowValues);
        writer.write(result);
      }
    } finally {
      if (writer != null) {
        writer.flush();
        writer.close();
      }
    }
  }

  public synchronized void write(ResultHandler resultHandler, DataModelResult dataModelResult,
    RulesApplier ruleApplier) throws Exception {
    ResultFileDetails resultFileDetails = resultHandler.getResultFileDetails();
    switch (resultFileDetails) {
      case CSV_AGGREGATE_PERFORMANCE:
      case CSV_DETAILED_PERFORMANCE:
      case CSV_DETAILED_FUNCTIONAL:
        List<List<ResultValue>> rowDetails =
          getCSVResults(dataModelResult, resultFileDetails, ruleApplier);
        for (List<ResultValue> row : rowDetails) {
          Result result =
            new Result(resultFileDetails, resultFileDetails.getHeader().toString(), row);
          resultHandler.write(result);
        }
        break;
      default:
        List<ResultValue> resultValue = new ArrayList();
        resultValue.add(new ResultValue<>(dataModelResult));
        resultHandler.write(new Result(resultFileDetails, null, resultValue));
        break;
    }
  }

  public void ensureBaseResultDirExists() {
    PherfConstants constants = PherfConstants.create();
    String resultDir = constants.getProperty("pherf.default.results.dir");
    ensureBaseDirExists(resultDir);
  }

  /**
   * Utility method to check if base result dir exists
   */
  public void ensureBaseDirExists(String directory) {
    File baseDir = new File(directory);
    if (!baseDir.exists()) {
      baseDir.mkdir();
    }
  }

  /**
   * Utility method to delete directory
   */
  public void deleteDir(String directory) throws IOException {
    File baseDir = new File(directory);
    if (baseDir.exists()) {
      // deleteDirectory seems to have a problem with MacOs tmp dir symlinks
      FileUtils.cleanDirectory(baseDir);
      FileUtils.deleteDirectory(baseDir);
    }
  }

  public String getSuffix() {
    if (null == FILE_SUFFIX) {
      Date date = new Date();
      Format formatter = new SimpleDateFormat("YYYY-MM-dd_hh-mm-ss");
      FILE_SUFFIX = formatter.format(date);
    }
    return "_" + FILE_SUFFIX;
  }

  public String convertNull(String str) {
    if ((str == null) || str.equals("")) {
      return "null";
    }
    return str;
  }

  /**
   * Used by custom ResultWriter out Pherf's normal code base
   * @return Header field as a {@link Result}
   */
  @SuppressWarnings("unused")
  public Result getCSVHeaderAsResult(String row) {
    List<ResultValue> resultValues = new ArrayList<>();
    resultValues.add(new ResultValue(row));
    return new Result(ResultFileDetails.CSV, row, resultValues);
  }

  private List<List<ResultValue>> getCSVResults(DataModelResult dataModelResult,
    ResultFileDetails resultFileDetails, RulesApplier ruleApplier) {
    List<List<ResultValue>> rowList = new ArrayList<>();

    for (ScenarioResult result : dataModelResult.getScenarioResult()) {
      for (QuerySetResult querySetResult : result.getQuerySetResult()) {
        for (QueryResult queryResult : querySetResult.getQueryResults()) {
          switch (resultFileDetails) {
            case CSV_AGGREGATE_PERFORMANCE:
              List<ResultValue> csvResult =
                queryResult.getCsvRepresentation(this, result, ruleApplier);
              rowList.add(csvResult);
              break;
            case CSV_DETAILED_PERFORMANCE:
            case CSV_DETAILED_FUNCTIONAL:
              List<List<ResultValue>> detailedRows =
                queryResult.getCsvDetailedRepresentation(this, resultFileDetails);
              for (List<ResultValue> detailedRowList : detailedRows) {
                List<ResultValue> valueList = new ArrayList<>();
                valueList.add(new ResultValue(convertNull(result.getTableName())));
                valueList.add(new ResultValue(convertNull(result.getName())));
                valueList.add(new ResultValue(convertNull(dataModelResult.getZookeeper())));
                valueList.add(new ResultValue(convertNull(String.valueOf(result.getRowCount()))));
                valueList.add(new ResultValue(
                  convertNull(String.valueOf(querySetResult.getNumberOfExecutions()))));
                valueList.add(
                  new ResultValue(convertNull(String.valueOf(querySetResult.getExecutionType()))));
                if (result.getPhoenixProperties() != null) {
                  String props = buildProperty(result);
                  valueList.add(new ResultValue(convertNull(props)));
                } else {
                  valueList.add(new ResultValue("null"));
                }
                valueList.addAll(detailedRowList);
                rowList.add(valueList);
              }
              break;
            default:
              break;
          }
        }
      }
    }
    return rowList;
  }

  private String buildProperty(ScenarioResult result) {
    StringBuffer sb = new StringBuffer();
    boolean firstPartialSeparator = true;

    for (Map.Entry<String, String> entry : result.getPhoenixProperties().entrySet()) {
      if (!firstPartialSeparator) sb.append("|");
      firstPartialSeparator = false;
      sb.append(entry.getKey() + "=" + entry.getValue());
    }
    return sb.toString();
  }

  /**
   * Set the file suffix
   */
  public static void setFileSuffix(String suffix) {
    FILE_SUFFIX = suffix;
  }
}
