// Copyright (c) 2011 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 "base/time.h"
#include "chrome/browser/sync/engine/mock_model_safe_workers.h"
#include "chrome/browser/sync/engine/syncer_thread.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
#include "chrome/browser/sync/sessions/test_util.h"
#include "chrome/test/sync/engine/mock_connection_manager.h"
#include "chrome/test/sync/engine/test_directory_setter_upper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gmock/include/gmock/gmock.h"

using base::TimeDelta;
using base::TimeTicks;

namespace browser_sync {
using sessions::SyncSessionContext;
using browser_sync::Syncer;

class SyncerThread2WhiteboxTest : public testing::Test {
 public:
  virtual void SetUp() {
    syncdb_.SetUp();
    Syncer* syncer = new Syncer();
    registrar_.reset(MockModelSafeWorkerRegistrar::PassiveBookmarks());
    context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(),
        registrar_.get(), std::vector<SyncEngineEventListener*>());
    context_->set_notifications_enabled(true);
    context_->set_account_name("Test");
    syncer_thread_.reset(new SyncerThread(context_, syncer));
  }

  virtual void TearDown() {
    syncdb_.TearDown();
  }

  void SetMode(SyncerThread::Mode mode) {
    syncer_thread_->mode_ = mode;
  }

  void SetLastSyncedTime(base::TimeTicks ticks) {
    syncer_thread_->last_sync_session_end_time_ = ticks;
  }

  void SetServerConnection(bool connected) {
    syncer_thread_->server_connection_ok_ = connected;
  }

  void ResetWaitInterval() {
    syncer_thread_->wait_interval_.reset();
  }

  void SetWaitIntervalToThrottled() {
    syncer_thread_->wait_interval_.reset(new SyncerThread::WaitInterval(
        SyncerThread::WaitInterval::THROTTLED, TimeDelta::FromSeconds(1)));
  }

  void SetWaitIntervalToExponentialBackoff() {
    syncer_thread_->wait_interval_.reset(
       new SyncerThread::WaitInterval(
       SyncerThread::WaitInterval::EXPONENTIAL_BACKOFF,
       TimeDelta::FromSeconds(1)));
  }

  SyncerThread::JobProcessDecision DecideOnJob(
      const SyncerThread::SyncSessionJob& job) {
    return syncer_thread_->DecideOnJob(job);
  }

  void InitializeSyncerOnNormalMode() {
    SetMode(SyncerThread::NORMAL_MODE);
    ResetWaitInterval();
    SetServerConnection(true);
    SetLastSyncedTime(base::TimeTicks::Now());
  }

  SyncerThread::JobProcessDecision CreateAndDecideJob(
      SyncerThread::SyncSessionJob::SyncSessionJobPurpose purpose) {
    struct SyncerThread::SyncSessionJob job;
    job.purpose = purpose;
    job.scheduled_start = TimeTicks::Now();
    return DecideOnJob(job);
  }

 protected:
  scoped_ptr<SyncerThread> syncer_thread_;

 private:
  scoped_ptr<MockConnectionManager> connection_;
  SyncSessionContext* context_;
  //MockDelayProvider* delay_;
  scoped_ptr<MockModelSafeWorkerRegistrar> registrar_;
  MockDirectorySetterUpper syncdb_;
};

TEST_F(SyncerThread2WhiteboxTest, SaveNudge) {
  InitializeSyncerOnNormalMode();

  // Now set the mode to configure.
  SetMode(SyncerThread::CONFIGURATION_MODE);

  SyncerThread::JobProcessDecision decision =
      CreateAndDecideJob(SyncerThread::SyncSessionJob::NUDGE);

  EXPECT_EQ(decision, SyncerThread::SAVE);
}

TEST_F(SyncerThread2WhiteboxTest, ContinueNudge) {
  InitializeSyncerOnNormalMode();

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::NUDGE);

  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

TEST_F(SyncerThread2WhiteboxTest, DropPoll) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::CONFIGURATION_MODE);

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::POLL);

  EXPECT_EQ(decision, SyncerThread::DROP);
}

TEST_F(SyncerThread2WhiteboxTest, ContinuePoll) {
  InitializeSyncerOnNormalMode();

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::POLL);

  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

TEST_F(SyncerThread2WhiteboxTest, ContinueConfiguration) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::CONFIGURATION_MODE);

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::CONFIGURATION);

  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

TEST_F(SyncerThread2WhiteboxTest, SaveConfigurationWhileThrottled) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::CONFIGURATION_MODE);

  SetWaitIntervalToThrottled();

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::CONFIGURATION);

  EXPECT_EQ(decision, SyncerThread::SAVE);
}

TEST_F(SyncerThread2WhiteboxTest, SaveNudgeWhileThrottled) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::CONFIGURATION_MODE);

  SetWaitIntervalToThrottled();

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::NUDGE);

  EXPECT_EQ(decision, SyncerThread::SAVE);

}

TEST_F(SyncerThread2WhiteboxTest, ContinueClearUserDataUnderAllCircumstances) {
  InitializeSyncerOnNormalMode();

  SetMode(SyncerThread::CONFIGURATION_MODE);
  SetWaitIntervalToThrottled();
  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::CLEAR_USER_DATA);
  EXPECT_EQ(decision, SyncerThread::CONTINUE);

  SetMode(SyncerThread::NORMAL_MODE);
  SetWaitIntervalToExponentialBackoff();
  decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::CLEAR_USER_DATA);
  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

TEST_F(SyncerThread2WhiteboxTest, ContinueNudgeWhileExponentialBackOff) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::NORMAL_MODE);
  SetWaitIntervalToExponentialBackoff();

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::NUDGE);

  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

TEST_F(SyncerThread2WhiteboxTest, DropNudgeWhileExponentialBackOff) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::NORMAL_MODE);
  SetWaitIntervalToExponentialBackoff();

  syncer_thread_->wait_interval_->had_nudge = true;

  SyncerThread::JobProcessDecision decision = CreateAndDecideJob(
      SyncerThread::SyncSessionJob::NUDGE);

  EXPECT_EQ(decision, SyncerThread::DROP);
}

TEST_F(SyncerThread2WhiteboxTest, ContinueCanaryJobConfig) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncerThread::CONFIGURATION_MODE);
  SetWaitIntervalToExponentialBackoff();

  struct SyncerThread::SyncSessionJob job;
  job.purpose = SyncerThread::SyncSessionJob::CONFIGURATION;
  job.scheduled_start = TimeTicks::Now();
  job.is_canary_job = true;
  SyncerThread::JobProcessDecision decision = DecideOnJob(job);

  EXPECT_EQ(decision, SyncerThread::CONTINUE);
}

}  // namespace browser_sync

// SyncerThread won't outlive the test!
DISABLE_RUNNABLE_METHOD_REFCOUNT(
    browser_sync::SyncerThread2WhiteboxTest);