// 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/ui/webui/chrome_url_data_manager.h"

#include <vector>

#include "base/i18n/rtl.h"
#include "base/memory/ref_counted_memory.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "base/synchronization/lock.h"
#include "base/values.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/chrome_url_data_manager_backend.h"
#include "content/browser/browser_thread.h"
#include "grit/platform_locale_settings.h"
#include "ui/base/l10n/l10n_util.h"

#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif

// static
base::Lock ChromeURLDataManager::delete_lock_;

// static
ChromeURLDataManager::DataSources* ChromeURLDataManager::data_sources_ = NULL;

// Invoked on the IO thread to do the actual adding of the DataSource.
static void AddDataSourceOnIOThread(
    scoped_refptr<net::URLRequestContextGetter> context_getter,
    scoped_refptr<ChromeURLDataManager::DataSource> data_source) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  static_cast<ChromeURLRequestContext*>(
      context_getter->GetURLRequestContext())->
      GetChromeURLDataManagerBackend()->AddDataSource(data_source.get());
}

ChromeURLDataManager::ChromeURLDataManager(Profile* profile)
    : profile_(profile) {
}

ChromeURLDataManager::~ChromeURLDataManager() {
}

void ChromeURLDataManager::AddDataSource(DataSource* source) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableFunction(AddDataSourceOnIOThread,
                          make_scoped_refptr(profile_->GetRequestContext()),
                          make_scoped_refptr(source)));
}

// static
void ChromeURLDataManager::DeleteDataSources() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DataSources sources;
  {
    base::AutoLock lock(delete_lock_);
    if (!data_sources_)
      return;
    data_sources_->swap(sources);
  }
  for (size_t i = 0; i < sources.size(); ++i)
    delete sources[i];
}

// static
void ChromeURLDataManager::DeleteDataSource(const DataSource* data_source) {
  // Invoked when a DataSource is no longer referenced and needs to be deleted.
  if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    // We're on the UI thread, delete right away.
    delete data_source;
    return;
  }

  // We're not on the UI thread, add the DataSource to the list of DataSources
  // to delete.
  bool schedule_delete = false;
  {
    base::AutoLock lock(delete_lock_);
    if (!data_sources_)
      data_sources_ = new DataSources();
    schedule_delete = data_sources_->empty();
    data_sources_->push_back(data_source);
  }
  if (schedule_delete) {
    // Schedule a task to delete the DataSource back on the UI thread.
    BrowserThread::PostTask(BrowserThread::UI,
                            FROM_HERE,
                            NewRunnableFunction(
                                &ChromeURLDataManager::DeleteDataSources));
  }
}

// static
bool ChromeURLDataManager::IsScheduledForDeletion(
    const DataSource* data_source) {
  base::AutoLock lock(delete_lock_);
  if (!data_sources_)
    return false;
  return std::find(data_sources_->begin(), data_sources_->end(), data_source) !=
      data_sources_->end();
}

ChromeURLDataManager::DataSource::DataSource(const std::string& source_name,
                                             MessageLoop* message_loop)
    : source_name_(source_name),
      message_loop_(message_loop),
      backend_(NULL) {
}

ChromeURLDataManager::DataSource::~DataSource() {
}

void ChromeURLDataManager::DataSource::SendResponse(int request_id,
                                                    RefCountedMemory* bytes) {
  if (IsScheduledForDeletion(this)) {
    // We're scheduled for deletion. Servicing the request would result in
    // this->AddRef being invoked, even though the ref count is 0 and 'this' is
    // about to be deleted. If the AddRef were allowed through, when 'this' is
    // released it would be deleted again.
    //
    // This scenario occurs with DataSources that make history requests. Such
    // DataSources do a history query in |StartDataRequest| and the request is
    // live until the object is deleted (history requests don't up the ref
    // count). This means it's entirely possible for the DataSource to invoke
    // |SendResponse| between the time when there are no more refs and the time
    // when the object is deleted.
    return;
  }
  BrowserThread::PostTask(
      BrowserThread::IO, FROM_HERE,
      NewRunnableMethod(this, &DataSource::SendResponseOnIOThread,
                        request_id, make_scoped_refptr(bytes)));
}

MessageLoop* ChromeURLDataManager::DataSource::MessageLoopForRequestPath(
    const std::string& path) const {
  return message_loop_;
}

bool ChromeURLDataManager::DataSource::ShouldReplaceExistingSource() const {
  return true;
}

// static
void ChromeURLDataManager::DataSource::SetFontAndTextDirection(
    DictionaryValue* localized_strings) {
  localized_strings->SetString("fontfamily",
      l10n_util::GetStringUTF16(IDS_WEB_FONT_FAMILY));

  int web_font_size_id = IDS_WEB_FONT_SIZE;
#if defined(OS_WIN)
  // Some fonts used for some languages changed a lot in terms of the font
  // metric in Vista. So, we need to use different size before Vista.
  if (base::win::GetVersion() < base::win::VERSION_VISTA)
    web_font_size_id = IDS_WEB_FONT_SIZE_XP;
#endif
  localized_strings->SetString("fontsize",
      l10n_util::GetStringUTF16(web_font_size_id));

  localized_strings->SetString("textdirection",
      base::i18n::IsRTL() ? "rtl" : "ltr");
}

void ChromeURLDataManager::DataSource::SendResponseOnIOThread(
    int request_id,
    scoped_refptr<RefCountedMemory> bytes) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  if (backend_)
    backend_->DataAvailable(request_id, bytes);
}