// Copyright (c) 2011 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/cros/libcros_service_library.h"

#include "base/synchronization/lock.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/profiles/profile.h"
#include "content/browser/browser_thread.h"
#include "cros/chromeos_libcros_service.h"
#include "net/base/net_errors.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

namespace chromeos {

class LibCrosServiceLibraryImpl : public LibCrosServiceLibrary {
 public:
  // Base class for all services of LibCrosService.
  // Each subclass should declare DISABLE_RUNNABLE_METHOD_REFCOUNT to disable
  // refcounting, which is okay since the subclass's object will exist as a
  // scoped_ptr in Singleton LibCrosServiceLibraryImpl, guaranteeing that its
  // lifetime is longer than that of any message loop.
  class ServicingLibrary {
   public:
    explicit ServicingLibrary(LibCrosServiceConnection service_connection);
    virtual ~ServicingLibrary();

    // Clears service_connection_ (which is stored as weak pointer) so that it
    // can't be used anymore.
    virtual void ClearServiceConnection();

   protected:
    LibCrosServiceConnection service_connection_;  // Weak pointer.
    // Lock for data members to synchronize access on multiple threads.
    base::Lock data_lock_;

   private:
    DISALLOW_COPY_AND_ASSIGN(ServicingLibrary);
  };

  // Library that provides network proxy service for LibCrosService.
  // For now, it only processes proxy resolution requests for ChromeOS clients.
  class NetworkProxyLibrary : public ServicingLibrary {
   public:
    explicit NetworkProxyLibrary(LibCrosServiceConnection connection);
    virtual ~NetworkProxyLibrary();

   private:
    // Data being used in one proxy resolution.
    class Request {
     public:
      explicit Request(const std::string& source_url);
      virtual ~Request() {}

      // Callback on IO thread for when net::ProxyService::ResolveProxy
      // completes, synchronously or asynchronously.
      void OnCompletion(int result);
      net::CompletionCallbackImpl<Request> completion_callback_;

      std::string source_url_;  // URL being resolved.
      int result_;  // Result of proxy resolution.
      net::ProxyInfo proxy_info_;  // ProxyInfo resolved for source_url_.
      std::string error_;  // Error from proxy resolution.
      Task* notify_task_;  // Task to notify of resolution result.

     private:
      DISALLOW_COPY_AND_ASSIGN(Request);
    };

    // Static callback passed to LibCrosService to be invoked when ChromeOS
    // clients send network proxy resolution requests to the service running in
    // chrome executable.  Called on UI thread from dbus request.
    static void ResolveProxyHandler(void* object, const char* source_url);

    void ResolveProxy(const std::string& source_url);

    // Wrapper on UI thread to call LibCrosService::NotifyNetworkProxyResolved.
    void NotifyProxyResolved(Request* request);

    std::vector<Request*> all_requests_;

    DISALLOW_COPY_AND_ASSIGN(NetworkProxyLibrary);
  };

  LibCrosServiceLibraryImpl();
  virtual ~LibCrosServiceLibraryImpl();

  // LibCrosServiceLibrary implementation.

  // Starts LibCrosService running on dbus if not already started.
  virtual void StartService();

 private:
  // Connection to LibCrosService.
  LibCrosServiceConnection service_connection_;

  // Libraries that form LibCrosService.
  scoped_ptr<NetworkProxyLibrary> network_proxy_lib_;

  DISALLOW_COPY_AND_ASSIGN(LibCrosServiceLibraryImpl);
};

//---------------- LibCrosServiceLibraryImpl: public ---------------------------

LibCrosServiceLibraryImpl::LibCrosServiceLibraryImpl()
    : service_connection_(NULL) {
  if (!CrosLibrary::Get()->EnsureLoaded()) {
    LOG(ERROR) << "Cros library has not been loaded.";
  }
}

LibCrosServiceLibraryImpl::~LibCrosServiceLibraryImpl() {
  if (service_connection_) {
    // Clear service connections in servicing libraries which held the former
    // as weak pointers.
    if (network_proxy_lib_.get())
      network_proxy_lib_->ClearServiceConnection();

    StopLibCrosService(service_connection_);
    VLOG(1) << "LibCrosService stopped.";
    service_connection_ = NULL;
  }
}

// Called on UI thread to start service for LibCrosService.
void LibCrosServiceLibraryImpl::StartService() {
  // Make sure we're running on UI thread.
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
        NewRunnableMethod(this,
                          &LibCrosServiceLibraryImpl::StartService));
    return;
  }
  if (service_connection_)  // Service has already been started.
    return;
  // Starts LibCrosService; the returned connection is used for future
  // interactions with the service.
  service_connection_ = StartLibCrosService();
  if (!service_connection_) {
    LOG(WARNING) << "Error starting LibCrosService";
    return;
  }
  network_proxy_lib_.reset(new NetworkProxyLibrary(service_connection_));
  VLOG(1) << "LibCrosService started.";
}

//------------- LibCrosServiceLibraryImpl::ServicingLibrary: public ------------

LibCrosServiceLibraryImpl::ServicingLibrary::ServicingLibrary(
    LibCrosServiceConnection connection)
    : service_connection_(connection) {
}

LibCrosServiceLibraryImpl::ServicingLibrary::~ServicingLibrary() {
  ClearServiceConnection();
}

void LibCrosServiceLibraryImpl::ServicingLibrary::ClearServiceConnection() {
  base::AutoLock lock(data_lock_);
  service_connection_ = NULL;
}

//----------- LibCrosServiceLibraryImpl::NetworkProxyLibrary: public -----------

LibCrosServiceLibraryImpl::NetworkProxyLibrary::NetworkProxyLibrary(
    LibCrosServiceConnection connection)
    : ServicingLibrary(connection) {
  // Register callback for LibCrosService::ResolveNetworkProxy.
  SetNetworkProxyResolver(&ResolveProxyHandler, this, service_connection_);
}

LibCrosServiceLibraryImpl::NetworkProxyLibrary::~NetworkProxyLibrary() {
  base::AutoLock lock(data_lock_);
  if (!all_requests_.empty()) {
    for (size_t i = all_requests_.size() - 1;  i >= 0; --i) {
      LOG(WARNING) << "Pending request for " << all_requests_[i]->source_url_;
      delete all_requests_[i];
    }
    all_requests_.clear();
  }
}

//----------- LibCrosServiceLibraryImpl::NetworkProxyLibrary: private ----------

// Static, called on UI thread from LibCrosService::ResolveProxy via dbus.
void LibCrosServiceLibraryImpl::NetworkProxyLibrary::ResolveProxyHandler(
    void* object, const char* source_url) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  NetworkProxyLibrary* lib = static_cast<NetworkProxyLibrary*>(object);
  // source_url will be freed when this function returns, so make a copy of it.
  std::string url(source_url);
  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
      NewRunnableMethod(lib, &NetworkProxyLibrary::ResolveProxy, url));
}

// Called on IO thread as task posted from ResolveProxyHandler on UI thread.
void LibCrosServiceLibraryImpl::NetworkProxyLibrary::ResolveProxy(
    const std::string& source_url) {
  // Make sure we're running on IO thread.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Create a request slot for this proxy resolution request.
  Request* request = new Request(source_url);
  request->notify_task_ = NewRunnableMethod(this,
      &NetworkProxyLibrary::NotifyProxyResolved, request);

  // Retrieve ProxyService from profile's request context.
  net::URLRequestContextGetter* getter = Profile::GetDefaultRequestContext();
  net::ProxyService* proxy_service = NULL;
  if (getter)
    proxy_service = getter->GetURLRequestContext()->proxy_service();

  // Check that we have valid proxy service and service_connection.
  if (!proxy_service) {
     request->error_ = "No proxy service in chrome";
  } else {
    base::AutoLock lock(data_lock_);
    // Queue request slot.
    all_requests_.push_back(request);
    if (!service_connection_)
      request->error_ = "LibCrosService not started";
  }
  if (request->error_ != "") {  // Error string was just set.
    LOG(ERROR) << request->error_;
    request->result_ = net::OK;  // Set to OK since error string is set.
  } else {
    VLOG(1) << "Starting networy proxy resolution for " << request->source_url_;
    request->result_ = proxy_service->ResolveProxy(
        GURL(request->source_url_), &request->proxy_info_,
        &request->completion_callback_, NULL, net::BoundNetLog());
  }
  if (request->result_ != net::ERR_IO_PENDING) {
    VLOG(1) << "Network proxy resolution completed synchronously.";
    request->OnCompletion(request->result_);
  }
}

// Called on UI thread as task posted from Request::OnCompletion on IO thread.
void LibCrosServiceLibraryImpl::NetworkProxyLibrary::NotifyProxyResolved(
    Request* request) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  base::AutoLock lock(data_lock_);
  if (service_connection_) {
    if (!NotifyNetworkProxyResolved(request->source_url_.c_str(),
                                    request->proxy_info_.ToPacString().c_str(),
                                    request->error_.c_str(),
                                    service_connection_)) {
      LOG(ERROR) << "LibCrosService has error with NotifyNetworkProxyResolved";
    } else {
      VLOG(1) << "LibCrosService has notified proxy resoloution for "
              << request->source_url_;
    }
  }
  std::vector<Request*>::iterator iter =
      std::find(all_requests_.begin(), all_requests_.end(), request);
  DCHECK(iter != all_requests_.end());
  all_requests_.erase(iter);
  delete request;
}

//---------- LibCrosServiceLibraryImpl::NetworkProxyLibrary::Request -----------

LibCrosServiceLibraryImpl::NetworkProxyLibrary::Request::Request(
    const std::string& source_url)
    : ALLOW_THIS_IN_INITIALIZER_LIST(
          completion_callback_(this, &Request::OnCompletion)),
      source_url_(source_url),
      result_(net::ERR_FAILED),
      notify_task_(NULL) {
}

// Called on IO thread when net::ProxyService::ResolveProxy has completed.
void LibCrosServiceLibraryImpl::NetworkProxyLibrary::Request::OnCompletion(
    int result) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  result_ = result;
  if (result_ != net::OK)
    error_ = net::ErrorToString(result_);
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, notify_task_);
  notify_task_ = NULL;
}

//--------------------- LibCrosServiceLibraryStubImpl --------------------------

class LibCrosServiceLibraryStubImpl : public LibCrosServiceLibrary {
 public:
  LibCrosServiceLibraryStubImpl() {}
  virtual ~LibCrosServiceLibraryStubImpl() {}

  // LibCrosServiceLibrary overrides.
  virtual void StartService() {}

  DISALLOW_COPY_AND_ASSIGN(LibCrosServiceLibraryStubImpl);
};

//--------------------------- LibCrosServiceLibrary ----------------------------

// Static.
LibCrosServiceLibrary* LibCrosServiceLibrary::GetImpl(bool stub) {
  if (stub)
    return new LibCrosServiceLibraryStubImpl();
  return new LibCrosServiceLibraryImpl();
}

}  // namespace chromeos

// Allows InvokeLater without adding refcounting. This class is a Singleton and
// won't be deleted until it's last InvokeLater is run, so are all its
// scoped_ptred class members.
DISABLE_RUNNABLE_METHOD_REFCOUNT(chromeos::LibCrosServiceLibraryImpl);
DISABLE_RUNNABLE_METHOD_REFCOUNT(
    chromeos::LibCrosServiceLibraryImpl::NetworkProxyLibrary);