// Copyright (c) 2011 The Chromium Embedded Framework 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 "libcef/web_urlrequest_impl.h"
#include "libcef/browser_webkit_init.h"
#include "libcef/cef_thread.h"
#include "libcef/request_impl.h"
#include "libcef/response_impl.h"

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "googleurl/src/gurl.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLError.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLLoaderClient.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLRequest.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebURLResponse.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebKit.h"
#include "webkit/glue/weburlloader_impl.h"

using WebKit::WebURLError;
using WebKit::WebURLLoader;
using WebKit::WebURLRequest;
using WebKit::WebURLResponse;

namespace {
class CefWebURLLoaderClientImpl;
};

// Manages the lifespan of WebKit objects. Methods of this class will only be
// called on the UI thread.
class CefWebURLRequestImpl::Context
    : public base::RefCountedThreadSafe<CefWebURLRequestImpl::Context,
                                        CefThread::DeleteOnUIThread> {
 public:
  explicit Context(CefRefPtr<CefWebURLRequestImpl> client)
    : client_(client) {
  }
  virtual ~Context() {
  }

  void initialize(CefRefPtr<CefRequest> request);
  void destroy();
  void cancel();

  CefRefPtr<CefWebURLRequestImpl> client() { return client_; }

 protected:
  CefRefPtr<CefWebURLRequestImpl> client_;
  scoped_ptr<webkit_glue::WebURLLoaderImpl> url_loader_;
  scoped_ptr<CefWebURLLoaderClientImpl> url_client_;
};


namespace {

// Implements the WebURLLoaderClient interface. Methods of this class will only
// be called on the UI thread.
class CefWebURLLoaderClientImpl : public WebKit::WebURLLoaderClient {
 public:
  explicit CefWebURLLoaderClientImpl(CefWebURLRequestImpl::Context* context)
    : context_(context) {
  }
  virtual ~CefWebURLLoaderClientImpl() {
  }

  // =====================================================================
  // WebKit::WebURLLoaderClient API
  //

  // Called when following a redirect.  |newRequest| contains the request
  // generated by the redirect.  The client may modify |newRequest|.
  virtual void willSendRequest(WebKit::WebURLLoader*,
                               WebKit::WebURLRequest& newRequest,
                               const WebKit::WebURLResponse& redirectResponse) {
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get()) {
      CefRefPtr<CefWebURLRequestClient> handler = client->GetHandler();
      if (handler.get()) {
        CefRefPtr<CefRequestImpl> cefRequest(new CefRequestImpl());
        CefRefPtr<CefResponse> cefResponse(
            new CefResponseImpl(redirectResponse));

        cefRequest->Set(newRequest);
        handler->OnRedirect(client.get(), cefRequest.get(), cefResponse);
        cefRequest->Get(newRequest);
      }
    }
  }

  // Called to report upload progress.  The bytes reported correspond to
  // the HTTP message body.
  virtual void didSendData(
      WebKit::WebURLLoader*,
      unsigned long long bytesSent,  // NOLINT(runtime/int)
      unsigned long long totalBytesToBeSent) {  // NOLINT(runtime/int)
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get()) {
      CefRefPtr<CefWebURLRequestClient> handler = client->GetHandler();
      if (handler.get())
        handler->OnProgress(client.get(), bytesSent, totalBytesToBeSent);
    }
  }

  // Called when response headers are received.
  virtual void didReceiveResponse(WebKit::WebURLLoader*,
                                  const WebKit::WebURLResponse& response) {
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get()) {
      client->DoStateChange(WUR_STATE_HEADERS_RECEIVED);
      CefRefPtr<CefWebURLRequestClient> handler = client->GetHandler();
      if (handler.get()) {
        CefRefPtr<CefResponse> cefResponse(new CefResponseImpl(response));
        handler->OnHeadersReceived(client.get(), cefResponse);
      }
    }
  }

  // Called when a chunk of response data is downloaded.  This is only called
  // if WebURLRequest's downloadToFile flag was set to true.
  virtual void didDownloadData(WebKit::WebURLLoader*, int dataLength) {
    NOTREACHED();
  }

  // Called when a chunk of response data is received.
  virtual void didReceiveData(WebURLLoader*, const char* data, int dataLength,
                              int lengthReceived) {
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get()) {
      client->DoStateChange(WUR_STATE_LOADING);
      CefRefPtr<CefWebURLRequestClient> handler = client->GetHandler();
      if (handler.get()) {
        handler->OnData(client.get(), data, dataLength);
      }
    }
  }

  // Called when a chunk of renderer-generated metadata is received from
  // the cache.
  virtual void didReceiveCachedMetadata(WebKit::WebURLLoader*, const char* data,
                                        int dataLength) {
    NOTREACHED();
  }

  // Called when the load completes successfully.
  virtual void didFinishLoading(WebKit::WebURLLoader*, double finishTime) {
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get())
      client->DoStateChange(WUR_STATE_DONE);

    complete();
  }

  // Called when the load completes with an error.
  virtual void didFail(WebKit::WebURLLoader*,
                       const WebKit::WebURLError& error) {
    REQUIRE_UIT();
    if (!context_)
      return;

    CefRefPtr<CefWebURLRequestImpl> client = context_->client();
    if (client.get() && client->GetState() != WUR_STATE_ABORT) {
      client->DoStateChange(WUR_STATE_ERROR);
      CefRefPtr<CefWebURLRequestClient> handler = client->GetHandler();
      if (handler.get()) {
        handler->OnError(client.get(),
            static_cast<CefWebURLRequestClient::ErrorCode>(error.reason));
      }
    }

    complete();
  }

  void complete() {
    context_->destroy();
    context_ = NULL;
  }

 protected:
  scoped_refptr<CefWebURLRequestImpl::Context> context_;
};

}  // namespace


// CefWebURLManager

void CefWebURLRequestImpl::Context::initialize(
    CefRefPtr<CefRequest> request) {
  REQUIRE_UIT();

  url_loader_.reset(
      new webkit_glue::WebURLLoaderImpl(reinterpret_cast<BrowserWebKitInit*>(
          WebKit::webKitPlatformSupport())));
  url_client_.reset(new CefWebURLLoaderClientImpl(this));

  WebURLRequest urlRequest;
  static_cast<CefRequestImpl*>(request.get())->Get(urlRequest);
  url_loader_->loadAsynchronously(urlRequest, url_client_.get());
}

void CefWebURLRequestImpl::Context::destroy() {
  REQUIRE_UIT();
  client_ = NULL;
}

void CefWebURLRequestImpl::Context::cancel() {
  REQUIRE_UIT();
  url_loader_->cancel();
  url_client_->complete();
}


// CefWebURLRequest

// static
CefRefPtr<CefWebURLRequest>
CefWebURLRequest::CreateWebURLRequest(
    CefRefPtr<CefRequest> request, CefRefPtr<CefWebURLRequestClient> client) {
  CefRefPtr<CefWebURLRequestImpl> requester = new CefWebURLRequestImpl(client);

  // Send the request from the UI thread.
  CefThread::PostTask(CefThread::UI, FROM_HERE,
      base::Bind(&CefWebURLRequestImpl::DoSend, requester.get(), request));

  return requester.get();
}


// CefWebURLRequestImpl

CefWebURLRequestImpl::CefWebURLRequestImpl(
    CefRefPtr<CefWebURLRequestClient> handler)
    : handler_(handler),
      state_(WUR_STATE_UNSENT) {
}

CefWebURLRequestImpl::~CefWebURLRequestImpl() {
}

CefWebURLRequestImpl::RequestState CefWebURLRequestImpl::GetState() {
  AutoLock lock_scope(this);
  return state_;
}

void CefWebURLRequestImpl::Cancel() {
  CefThread::PostTask(CefThread::UI, FROM_HERE,
      base::Bind(&CefWebURLRequestImpl::DoCancel, this));
}

void CefWebURLRequestImpl::DoSend(CefRefPtr<CefRequest> request) {
  REQUIRE_UIT();
  DCHECK(state_ == WUR_STATE_UNSENT);

  context_ = new CefWebURLRequestImpl::Context(this);
  context_->initialize(request);

  DoStateChange(WUR_STATE_STARTED);
}

void CefWebURLRequestImpl::DoCancel() {
  REQUIRE_UIT();

  if (state_ < WUR_STATE_DONE) {
    DoStateChange(WUR_STATE_ABORT);
    context_->cancel();
  }
}

void CefWebURLRequestImpl::DoStateChange(RequestState newState) {
  REQUIRE_UIT();

  if (state_ == newState)
    return;

  {
    AutoLock lock_scope(this);
    state_ = newState;
  }

  if (handler_.get())
    handler_->OnStateChange(this, newState);
}
