// 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/policy/device_management_service.h"

#include "chrome/browser/io_thread.h"
#include "chrome/browser/net/chrome_net_log.h"
#include "chrome/browser/policy/device_management_backend_impl.h"
#include "content/browser/browser_thread.h"
#include "net/base/cookie_monster.h"
#include "net/base/host_resolver.h"
#include "net/base/load_flags.h"
#include "net/base/ssl_config_service_defaults.h"
#include "net/http/http_auth_handler_factory.h"
#include "net/http/http_network_layer.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
#include "webkit/glue/webkit_glue.h"

namespace policy {

namespace {

// Custom request context implementation that allows to override the user agent,
// amongst others. Wraps a baseline request context from which we reuse the
// networking components.
class DeviceManagementRequestContext : public net::URLRequestContext {
 public:
  explicit DeviceManagementRequestContext(net::URLRequestContext* base_context);
  virtual ~DeviceManagementRequestContext();

 private:
  // Overridden from net::URLRequestContext:
  virtual const std::string& GetUserAgent(const GURL& url) const;
};

DeviceManagementRequestContext::DeviceManagementRequestContext(
    net::URLRequestContext* base_context) {
  // Share resolver, proxy service and ssl bits with the baseline context. This
  // is important so we don't make redundant requests (e.g. when resolving proxy
  // auto configuration).
  set_net_log(base_context->net_log());
  set_host_resolver(base_context->host_resolver());
  set_proxy_service(base_context->proxy_service());
  set_ssl_config_service(base_context->ssl_config_service());

  // Share the http session.
  set_http_transaction_factory(
      new net::HttpNetworkLayer(
          base_context->http_transaction_factory()->GetSession()));

  // No cookies, please.
  set_cookie_store(new net::CookieMonster(NULL, NULL));

  // Initialize these to sane values for our purposes.
  set_accept_language("*");
  set_accept_charset("*");
}

DeviceManagementRequestContext::~DeviceManagementRequestContext() {
  delete http_transaction_factory();
}

const std::string& DeviceManagementRequestContext::GetUserAgent(
    const GURL& url) const {
  return webkit_glue::GetUserAgent(url);
}

// Request context holder.
class DeviceManagementRequestContextGetter
    : public net::URLRequestContextGetter {
 public:
  DeviceManagementRequestContextGetter(
      net::URLRequestContextGetter* base_context_getter)
      : base_context_getter_(base_context_getter) {}

  // Overridden from net::URLRequestContextGetter:
  virtual net::URLRequestContext* GetURLRequestContext();
  virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const;

 private:
  scoped_refptr<net::URLRequestContext> context_;
  scoped_refptr<net::URLRequestContextGetter> base_context_getter_;
};


net::URLRequestContext*
DeviceManagementRequestContextGetter::GetURLRequestContext() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (!context_) {
    context_ = new DeviceManagementRequestContext(
        base_context_getter_->GetURLRequestContext());
  }

  return context_.get();
}

scoped_refptr<base::MessageLoopProxy>
DeviceManagementRequestContextGetter::GetIOMessageLoopProxy() const {
  return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
}

}  // namespace

DeviceManagementService::~DeviceManagementService() {
  // All running jobs should have been canceled by now. If not, there are
  // backend objects still around, which is an error.
  DCHECK(pending_jobs_.empty());
  DCHECK(queued_jobs_.empty());
}

DeviceManagementBackend* DeviceManagementService::CreateBackend() {
  return new DeviceManagementBackendImpl(this);
}

void DeviceManagementService::Initialize(
    net::URLRequestContextGetter* request_context_getter) {
  DCHECK(!request_context_getter_);
  request_context_getter_ =
      new DeviceManagementRequestContextGetter(request_context_getter);
  while (!queued_jobs_.empty()) {
    StartJob(queued_jobs_.front());
    queued_jobs_.pop_front();
  }
}

void DeviceManagementService::Shutdown() {
  for (JobFetcherMap::iterator job(pending_jobs_.begin());
       job != pending_jobs_.end();
       ++job) {
    delete job->first;
    queued_jobs_.push_back(job->second);
  }
  pending_jobs_.clear();
}

DeviceManagementService::DeviceManagementService(
    const std::string& server_url)
    : server_url_(server_url) {
}

void DeviceManagementService::AddJob(DeviceManagementJob* job) {
  if (request_context_getter_.get())
    StartJob(job);
  else
    queued_jobs_.push_back(job);
}

void DeviceManagementService::RemoveJob(DeviceManagementJob* job) {
  for (JobFetcherMap::iterator entry(pending_jobs_.begin());
       entry != pending_jobs_.end();
       ++entry) {
    if (entry->second == job) {
      delete entry->first;
      pending_jobs_.erase(entry);
      return;
    }
  }

  const JobQueue::iterator elem =
      std::find(queued_jobs_.begin(), queued_jobs_.end(), job);
  if (elem != queued_jobs_.end())
    queued_jobs_.erase(elem);
}

void DeviceManagementService::StartJob(DeviceManagementJob* job) {
  URLFetcher* fetcher = URLFetcher::Create(0, job->GetURL(server_url_),
                                           URLFetcher::POST, this);
  fetcher->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
                          net::LOAD_DO_NOT_SAVE_COOKIES |
                          net::LOAD_DISABLE_CACHE);
  fetcher->set_request_context(request_context_getter_.get());
  job->ConfigureRequest(fetcher);
  pending_jobs_[fetcher] = job;
  fetcher->Start();
}

void DeviceManagementService::OnURLFetchComplete(
    const URLFetcher* source,
    const GURL& url,
    const net::URLRequestStatus& status,
    int response_code,
    const ResponseCookies& cookies,
    const std::string& data) {
  JobFetcherMap::iterator entry(pending_jobs_.find(source));
  if (entry != pending_jobs_.end()) {
    DeviceManagementJob* job = entry->second;
    pending_jobs_.erase(entry);
    job->HandleResponse(status, response_code, cookies, data);
  } else {
    NOTREACHED() << "Callback from foreign URL fetcher";
  }
  delete source;
}

}  // namespace policy