// 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/browser/chromeos/gdata/gdata_operation_registry.h"

#include "base/string_number_conversions.h"
#include "content/public/browser/browser_thread.h"

using content::BrowserThread;

namespace gdata {

// static
std::string GDataOperationRegistry::OperationTypeToString(OperationType type) {
  switch (type) {
    case OPERATION_UPLOAD: return "upload";
    case OPERATION_DOWNLOAD: return "download";
    case OPERATION_OTHER: return "other";
  }
  NOTREACHED();
  return "unknown_transfer_state";
}

// static
std::string GDataOperationRegistry::OperationTransferStateToString(
    OperationTransferState state) {
  switch (state) {
    case OPERATION_NOT_STARTED: return "not_started";
    case OPERATION_STARTED: return "started";
    case OPERATION_IN_PROGRESS: return "in_progress";
    case OPERATION_COMPLETED: return "completed";
    case OPERATION_FAILED: return "failed";
    // Suspended state is opaque to users and looks as same as "in_progress".
    case OPERATION_SUSPENDED: return "in_progress";
  }
  NOTREACHED();
  return "unknown_transfer_state";
}

GDataOperationRegistry::ProgressStatus::ProgressStatus(OperationType type,
                                                       const FilePath& path)
    : operation_id(-1),
      operation_type(type),
      file_path(path),
      transfer_state(OPERATION_NOT_STARTED),
      progress_current(0),
      progress_total(-1) {
}

std::string GDataOperationRegistry::ProgressStatus::DebugString() const {
  std::string str;
  str += "id=";
  str += base::IntToString(operation_id);
  str += " type=";
  str += OperationTypeToString(operation_type);
  str += " path=";
  str += file_path.AsUTF8Unsafe();
  str += " state=";
  str += OperationTransferStateToString(transfer_state);
  str += " progress=";
  str += base::Int64ToString(progress_current);
  str += "/";
  str += base::Int64ToString(progress_total);
  return str;
}

GDataOperationRegistry::Operation::Operation(GDataOperationRegistry* registry)
    : registry_(registry),
      progress_status_(GDataOperationRegistry::OPERATION_OTHER, FilePath()) {
}

GDataOperationRegistry::Operation::Operation(GDataOperationRegistry* registry,
                                             OperationType type,
                                             const FilePath& path)
    : registry_(registry),
      progress_status_(type, path) {
}

GDataOperationRegistry::Operation::~Operation() {
  DCHECK(progress_status_.transfer_state == OPERATION_COMPLETED ||
         progress_status_.transfer_state == OPERATION_SUSPENDED ||
         progress_status_.transfer_state == OPERATION_FAILED);
}

void GDataOperationRegistry::Operation::Cancel() {
  DoCancel();
  NotifyFinish(OPERATION_FAILED);
}

void GDataOperationRegistry::Operation::NotifyStart() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  // Some operations may be restarted. Report only the first "start".
  if (progress_status_.transfer_state == OPERATION_NOT_STARTED) {
    progress_status_.transfer_state = OPERATION_STARTED;
    progress_status_.start_time = base::Time::Now();
    registry_->OnOperationStart(this, &progress_status_.operation_id);
  }
}

void GDataOperationRegistry::Operation::NotifyProgress(
    int64 current, int64 total) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(progress_status_.transfer_state >= OPERATION_STARTED);
  progress_status_.transfer_state = OPERATION_IN_PROGRESS;
  progress_status_.progress_current = current;
  progress_status_.progress_total = total;
  registry_->OnOperationProgress(progress_status().operation_id);
}

void GDataOperationRegistry::Operation::NotifyFinish(
    OperationTransferState status) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(progress_status_.transfer_state >= OPERATION_STARTED);
  DCHECK(status == OPERATION_COMPLETED || status == OPERATION_FAILED);
  progress_status_.transfer_state = status;
  registry_->OnOperationFinish(progress_status().operation_id);
}

void GDataOperationRegistry::Operation::NotifySuspend() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(progress_status_.transfer_state >= OPERATION_STARTED);
  progress_status_.transfer_state = OPERATION_SUSPENDED;
  registry_->OnOperationSuspend(progress_status().operation_id);
}

void GDataOperationRegistry::Operation::NotifyResume() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (progress_status_.transfer_state == OPERATION_NOT_STARTED) {
    progress_status_.transfer_state = OPERATION_IN_PROGRESS;
    registry_->OnOperationResume(this, &progress_status_);
  }
}

void GDataOperationRegistry::Operation::NotifyAuthFailed() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  registry_->OnOperationAuthFailed();
}

GDataOperationRegistry::GDataOperationRegistry() {
  in_flight_operations_.set_check_on_null_data(true);
}

GDataOperationRegistry::~GDataOperationRegistry() {
  DCHECK(in_flight_operations_.IsEmpty());
}

void GDataOperationRegistry::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void GDataOperationRegistry::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

void GDataOperationRegistry::CancelAll() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  for (OperationIDMap::iterator iter(&in_flight_operations_);
       !iter.IsAtEnd();
       iter.Advance()) {
    Operation* operation = iter.GetCurrentValue();
    operation->Cancel();
    // Cancel() may immediately trigger OnOperationFinish and remove the
    // operation from the map, but IDMap is designed to be safe on such remove
    // while iteration.
  }
}

bool GDataOperationRegistry::CancelForFilePath(const FilePath& file_path) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  for (OperationIDMap::iterator iter(&in_flight_operations_);
       !iter.IsAtEnd();
       iter.Advance()) {
    Operation* operation = iter.GetCurrentValue();
    if (operation->progress_status().file_path == file_path) {
      operation->Cancel();
      return true;
    }
  }
  return false;
}

void GDataOperationRegistry::OnOperationStart(
    GDataOperationRegistry::Operation* operation,
    OperationID* id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  *id = in_flight_operations_.Add(operation);
  DVLOG(1) << "GDataOperation[" << *id << "] started.";
  if (IsFileTransferOperation(operation)) {
    FOR_EACH_OBSERVER(Observer, observer_list_,
                      OnProgressUpdate(GetProgressStatusList()));
  }
}

void GDataOperationRegistry::OnOperationProgress(OperationID id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  Operation* operation = in_flight_operations_.Lookup(id);
  DCHECK(operation);

  DVLOG(1) << "GDataOperation[" << id << "] "
           << operation->progress_status().DebugString();
  if (IsFileTransferOperation(operation)) {
    FOR_EACH_OBSERVER(Observer, observer_list_,
                      OnProgressUpdate(GetProgressStatusList()));
  }
}

void GDataOperationRegistry::OnOperationFinish(OperationID id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  Operation* operation = in_flight_operations_.Lookup(id);
  DCHECK(operation);

  DVLOG(1) << "GDataOperation[" << id << "] finished.";
  if (IsFileTransferOperation(operation)) {
    FOR_EACH_OBSERVER(Observer, observer_list_,
                      OnProgressUpdate(GetProgressStatusList()));
  }
  in_flight_operations_.Remove(id);
}

void GDataOperationRegistry::OnOperationResume(
    GDataOperationRegistry::Operation* operation,
    GDataOperationRegistry::ProgressStatus* new_status) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  // Find the corresponding suspended task.
  Operation* suspended = NULL;
  for (OperationIDMap::iterator iter(&in_flight_operations_);
       !iter.IsAtEnd();
       iter.Advance()) {
    Operation* in_flight_operation = iter.GetCurrentValue();
    const ProgressStatus& status = in_flight_operation->progress_status();
    if (status.transfer_state == OPERATION_SUSPENDED &&
        status.file_path == operation->progress_status().file_path) {
      suspended = in_flight_operation;
      break;
    }
  }
  DCHECK(suspended);

  // Copy the progress status.
  const ProgressStatus& old_status = suspended->progress_status();
  OperationID old_id = old_status.operation_id;

  new_status->progress_current = old_status.progress_current;
  new_status->progress_total = old_status.progress_total;
  new_status->start_time = old_status.start_time;

  // Remove the old one and initiate the new operation.
  in_flight_operations_.Remove(old_id);
  new_status->operation_id = in_flight_operations_.Add(operation);
  DVLOG(1) << "GDataOperation[" << old_id << " -> " <<
           new_status->operation_id << "] resumed.";
  if (IsFileTransferOperation(operation)) {
    FOR_EACH_OBSERVER(Observer, observer_list_,
                      OnProgressUpdate(GetProgressStatusList()));
  }
}

void GDataOperationRegistry::OnOperationSuspend(OperationID id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  Operation* operation = in_flight_operations_.Lookup(id);
  DCHECK(operation);

  DVLOG(1) << "GDataOperation[" << id << "] suspended.";
  if (IsFileTransferOperation(operation)) {
    FOR_EACH_OBSERVER(Observer, observer_list_,
                      OnProgressUpdate(GetProgressStatusList()));
  }
}

void GDataOperationRegistry::OnOperationAuthFailed() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  DVLOG(1) << "GDataOperation authentication failed.";
  FOR_EACH_OBSERVER(Observer, observer_list_, OnAuthenticationFailed());
}

bool GDataOperationRegistry::IsFileTransferOperation(
    const Operation* operation) const {
  OperationType type = operation->progress_status().operation_type;
  return type == OPERATION_UPLOAD || type == OPERATION_DOWNLOAD;
}

GDataOperationRegistry::ProgressStatusList
GDataOperationRegistry::GetProgressStatusList() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  ProgressStatusList status_list;
  for (OperationIDMap::const_iterator iter(&in_flight_operations_);
       !iter.IsAtEnd();
       iter.Advance()) {
    const Operation* operation = iter.GetCurrentValue();
    if (IsFileTransferOperation(operation))
      status_list.push_back(operation->progress_status());
  }
  return status_list;
}

}  // namespace gdata