// 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/android/android_stream_reader_url_request_job.h"

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "net/base/io_buffer.h"
#include "net/base/mime_util.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/http/http_util.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_file_job.h"
#include "net/url_request/url_request_job_manager.h"
// Disable "Warnings treated as errors" for input_stream_jni as it's a Java
// system class and we have to generate C++ hooks for all methods in the class
// even if they're unused.
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni/InputStream_jni.h"

using base::android::AttachCurrentThread;
using base::android::ClearException;
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
using JNI_InputStream::Java_InputStream_available;
using JNI_InputStream::Java_InputStream_skip;
using JNI_InputStream::Java_InputStream_read;


namespace {

// Maximum number of bytes to be read in a single read.
const int kBufferSize = 4096;

} // namespace

AndroidStreamReaderURLRequestJob::AndroidStreamReaderURLRequestJob(
    net::URLRequest* request,
    net::NetworkDelegate* network_delegate,
    scoped_ptr<Delegate> delegate)
    : URLRequestJob(request, network_delegate),
      delegate_(delegate.Pass()) {
  DCHECK(delegate_.get());
}

AndroidStreamReaderURLRequestJob::~AndroidStreamReaderURLRequestJob() {
}

bool AndroidStreamReaderURLRequestJob::InitJNIBindings(JNIEnv* env) {
  return JNI_InputStream::RegisterNativesImpl(env);
}

void AndroidStreamReaderURLRequestJob::Start() {
  JNIEnv* env = AttachCurrentThread();
  DCHECK(env);

  stream_.Reset(env, delegate_->OpenInputStream(env, request()).obj());
  if (!stream_.obj()) {
    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                     net::ERR_FAILED));
    return;
  }

  if (VerifyRequestedRange(env) && SkipToRequestedRange(env))
    NotifyHeadersComplete();
}

bool AndroidStreamReaderURLRequestJob::VerifyRequestedRange(JNIEnv* env) {
  int32_t size = Java_InputStream_available(env, stream_.obj());
  if (ClearException(env)) {
    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                     net::ERR_FAILED));
    return false;
  }

  if (size <= 0)
    return true;

  // Check that the requested range was valid.
  if (!byte_range_.ComputeBounds(size)) {
    NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
               net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
    return false;
  }

  size = byte_range_.last_byte_position() -
         byte_range_.first_byte_position() + 1;
  DCHECK_GE(size, 0);
  set_expected_content_size(size);

  return true;
}

bool AndroidStreamReaderURLRequestJob::SkipToRequestedRange(JNIEnv* env) {
  // Skip to the start of the requested data. This has to be done in a loop
  // because the underlying InputStream is not guaranteed to skip the requested
  // number of bytes.
  if (byte_range_.first_byte_position() != 0) {
    int64_t skipped, bytes_to_skip = byte_range_.first_byte_position();
    do {
      skipped = Java_InputStream_skip(env, stream_.obj(), bytes_to_skip);
      if (ClearException(env)) {
        NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                         net::ERR_FAILED));
        return false;
      }
      if (skipped <= 0) {
        NotifyDone(
            net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                  net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
        return false;
      }
    } while ((bytes_to_skip -= skipped) > 0);
  }
  return true;
}

bool AndroidStreamReaderURLRequestJob::ReadRawData(net::IOBuffer* dest,
                                                   int dest_size,
                                                   int *bytes_read) {
  DCHECK_NE(dest_size, 0);
  DCHECK(bytes_read);
  DCHECK(stream_.obj());

  JNIEnv* env = AttachCurrentThread();
  DCHECK(env);

  if (!buffer_.obj()) {
    // Allocate transfer buffer.
    buffer_.Reset(env, env->NewByteArray(kBufferSize));
    if (ClearException(env)) {
      NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                       net::ERR_FAILED));
      return false;
    }
  }

  jbyteArray buffer = buffer_.obj();
  *bytes_read = 0;
  if (!dest_size)
    return true;

  // Read data in multiples of the buffer size.
  while (dest_size > 0) {
    int read_size = std::min(dest_size, kBufferSize);
    // TODO(skyostil): Make this non-blocking
    int32_t byte_count =
        Java_InputStream_read(env, stream_.obj(), buffer, 0, read_size);
    if (byte_count <= 0) {
      // net::URLRequestJob will call NotifyDone for us after the end of the
      // file is reached.
      break;
    }

    if (ClearException(env)) {
      NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                       net::ERR_FAILED));
      return false;
    }

#ifndef NDEBUG
    int32_t buffer_length = env->GetArrayLength(buffer);
    DCHECK_GE(read_size, byte_count);
    DCHECK_GE(buffer_length, byte_count);
#endif // NDEBUG

    // Copy the data over to the provided C++ side buffer.
    DCHECK_GE(dest_size, byte_count);
    env->GetByteArrayRegion(buffer, 0, byte_count,
        reinterpret_cast<jbyte*>(dest->data() + *bytes_read));

    if (ClearException(env)) {
      NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
                                       net::ERR_FAILED));
      return false;
    }

    *bytes_read += byte_count;
    dest_size -= byte_count;
  }
  return true;
}

bool AndroidStreamReaderURLRequestJob::GetMimeType(
    std::string* mime_type) const {
  JNIEnv* env = AttachCurrentThread();
  DCHECK(env);

  return delegate_->GetMimeType(env,
                                request(),
                                stream_.obj(),
                                mime_type);
}

bool AndroidStreamReaderURLRequestJob::GetCharset(
    std::string* charset) {
  JNIEnv* env = AttachCurrentThread();
  DCHECK(env);

  return delegate_->GetCharset(env,
                               request(),
                               stream_.obj(),
                               charset);
}

void AndroidStreamReaderURLRequestJob::SetExtraRequestHeaders(
    const net::HttpRequestHeaders& headers) {
  std::string range_header;
  if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
    // We only care about "Range" header here.
    std::vector<net::HttpByteRange> ranges;
    if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
      if (ranges.size() == 1) {
        byte_range_ = ranges[0];
      } else {
        // We don't support multiple range requests in one single URL request,
        // because we need to do multipart encoding here.
        NotifyDone(net::URLRequestStatus(
            net::URLRequestStatus::FAILED,
            net::ERR_REQUEST_RANGE_NOT_SATISFIABLE));
      }
    }
  }
}