// Copyright (c) 2009 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 "webkit/appcache/appcache_response.h"

#include "base/logging.h"
#include "base/message_loop.h"
#include "base/pickle.h"
#include "base/string_util.h"
#include "net/base/net_errors.h"
#include "net/base/io_buffer.h"
#include "net/disk_cache/disk_cache.h"
#include "webkit/appcache/appcache_disk_cache.h"
#include "webkit/appcache/appcache_service.h"

using disk_cache::Entry;

namespace appcache {

namespace {

// Disk cache entry data indices.
enum {
  kResponseInfoIndex,
  kResponseContentIndex
};

// An IOBuffer that wraps a pickle's data. Ownership of the
// pickle is transfered to the WrappedPickleIOBuffer object.
class WrappedPickleIOBuffer : public net::WrappedIOBuffer {
 public:
  explicit WrappedPickleIOBuffer(const Pickle* pickle) :
      net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data())),
      pickle_(pickle) {
    DCHECK(pickle->data());
  }

 private:
  ~WrappedPickleIOBuffer() {}

  scoped_ptr<const Pickle> pickle_;
};

}  // anon namespace


// AppCacheResponseInfo ----------------------------------------------

AppCacheResponseInfo::AppCacheResponseInfo(
    AppCacheService* service, const GURL& manifest_url,
    int64 response_id,  net::HttpResponseInfo* http_info,
    int64 response_data_size)
    : manifest_url_(manifest_url), response_id_(response_id),
      http_response_info_(http_info), response_data_size_(response_data_size),
      service_(service) {
  DCHECK(http_info);
  DCHECK(response_id != kNoResponseId);
  service_->storage()->working_set()->AddResponseInfo(this);
}

AppCacheResponseInfo::~AppCacheResponseInfo() {
  service_->storage()->working_set()->RemoveResponseInfo(this);
}


// AppCacheResponseIO ----------------------------------------------

AppCacheResponseIO::AppCacheResponseIO(
    int64 response_id, AppCacheDiskCache* disk_cache)
    : response_id_(response_id), disk_cache_(disk_cache),
      entry_(NULL), buffer_len_(0), user_callback_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
      ALLOW_THIS_IN_INITIALIZER_LIST(raw_callback_(
          new net::CancelableCompletionCallback<AppCacheResponseIO>(
              this, &AppCacheResponseIO::OnRawIOComplete))) {
}

AppCacheResponseIO::~AppCacheResponseIO() {
  raw_callback_->Cancel();
  if (entry_)
    entry_->Close();
}

void AppCacheResponseIO::ScheduleIOCompletionCallback(int result) {
  MessageLoop::current()->PostTask(FROM_HERE,
      method_factory_.NewRunnableMethod(
          &AppCacheResponseIO::OnIOComplete, result));
}

void AppCacheResponseIO::InvokeUserCompletionCallback(int result) {
  // Clear the user callback and buffers prior to invoking the callback
  // so the caller can schedule additional operations in the callback.
  buffer_ = NULL;
  info_buffer_ = NULL;
  net::CompletionCallback* temp_user_callback = user_callback_;
  user_callback_ = NULL;
  temp_user_callback->Run(result);
}

void AppCacheResponseIO::ReadRaw(int index, int offset,
                                 net::IOBuffer* buf, int buf_len) {
  DCHECK(entry_);
  raw_callback_->AddRef();  // Balanced in OnRawIOComplete.
  int rv = entry_->ReadData(index, offset, buf, buf_len, raw_callback_);
  if (rv != net::ERR_IO_PENDING) {
    raw_callback_->Release();
    ScheduleIOCompletionCallback(rv);
  }
}

void AppCacheResponseIO::WriteRaw(int index, int offset,
                                 net::IOBuffer* buf, int buf_len) {
  DCHECK(entry_);
  const bool kTruncate = true;
  raw_callback_->AddRef();  // Balanced in OnRawIOComplete.
  int rv = entry_->WriteData(index, offset, buf, buf_len, raw_callback_,
                             kTruncate);
  if (rv != net::ERR_IO_PENDING) {
    raw_callback_->Release();
    ScheduleIOCompletionCallback(rv);
  }
}

void AppCacheResponseIO::OnRawIOComplete(int result) {
  raw_callback_->Release();  // Balance the AddRefs
  OnIOComplete(result);
}


// AppCacheResponseReader ----------------------------------------------

AppCacheResponseReader::AppCacheResponseReader(
    int64 response_id, AppCacheDiskCache* disk_cache)
    : AppCacheResponseIO(response_id, disk_cache),
      range_offset_(0), range_length_(kint32max),
      read_position_(0) {
}

AppCacheResponseReader::~AppCacheResponseReader() {
  if (open_callback_)
    open_callback_.release()->Cancel();
}

void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf,
                                      net::CompletionCallback* callback) {
  DCHECK(callback && !IsReadPending());
  DCHECK(info_buf && !info_buf->http_info.get());
  DCHECK(!buffer_.get() && !info_buffer_.get());

  info_buffer_ = info_buf;
  user_callback_ = callback;  // cleared on completion
  OpenEntryIfNeededAndContinue();
}

void AppCacheResponseReader::ContinueReadInfo() {
  if (!entry_)  {
    ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
    return;
  }

  int size = entry_->GetDataSize(kResponseInfoIndex);
  if (size <= 0) {
    ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
    return;
  }

  buffer_ = new net::IOBuffer(size);
  ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size);
}

void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len,
                                      net::CompletionCallback* callback) {
  DCHECK(callback && !IsReadPending());
  DCHECK(buf && (buf_len >= 0));
  DCHECK(!buffer_.get() && !info_buffer_.get());

  buffer_ = buf;
  buffer_len_ = buf_len;
  user_callback_ = callback;  // cleared on completion
  OpenEntryIfNeededAndContinue();
}

void AppCacheResponseReader::ContinueReadData() {
  if (!entry_)  {
    ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
    return;
  }

  if (read_position_ + buffer_len_ > range_length_) {
    // TODO(michaeln): What about integer overflows?
    DCHECK(range_length_ >= read_position_);
    buffer_len_ = range_length_ - read_position_;
  }
  ReadRaw(kResponseContentIndex, range_offset_ + read_position_,
          buffer_, buffer_len_);
}

void AppCacheResponseReader::SetReadRange(int offset, int length) {
  DCHECK(!IsReadPending() && !read_position_);
  range_offset_ = offset;
  range_length_ = length;
}

void AppCacheResponseReader::OnIOComplete(int result) {
  if (result >= 0) {
    if (info_buffer_.get()) {
      // Allocate and deserialize the http info structure.
      Pickle pickle(buffer_->data(), result);
      bool response_truncated = false;
      info_buffer_->http_info.reset(new net::HttpResponseInfo);
      info_buffer_->http_info->InitFromPickle(pickle, &response_truncated);
      DCHECK(!response_truncated);

      // Also return the size of the response body
      DCHECK(entry_);
      info_buffer_->response_data_size =
          entry_->GetDataSize(kResponseContentIndex);
    } else {
      read_position_ += result;
    }
  }
  InvokeUserCompletionCallback(result);
}

void AppCacheResponseReader::OpenEntryIfNeededAndContinue() {
  int rv;
  if (entry_) {
    rv = net::OK;
  } else if (!disk_cache_) {
    rv = net::ERR_FAILED;
  } else {
    open_callback_ = new EntryCallback<AppCacheResponseReader>(
        this, &AppCacheResponseReader::OnOpenEntryComplete);
    rv = disk_cache_->OpenEntry(response_id_, &open_callback_->entry_ptr_,
                                open_callback_.get());
  }

  if (rv != net::ERR_IO_PENDING)
    OnOpenEntryComplete(rv);
}

void AppCacheResponseReader::OnOpenEntryComplete(int rv) {
  DCHECK(info_buffer_.get() || buffer_.get());

  if (open_callback_) {
    if (rv == net::OK) {
      entry_ = open_callback_->entry_ptr_;
      open_callback_->entry_ptr_ = NULL;
    }
    open_callback_ = NULL;
  }

  if (info_buffer_)
    ContinueReadInfo();
  else
    ContinueReadData();
}

// AppCacheResponseWriter ----------------------------------------------

AppCacheResponseWriter::AppCacheResponseWriter(
    int64 response_id, AppCacheDiskCache* disk_cache)
    : AppCacheResponseIO(response_id, disk_cache),
      info_size_(0), write_position_(0), write_amount_(0),
      creation_phase_(INITIAL_ATTEMPT) {
}

AppCacheResponseWriter::~AppCacheResponseWriter() {
  if (create_callback_)
    create_callback_.release()->Cancel();
}

void AppCacheResponseWriter::WriteInfo(HttpResponseInfoIOBuffer* info_buf,
                                       net::CompletionCallback* callback) {
  DCHECK(callback && !IsWritePending());
  DCHECK(info_buf && info_buf->http_info.get());
  DCHECK(!buffer_.get() && !info_buffer_.get());

  info_buffer_ = info_buf;
  user_callback_ = callback;  // cleared on completion
  CreateEntryIfNeededAndContinue();
}

void AppCacheResponseWriter::ContinueWriteInfo() {
  if (!entry_) {
    ScheduleIOCompletionCallback(net::ERR_FAILED);
    return;
  }

  const bool kSkipTransientHeaders = true;
  const bool kTruncated = false;
  Pickle* pickle = new Pickle;
  info_buffer_->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated);
  write_amount_ = static_cast<int>(pickle->size());
  buffer_ = new WrappedPickleIOBuffer(pickle);  // takes ownership of pickle
  WriteRaw(kResponseInfoIndex, 0, buffer_, write_amount_);
}

void AppCacheResponseWriter::WriteData(net::IOBuffer* buf, int buf_len,
                                       net::CompletionCallback* callback) {
  DCHECK(callback && !IsWritePending());
  DCHECK(buf && (buf_len >= 0));
  DCHECK(!buffer_.get() && !info_buffer_.get());

  buffer_ = buf;
  write_amount_ = buf_len;
  user_callback_ = callback;  // cleared on completion
  CreateEntryIfNeededAndContinue();
}

void AppCacheResponseWriter::ContinueWriteData() {
  if (!entry_) {
    ScheduleIOCompletionCallback(net::ERR_FAILED);
    return;
  }
  WriteRaw(kResponseContentIndex, write_position_, buffer_, write_amount_);
}

void AppCacheResponseWriter::OnIOComplete(int result) {
  if (result >= 0) {
    DCHECK(write_amount_ == result);
    if (!info_buffer_.get())
      write_position_ += result;
    else
      info_size_ = result;
  }
  InvokeUserCompletionCallback(result);
}

void AppCacheResponseWriter::CreateEntryIfNeededAndContinue() {
  int rv;
  if (entry_) {
    creation_phase_ = NO_ATTEMPT;
    rv = net::OK;
  } else if (!disk_cache_) {
    creation_phase_ = NO_ATTEMPT;
    rv = net::ERR_FAILED;
  } else {
    creation_phase_ = INITIAL_ATTEMPT;
    create_callback_ = new EntryCallback<AppCacheResponseWriter>(
        this, &AppCacheResponseWriter::OnCreateEntryComplete);
    rv = disk_cache_->CreateEntry(response_id_, &create_callback_->entry_ptr_,
                                  create_callback_.get());
  }
  if (rv != net::ERR_IO_PENDING)
    OnCreateEntryComplete(rv);
}

void AppCacheResponseWriter::OnCreateEntryComplete(int rv) {
  DCHECK(info_buffer_.get() || buffer_.get());

  if (creation_phase_ == INITIAL_ATTEMPT) {
    if (rv != net::OK) {
      // We may try to overrite existing entries.
      creation_phase_ = DOOM_EXISTING;
      rv = disk_cache_->DoomEntry(response_id_, create_callback_.get());
      if (rv != net::ERR_IO_PENDING)
        OnCreateEntryComplete(rv);
      return;
    }
  } else if (creation_phase_ == DOOM_EXISTING) {
    creation_phase_ = SECOND_ATTEMPT;
    rv = disk_cache_->CreateEntry(response_id_, &create_callback_->entry_ptr_,
                                  create_callback_.get());
    if (rv != net::ERR_IO_PENDING)
      OnCreateEntryComplete(rv);
    return;
  }

  if (create_callback_) {
    if (rv == net::OK) {
      entry_ = create_callback_->entry_ptr_;
      create_callback_->entry_ptr_ = NULL;
    }
    create_callback_ = NULL;
  }

  if (info_buffer_)
    ContinueWriteInfo();
  else
    ContinueWriteData();
}

}  // namespace appcache