// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed 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 com.google.devtools.build.lib.profiler.chart;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Data of a Gantt Chart to visualize the data of a profiled build.
 */
public class Chart {

  /** The type that is returned when an unknown type is looked up. */
  public static final ChartBarType UNKNOWN_TYPE = new ChartBarType("Unknown type", Color.RED);

  /** The rows of the chart. */
  private final Map<Long, ChartRow> rows = new HashMap<>();

  /** The columns on the chart. */
  private final List<ChartColumn> columns = new ArrayList<>();

  /** The lines on the chart. */
  private final List<ChartLine> lines = new ArrayList<>();

  /** The types of the bars in the chart. */
  private final Map<String, ChartBarType> types = new HashMap<>();

  /** The running index of the rows in the chart. */
  private int rowIndex = 0;

  /** The maximum stop value of any bar in the chart. */
  private long maxStop;

  /**
   * Adds a bar to a row of the chart. If a row with the given id already
   * exists, the bar is added to the row, otherwise a new row is created and the
   * bar is added to it.
   *
   * @param id the id of the row the new bar belongs to
   * @param start the start value of the bar
   * @param stop the stop value of the bar
   * @param type the type of the bar
   * @param highlight emphasize the bar
   * @param label the label of the bar
   */
  public void addBar(long id, long start, long stop, ChartBarType type, boolean highlight,
      String label) {
    ChartRow slot = addSlotIfAbsent(id);
    ChartBar bar = new ChartBar(slot, start, stop, type, highlight, label);
    slot.addBar(bar);
    maxStop = Math.max(maxStop, stop);
  }

  /**
   * Adds a bar to a row of the chart. If a row with the given id already
   * exists, the bar is added to the row, otherwise a new row is created and the
   * bar is added to it.
   *
   * @param id the id of the row the new bar belongs to
   * @param start the start value of the bar
   * @param stop the stop value of the bar
   * @param type the type of the bar
   * @param label the label of the bar
   */
  public void addBar(long id, long start, long stop, ChartBarType type, String label) {
    addBar(id, start, stop, type, false, label);
  }

  /**
   * Adds a vertical line to the chart.
   */
  public void addVerticalLine(long startId, long stopId, long pos) {
    ChartRow startSlot = addSlotIfAbsent(startId);
    ChartRow stopSlot = addSlotIfAbsent(stopId);
    ChartLine line = new ChartLine(startSlot, stopSlot, pos, pos);
    lines.add(line);
  }

  /**
   * Adds a column to the chart.
   *
   * @param start the start value of the bar
   * @param stop the stop value of the bar
   * @param type the type of the bar
   * @param label the label of the bar
   */
  public void addTimeRange(long start, long stop, ChartBarType type, String label) {
    ChartColumn column = new ChartColumn(start, stop, type, label);
    columns.add(column);
    maxStop = Math.max(maxStop, stop);
  }

  /**
   * Creates a new {@link ChartBarType} and adds it to the list of types of the
   * chart.
   *
   * @param name the name of the type
   * @param color the color of the chart
   * @return the newly created type
   */
  public ChartBarType createType(String name, Color color) {
    ChartBarType type = new ChartBarType(name, color);
    types.put(name, type);
    return type;
  }

  /**
   * Returns the type with the given name. If no type with the given name
   * exists, a type with name 'Unknown type' is added to the chart and returned.
   *
   * @param name the name of the type to look up
   */
  public ChartBarType lookUpType(String name) {
    ChartBarType type = types.get(name);
    if (type == null) {
      type = UNKNOWN_TYPE;
      types.put(type.getName(), type);
    }
    return type;
  }

  /**
   * Creates a new row with the given id if no row with this id existed.
   * Otherwise the existing row with the given id is returned.
   *
   * @param id the ID of the row
   * @return the existing row, if it was already present, the newly created one
   *         otherwise
   */
  private ChartRow addSlotIfAbsent(long id) {
    ChartRow slot = rows.get(id);
    if (slot == null) {
      slot = new ChartRow(Long.toString(id), rowIndex++);
      rows.put(id, slot);
    }
    return slot;
  }

  /**
   * Accepts a {@link ChartVisitor}. Calls {@link ChartVisitor#visit(Chart)},
   * delegates the visitor to the rows of the chart and calls
   * {@link ChartVisitor#endVisit(Chart)}.
   *
   * @param visitor the visitor to accept
   */
  public void accept(ChartVisitor visitor) {
    visitor.visit(this);
    for (ChartRow slot : rows.values()) {
      slot.accept(visitor);
    }
    int rowCount = getRowCount();
    for (ChartColumn column : columns) {
      column.setRowCount(rowCount);
      column.accept(visitor);
    }
    for (ChartLine line : lines) {
      line.accept(visitor);
    }
    visitor.endVisit(this);
  }

  /**
   * Returns the {@link ChartBarType}s, sorted by name.
   */
  public List<ChartBarType> getSortedTypes() {
    List<ChartBarType> list = new ArrayList<>(types.values());
    Collections.sort(list);
    return list;
  }

  /**
   * Returns the {@link ChartRow}s, sorted by their index.
   */
  public List<ChartRow> getSortedRows() {
    List<ChartRow> list = new ArrayList<>(rows.values());
    Collections.sort(list);
    return list;
  }

  public int getRowCount() {
    return rows.size();
  }

  public long getMaxStop() {
    return maxStop;
  }
}
