// 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/login/login_prompt.h"

#include <string>

#include "base/json/json_reader.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
#include "chrome/browser/ui/webui/constrained_html_ui.h"
#include "chrome/browser/ui/webui/html_dialog_ui.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/url_constants.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/size.h"

class LoginHandlerSource : public ChromeURLDataManager::DataSource {
 public:
  LoginHandlerSource()
      : DataSource(chrome::kChromeUIHttpAuthHost, MessageLoop::current()) {}

  virtual void StartDataRequest(const std::string& path,
                                bool is_off_the_record,
                                int request_id) {
    DictionaryValue dict;
    dict.SetString("username",
                   l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_USERNAME_FIELD));
    dict.SetString("password",
                   l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_PASSWORD_FIELD));
    dict.SetString("signin",
                   l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_OK_BUTTON_LABEL));
    dict.SetString("cancel", l10n_util::GetStringUTF16(IDS_CANCEL));

    SetFontAndTextDirection(&dict);

    const base::StringPiece html(
        ResourceBundle::GetSharedInstance().GetRawDataResource(
            IDR_HTTP_AUTH_HTML));
    std::string response = jstemplate_builder::GetI18nTemplateHtml(html, &dict);

    // Send the response.
    scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
    html_bytes->data.resize(response.size());
    std::copy(response.begin(), response.end(), html_bytes->data.begin());
    SendResponse(request_id, html_bytes);
  }

  virtual std::string GetMimeType(const std::string& path) const {
    return "text/html";
  }

  static void RegisterDataSource(Profile *profile) {
    ChromeURLDataManager* url_manager = profile->GetChromeURLDataManager();
    LoginHandlerSource *source = new LoginHandlerSource();
    url_manager->AddDataSource(source);
  }

 private:
  virtual ~LoginHandlerSource() {}

  DISALLOW_COPY_AND_ASSIGN(LoginHandlerSource);
};

class LoginHandlerHtml;

class LoginHandlerHtmlDelegate : public HtmlDialogUIDelegate,
                                 public WebUIMessageHandler {
 public:
  LoginHandlerHtmlDelegate(LoginHandlerHtml *login_handler,
                           TabContents *tab_contents,
                           const string16 explanation)
      : login_handler_(login_handler),
        tab_contents_(tab_contents),
        explanation_(UTF16ToUTF8(explanation)),
        closed_(false),
        has_autofill_(false),
        ready_for_autofill_(false) {
  }

  // HtmlDialogUIDelegate methods:
  virtual bool IsDialogModal() const {
    return true;
  }

  virtual std::wstring GetDialogTitle() const {
    return UTF16ToWide(l10n_util::GetStringUTF16(IDS_LOGIN_DIALOG_TITLE));
  }

  virtual GURL GetDialogContentURL() const {
    return GURL(chrome::kChromeUIHttpAuthURL);
  }

  virtual void GetWebUIMessageHandlers(
      std::vector<WebUIMessageHandler*>* handlers) const {
    const WebUIMessageHandler* handler = this;
    handlers->push_back(const_cast<WebUIMessageHandler*>(handler));
  }

  virtual void GetDialogSize(gfx::Size* size) const {
    size->set_width(kDialogWidth);
    size->set_height(kDialogHeight);
  }

  virtual std::string GetDialogArgs() const {
    return explanation_;
  }

  virtual void OnDialogClosed(const std::string& json_retval);

  virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) {}

  virtual bool ShouldShowDialogTitle() const {
    return true;
  }

  // WebUIMessageHandler method:
  virtual void RegisterMessages() {
    web_ui_->RegisterMessageCallback(
        "GetAutofill",
        NewCallback(this, &LoginHandlerHtmlDelegate::GetAutofill));
  }

  void ShowAutofillData(const std::wstring& username,
                        const std::wstring& password);

 private:
  // Send autofill data to HTML once the dialog is ready and the data is
  // available.
  void SendAutofillData();

  // Handle the request for autofill data from HTML.
  void GetAutofill(const ListValue* args) {
    ready_for_autofill_ = true;
    SendAutofillData();
  }

  LoginHandlerHtml *login_handler_;
  TabContents *tab_contents_;
  std::string explanation_;
  bool closed_;

  bool has_autofill_;
  bool ready_for_autofill_;
  std::string autofill_username_;
  std::string autofill_password_;

  static const int kDialogWidth = 400;
  static const int kDialogHeight = 130;

  DISALLOW_COPY_AND_ASSIGN(LoginHandlerHtmlDelegate);
};

class LoginHandlerHtml : public LoginHandler {
 public:
  LoginHandlerHtml(net::AuthChallengeInfo* auth_info, net::URLRequest* request)
      : LoginHandler(auth_info, request),
        delegate_(NULL) {
  }

  // LoginModelObserver method:
  virtual void OnAutofillDataAvailable(const std::wstring& username,
                                       const std::wstring& password) {
    if (delegate_)
      delegate_->ShowAutofillData(username, password);
  }

  // LoginHandler method:
  virtual void BuildViewForPasswordManager(PasswordManager* manager,
                                           const string16& explanation);

  friend class LoginHandlerHtmlDelegate;

 protected:
  virtual ~LoginHandlerHtml() {}

 private:
  LoginHandlerHtmlDelegate *delegate_;

  void FreeAndRelease() {
    SetDialog(NULL);
    SetModel(NULL);
    ReleaseSoon();
  }

  DISALLOW_COPY_AND_ASSIGN(LoginHandlerHtml);
};

void LoginHandlerHtmlDelegate::OnDialogClosed(const std::string& json_retval) {
  if (closed_)
    return;
  closed_ = true;

  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json_retval, false));

  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) {
    login_handler_->CancelAuth();
  } else {
    DictionaryValue* res = static_cast<DictionaryValue*>(parsed_value.get());
    string16 username;
    string16 password;

    if (!res->GetString("username", &username) ||
        !res->GetString("password", &password)) {
      login_handler_->CancelAuth();
    } else {
      login_handler_->SetAuth(username, password);
    }
  }

  login_handler_->FreeAndRelease();
}

void LoginHandlerHtmlDelegate::ShowAutofillData(const std::wstring& username,
                                                const std::wstring& password) {
  autofill_username_ = WideToUTF8(username);
  autofill_password_ = WideToUTF8(password);
  has_autofill_ = true;
  SendAutofillData();
}

void LoginHandlerHtmlDelegate::SendAutofillData() {
  if (!closed_ && web_ui_ && has_autofill_ && ready_for_autofill_) {
    StringValue username_v(autofill_username_);
    StringValue password_v(autofill_password_);
    web_ui_->CallJavascriptFunction("setAutofillCredentials",
                                    username_v, password_v);
  }
}

void LoginHandlerHtml::BuildViewForPasswordManager(
    PasswordManager* manager, const string16& explanation) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  LOG(INFO) << "BuildViewForPasswordManager";

  TabContents* tab_contents = GetTabContentsForLogin();
  LoginHandlerSource::RegisterDataSource(tab_contents->profile());
  delegate_ = new LoginHandlerHtmlDelegate(this, tab_contents, explanation);
  ConstrainedWindow* dialog = ConstrainedHtmlUI::CreateConstrainedHtmlDialog(
      tab_contents->profile(), delegate_, tab_contents);

  SetModel(manager);
  SetDialog(dialog);

  NotifyAuthNeeded();
}

// static
LoginHandler* LoginHandler::Create(net::AuthChallengeInfo* auth_info,
                                   net::URLRequest* request) {
  return new LoginHandlerHtml(auth_info, request);
}