// Copyright 2015 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.statistics;

import com.google.common.base.Supplier;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
import com.google.common.collect.Tables;
import com.google.devtools.build.lib.profiler.ProfileInfo;
import com.google.devtools.build.lib.profiler.ProfileInfo.Task;
import com.google.devtools.build.lib.profiler.ProfilePhase;
import com.google.devtools.build.lib.profiler.ProfilerTask;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;

/**
 * Compute and store statistics of all {@link ProfilerTask}s that begin with VFS_ in sorted order.
 */
public final class PhaseVfsStatistics implements Iterable<ProfilerTask> {

  /**
   * Duration, count and path for sorting by duration first and count in case of tie. Path for
   * easy returning of a {@link SortedSet}.
   */
  public static final class Stat implements Comparable<Stat> {
    private long duration;
    private long count;
    public final String path;

    public Stat(String path) {
      this.path = path;
    }

    public Stat(Stat other) {
      this.duration = other.duration;
      this.count = other.count;
      this.path = other.path;
    }

    public long getDuration() {
      return duration;
    }

    public long getCount() {
      return count;
    }

    private void add(Stat other) {
      this.duration += other.duration;
      this.count += other.count;
    }

    private void add(long duration) {
      this.duration += duration;
      this.count++;
    }

    /**
     * Order first by duration, then count, then path
     */
    @Override
    public int compareTo(Stat o) {
      return ComparisonChain.start()
          .compare(duration, o.duration)
          .compare(count, o.count)
          .compare(path, o.path)
          .result();
    }

    @Override
    public boolean equals(Object obj) {
      if (obj instanceof Stat) {
        return compareTo((Stat) obj) == 0;
      }
      return false;
    }

    @Override
    public int hashCode() {
      return Objects.hash(duration, count, path);
    }
  }

  private final ProfilePhase phase;
  private final Table<ProfilerTask, String, Stat> statistics;

  public PhaseVfsStatistics(ProfilePhase phase) {
    this.phase = phase;
    this.statistics =
        Tables.newCustomTable(
            new EnumMap<ProfilerTask, Map<String, Stat>>(ProfilerTask.class),
            new Supplier<Map<String, Stat>>() {
              @Override
              public Map<String, Stat> get() {
                return new HashMap<>();
              }
            });
  }

  public PhaseVfsStatistics(final String workSpaceName, ProfilePhase phase, ProfileInfo info) {
    this(phase);
    addProfileInfo(workSpaceName, info);
  }

  /**
   * Accumulate statistics from another {@link ProfileInfo} in this object.
   */
  public void addProfileInfo(final String workSpaceName, ProfileInfo info) {
    Task phaseTask = info.getPhaseTask(phase);
    if (phaseTask == null) {
      return;
    }
    collectVfsEntries(workSpaceName, info.getTasksForPhase(phaseTask));
  }

  public ProfilePhase getProfilePhase() {
    return phase;
  }

  public boolean isEmpty() {
    return statistics.isEmpty();
  }

  /**
   * Builds a new {@link ImmutableSortedSet} of the path statistics for the given
   * {@link ProfilerTask}.
   *
   * <p>{@link Stat}s are sorted by their natural order.
   */
  public ImmutableSortedSet<Stat> getSortedStatistics(ProfilerTask taskType) {
    return ImmutableSortedSet.copyOf(statistics.row(taskType).values());
  }

  public int getStatisticsCount(ProfilerTask taskType) {
    return statistics.row(taskType).size();
  }

  @Override
  public Iterator<ProfilerTask> iterator() {
    return statistics.rowKeySet().iterator();
  }

  /**
   * Add statistics from another PhaseVfsStatistics aggregation to this one.
   */
  public void add(PhaseVfsStatistics other) {
    for (Cell<ProfilerTask, String, Stat> cell : other.statistics.cellSet()) {
      Stat stat = statistics.get(cell.getRowKey(), cell.getColumnKey());
      if (stat == null) {
        stat = new Stat(cell.getValue());
        statistics.put(cell.getRowKey(), stat.path, stat);
      } else {
        stat.add(cell.getValue());
      }
    }
  }

  /**
   * Add the VFS operations from the list of tasks to the {@link #statistics} table
   */
  private void collectVfsEntries(String workSpaceName, List<Task> taskList) {
    for (Task task : taskList) {
      collectVfsEntries(workSpaceName, Arrays.asList(task.subtasks));
      if (!task.type.name().startsWith("VFS_")) {
        continue;
      }

      String path = pathMapping(workSpaceName, task.getDescription());

      Stat stat = statistics.get(task.type, path);
      if (stat == null) {
        stat = new Stat(path);
        statistics.put(task.type, path, stat);
      }

      stat.add(task.durationNanos);
    }
  }

  private String pathMapping(String workSpaceName, String input) {
    if (workSpaceName.isEmpty()) {
      return input;
    } else {
      return input.substring(input.lastIndexOf("/" + workSpaceName) + 1);
    }
  }
}

