// Copyright 2013 The Chromium 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 "gin/modules/timer.h"

#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "gin/handle.h"
#include "gin/object_template_builder.h"
#include "gin/public/isolate_holder.h"
#include "gin/shell_runner.h"
#include "gin/test/v8_test.h"
#include "gin/try_catch.h"
#include "gin/wrappable.h"
#include "v8/include/v8.h"

namespace gin {

namespace {

class Result : public Wrappable<Result> {
 public:
  static WrapperInfo kWrapperInfo;
  static Handle<Result> Create(v8::Isolate* isolate) {
    return CreateHandle(isolate, new Result());
  }

  int count() const { return count_; }
  void set_count(int count) { count_ = count; }

  void Quit() {
    base::MessageLoop::current()->QuitNow();
  }

 private:
  Result() : count_(0) {
  }

  virtual ~Result() {
  }

  virtual ObjectTemplateBuilder GetObjectTemplateBuilder(
      v8::Isolate* isolate) OVERRIDE {
    return Wrappable<Result>::GetObjectTemplateBuilder(isolate)
        .SetProperty("count", &Result::count, &Result::set_count)
        .SetMethod("quit", &Result::Quit);
  }

  int count_;
};

WrapperInfo Result::kWrapperInfo = { gin::kEmbedderNativeGin };

struct TestHelper {
  TestHelper(v8::Isolate* isolate)
      : runner(new ShellRunner(&delegate, isolate)),
        scope(runner.get()),
        timer_module(TimerModule::Create(isolate)),
        result(Result::Create(isolate)) {
    EXPECT_FALSE(runner->global().IsEmpty());
    runner->global()->Set(StringToV8(isolate, "timer"),
                          timer_module->GetWrapper(isolate));
    runner->global()->Set(StringToV8(isolate, "result"),
                          result->GetWrapper(isolate));
  }

  void QuitSoon() {
    loop.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(),
                         base::TimeDelta::FromMilliseconds(0));
  }

  ShellRunnerDelegate delegate;
  scoped_ptr<ShellRunner> runner;
  Runner::Scope scope;
  Handle<TimerModule> timer_module;
  Handle<Result> result;
  base::MessageLoop loop;
};

}  // namespace

typedef V8Test TimerUnittest;

TEST_F(TimerUnittest, OneShot) {
  TestHelper helper(instance_->isolate());
  std::string source =
     "timer.createOneShot(0, function() {"
     "  result.count++;"
     "});";

  helper.runner->Run(source, "script");
  EXPECT_EQ(0, helper.result->count());

  helper.QuitSoon();
  helper.loop.Run();
  EXPECT_EQ(1, helper.result->count());
}

TEST_F(TimerUnittest, OneShotCancel) {
  TestHelper helper(instance_->isolate());
  std::string source =
     "var t = timer.createOneShot(0, function() {"
     "  result.count++;"
     "});"
     "t.cancel()";

  helper.runner->Run(source, "script");
  EXPECT_EQ(0, helper.result->count());

  helper.QuitSoon();
  helper.loop.Run();
  EXPECT_EQ(0, helper.result->count());
}

TEST_F(TimerUnittest, Repeating) {
  TestHelper helper(instance_->isolate());

  // TODO(aa): Cannot do: if (++result.count == 3) because of v8 bug. Create
  // test case and report.
  std::string source =
     "timer.createRepeating(0, function() {"
     "  result.count++;"
     "  if (result.count == 3) {"
     "    result.quit();"
     "  }"
     "});";

  helper.runner->Run(source, "script");
  EXPECT_EQ(0, helper.result->count());

  helper.loop.Run();
  EXPECT_EQ(3, helper.result->count());
}

TEST_F(TimerUnittest, TimerCallbackToDestroyedRunner) {
  TestHelper helper(instance_->isolate());
  std::string source =
     "timer.createOneShot(0, function() {"
     "  result.count++;"
     "});";

  helper.runner->Run(source, "script");
  EXPECT_EQ(0, helper.result->count());

  // Destroy runner, which should destroy the timer object we created.
  helper.QuitSoon();
  helper.runner.reset(NULL);
  helper.loop.Run();

  // Timer should not have run because it was deleted.
  EXPECT_EQ(0, helper.result->count());
}

}  // namespace gin
