/*
 * Copyright (C) 2012 The Guava Authors
 *
 * 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.common.collect;

import com.google.caliper.BeforeExperiment;
import com.google.caliper.Benchmark;
import com.google.caliper.Param;
import com.google.common.base.Optional;
import com.google.common.primitives.Ints;
import java.util.List;
import java.util.Random;

/**
 * Benchmarks for the {@code TreeTraverser} and optimized {@code BinaryTreeTraverser} operations on
 * binary trees.
 *
 * @author Louis Wasserman
 */
public class BinaryTreeTraverserBenchmark {
  private static class BinaryNode {
    final int x;
    final Optional<BinaryNode> left;
    final Optional<BinaryNode> right;

    BinaryNode(int x, Optional<BinaryNode> left, Optional<BinaryNode> right) {
      this.x = x;
      this.left = left;
      this.right = right;
    }
  }

  enum Topology {
    BALANCED {
      @Override
      Optional<BinaryNode> createTree(int size, Random rng) {
        if (size == 0) {
          return Optional.absent();
        } else {
          int leftChildSize = (size - 1) / 2;
          int rightChildSize = size - 1 - leftChildSize;
          return Optional.of(new BinaryNode(
              rng.nextInt(), createTree(leftChildSize, rng), createTree(rightChildSize, rng)));
        }
      }
    },
    ALL_LEFT {
      @Override
      Optional<BinaryNode> createTree(int size, Random rng) {
        Optional<BinaryNode> root = Optional.absent();
        for (int i = 0; i < size; i++) {
          root = Optional.of(new BinaryNode(rng.nextInt(), root, Optional.<BinaryNode>absent()));
        }
        return root;
      }
    },
    ALL_RIGHT {
      @Override
      Optional<BinaryNode> createTree(int size, Random rng) {
        Optional<BinaryNode> root = Optional.absent();
        for (int i = 0; i < size; i++) {
          root = Optional.of(new BinaryNode(rng.nextInt(), Optional.<BinaryNode>absent(), root));
        }
        return root;
      }
    },
    RANDOM {
      /**
       * Generates a tree with topology selected uniformly at random from the topologies of binary
       * trees of the specified size.
       */
      @Override
      Optional<BinaryNode> createTree(int size, Random rng) {
        int[] keys = new int[size];
        for (int i = 0; i < size; i++) {
          keys[i] = rng.nextInt();
        }
        return createTreap(Ints.asList(keys));
      }

      // See http://en.wikipedia.org/wiki/Treap for details on the algorithm.
      private Optional<BinaryNode> createTreap(List<Integer> keys) {
        if (keys.isEmpty()) {
          return Optional.absent();
        }
        int minIndex = 0;
        for (int i = 1; i < keys.size(); i++) {
          if (keys.get(i) < keys.get(minIndex)) {
            minIndex = i;
          }
        }
        Optional<BinaryNode> leftChild = createTreap(keys.subList(0, minIndex));
        Optional<BinaryNode> rightChild = createTreap(keys.subList(minIndex + 1, keys.size()));
        return Optional.of(new BinaryNode(keys.get(minIndex), leftChild, rightChild));
      }
    };

    abstract Optional<BinaryNode> createTree(int size, Random rng);
  }

  private static final BinaryTreeTraverser<BinaryNode> BINARY_VIEWER =
      new BinaryTreeTraverser<BinaryNode>() {

    @Override
    public Optional<BinaryNode> leftChild(BinaryNode node) {
      return node.left;
    }

    @Override
    public Optional<BinaryNode> rightChild(BinaryNode node) {
      return node.right;
    }
  };

  private static final TreeTraverser<BinaryNode> VIEWER = new TreeTraverser<BinaryNode>() {
    @Override
    public Iterable<BinaryNode> children(BinaryNode root) {
      return BINARY_VIEWER.children(root);
    }
  };

  enum Traversal {
    PRE_ORDER {
      @Override
      <T> Iterable<T> view(T root, TreeTraverser<T> viewer) {
        return viewer.preOrderTraversal(root);
      }
    },
    POST_ORDER {
      @Override
      <T> Iterable<T> view(T root, TreeTraverser<T> viewer) {
        return viewer.postOrderTraversal(root);
      }
    },
    BREADTH_FIRST {
      @Override
      <T> Iterable<T> view(T root, TreeTraverser<T> viewer) {
        return viewer.breadthFirstTraversal(root);
      }
    };

    abstract <T> Iterable<T> view(T root, TreeTraverser<T> viewer);
  }

  private Iterable<BinaryNode> view;

  @Param
  Topology topology;

  @Param({"1", "100", "10000", "1000000"})
  int size;

  @Param
  Traversal traversal;

  @Param
  boolean useBinaryTraverser;

  @Param({"1234"})
  SpecialRandom rng;

  @BeforeExperiment
  void setUp() {
    this.view = traversal.view(
        topology.createTree(size, rng).get(),
        useBinaryTraverser ? BINARY_VIEWER : VIEWER);
  }

  @Benchmark int traversal(int reps) {
    int tmp = 0;

    for (int i = 0; i < reps; i++) {
      for (BinaryNode node : view) {
        tmp += node.x;
      }
    }
    return tmp;
  }
}
