/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.zookeeper.server;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.jute.BinaryInputArchive;
import org.apache.jute.InputArchive;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.server.persistence.FileSnap;
import org.apache.zookeeper.server.persistence.SnapStream;

/**
 * Recursively processes a snapshot file collecting child node count and summarizes the data size
 * below each node.
 * "starting_node" defines the node where the recursion starts
 * "max_depth" defines the depth where the tool still writes to the output.
 * 0 means there is no depth limit, every non-leaf node's stats will be displayed, 1 means it will
 * only contain the starting node's and it's children's stats, 2 ads another level and so on.
 * This ONLY affects the level of details displayed, NOT the calculation.
 */
@InterfaceAudience.Public public class SnapshotRecursiveSummary {

  /**
   * USAGE: SnapsotRecursiveSummary snapshot_file starting_node max_depth
   *
   */
  public static void main(String[] args) throws Exception {
    if (args.length != 3) {
      System.err.println(getUsage());
      System.exit(2);
    }
    int maxDepth = 0;
    try {
      maxDepth = Integer.parseInt(args[2]);
    } catch (NumberFormatException e) {
      System.err.println(getUsage());
      System.exit(2);
    }

    new SnapshotRecursiveSummary().run(args[0], args[1], maxDepth);
  }

  public void run(String snapshotFileName, String startingNode, int maxDepth) throws IOException {
    File snapshotFile = new File(snapshotFileName);
    try (InputStream is = SnapStream.getInputStream(snapshotFile)) {
      InputArchive ia = BinaryInputArchive.getArchive(is);

      DataTree dataTree = new DataTree();
      Map<Long, Integer> sessions = new HashMap<>();
      FileSnap.deserialize(dataTree, sessions, ia);

      printZnodeDetails(dataTree, startingNode, maxDepth);
    }
  }

  private void printZnodeDetails(DataTree dataTree, String startingNode, int maxDepth) {
    StringBuilder builder = new StringBuilder();
    printZnode(dataTree, startingNode, builder, 0, maxDepth);
    System.out.println(builder);
  }

  private long[] printZnode(DataTree dataTree, String name, StringBuilder builder, int level,
      int maxDepth) {
    DataNode n = dataTree.getNode(name);
    Set<String> children;
    long dataSum = 0L;
    synchronized (n) { // keep findbugs happy
      if (n.data != null) {
        dataSum += n.data.length;
      }
      children = n.getChildren();
    }

    long[] result = {1L, dataSum};
    if (children.size() == 0) {
      return result;
    }
    StringBuilder childBuilder = new StringBuilder();
    for (String child : children) {
      long[] childResult =
          printZnode(dataTree, name + (name.equals("/") ? "" : "/") + child, childBuilder,
              level + 1, maxDepth);
      result[0] = result[0] + childResult[0];
      result[1] = result[1] + childResult[1];
    }

    if (maxDepth == 0 || level <= maxDepth) {
      String tab = String.join("", Collections.nCopies(level, "--"));
      builder.append(tab + " " + name + "\n");
      builder.append(tab + "   children: " + (result[0] - 1) + "\n");
      builder.append(tab + "   data: " + result[1] + "\n");
      builder.append(childBuilder);
    }
    return result;
  }

  public static String getUsage() {
    String newLine = System.getProperty("line.separator");
    return String.join(newLine,
        "USAGE:",
        newLine,
        "SnapshotRecursiveSummary  <snapshot_file>  <starting_node>  <max_depth>",
        newLine,
        "snapshot_file:    path to the zookeeper snapshot",
        "starting_node:    the path in the zookeeper tree where the traversal should begin",
        "max_depth:        defines the depth where the tool still writes to the output. "
            + "0 means there is no depth limit, every non-leaf node's stats will be displayed, "
            + "1 means it will only contain the starting node's and it's children's stats, "
            + "2 ads another level and so on. This ONLY affects the level of details displayed, "
            + "NOT the calculation.");
  }
}
