/*
 * 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.heapdump;

import com.android.tools.perflib.heap.StackFrame;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Site implements Diffable<Site> {
  // The site that this site was directly called from.
  // mParent is null for the root site.
  private Site mParent;

  private String mMethodName;
  private String mSignature;
  private String mFilename;
  private int mLineNumber;

  // To identify this site, we pick a stack trace that includes the site.
  // mId is the id of an object allocated at that stack trace, and mDepth
  // is the number of calls between this site and the innermost site of
  // allocation of the object with mId.
  // For the root site, mId is 0 and mDepth is 0.
  private long mId;
  private int mDepth;

  // The total size of objects allocated in this site (including child sites),
  // organized by heap index. Heap indices outside the range of mSizesByHeap
  // implicitly have size 0.
  private long[] mSizesByHeap;

  // List of child sites.
  private List<Site> mChildren;

  // List of all objects allocated in this site (including child sites).
  private List<AhatInstance> mObjects;
  private List<ObjectsInfo> mObjectsInfos;
  private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;

  private Site mBaseline;

  public static class ObjectsInfo implements Diffable<ObjectsInfo> {
    public AhatHeap heap;
    public AhatClassObj classObj;   // May be null.
    public long numInstances;
    public long numBytes;
    private ObjectsInfo baseline;

    public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
      this.heap = heap;
      this.classObj = classObj;
      this.numInstances = numInstances;
      this.numBytes = numBytes;
      this.baseline = this;
    }

    /**
     * Returns the name of the class this ObjectsInfo is associated with.
     */
    public String getClassName() {
      return classObj == null ? "???" : classObj.getName();
    }

    public void setBaseline(ObjectsInfo baseline) {
      this.baseline = baseline;
    }

    @Override public ObjectsInfo getBaseline() {
      return baseline;
    }

    @Override public boolean isPlaceHolder() {
      return false;
    }
  }

  /**
   * Construct a root site.
   */
  public Site(String name) {
    this(null, name, "", "", 0, 0, 0);
  }

  public Site(Site parent, String method, String signature, String file,
      int line, long id, int depth) {
    mParent = parent;
    mMethodName = method;
    mSignature = signature;
    mFilename = file;
    mLineNumber = line;
    mId = id;
    mDepth = depth;
    mSizesByHeap = new long[1];
    mChildren = new ArrayList<Site>();
    mObjects = new ArrayList<AhatInstance>();
    mObjectsInfos = new ArrayList<ObjectsInfo>();
    mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>();
    mBaseline = this;
  }

  /**
   * Add an instance to this site.
   * Returns the site at which the instance was allocated.
   * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
   * @param depth - The number of frames remaining before the inner-most frame is reached.
   */
  Site add(StackFrame[] frames, int depth, AhatInstance inst) {
    return add(this, frames, depth, inst);
  }

  private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
    while (true) {
      site.mObjects.add(inst);

      ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
      if (inst.isReachable()) {
        AhatHeap heap = inst.getHeap();
        if (heap.getIndex() >= site.mSizesByHeap.length) {
          long[] newSizes = new long[heap.getIndex() + 1];
          for (int i = 0; i < site.mSizesByHeap.length; i++) {
            newSizes[i] = site.mSizesByHeap[i];
          }
          site.mSizesByHeap = newSizes;
        }
        site.mSizesByHeap[heap.getIndex()] += inst.getSize();

        info.numInstances++;
        info.numBytes += inst.getSize();
      }

      if (depth > 0) {
        StackFrame next = frames[depth - 1];
        Site child = null;
        for (int i = 0; i < site.mChildren.size(); i++) {
          Site curr = site.mChildren.get(i);
          if (curr.mLineNumber == next.getLineNumber()
              && curr.mMethodName.equals(next.getMethodName())
              && curr.mSignature.equals(next.getSignature())
              && curr.mFilename.equals(next.getFilename())) {
            child = curr;
            break;
          }
        }
        if (child == null) {
          child = new Site(site, next.getMethodName(), next.getSignature(),
              next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
          site.mChildren.add(child);
        }
        depth = depth - 1;
        site = child;
      } else {
        return site;
      }
    }
  }

  // Get the size of a site for a specific heap.
  public long getSize(AhatHeap heap) {
    int index = heap.getIndex();
    return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : 0;
  }

  /**
   * Get the list of objects allocated under this site. Includes objects
   * allocated in children sites.
   */
  public Collection<AhatInstance> getObjects() {
    return mObjects;
  }

  /**
   * Returns the ObjectsInfo at this site for the given heap and class
   * objects. Creates a new empty ObjectsInfo if none existed before.
   */
  ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) {
    Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap);
    if (classToObjectsInfo == null) {
      classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
      mObjectsInfoMap.put(heap, classToObjectsInfo);
    }

    ObjectsInfo info = classToObjectsInfo.get(classObj);
    if (info == null) {
      info = new ObjectsInfo(heap, classObj, 0, 0);
      mObjectsInfos.add(info);
      classToObjectsInfo.put(classObj, info);
    }
    return info;
  }

  public List<ObjectsInfo> getObjectsInfos() {
    return mObjectsInfos;
  }

  // Get the combined size of the site for all heaps.
  public long getTotalSize() {
    long total = 0;
    for (int i = 0; i < mSizesByHeap.length; i++) {
      total += mSizesByHeap[i];
    }
    return total;
  }

  /**
   * Return the site this site was called from.
   * Returns null for the root site.
   */
  public Site getParent() {
    return mParent;
  }

  public String getMethodName() {
    return mMethodName;
  }

  public String getSignature() {
    return mSignature;
  }

  public String getFilename() {
    return mFilename;
  }

  public int getLineNumber() {
    return mLineNumber;
  }

  /**
   * Returns the id of some object allocated in this site.
   */
  public long getId() {
    return mId;
  }

  /**
   * Returns the number of frames between this site and the site where the
   * object with id getId() was allocated.
   */
  public int getDepth() {
    return mDepth;
  }

  public List<Site> getChildren() {
    return mChildren;
  }

  void setBaseline(Site baseline) {
    mBaseline = baseline;
  }

  @Override public Site getBaseline() {
    return mBaseline;
  }

  @Override public boolean isPlaceHolder() {
    return false;
  }

  /**
   * Adds a place holder instance to this site and all parent sites.
   */
  void addPlaceHolderInstance(AhatInstance placeholder) {
    for (Site site = this; site != null; site = site.mParent) {
      site.mObjects.add(placeholder);
    }
  }
}