// Copyright (c) 2021 The Chromium Embedded Framework Authors.
// Portions copyright (c) 2012 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 "libcef/browser/simple_menu_model_impl.h"

#include <vector>

#include "libcef/browser/thread_util.h"
#include "libcef/common/task_runner_impl.h"

namespace {

// Value based on the documentation on SimpleMenuModel::GetIndexOfCommandId().
const int kInvalidIndex = -1;
const int kInvalidCommandId = -1;
const int kInvalidGroupId = -1;

cef_menu_item_type_t GetCefItemType(ui::MenuModel::ItemType type) {
  switch (type) {
    case ui::MenuModel::TYPE_COMMAND:
      return MENUITEMTYPE_COMMAND;
    case ui::MenuModel::TYPE_CHECK:
      return MENUITEMTYPE_CHECK;
    case ui::MenuModel::TYPE_RADIO:
      return MENUITEMTYPE_RADIO;
    case ui::MenuModel::TYPE_SEPARATOR:
      return MENUITEMTYPE_SEPARATOR;
    case ui::MenuModel::TYPE_SUBMENU:
      return MENUITEMTYPE_SUBMENU;
    default:
      return MENUITEMTYPE_NONE;
  }
}

}  // namespace

CefSimpleMenuModelImpl::CefSimpleMenuModelImpl(
    ui::SimpleMenuModel* model,
    ui::SimpleMenuModel::Delegate* delegate,
    StateDelegate* state_delegate,
    bool is_owned,
    bool is_submenu)
    : supported_thread_id_(base::PlatformThread::CurrentId()),
      model_(model),
      delegate_(delegate),
      state_delegate_(state_delegate),
      is_owned_(is_owned),
      is_submenu_(is_submenu) {
  DCHECK(model_);
  DCHECK(delegate_);
  DCHECK(state_delegate_);
}

CefSimpleMenuModelImpl::~CefSimpleMenuModelImpl() {
  // Detach() must be called before object destruction.
  DCHECK(!model_);
  DCHECK(submenumap_.empty());
}

void CefSimpleMenuModelImpl::Detach() {
  DCHECK(VerifyContext());

  if (!submenumap_.empty()) {
    auto it = submenumap_.begin();
    for (; it != submenumap_.end(); ++it) {
      it->second->Detach();
    }
    submenumap_.clear();
  }

  if (is_owned_)
    delete model_;
  model_ = nullptr;
}

bool CefSimpleMenuModelImpl::IsSubMenu() {
  if (!VerifyContext())
    return false;
  return is_submenu_;
}

bool CefSimpleMenuModelImpl::Clear() {
  if (!VerifyContext())
    return false;

  model_->Clear();
  return true;
}

int CefSimpleMenuModelImpl::GetCount() {
  if (!VerifyContext())
    return 0;

  return model_->GetItemCount();
}

bool CefSimpleMenuModelImpl::AddSeparator() {
  if (!VerifyContext())
    return false;

  model_->AddSeparator(ui::NORMAL_SEPARATOR);
  return true;
}

bool CefSimpleMenuModelImpl::AddItem(int command_id, const CefString& label) {
  if (!VerifyContext())
    return false;

  model_->AddItem(command_id, label);
  return true;
}

bool CefSimpleMenuModelImpl::AddCheckItem(int command_id,
                                          const CefString& label) {
  if (!VerifyContext())
    return false;

  model_->AddCheckItem(command_id, label);
  return true;
}

bool CefSimpleMenuModelImpl::AddRadioItem(int command_id,
                                          const CefString& label,
                                          int group_id) {
  if (!VerifyContext())
    return false;

  model_->AddRadioItem(command_id, label, group_id);
  return true;
}

CefRefPtr<CefMenuModel> CefSimpleMenuModelImpl::AddSubMenu(
    int command_id,
    const CefString& label) {
  if (!VerifyContext())
    return nullptr;

  auto new_menu = CreateNewSubMenu(nullptr);
  model_->AddSubMenu(command_id, label, new_menu->model());
  return new_menu;
}

bool CefSimpleMenuModelImpl::InsertSeparatorAt(int index) {
  if (!VerifyContext())
    return false;

  model_->InsertSeparatorAt(index, ui::NORMAL_SEPARATOR);
  return true;
}

bool CefSimpleMenuModelImpl::InsertItemAt(int index,
                                          int command_id,
                                          const CefString& label) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->InsertItemAt(index, command_id, label);
  return true;
}

bool CefSimpleMenuModelImpl::InsertCheckItemAt(int index,
                                               int command_id,
                                               const CefString& label) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->InsertCheckItemAt(index, command_id, label);
  return true;
}

bool CefSimpleMenuModelImpl::InsertRadioItemAt(int index,
                                               int command_id,
                                               const CefString& label,
                                               int group_id) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->InsertRadioItemAt(index, command_id, label, group_id);
  return true;
}

CefRefPtr<CefMenuModel> CefSimpleMenuModelImpl::InsertSubMenuAt(
    int index,
    int command_id,
    const CefString& label) {
  if (!VerifyContext() || !ValidIndex(index))
    return nullptr;

  auto new_menu = CreateNewSubMenu(nullptr);
  model_->InsertSubMenuAt(index, command_id, label, new_menu->model());
  return new_menu;
}

bool CefSimpleMenuModelImpl::Remove(int command_id) {
  return RemoveAt(GetIndexOf(command_id));
}

bool CefSimpleMenuModelImpl::RemoveAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  auto* sub_menu =
      static_cast<ui::SimpleMenuModel*>(model_->GetSubmenuModelAt(index));
  if (sub_menu) {
    auto it = submenumap_.find(sub_menu);
    if (it != submenumap_.end()) {
      it->second->Detach();
      submenumap_.erase(it);
    }
  }

  model_->RemoveItemAt(index);
  return true;
}

int CefSimpleMenuModelImpl::GetIndexOf(int command_id) {
  if (!VerifyContext())
    return kInvalidIndex;

  return model_->GetIndexOfCommandId(command_id);
}

int CefSimpleMenuModelImpl::GetCommandIdAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return kInvalidCommandId;

  return model_->GetCommandIdAt(index);
}

bool CefSimpleMenuModelImpl::SetCommandIdAt(int index, int command_id) {
  NOTIMPLEMENTED();
  return false;
}

CefString CefSimpleMenuModelImpl::GetLabel(int command_id) {
  return GetLabelAt(GetIndexOf(command_id));
}

CefString CefSimpleMenuModelImpl::GetLabelAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return CefString();

  return model_->GetLabelAt(index);
}

bool CefSimpleMenuModelImpl::SetLabel(int command_id, const CefString& label) {
  return SetLabelAt(GetIndexOf(command_id), label);
}

bool CefSimpleMenuModelImpl::SetLabelAt(int index, const CefString& label) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->SetLabel(index, label);
  return true;
}

CefSimpleMenuModelImpl::MenuItemType CefSimpleMenuModelImpl::GetType(
    int command_id) {
  return GetTypeAt(GetIndexOf(command_id));
}

CefSimpleMenuModelImpl::MenuItemType CefSimpleMenuModelImpl::GetTypeAt(
    int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return MENUITEMTYPE_NONE;

  return GetCefItemType(model_->GetTypeAt(index));
}

int CefSimpleMenuModelImpl::GetGroupId(int command_id) {
  return GetGroupIdAt(GetIndexOf(command_id));
}

int CefSimpleMenuModelImpl::GetGroupIdAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return kInvalidGroupId;

  return model_->GetGroupIdAt(index);
}

bool CefSimpleMenuModelImpl::SetGroupId(int command_id, int group_id) {
  return SetGroupIdAt(GetIndexOf(command_id), group_id);
}

bool CefSimpleMenuModelImpl::SetGroupIdAt(int index, int group_id) {
  NOTIMPLEMENTED();
  return false;
}

CefRefPtr<CefMenuModel> CefSimpleMenuModelImpl::GetSubMenu(int command_id) {
  return GetSubMenuAt(GetIndexOf(command_id));
}

CefRefPtr<CefMenuModel> CefSimpleMenuModelImpl::GetSubMenuAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return nullptr;

  auto* sub_model =
      static_cast<ui::SimpleMenuModel*>(model_->GetSubmenuModelAt(index));
  auto it = submenumap_.find(sub_model);
  if (it != submenumap_.end())
    return it->second;
  return CreateNewSubMenu(sub_model);
}

bool CefSimpleMenuModelImpl::IsVisible(int command_id) {
  return IsVisibleAt(GetIndexOf(command_id));
}

bool CefSimpleMenuModelImpl::IsVisibleAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  return model_->IsVisibleAt(index);
}

bool CefSimpleMenuModelImpl::SetVisible(int command_id, bool visible) {
  return SetVisibleAt(GetIndexOf(command_id), visible);
}

bool CefSimpleMenuModelImpl::SetVisibleAt(int index, bool visible) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->SetVisibleAt(index, visible);
  return true;
}

bool CefSimpleMenuModelImpl::IsEnabled(int command_id) {
  return IsEnabledAt(GetIndexOf(command_id));
}

bool CefSimpleMenuModelImpl::IsEnabledAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  return model_->IsEnabledAt(index);
}

bool CefSimpleMenuModelImpl::SetEnabled(int command_id, bool enabled) {
  return SetEnabledAt(GetIndexOf(command_id), enabled);
}

bool CefSimpleMenuModelImpl::SetEnabledAt(int index, bool enabled) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  model_->SetEnabledAt(index, enabled);
  return true;
}

bool CefSimpleMenuModelImpl::IsChecked(int command_id) {
  return IsCheckedAt(GetIndexOf(command_id));
}

bool CefSimpleMenuModelImpl::IsCheckedAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  return model_->IsItemCheckedAt(index);
}

bool CefSimpleMenuModelImpl::SetChecked(int command_id, bool checked) {
  if (!VerifyContext() || command_id == kInvalidIndex)
    return false;

  state_delegate_->SetChecked(command_id, checked);
  return true;
}

bool CefSimpleMenuModelImpl::SetCheckedAt(int index, bool checked) {
  return SetChecked(GetCommandIdAt(index), checked);
}

bool CefSimpleMenuModelImpl::HasAccelerator(int command_id) {
  return HasAcceleratorAt(GetIndexOf(command_id));
}

bool CefSimpleMenuModelImpl::HasAcceleratorAt(int index) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  ui::Accelerator accelerator;
  return model_->GetAcceleratorAt(index, &accelerator);
}

bool CefSimpleMenuModelImpl::SetAccelerator(int command_id,
                                            int key_code,
                                            bool shift_pressed,
                                            bool ctrl_pressed,
                                            bool alt_pressed) {
  if (!VerifyContext() || command_id == kInvalidIndex)
    return false;

  int modifiers = 0;
  if (shift_pressed)
    modifiers |= ui::EF_SHIFT_DOWN;
  if (ctrl_pressed)
    modifiers |= ui::EF_CONTROL_DOWN;
  if (alt_pressed)
    modifiers |= ui::EF_ALT_DOWN;

  state_delegate_->SetAccelerator(
      command_id,
      ui::Accelerator(static_cast<ui::KeyboardCode>(key_code), modifiers));
  return true;
}

bool CefSimpleMenuModelImpl::SetAcceleratorAt(int index,
                                              int key_code,
                                              bool shift_pressed,
                                              bool ctrl_pressed,
                                              bool alt_pressed) {
  return SetAccelerator(GetCommandIdAt(index), key_code, shift_pressed,
                        ctrl_pressed, alt_pressed);
}

bool CefSimpleMenuModelImpl::RemoveAccelerator(int command_id) {
  if (!VerifyContext() || command_id == kInvalidIndex)
    return false;
  state_delegate_->SetAccelerator(command_id, absl::nullopt);
  return true;
}

bool CefSimpleMenuModelImpl::RemoveAcceleratorAt(int index) {
  return RemoveAccelerator(GetCommandIdAt(index));
}

bool CefSimpleMenuModelImpl::GetAccelerator(int command_id,
                                            int& key_code,
                                            bool& shift_pressed,
                                            bool& ctrl_pressed,
                                            bool& alt_pressed) {
  return GetAcceleratorAt(GetIndexOf(command_id), key_code, shift_pressed,
                          ctrl_pressed, alt_pressed);
}

bool CefSimpleMenuModelImpl::GetAcceleratorAt(int index,
                                              int& key_code,
                                              bool& shift_pressed,
                                              bool& ctrl_pressed,
                                              bool& alt_pressed) {
  if (!VerifyContext() || !ValidIndex(index))
    return false;

  ui::Accelerator accel;
  if (model_->GetAcceleratorAt(index, &accel)) {
    key_code = accel.key_code();
    shift_pressed = accel.modifiers() & ui::EF_SHIFT_DOWN;
    ctrl_pressed = accel.modifiers() & ui::EF_CONTROL_DOWN;
    alt_pressed = accel.modifiers() & ui::EF_ALT_DOWN;
    return true;
  }
  return false;
}

bool CefSimpleMenuModelImpl::SetColor(int command_id,
                                      cef_menu_color_type_t color_type,
                                      cef_color_t color) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::SetColorAt(int index,
                                        cef_menu_color_type_t color_type,
                                        cef_color_t color) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::GetColor(int command_id,
                                      cef_menu_color_type_t color_type,
                                      cef_color_t& color) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::GetColorAt(int index,
                                        cef_menu_color_type_t color_type,
                                        cef_color_t& color) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::SetFontList(int command_id,
                                         const CefString& font_list) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::SetFontListAt(int index,
                                           const CefString& font_list) {
  NOTIMPLEMENTED();
  return false;
}

bool CefSimpleMenuModelImpl::VerifyContext() {
  if (base::PlatformThread::CurrentId() != supported_thread_id_) {
    // This object should only be accessed from the thread that created it.
    NOTREACHED();
    return false;
  }

  if (!model_)
    return false;

  return true;
}

bool CefSimpleMenuModelImpl::ValidIndex(int index) {
  return index > kInvalidIndex && index < model_->GetItemCount();
}

CefRefPtr<CefSimpleMenuModelImpl> CefSimpleMenuModelImpl::CreateNewSubMenu(
    ui::SimpleMenuModel* model) {
  bool is_owned = false;
  if (!model) {
    model = new ui::SimpleMenuModel(delegate_);
    is_owned = true;
  }

  CefRefPtr<CefSimpleMenuModelImpl> new_impl = new CefSimpleMenuModelImpl(
      model, delegate_, state_delegate_, is_owned, /*is_submodel=*/true);
  submenumap_.insert(std::make_pair(model, new_impl));
  return new_impl;
}