/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdarg.h>

#include "gc/FindSCCs.h"
#include "jsapi-tests/tests.h"

static const unsigned MaxVertices = 10;

using js::gc::ComponentFinder;
using js::gc::GraphNodeBase;

struct TestNode : public GraphNodeBase<TestNode> {
  unsigned index;
};

using TestComponentFinder = ComponentFinder<TestNode>;

MOZ_RUNINIT static TestNode Vertex[MaxVertices];

BEGIN_TEST(testFindSCCs) {
  // no vertices

  setup(0);
  findSCCs();
  CHECK(end());

  // no edges

  setup(1);
  findSCCs();
  CHECK(group(0, -1));
  CHECK(end());

  setup(3);
  findSCCs();
  CHECK(group(2, -1));
  CHECK(group(1, -1));
  CHECK(group(0, -1));
  CHECK(end());

  // linear

  setup(3);
  CHECK(edge(0, 1));
  CHECK(edge(1, 2));
  findSCCs();
  CHECK(group(0, -1));
  CHECK(group(1, -1));
  CHECK(group(2, -1));
  CHECK(end());

  // tree

  setup(3);
  CHECK(edge(0, 1));
  CHECK(edge(0, 2));
  findSCCs();
  CHECK(group(0, -1));
  if (resultsList && resultsList->index == 1) {
    CHECK(group(1, -1));
    CHECK(group(2, -1));
  } else {
    CHECK(group(2, -1));
    CHECK(group(1, -1));
  }
  CHECK(end());

  // cycles

  setup(3);
  CHECK(edge(0, 1));
  CHECK(edge(1, 2));
  CHECK(edge(2, 0));
  findSCCs();
  CHECK(group(0, 1, 2, -1));
  CHECK(end());

  setup(4);
  CHECK(edge(0, 1));
  CHECK(edge(1, 2));
  CHECK(edge(2, 1));
  CHECK(edge(2, 3));
  findSCCs();
  CHECK(group(0, -1));
  CHECK(group(1, 2, -1));
  CHECK(group(3, -1));
  CHECK(end());

  // remaining

  setup(2);
  CHECK(edge(0, 1));
  findSCCs();
  CHECK(remaining(0, 1, -1));
  CHECK(end());

  setup(2);
  CHECK(edge(0, 1));
  findSCCs();
  CHECK(group(0, -1));
  CHECK(remaining(1, -1));
  CHECK(end());

  setup(2);
  CHECK(edge(0, 1));
  findSCCs();
  CHECK(group(0, -1));
  CHECK(group(1, -1));
  CHECK(remaining(-1));
  CHECK(end());

  return true;
}

unsigned vertex_count;
TestComponentFinder* finder;
TestNode* resultsList;

void setup(unsigned count) {
  vertex_count = count;
  for (unsigned i = 0; i < MaxVertices; ++i) {
    TestNode& v = Vertex[i];
    v.gcGraphEdges.clear();
    v.gcNextGraphNode = nullptr;
    v.index = i;
  }
}

bool edge(unsigned src_index, unsigned dest_index) {
  return Vertex[src_index].gcGraphEdges.put(&Vertex[dest_index]);
}

void findSCCs() {
  finder = new TestComponentFinder(cx);
  for (unsigned i = 0; i < vertex_count; ++i) {
    finder->addNode(&Vertex[i]);
  }
  resultsList = finder->getResultsList();
}

bool group(int vertex, ...) {
  TestNode* v = resultsList;

  va_list ap;
  va_start(ap, vertex);
  while (vertex != -1) {
    CHECK(v != nullptr);
    CHECK(v->index == unsigned(vertex));
    v = v->nextNodeInGroup();
    vertex = va_arg(ap, int);
  }
  va_end(ap);

  CHECK(v == nullptr);
  resultsList = resultsList->nextGroup();
  return true;
}

bool remaining(int vertex, ...) {
  TestNode* v = resultsList;

  va_list ap;
  va_start(ap, vertex);
  while (vertex != -1) {
    CHECK(v != nullptr);
    CHECK(v->index == unsigned(vertex));
    v = (TestNode*)v->gcNextGraphNode;
    vertex = va_arg(ap, int);
  }
  va_end(ap);

  CHECK(v == nullptr);
  resultsList = nullptr;
  return true;
}

bool end() {
  CHECK(resultsList == nullptr);

  delete finder;
  finder = nullptr;
  return true;
}
END_TEST(testFindSCCs)

BEGIN_TEST(testFindSCCsStackLimit) {
  /*
   * Test what happens if recusion causes the stack to become full while
   * traversing the graph.
   *
   * The test case is a large number of vertices, almost all of which are
   * arranged in a linear chain.  The last few are left unlinked to exercise
   * adding vertices after the stack full condition has already been detected.
   *
   * Such an arrangement with no cycles would normally result in one group for
   * each vertex, but since the stack is exhasted in processing a single group
   * is returned containing all the vertices.
   */
  const unsigned max = 1000000;
  const unsigned initial = 10;

  TestNode* vertices = new TestNode[max]();
  for (unsigned i = initial; i < (max - 10); ++i) {
    CHECK(vertices[i].gcGraphEdges.put(&vertices[i + 1]));
  }

  TestComponentFinder finder(cx);
  for (unsigned i = 0; i < max; ++i) {
    finder.addNode(&vertices[i]);
  }

  TestNode* r = finder.getResultsList();
  CHECK(r);
  TestNode* v = r;

  unsigned count = 0;
  while (v) {
    ++count;
    v = v->nextNodeInGroup();
  }
  CHECK(count == max - initial);

  count = 0;
  v = r->nextGroup();
  while (v) {
    ++count;
    CHECK(!v->nextNodeInGroup());
    v = v->nextGroup();
  }

  delete[] vertices;
  return true;
}
END_TEST(testFindSCCsStackLimit)
