// 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 "chrome/common/metrics/metrics_log_manager.h"

#include <algorithm>

#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "chrome/common/metrics/metrics_log_base.h"

MetricsLogManager::MetricsLogManager()
    : current_log_type_(NO_LOG),
      paused_log_type_(NO_LOG),
      staged_log_type_(NO_LOG),
      max_ongoing_log_store_size_(0),
      last_provisional_store_index_(-1),
      last_provisional_store_type_(INITIAL_LOG) {}

MetricsLogManager::~MetricsLogManager() {}

void MetricsLogManager::BeginLoggingWithLog(MetricsLogBase* log,
                                            LogType log_type) {
  DCHECK(log_type != NO_LOG);
  DCHECK(!current_log_.get());
  current_log_.reset(log);
  current_log_type_ = log_type;
}

void MetricsLogManager::FinishCurrentLog() {
  DCHECK(current_log_.get());
  DCHECK(current_log_type_ != NO_LOG);
  current_log_->CloseLog();
  std::string compressed_log;
  CompressCurrentLog(&compressed_log);
  if (!compressed_log.empty())
    StoreLog(&compressed_log, current_log_type_, NORMAL_STORE);
  current_log_.reset();
  current_log_type_ = NO_LOG;
}

void MetricsLogManager::StageNextLogForUpload() {
  // Prioritize initial logs for uploading.
  std::vector<std::string>* source_list =
      unsent_initial_logs_.empty() ? &unsent_ongoing_logs_
                                   : &unsent_initial_logs_;
  LogType source_type = (source_list == &unsent_ongoing_logs_) ? ONGOING_LOG
                                                               : INITIAL_LOG;
  // CHECK, rather than DCHECK, because swap()ing with an empty list causes
  // hard-to-identify crashes much later.
  CHECK(!source_list->empty());
  DCHECK(staged_log_text_.empty());
  DCHECK(staged_log_type_ == NO_LOG);
  staged_log_text_.swap(source_list->back());
  staged_log_type_ = source_type;
  source_list->pop_back();

  // If the staged log was the last provisional store, clear that.
  if (last_provisional_store_index_ != -1) {
    if (source_type == last_provisional_store_type_ &&
        static_cast<unsigned int>(last_provisional_store_index_) ==
            source_list->size()) {
      last_provisional_store_index_ = -1;
    }
  }
}

bool MetricsLogManager::has_staged_log() const {
  return !staged_log_text().empty();
}

void MetricsLogManager::DiscardStagedLog() {
  staged_log_text_.clear();
  staged_log_type_ = NO_LOG;
}

void MetricsLogManager::DiscardCurrentLog() {
  current_log_->CloseLog();
  current_log_.reset();
  current_log_type_ = NO_LOG;
}

void MetricsLogManager::PauseCurrentLog() {
  DCHECK(!paused_log_.get());
  DCHECK(paused_log_type_ == NO_LOG);
  paused_log_.reset(current_log_.release());
  paused_log_type_ = current_log_type_;
  current_log_type_ = NO_LOG;
}

void MetricsLogManager::ResumePausedLog() {
  DCHECK(!current_log_.get());
  DCHECK(current_log_type_ == NO_LOG);
  current_log_.reset(paused_log_.release());
  current_log_type_ = paused_log_type_;
  paused_log_type_ = NO_LOG;
}

void MetricsLogManager::StoreStagedLogAsUnsent(StoreType store_type) {
  DCHECK(has_staged_log());

  // If compressing the log failed, there's nothing to store.
  if (staged_log_text_.empty())
    return;

  StoreLog(&staged_log_text_, staged_log_type_, store_type);
  DiscardStagedLog();
}

void MetricsLogManager::StoreLog(std::string* log_text,
                                 LogType log_type,
                                 StoreType store_type) {
  DCHECK(log_type != NO_LOG);
  std::vector<std::string>* destination_list =
      (log_type == INITIAL_LOG) ? &unsent_initial_logs_
                                : &unsent_ongoing_logs_;
  destination_list->push_back(std::string());
  destination_list->back().swap(*log_text);

  if (store_type == PROVISIONAL_STORE) {
    last_provisional_store_index_ = destination_list->size() - 1;
    last_provisional_store_type_ = log_type;
  }
}

void MetricsLogManager::DiscardLastProvisionalStore() {
  if (last_provisional_store_index_ == -1)
    return;
  std::vector<std::string>* source_list =
      (last_provisional_store_type_ == ONGOING_LOG) ? &unsent_ongoing_logs_
                                                    : &unsent_initial_logs_;
  DCHECK_LT(static_cast<unsigned int>(last_provisional_store_index_),
            source_list->size());
  source_list->erase(source_list->begin() + last_provisional_store_index_);
  last_provisional_store_index_ = -1;
}

void MetricsLogManager::PersistUnsentLogs() {
  DCHECK(log_serializer_.get());
  if (!log_serializer_.get())
    return;
  // Remove any ongoing logs that are over the serialization size limit.
  if (max_ongoing_log_store_size_) {
    for (std::vector<std::string>::iterator it = unsent_ongoing_logs_.begin();
         it != unsent_ongoing_logs_.end();) {
      size_t log_size = it->length();
      if (log_size > max_ongoing_log_store_size_) {
        UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
                             static_cast<int>(log_size));
        it = unsent_ongoing_logs_.erase(it);
      } else {
        ++it;
      }
    }
  }
  log_serializer_->SerializeLogs(unsent_initial_logs_, INITIAL_LOG);
  log_serializer_->SerializeLogs(unsent_ongoing_logs_, ONGOING_LOG);
}

void MetricsLogManager::LoadPersistedUnsentLogs() {
  DCHECK(log_serializer_.get());
  if (!log_serializer_.get())
    return;
  log_serializer_->DeserializeLogs(INITIAL_LOG, &unsent_initial_logs_);
  log_serializer_->DeserializeLogs(ONGOING_LOG, &unsent_ongoing_logs_);
}

void MetricsLogManager::CompressCurrentLog(std::string* compressed_log) {
  current_log_->GetEncodedLogProto(compressed_log);
}