// Copyright 2013 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/download/download_danger_prompt.h"

#include "base/compiler_specific.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/extensions/api/experience_sampling_private/experience_sampling.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_danger_type.h"
#include "content/public/browser/download_item.h"
#include "grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"

using extensions::ExperienceSamplingEvent;

namespace {

const int kMessageWidth = 320;
const int kParagraphPadding = 15;

// Views-specific implementation of download danger prompt dialog. We use this
// class rather than a TabModalConfirmDialog so that we can use custom
// formatting on the text in the body of the dialog.
class DownloadDangerPromptViews : public DownloadDangerPrompt,
                                  public content::DownloadItem::Observer,
                                  public views::DialogDelegate {
 public:
  DownloadDangerPromptViews(content::DownloadItem* item,
                            bool show_context,
                            const OnDone& done);

  // DownloadDangerPrompt methods:
  void InvokeActionForTesting(Action action) override;

  // views::DialogDelegate methods:
  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
  base::string16 GetWindowTitle() const override;
  void DeleteDelegate() override;
  ui::ModalType GetModalType() const override;
  bool Cancel() override;
  bool Accept() override;
  bool Close() override;
  views::View* GetContentsView() override;
  views::Widget* GetWidget() override;
  const views::Widget* GetWidget() const override;

  // content::DownloadItem::Observer:
  void OnDownloadUpdated(content::DownloadItem* download) override;

 private:
  base::string16 GetAcceptButtonTitle() const;
  base::string16 GetCancelButtonTitle() const;
  // The message lead is separated from the main text and is bolded.
  base::string16 GetMessageLead() const;
  base::string16 GetMessageBody() const;
  void RunDone(Action action);

  content::DownloadItem* download_;
  bool show_context_;
  OnDone done_;

  scoped_ptr<ExperienceSamplingEvent> sampling_event_;

  views::View* contents_view_;
};

DownloadDangerPromptViews::DownloadDangerPromptViews(
    content::DownloadItem* item,
    bool show_context,
    const OnDone& done)
    : download_(item),
      show_context_(show_context),
      done_(done),
      contents_view_(NULL) {
  DCHECK(!done_.is_null());
  download_->AddObserver(this);

  contents_view_ = new views::View;

  views::GridLayout* layout = views::GridLayout::CreatePanel(contents_view_);
  contents_view_->SetLayoutManager(layout);

  views::ColumnSet* column_set = layout->AddColumnSet(0);
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::FIXED, kMessageWidth, 0);

  const base::string16 message_lead = GetMessageLead();

  if (!message_lead.empty()) {
    ui::ResourceBundle* rb = &ui::ResourceBundle::GetSharedInstance();
    views::Label* message_lead_label = new views::Label(
        message_lead, rb->GetFontList(ui::ResourceBundle::BoldFont));
    message_lead_label->SetMultiLine(true);
    message_lead_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    message_lead_label->SetAllowCharacterBreak(true);

    layout->StartRow(0, 0);
    layout->AddView(message_lead_label);

    layout->AddPaddingRow(0, kParagraphPadding);
  }

  views::Label* message_body_label = new views::Label(GetMessageBody());
  message_body_label->SetMultiLine(true);
  message_body_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  message_body_label->SetAllowCharacterBreak(true);

  layout->StartRow(0, 0);
  layout->AddView(message_body_label);

  RecordOpenedDangerousConfirmDialog(download_->GetDangerType());

  // ExperienceSampling: A malicious download warning is being shown to the
  // user, so we start a new SamplingEvent and track it.
  sampling_event_.reset(new ExperienceSamplingEvent(
      ExperienceSamplingEvent::kDownloadDangerPrompt,
      item->GetURL(),
      item->GetReferrerUrl(),
      item->GetBrowserContext()));
}

// DownloadDangerPrompt methods:
void DownloadDangerPromptViews::InvokeActionForTesting(Action action) {
  switch (action) {
    case ACCEPT:
      // This inversion is intentional.
      Cancel();
      break;

    case DISMISS:
      Close();
      break;

    case CANCEL:
      Accept();
      break;

    default:
      NOTREACHED();
      break;
  }
}

// views::DialogDelegate methods:
base::string16 DownloadDangerPromptViews::GetDialogButtonLabel(
    ui::DialogButton button) const {
  switch (button) {
    case ui::DIALOG_BUTTON_OK:
      return GetAcceptButtonTitle();

    case ui::DIALOG_BUTTON_CANCEL:
      return GetCancelButtonTitle();

    default:
      return DialogDelegate::GetDialogButtonLabel(button);
  }
}

base::string16 DownloadDangerPromptViews::GetWindowTitle() const {
  if (show_context_)
    return l10n_util::GetStringUTF16(IDS_CONFIRM_KEEP_DANGEROUS_DOWNLOAD_TITLE);
  else
    return l10n_util::GetStringUTF16(IDS_RESTORE_KEEP_DANGEROUS_DOWNLOAD_TITLE);
}

void DownloadDangerPromptViews::DeleteDelegate() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  delete this;
}

ui::ModalType DownloadDangerPromptViews::GetModalType() const {
  return ui::MODAL_TYPE_CHILD;
}

bool DownloadDangerPromptViews::Accept() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // ExperienceSampling: User did not proceed down the dangerous path.
  sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
  // Note that the presentational concept of "Accept/Cancel" is inverted from
  // the model's concept of ACCEPT/CANCEL. In the UI, the safe path is "Accept"
  // and the dangerous path is "Cancel".
  RunDone(CANCEL);
  return true;
}

bool DownloadDangerPromptViews::Cancel() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // ExperienceSampling: User proceeded down the dangerous path.
  sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kProceed);
  RunDone(ACCEPT);
  return true;
}

bool DownloadDangerPromptViews::Close() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // ExperienceSampling: User did not proceed down the dangerous path.
  sampling_event_->CreateUserDecisionEvent(ExperienceSamplingEvent::kDeny);
  RunDone(DISMISS);
  return true;
}

views::View* DownloadDangerPromptViews::GetContentsView() {
  return contents_view_;
}

views::Widget* DownloadDangerPromptViews::GetWidget() {
  return contents_view_->GetWidget();
}

const views::Widget* DownloadDangerPromptViews::GetWidget() const {
  return contents_view_->GetWidget();
}

// content::DownloadItem::Observer:
void DownloadDangerPromptViews::OnDownloadUpdated(
    content::DownloadItem* download) {
  // If the download is nolonger dangerous (accepted externally) or the download
  // is in a terminal state, then the download danger prompt is no longer
  // necessary.
  if (!download_->IsDangerous() || download_->IsDone()) {
    RunDone(DISMISS);
    Cancel();
  }
}

base::string16 DownloadDangerPromptViews::GetAcceptButtonTitle() const {
  // "Be safe".
  return l10n_util::GetStringUTF16(IDS_CONFIRM_CANCEL_AGAIN_MALICIOUS);
}

base::string16 DownloadDangerPromptViews::GetCancelButtonTitle() const {
  if (show_context_)
    return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD);
  switch (download_->GetDangerType()) {
    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
    case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
      return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD_AGAIN_MALICIOUS);
    }
    default:
      return l10n_util::GetStringUTF16(IDS_CONFIRM_DOWNLOAD_AGAIN);
  }
}

base::string16 DownloadDangerPromptViews::GetMessageLead() const {
  if (!show_context_) {
    switch (download_->GetDangerType()) {
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST:
        return l10n_util::GetStringUTF16(
            IDS_PROMPT_CONFIRM_KEEP_MALICIOUS_DOWNLOAD_LEAD);

      default:
        break;
    }
  }

  return base::string16();
}

base::string16 DownloadDangerPromptViews::GetMessageBody() const {
  if (show_context_) {
    switch (download_->GetDangerType()) {
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
        return l10n_util::GetStringFUTF16(
            IDS_PROMPT_DANGEROUS_DOWNLOAD,
            download_->GetFileNameToReportUser().LossyDisplayName());
      }
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:  // Fall through
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
        return l10n_util::GetStringFUTF16(
            IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
            download_->GetFileNameToReportUser().LossyDisplayName());
      }
      case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
        return l10n_util::GetStringFUTF16(
            IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
            download_->GetFileNameToReportUser().LossyDisplayName());
      }
      case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
        return l10n_util::GetStringFUTF16(
            IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
            download_->GetFileNameToReportUser().LossyDisplayName());
      }
      case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
      case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
      case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
      case content::DOWNLOAD_DANGER_TYPE_MAX: {
        break;
      }
    }
  } else {
    switch (download_->GetDangerType()) {
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL:
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
      case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
        return l10n_util::GetStringUTF16(
            IDS_PROMPT_CONFIRM_KEEP_MALICIOUS_DOWNLOAD_BODY);
      }
      default: {
        return l10n_util::GetStringUTF16(
            IDS_PROMPT_CONFIRM_KEEP_DANGEROUS_DOWNLOAD);
      }
    }
  }
  NOTREACHED();
  return base::string16();
}

void DownloadDangerPromptViews::RunDone(Action action) {
  // Invoking the callback can cause the download item state to change or cause
  // the window to close, and |callback| refers to a member variable.
  OnDone done = done_;
  done_.Reset();
  if (download_ != NULL) {
    const bool accept = action == DownloadDangerPrompt::ACCEPT;
    RecordDownloadDangerPrompt(accept, *download_);
    if (!download_->GetURL().is_empty() &&
        !download_->GetBrowserContext()->IsOffTheRecord()) {
      SendSafeBrowsingDownloadRecoveryReport(accept, *download_);
    }
    download_->RemoveObserver(this);
    download_ = NULL;
  }
  if (!done.is_null())
    done.Run(action);
}

}  // namespace

DownloadDangerPrompt* DownloadDangerPrompt::Create(
    content::DownloadItem* item,
    content::WebContents* web_contents,
    bool show_context,
    const OnDone& done) {
  DownloadDangerPromptViews* download_danger_prompt =
      new DownloadDangerPromptViews(item, show_context, done);
  constrained_window::ShowWebModalDialogViews(download_danger_prompt,
                                              web_contents);
  return download_danger_prompt;
}