/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.ahat;

import com.android.ahat.heapdump.AhatHeap;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Diffable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Class for rendering a table that includes sizes of some kind for each heap.
 */
class HeapTable {
  /**
   * Configuration for a value column of a heap table.
   */
  public interface ValueConfig<T> {
    String getDescription();
    DocString render(T element);
  }

  /**
   * Configuration for the HeapTable.
   */
  public interface TableConfig<T> {
    String getHeapsDescription();
    long getSize(T element, AhatHeap heap);
    List<ValueConfig<T>> getValueConfigs();
  }

  private static DocString sizeString(long size, boolean isPlaceHolder) {
    DocString string = new DocString();
    if (isPlaceHolder) {
      string.append(DocString.removed("del"));
    } else if (size != 0) {
      string.appendFormat("%,14d", size);
    }
    return string;
  }

  /**
   * Render the table to the given document.
   * @param query - The page query.
   * @param id - A unique identifier for the table on the page.
   */
  public static <T extends Diffable<T>> void render(Doc doc, Query query, String id,
      TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
    // Only show the heaps that have non-zero entries.
    List<AhatHeap> heaps = new ArrayList<AhatHeap>();
    for (AhatHeap heap : snapshot.getHeaps()) {
      if (hasNonZeroEntry(heap, config, elements)) {
        heaps.add(heap);
      }
    }

    List<ValueConfig<T>> values = config.getValueConfigs();

    // Print the heap and values descriptions.
    List<Column> subcols = new ArrayList<Column>();
    for (AhatHeap heap : heaps) {
      subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
      subcols.add(new Column("Δ", Column.Align.RIGHT, snapshot.isDiffed()));
    }
    boolean showTotal = heaps.size() > 1;
    subcols.add(new Column("Total", Column.Align.RIGHT, showTotal));
    subcols.add(new Column("Δ", Column.Align.RIGHT, showTotal && snapshot.isDiffed()));
    List<Column> cols = new ArrayList<Column>();
    for (ValueConfig value : values) {
      cols.add(new Column(value.getDescription()));
    }
    doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);

    // Print the entries up to the selected limit.
    SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
    ArrayList<DocString> vals = new ArrayList<DocString>();
    for (T elem : selector.selected()) {
      T base = elem.getBaseline();
      vals.clear();
      long total = 0;
      long basetotal = 0;
      for (AhatHeap heap : heaps) {
        long size = config.getSize(elem, heap);
        long basesize = config.getSize(base, heap.getBaseline());
        total += size;
        basetotal += basesize;
        vals.add(sizeString(size, elem.isPlaceHolder()));
        vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize));
      }
      vals.add(sizeString(total, elem.isPlaceHolder()));
      vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal));

      for (ValueConfig<T> value : values) {
        vals.add(value.render(elem));
      }
      doc.row(vals.toArray(new DocString[0]));
    }

    // Print a summary of the remaining entries if there are any.
    List<T> remaining = selector.remaining();
    if (!remaining.isEmpty()) {
      Map<AhatHeap, Long> summary = new HashMap<AhatHeap, Long>();
      Map<AhatHeap, Long> basesummary = new HashMap<AhatHeap, Long>();
      for (AhatHeap heap : heaps) {
        summary.put(heap, 0L);
        basesummary.put(heap, 0L);
      }

      for (T elem : remaining) {
        for (AhatHeap heap : heaps) {
          long size = config.getSize(elem, heap);
          summary.put(heap, summary.get(heap) + size);

          long basesize = config.getSize(elem.getBaseline(), heap.getBaseline());
          basesummary.put(heap, basesummary.get(heap) + basesize);
        }
      }

      vals.clear();
      long total = 0;
      long basetotal = 0;
      for (AhatHeap heap : heaps) {
        long size = summary.get(heap);
        long basesize = basesummary.get(heap);
        total += size;
        basetotal += basesize;
        vals.add(sizeString(size, false));
        vals.add(DocString.delta(false, false, size, basesize));
      }
      vals.add(sizeString(total, false));
      vals.add(DocString.delta(false, false, total, basetotal));

      for (ValueConfig<T> value : values) {
        vals.add(DocString.text("..."));
      }
      doc.row(vals.toArray(new DocString[0]));
    }
    doc.end();
    selector.render(doc);
  }

  // Returns true if the given heap has a non-zero size entry.
  public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap,
      TableConfig<T> config, List<T> elements) {
    AhatHeap baseheap = heap.getBaseline();
    if (heap.getSize() > 0 || baseheap.getSize() > 0) {
      for (T element : elements) {
        if (config.getSize(element, heap) > 0 ||
            config.getSize(element.getBaseline(), baseheap) > 0) {
          return true;
        }
      }
    }
    return false;
  }
}