// 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/printing/print_dialog_cloud.h"
#include "chrome/browser/printing/print_dialog_cloud_internal.h"

#include <functional>

#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/threading/thread_restrictions.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/in_process_browser_test.h"
#include "chrome/test/ui_test_utils.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "net/url_request/url_request_filter.h"
#include "net/url_request/url_request_test_job.h"
#include "net/url_request/url_request_test_util.h"

namespace {

class TestData {
 public:
  static TestData* GetInstance() {
    return Singleton<TestData>::get();
  }

  const char* GetTestData() {
    // Fetching this data blocks the IO thread, but we don't really care because
    // this is a test.
    base::ThreadRestrictions::ScopedAllowIO allow_io;

    if (test_data_.empty()) {
      FilePath test_data_directory;
      PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
      FilePath test_file =
          test_data_directory.AppendASCII("printing/cloud_print_uitest.html");
      file_util::ReadFileToString(test_file, &test_data_);
    }
    return test_data_.c_str();
  }
 private:
  TestData() {}

  std::string test_data_;

  friend struct DefaultSingletonTraits<TestData>;
};

// A simple test net::URLRequestJob. We don't care what it does, only that
// whether it starts and finishes.
class SimpleTestJob : public net::URLRequestTestJob {
 public:
  explicit SimpleTestJob(net::URLRequest* request)
      : net::URLRequestTestJob(request, test_headers(),
                               TestData::GetInstance()->GetTestData(), true) {}

  virtual void GetResponseInfo(net::HttpResponseInfo* info) {
    net::URLRequestTestJob::GetResponseInfo(info);
    if (request_->url().SchemeIsSecure()) {
      // Make up a fake certificate for this response since we don't have
      // access to the real SSL info.
      const char* kCertIssuer = "Chrome Internal";
      const int kLifetimeDays = 100;

      info->ssl_info.cert =
          new net::X509Certificate(request_->url().GetWithEmptyPath().spec(),
                                   kCertIssuer,
                                   base::Time::Now(),
                                   base::Time::Now() +
                                   base::TimeDelta::FromDays(kLifetimeDays));
      info->ssl_info.cert_status = 0;
      info->ssl_info.security_bits = -1;
    }
  }

 private:
  ~SimpleTestJob() {}
};

class TestController {
 public:
  static TestController* GetInstance() {
    return Singleton<TestController>::get();
  }
  void set_result(bool value) {
    result_ = value;
  }
  bool result() {
    return result_;
  }
  void set_expected_url(const GURL& url) {
    expected_url_ = url;
  }
  const GURL expected_url() {
    return expected_url_;
  }
  void set_delegate(TestDelegate* delegate) {
    delegate_ = delegate;
  }
  TestDelegate* delegate() {
    return delegate_;
  }
  void set_use_delegate(bool value) {
    use_delegate_ = value;
  }
  bool use_delegate() {
    return use_delegate_;
  }
 private:
  TestController()
      : result_(false),
        use_delegate_(false),
        delegate_(NULL) {}

  bool result_;
  bool use_delegate_;
  GURL expected_url_;
  TestDelegate* delegate_;

  friend struct DefaultSingletonTraits<TestController>;
};

}  // namespace

class PrintDialogCloudTest : public InProcessBrowserTest {
 public:
  PrintDialogCloudTest() : handler_added_(false) {
    PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_);
  }

  // Must be static for handing into AddHostnameHandler.
  static net::URLRequest::ProtocolFactory Factory;

  class AutoQuitDelegate : public TestDelegate {
   public:
    AutoQuitDelegate() {}

    virtual void OnResponseCompleted(net::URLRequest* request) {
      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                              new MessageLoop::QuitTask());
    }
  };

  virtual void SetUp() {
    TestController::GetInstance()->set_result(false);
    InProcessBrowserTest::SetUp();
  }

  virtual void TearDown() {
    if (handler_added_) {
      net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance();
      filter->RemoveHostnameHandler(scheme_, host_name_);
      handler_added_ = false;
      TestController::GetInstance()->set_delegate(NULL);
    }
    InProcessBrowserTest::TearDown();
  }

  // Normally this is something I would expect could go into SetUp(),
  // but there seems to be some timing or ordering related issue with
  // the test harness that made that flaky.  Calling this from the
  // individual test functions seems to fix that.
  void AddTestHandlers() {
    if (!handler_added_) {
      net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance();
      GURL cloud_print_service_url =
          CloudPrintURL(browser()->profile()).
          GetCloudPrintServiceURL();
      scheme_ = cloud_print_service_url.scheme();
      host_name_ = cloud_print_service_url.host();
      filter->AddHostnameHandler(scheme_, host_name_,
                                 &PrintDialogCloudTest::Factory);
      handler_added_ = true;

      GURL cloud_print_dialog_url =
          CloudPrintURL(browser()->profile()).
          GetCloudPrintServiceDialogURL();
      TestController::GetInstance()->set_expected_url(cloud_print_dialog_url);
      TestController::GetInstance()->set_delegate(&delegate_);
    }

    CreateDialogForTest();
  }

  void CreateDialogForTest() {
    FilePath path_to_pdf =
        test_data_directory_.AppendASCII("printing/cloud_print_uitest.pdf");
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        NewRunnableFunction(&internal_cloud_print_helpers::CreateDialogImpl,
                            path_to_pdf,
                            string16(),
                            std::string("application/pdf"),
                            true));
  }

  bool handler_added_;
  std::string scheme_;
  std::string host_name_;
  FilePath test_data_directory_;
  AutoQuitDelegate delegate_;
};

net::URLRequestJob* PrintDialogCloudTest::Factory(net::URLRequest* request,
                                                  const std::string& scheme) {
  if (TestController::GetInstance()->use_delegate())
    request->set_delegate(TestController::GetInstance()->delegate());
  if (request &&
      (request->url() == TestController::GetInstance()->expected_url())) {
    TestController::GetInstance()->set_result(true);
  }
  return new SimpleTestJob(request);
}

IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, HandlersRegistered) {
  BrowserList::SetLastActive(browser());
  ASSERT_TRUE(BrowserList::GetLastActive());

  AddTestHandlers();

  TestController::GetInstance()->set_use_delegate(true);

  ui_test_utils::RunMessageLoop();

  ASSERT_TRUE(TestController::GetInstance()->result());
}

#if defined(OS_CHROMEOS)
// Disabled until the extern URL is live so that the Print menu item
// can be enabled for Chromium OS.
IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, DISABLED_DialogGrabbed) {
  BrowserList::SetLastActive(browser());
  ASSERT_TRUE(BrowserList::GetLastActive());

  AddTestHandlers();

  // This goes back one step further for the Chrome OS case, to making
  // sure 'window.print()' gets to the right place.
  ASSERT_TRUE(browser()->GetSelectedTabContents());
  ASSERT_TRUE(browser()->GetSelectedTabContents()->render_view_host());

  string16 window_print = ASCIIToUTF16("window.print()");
  browser()->GetSelectedTabContents()->render_view_host()->
      ExecuteJavascriptInWebFrame(string16(), window_print);

  ui_test_utils::RunMessageLoop();

  ASSERT_TRUE(TestController::GetInstance()->result());
}
#endif