// 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 "net/base/file_stream.h"

#include "base/location.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/task_runner_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/worker_pool.h"
#include "net/base/file_stream_context.h"
#include "net/base/file_stream_net_log_parameters.h"
#include "net/base/net_errors.h"

namespace net {

FileStream::FileStream(NetLog* net_log,
                       const scoped_refptr<base::TaskRunner>& task_runner)
      /* To allow never opened stream to be destroyed on any thread we set flags
         as if stream was opened asynchronously. */
    : open_flags_(base::PLATFORM_FILE_ASYNC),
      bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
      context_(new Context(bound_net_log_, task_runner)) {
  bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStream::FileStream(NetLog* net_log)
      /* To allow never opened stream to be destroyed on any thread we set flags
         as if stream was opened asynchronously. */
    : open_flags_(base::PLATFORM_FILE_ASYNC),
      bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
      context_(new Context(bound_net_log_,
                           base::WorkerPool::GetTaskRunner(true /* slow */))) {
  bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStream::FileStream(base::PlatformFile file,
                       int flags,
                       NetLog* net_log,
                       const scoped_refptr<base::TaskRunner>& task_runner)
    : open_flags_(flags),
      bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
      context_(new Context(file, bound_net_log_, open_flags_, task_runner)) {
  bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStream::FileStream(base::PlatformFile file, int flags, NetLog* net_log)
    : open_flags_(flags),
      bound_net_log_(BoundNetLog::Make(net_log, NetLog::SOURCE_FILESTREAM)),
      context_(new Context(file,
                           bound_net_log_,
                           open_flags_,
                           base::WorkerPool::GetTaskRunner(true /* slow */))) {
  bound_net_log_.BeginEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
}

FileStream::~FileStream() {
  if (!is_async()) {
    base::ThreadRestrictions::AssertIOAllowed();
    context_->CloseSync();
    context_.reset();
  } else {
    context_.release()->Orphan();
  }

  bound_net_log_.EndEvent(NetLog::TYPE_FILE_STREAM_ALIVE);
}

int FileStream::Open(const base::FilePath& path, int open_flags,
                     const CompletionCallback& callback) {
  if (IsOpen()) {
    DLOG(FATAL) << "File is already open!";
    return ERR_UNEXPECTED;
  }

  open_flags_ = open_flags;
  DCHECK(is_async());
  context_->OpenAsync(path, open_flags, callback);
  return ERR_IO_PENDING;
}

int FileStream::OpenSync(const base::FilePath& path, int open_flags) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (IsOpen()) {
    DLOG(FATAL) << "File is already open!";
    return ERR_UNEXPECTED;
  }

  open_flags_ = open_flags;
  DCHECK(!is_async());
  return context_->OpenSync(path, open_flags_);
}

int FileStream::Close(const CompletionCallback& callback) {
  DCHECK(is_async());
  context_->CloseAsync(callback);
  return ERR_IO_PENDING;
}

int FileStream::CloseSync() {
  DCHECK(!is_async());
  base::ThreadRestrictions::AssertIOAllowed();
  context_->CloseSync();
  return OK;
}

bool FileStream::IsOpen() const {
  return context_->file() != base::kInvalidPlatformFileValue;
}

int FileStream::Seek(Whence whence,
                     int64 offset,
                     const Int64CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  // Make sure we're async.
  DCHECK(is_async());
  context_->SeekAsync(whence, offset, callback);
  return ERR_IO_PENDING;
}

int64 FileStream::SeekSync(Whence whence, int64 offset) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  // If we're in async, make sure we don't have a request in flight.
  DCHECK(!is_async() || !context_->async_in_progress());
  return context_->SeekSync(whence, offset);
}

int64 FileStream::Available() {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  int64 cur_pos = SeekSync(FROM_CURRENT, 0);
  if (cur_pos < 0)
    return cur_pos;

  int64 size = context_->GetFileSize();
  if (size < 0)
    return size;

  DCHECK_GE(size, cur_pos);
  return size - cur_pos;
}

int FileStream::Read(IOBuffer* buf,
                     int buf_len,
                     const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  // read(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);
  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);
  DCHECK(is_async());

  return context_->ReadAsync(buf, buf_len, callback);
}

int FileStream::ReadSync(char* buf, int buf_len) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(!is_async());
  // read(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);
  DCHECK(open_flags_ & base::PLATFORM_FILE_READ);

  return context_->ReadSync(buf, buf_len);
}

int FileStream::ReadUntilComplete(char *buf, int buf_len) {
  base::ThreadRestrictions::AssertIOAllowed();

  int to_read = buf_len;
  int bytes_total = 0;

  do {
    int bytes_read = ReadSync(buf, to_read);
    if (bytes_read <= 0) {
      if (bytes_total == 0)
        return bytes_read;

      return bytes_total;
    }

    bytes_total += bytes_read;
    buf += bytes_read;
    to_read -= bytes_read;
  } while (bytes_total < buf_len);

  return bytes_total;
}

int FileStream::Write(IOBuffer* buf,
                      int buf_len,
                      const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(is_async());
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  // write(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);

  return context_->WriteAsync(buf, buf_len, callback);
}

int FileStream::WriteSync(const char* buf, int buf_len) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(!is_async());
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  // write(..., 0) will return 0, which indicates end-of-file.
  DCHECK_GT(buf_len, 0);

  return context_->WriteSync(buf, buf_len);
}

int64 FileStream::Truncate(int64 bytes) {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  // We'd better be open for writing.
  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);

  // Seek to the position to truncate from.
  int64 seek_position = SeekSync(FROM_BEGIN, bytes);
  if (seek_position != bytes)
    return ERR_UNEXPECTED;

  // And truncate the file.
  return context_->Truncate(bytes);
}

int FileStream::Flush(const CompletionCallback& callback) {
  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  // Make sure we're async.
  DCHECK(is_async());

  context_->FlushAsync(callback);
  return ERR_IO_PENDING;
}

int FileStream::FlushSync() {
  base::ThreadRestrictions::AssertIOAllowed();

  if (!IsOpen())
    return ERR_UNEXPECTED;

  DCHECK(open_flags_ & base::PLATFORM_FILE_WRITE);
  return context_->FlushSync();
}

void FileStream::EnableErrorStatistics() {
  context_->set_record_uma(true);
}

void FileStream::SetBoundNetLogSource(const BoundNetLog& owner_bound_net_log) {
  if ((owner_bound_net_log.source().id == NetLog::Source::kInvalidId) &&
      (bound_net_log_.source().id == NetLog::Source::kInvalidId)) {
    // Both |BoundNetLog|s are invalid.
    return;
  }

  // Should never connect to itself.
  DCHECK_NE(bound_net_log_.source().id, owner_bound_net_log.source().id);

  bound_net_log_.AddEvent(NetLog::TYPE_FILE_STREAM_BOUND_TO_OWNER,
      owner_bound_net_log.source().ToEventParametersCallback());

  owner_bound_net_log.AddEvent(NetLog::TYPE_FILE_STREAM_SOURCE,
      bound_net_log_.source().ToEventParametersCallback());
}

base::PlatformFile FileStream::GetPlatformFileForTesting() {
  return context_->file();
}

}  // namespace net