// 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 <string>
#include <utility>
#include <vector>

#include "chrome/common/metrics/metrics_log_base.h"
#include "chrome/common/metrics/metrics_log_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

typedef MetricsLogManager::SerializedLog SerializedLog;

namespace {

class MetricsLogManagerTest : public testing::Test {
};

// Dummy serializer that just stores logs in memory.
class DummyLogSerializer : public MetricsLogManager::LogSerializer {
 public:
  virtual void SerializeLogs(const std::vector<SerializedLog>& logs,
                             MetricsLogManager::LogType log_type) {
    persisted_logs_[log_type] = logs;
  }

  virtual void DeserializeLogs(MetricsLogManager::LogType log_type,
                               std::vector<SerializedLog>* logs) {
    ASSERT_NE(static_cast<void*>(NULL), logs);
    *logs = persisted_logs_[log_type];
  }

  // Returns the number of logs of the given type.
  size_t TypeCount(MetricsLogManager::LogType log_type) {
    return persisted_logs_[log_type].size();
  }

  // In-memory "persitent storage".
  std::vector<SerializedLog> persisted_logs_[2];
};

}  // namespace

TEST(MetricsLogManagerTest, StandardFlow) {
  MetricsLogManager log_manager;

  // Make sure a new manager has a clean slate.
  EXPECT_EQ(NULL, log_manager.current_log());
  EXPECT_FALSE(log_manager.has_staged_log());
  EXPECT_FALSE(log_manager.has_unsent_logs());

  // Check that the normal flow works.
  MetricsLogBase* initial_log = new MetricsLogBase("id", 0, "version");
  log_manager.BeginLoggingWithLog(initial_log, MetricsLogManager::INITIAL_LOG);
  EXPECT_EQ(initial_log, log_manager.current_log());
  EXPECT_FALSE(log_manager.has_staged_log());

  log_manager.FinishCurrentLog();
  EXPECT_EQ(NULL, log_manager.current_log());
  EXPECT_TRUE(log_manager.has_unsent_logs());
  EXPECT_FALSE(log_manager.has_staged_log());

  MetricsLogBase* second_log = new MetricsLogBase("id", 0, "version");
  log_manager.BeginLoggingWithLog(second_log, MetricsLogManager::ONGOING_LOG);
  EXPECT_EQ(second_log, log_manager.current_log());

  log_manager.StageNextLogForUpload();
  EXPECT_TRUE(log_manager.has_staged_log());
  EXPECT_FALSE(log_manager.staged_log_text().empty());

  log_manager.DiscardStagedLog();
  EXPECT_EQ(second_log, log_manager.current_log());
  EXPECT_FALSE(log_manager.has_staged_log());
  EXPECT_FALSE(log_manager.has_unsent_logs());
  EXPECT_TRUE(log_manager.staged_log_text().empty());

  EXPECT_FALSE(log_manager.has_unsent_logs());
}

TEST(MetricsLogManagerTest, AbandonedLog) {
  MetricsLogManager log_manager;

  MetricsLogBase* dummy_log = new MetricsLogBase("id", 0, "version");
  log_manager.BeginLoggingWithLog(dummy_log, MetricsLogManager::INITIAL_LOG);
  EXPECT_EQ(dummy_log, log_manager.current_log());

  log_manager.DiscardCurrentLog();
  EXPECT_EQ(NULL, log_manager.current_log());
  EXPECT_FALSE(log_manager.has_staged_log());
}

TEST(MetricsLogManagerTest, InterjectedLog) {
  MetricsLogManager log_manager;

  MetricsLogBase* ongoing_log = new MetricsLogBase("id", 0, "version");
  MetricsLogBase* temp_log = new MetricsLogBase("id", 0, "version");

  log_manager.BeginLoggingWithLog(ongoing_log, MetricsLogManager::ONGOING_LOG);
  EXPECT_EQ(ongoing_log, log_manager.current_log());

  log_manager.PauseCurrentLog();
  EXPECT_EQ(NULL, log_manager.current_log());

  log_manager.BeginLoggingWithLog(temp_log, MetricsLogManager::INITIAL_LOG);
  EXPECT_EQ(temp_log, log_manager.current_log());
  log_manager.FinishCurrentLog();
  EXPECT_EQ(NULL, log_manager.current_log());

  log_manager.ResumePausedLog();
  EXPECT_EQ(ongoing_log, log_manager.current_log());

  EXPECT_FALSE(log_manager.has_staged_log());
  log_manager.StageNextLogForUpload();
  log_manager.DiscardStagedLog();
  EXPECT_FALSE(log_manager.has_unsent_logs());
}

TEST(MetricsLogManagerTest, StoreAndLoad) {
  std::vector<SerializedLog> initial_logs;
  std::vector<SerializedLog> ongoing_logs;

  // Set up some in-progress logging in a scoped log manager simulating the
  // leadup to quitting, then persist as would be done on quit.
  {
    MetricsLogManager log_manager;
    DummyLogSerializer* serializer = new DummyLogSerializer;
    log_manager.set_log_serializer(serializer);
    // Simulate a log having already been unsent from a previous session.
    SerializedLog log = {"xml", "proto"};
    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG].push_back(log);
    EXPECT_FALSE(log_manager.has_unsent_logs());
    log_manager.LoadPersistedUnsentLogs();
    EXPECT_TRUE(log_manager.has_unsent_logs());

    MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
    MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
    log_manager.BeginLoggingWithLog(log1, MetricsLogManager::INITIAL_LOG);
    log_manager.FinishCurrentLog();
    log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
    log_manager.StageNextLogForUpload();
    log_manager.StoreStagedLogAsUnsent();
    log_manager.FinishCurrentLog();

    // Nothing should be written out until PersistUnsentLogs is called.
    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
    log_manager.PersistUnsentLogs();
    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));

    // Save the logs to transfer over to a new serializer (since log_manager
    // owns |serializer|, so it's about to go away.
    initial_logs = serializer->persisted_logs_[MetricsLogManager::INITIAL_LOG];
    ongoing_logs = serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG];
  }

  // Now simulate the relaunch, ensure that the log manager restores
  // everything correctly, and verify that once the are handled they are not
  // re-persisted.
  {
    MetricsLogManager log_manager;

    DummyLogSerializer* serializer = new DummyLogSerializer;
    serializer->persisted_logs_[MetricsLogManager::INITIAL_LOG] = initial_logs;
    serializer->persisted_logs_[MetricsLogManager::ONGOING_LOG] = ongoing_logs;

    log_manager.set_log_serializer(serializer);
    log_manager.LoadPersistedUnsentLogs();
    EXPECT_TRUE(log_manager.has_unsent_logs());

    log_manager.StageNextLogForUpload();
    log_manager.DiscardStagedLog();
    // The initial log should be sent first; update the persisted storage to
    // verify.
    log_manager.PersistUnsentLogs();
    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));

    // Handle the first ongoing log.
    log_manager.StageNextLogForUpload();
    log_manager.DiscardStagedLog();
    EXPECT_TRUE(log_manager.has_unsent_logs());

    // Handle the last log.
    log_manager.StageNextLogForUpload();
    log_manager.DiscardStagedLog();
    EXPECT_FALSE(log_manager.has_unsent_logs());

    // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been
    // called again.
    EXPECT_EQ(2U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
    // Persist, and make sure nothing is left.
    log_manager.PersistUnsentLogs();
    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
  }
}

TEST(MetricsLogManagerTest, StoreStagedLogTypes) {
  // Ensure that types are preserved when storing staged logs.
  {
    MetricsLogManager log_manager;
    DummyLogSerializer* serializer = new DummyLogSerializer;
    log_manager.set_log_serializer(serializer);

    MetricsLogBase* log = new MetricsLogBase("id", 0, "version");
    log_manager.BeginLoggingWithLog(log, MetricsLogManager::ONGOING_LOG);
    log_manager.FinishCurrentLog();
    log_manager.StageNextLogForUpload();
    log_manager.StoreStagedLogAsUnsent();
    log_manager.PersistUnsentLogs();

    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
  }

  {
    MetricsLogManager log_manager;
    DummyLogSerializer* serializer = new DummyLogSerializer;
    log_manager.set_log_serializer(serializer);

    MetricsLogBase* log = new MetricsLogBase("id", 0, "version");
    log_manager.BeginLoggingWithLog(log, MetricsLogManager::INITIAL_LOG);
    log_manager.FinishCurrentLog();
    log_manager.StageNextLogForUpload();
    log_manager.StoreStagedLogAsUnsent();
    log_manager.PersistUnsentLogs();

    EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
    EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
  }
}

TEST(MetricsLogManagerTest, LargeLogDiscarding) {
  MetricsLogManager log_manager;
  DummyLogSerializer* serializer = new DummyLogSerializer;
  log_manager.set_log_serializer(serializer);
  // Set the size threshold very low, to verify that it's honored.
  log_manager.set_max_ongoing_log_store_size(1);

  MetricsLogBase* log1 = new MetricsLogBase("id", 0, "version");
  MetricsLogBase* log2 = new MetricsLogBase("id", 0, "version");
  log_manager.BeginLoggingWithLog(log1, MetricsLogManager::INITIAL_LOG);
  log_manager.FinishCurrentLog();
  log_manager.BeginLoggingWithLog(log2, MetricsLogManager::ONGOING_LOG);
  log_manager.FinishCurrentLog();

  // Only the ongoing log should be written out, due to the threshold.
  log_manager.PersistUnsentLogs();
  EXPECT_EQ(1U, serializer->TypeCount(MetricsLogManager::INITIAL_LOG));
  EXPECT_EQ(0U, serializer->TypeCount(MetricsLogManager::ONGOING_LOG));
}