// Copyright (c) 2012 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/message_loop.h"
#include "base/time.h"
#include "sync/engine/backoff_delay_provider.h"
#include "sync/engine/sync_scheduler_impl.h"
#include "sync/engine/throttled_data_type_tracker.h"
#include "sync/internal_api/public/engine/polling_constants.h"
#include "sync/sessions/sync_session_context.h"
#include "sync/sessions/test_util.h"
#include "sync/test/engine/fake_model_worker.h"
#include "sync/test/engine/mock_connection_manager.h"
#include "sync/test/engine/test_directory_setter_upper.h"
#include "sync/test/fake_extensions_activity_monitor.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

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

namespace syncer {
using sessions::SyncSession;
using sessions::SyncSessionContext;
using sessions::SyncSourceInfo;
using sync_pb::GetUpdatesCallerInfo;

class SyncSchedulerWhiteboxTest : public testing::Test {
 public:
  virtual void SetUp() {
    dir_maker_.SetUp();
    Syncer* syncer = new Syncer();

    ModelSafeRoutingInfo routes;
    routes[BOOKMARKS] = GROUP_UI;
    routes[NIGORI] = GROUP_PASSIVE;

    workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_UI)));
    workers_.push_back(make_scoped_refptr(new FakeModelWorker(GROUP_PASSIVE)));

    std::vector<ModelSafeWorker*> workers;
    for (std::vector<scoped_refptr<FakeModelWorker> >::iterator it =
         workers_.begin(); it != workers_.end(); ++it) {
      workers.push_back(it->get());
    }

    connection_.reset(new MockConnectionManager(NULL));
    throttled_data_type_tracker_.reset(new ThrottledDataTypeTracker(NULL));
    context_.reset(
        new SyncSessionContext(
            connection_.get(), dir_maker_.directory(),
            workers, &extensions_activity_monitor_,
            throttled_data_type_tracker_.get(),
            std::vector<SyncEngineEventListener*>(), NULL, NULL,
            true  /* enable keystore encryption */));
    context_->set_notifications_enabled(true);
    context_->set_account_name("Test");
    scheduler_.reset(
        new SyncSchedulerImpl("TestSyncSchedulerWhitebox",
            BackoffDelayProvider::FromDefaults(),
            context(),
            syncer));
  }

  virtual void TearDown() {
    scheduler_.reset();
  }

  void SetMode(SyncScheduler::Mode mode) {
    scheduler_->mode_ = mode;
  }

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

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

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

  void SetWaitIntervalHadNudge(bool had_nudge) {
    scheduler_->wait_interval_->had_nudge = had_nudge;
  }

  SyncSchedulerImpl::JobProcessDecision DecideOnJob(
      const SyncSchedulerImpl::SyncSessionJob& job) {
    return scheduler_->DecideOnJob(job);
  }

  void InitializeSyncerOnNormalMode() {
    SetMode(SyncScheduler::NORMAL_MODE);
    ResetWaitInterval();
  }

  SyncSchedulerImpl::JobProcessDecision CreateAndDecideJob(
      SyncSchedulerImpl::SyncSessionJob::SyncSessionJobPurpose purpose) {
    SyncSession* s = scheduler_->CreateSyncSession(SyncSourceInfo());
    SyncSchedulerImpl::SyncSessionJob job(purpose, TimeTicks::Now(),
         make_linked_ptr(s),
         false,
         ConfigurationParams(),
         FROM_HERE);
    return DecideOnJob(job);
  }

  SyncSessionContext* context() { return context_.get(); }

 private:
  MessageLoop message_loop_;
  scoped_ptr<MockConnectionManager> connection_;
  scoped_ptr<SyncSessionContext> context_;
  std::vector<scoped_refptr<FakeModelWorker> > workers_;
  FakeExtensionsActivityMonitor extensions_activity_monitor_;
  scoped_ptr<ThrottledDataTypeTracker> throttled_data_type_tracker_;
  TestDirectorySetterUpper dir_maker_;

 protected:
  // Declared here to ensure it is destructed before the objects it references.
  scoped_ptr<SyncSchedulerImpl> scheduler_;
};

TEST_F(SyncSchedulerWhiteboxTest, SaveNudge) {
  InitializeSyncerOnNormalMode();

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

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

  EXPECT_EQ(decision, SyncSchedulerImpl::SAVE);
}

TEST_F(SyncSchedulerWhiteboxTest, SaveNudgeWhileTypeThrottled) {
  InitializeSyncerOnNormalMode();

  ModelTypeSet types;
  types.Put(BOOKMARKS);

  // Mark bookmarks as throttled.
  context()->throttled_data_type_tracker()->SetUnthrottleTime(
      types, base::TimeTicks::Now() + base::TimeDelta::FromHours(2));

  ModelTypeStateMap type_state_map;
  type_state_map.insert(std::make_pair(BOOKMARKS, InvalidationState()));

  SyncSourceInfo info(GetUpdatesCallerInfo::LOCAL, type_state_map);
  SyncSession* s = scheduler_->CreateSyncSession(info);

  // Now schedule a nudge with just bookmarks and the change is local.
  SyncSchedulerImpl::SyncSessionJob job(
        SyncSchedulerImpl::SyncSessionJob::NUDGE,
        TimeTicks::Now(),
        make_linked_ptr(s),
        false,
        ConfigurationParams(),
        FROM_HERE);

  SyncSchedulerImpl::JobProcessDecision decision = DecideOnJob(job);
  EXPECT_EQ(decision, SyncSchedulerImpl::SAVE);
}

TEST_F(SyncSchedulerWhiteboxTest, ContinueNudge) {
  InitializeSyncerOnNormalMode();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::CONTINUE);
}

TEST_F(SyncSchedulerWhiteboxTest, DropPoll) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::CONFIGURATION_MODE);

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

  EXPECT_EQ(decision, SyncSchedulerImpl::DROP);
}

TEST_F(SyncSchedulerWhiteboxTest, ContinuePoll) {
  InitializeSyncerOnNormalMode();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::CONTINUE);
}

TEST_F(SyncSchedulerWhiteboxTest, ContinueConfiguration) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::CONFIGURATION_MODE);

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

  EXPECT_EQ(decision, SyncSchedulerImpl::CONTINUE);
}

TEST_F(SyncSchedulerWhiteboxTest, SaveConfigurationWhileThrottled) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::CONFIGURATION_MODE);

  SetWaitIntervalToThrottled();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::SAVE);
}

TEST_F(SyncSchedulerWhiteboxTest, SaveNudgeWhileThrottled) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::CONFIGURATION_MODE);

  SetWaitIntervalToThrottled();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::SAVE);
}

TEST_F(SyncSchedulerWhiteboxTest, ContinueNudgeWhileExponentialBackOff) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::NORMAL_MODE);
  SetWaitIntervalToExponentialBackoff();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::CONTINUE);
}

TEST_F(SyncSchedulerWhiteboxTest, DropNudgeWhileExponentialBackOff) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::NORMAL_MODE);
  SetWaitIntervalToExponentialBackoff();
  SetWaitIntervalHadNudge(true);

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

  EXPECT_EQ(decision, SyncSchedulerImpl::DROP);
}

TEST_F(SyncSchedulerWhiteboxTest, ContinueCanaryJobConfig) {
  InitializeSyncerOnNormalMode();
  SetMode(SyncScheduler::CONFIGURATION_MODE);
  SetWaitIntervalToExponentialBackoff();

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

  EXPECT_EQ(decision, SyncSchedulerImpl::CONTINUE);
}

}  // namespace syncer