// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <gflags/gflags.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctime>

#include "base/logging.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"

#include "glinterface.h"
#include "main.h"
#include "utils.h"

#include "all_tests.h"
#include "testbase.h"

using std::string;
using std::vector;

DEFINE_int32(duration, 0,
             "Run all tests again and again in a loop for at least this many seconds.");
DEFINE_string(tests, "",
              "Colon-separated list of tests to run; all tests if omitted.");
DEFINE_string(blacklist, "", "colon-separated list of tests to disable");
DEFINE_bool(hasty, false,
            "Run a smaller set of tests with less accurate results. "
            "Useful for running in BVT or debugging a failure.  Implies notemp");
DEFINE_bool(list, false, "List available tests");
DEFINE_bool(notemp, false, "Skip temperature checking");
DEFINE_bool(verbose, false, "Print extra debugging messages");

bool g_verbose;
GLint g_max_texture_size;
bool g_hasty;
bool g_notemp;

bool test_is_enabled(glbench::TestBase* test,
                     const vector<string>& enabled_tests) {
  if (enabled_tests.empty())
    return true;

  const char* test_name = test->Name();
  for (vector<string>::const_iterator i = enabled_tests.begin();
       i != enabled_tests.end(); ++i) {
    // This is not very precise, but will do until there's a need for something
    // more flexible.
    if (strstr(test_name, i->c_str()))
      return true;
  }

  return false;
}

bool test_is_disabled(glbench::TestBase* test,
                     const vector<string>& disabled_tests) {
  if (disabled_tests.empty())
    return false;

  const char* test_name = test->Name();
  for (vector<string>::const_iterator i = disabled_tests.begin();
       i != disabled_tests.end(); ++i) {
    // This is not very precise, but will do until there's a need for something
    // more flexible.
    if (strstr(test_name, i->c_str()))
      return true;
  }

  return false;
}

void printDateTime(void) {
  struct tm *ttime;
  time_t tm = time(0);
  char time_string[64];
  ttime = localtime(&tm);
  strftime(time_string, 63, "%c",ttime);
  printf("# DateTime: %s\n", time_string);
}

bool PassesSanityCheck(void) {
  GLint size[2];
  glGetIntegerv(GL_MAX_VIEWPORT_DIMS, size);
  printf("# MAX_VIEWPORT_DIMS=(%d, %d)\n", size[0], size[1]);
  if (size[0] < g_width || size[1] < g_height) {
    printf("# Error: MAX_VIEWPORT_DIMS=(%d, %d) are too small.\n",
           size[0], size[1]);
    return false;
  }
  glGetIntegerv(GL_MAX_TEXTURE_SIZE, size);
  printf("# GL_MAX_TEXTURE_SIZE=%d\n", size[0]);
  if (size[0] < g_width || size[0] < g_height) {
    printf("# Error: MAX_TEXTURE_SIZE=%d is too small.\n",
           size[0]);
    return false;
  }
  g_max_texture_size = size[0];

  return true;
}

int main(int argc, char *argv[]) {
  SetBasePathFromArgv0(argv[0], "src");
  google::ParseCommandLineFlags(&argc, &argv, false);

  g_verbose = FLAGS_verbose;

  g_main_gl_interface.reset(GLInterface::Create());
  if (!g_main_gl_interface->Init()) {
    printf("# Error: Failed to initialize %s.\n", argv[0]);
    return 1;
  }

  printf("# board_id: %s - %s\n",
         glGetString(GL_VENDOR), glGetString(GL_RENDERER));
  if (!PassesSanityCheck())
    return 1;
  g_main_gl_interface->Cleanup();

  if (argc == 1) {
    printf("# Usage: %s [-save [-outdir=<directory>]] to save images\n", argv[0]);
  } else {
    printf("# Running: ");
    for (int i = 0; i < argc; i++) printf("%s ", argv[i]);
    printf("\n");
  }
  printDateTime();

  g_hasty = FLAGS_hasty;
  g_notemp = FLAGS_notemp || g_hasty;

  if (!g_notemp)
    g_initial_temperature = GetMachineTemperature();

  vector<string> enabled_tests =
      base::SplitString(FLAGS_tests, ":", base::TRIM_WHITESPACE,
                        base::SPLIT_WANT_ALL);
  vector<string> disabled_tests =
      base::SplitString(FLAGS_blacklist, ":", base::TRIM_WHITESPACE,
                        base::SPLIT_WANT_ALL);

  glbench::TestBase* tests[] = {
    // Please add new tests at the end of this list as tests are known to bleed
    // state. Reordering them or inserting a new test may cause a change in the
    // output images and MD5 causing graphics_GLBench failures.
    // TODO(ihf): Fix this.
    glbench::GetSwapTest(),
    glbench::GetContextTest(),
    glbench::GetClearTest(),
    glbench::GetFillRateTest(),
    glbench::GetWindowManagerCompositingTest(false),
    glbench::GetWindowManagerCompositingTest(true),
    glbench::GetTriangleSetupTest(),
    glbench::GetYuvToRgbTest(),
    glbench::GetReadPixelTest(),
    glbench::GetAttributeFetchShaderTest(),
    glbench::GetVaryingsAndDdxyShaderTest(),
    glbench::GetTextureReuseTest(),
    glbench::GetTextureUpdateTest(),
    glbench::GetTextureUploadTest(),
    glbench::GetFboFillRateTest(),
  };

  if (FLAGS_list) {
    for (unsigned int i = 0; i < arraysize(tests); i++)
      printf("%s\n", tests[i]->Name());
    return 0;
  }

  uint64_t done = GetUTime() + 1000000ULL * FLAGS_duration;
  do {
    for (unsigned int i = 0; i < arraysize(tests); i++) {
      if (!test_is_enabled(tests[i], enabled_tests) ||
          test_is_disabled(tests[i], disabled_tests))
        continue;
      if (!g_main_gl_interface->Init()) {
        printf("Initialize failed\n");
        return 1;
      }
      glbench::ClearBuffers();
      tests[i]->Run();
      g_main_gl_interface->Cleanup();
    }
  } while (GetUTime() < done);

  for (unsigned int i = 0; i < arraysize(tests); i++) {
    delete tests[i];
    tests[i] = NULL;
  }

  printDateTime();
  // Signal to harness that we finished normally.
  printf("@TEST_END\n");

  return 0;
}