/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2014 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "el/elemental_forms.h"
#include "xenia/base/logging.h"
#include "xenia/emulator.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/xam_private.h"
#include "xenia/ui/window.h"
#include "xenia/xbox.h"

namespace xe {
namespace kernel {
namespace xam {

SHIM_CALL XamIsUIActive_shim(PPCContext* ppc_context,
                             KernelState* kernel_state) {
  XELOGD("XamIsUIActive()");
  SHIM_SET_RETURN_32(el::ModalForm::is_any_visible());
}

class MessageBoxWindow : public el::ModalForm {
 public:
  explicit MessageBoxWindow(xe::threading::Fence* fence)
      : ModalForm([fence]() { fence->Signal(); }) {}
  ~MessageBoxWindow() override = default;

  // TODO(benvanik): icon.
  void Show(el::Element* root_element, std::wstring title, std::wstring message,
            std::vector<std::wstring> buttons, uint32_t default_button,
            uint32_t* out_chosen_button) {
    title_ = std::move(title);
    message_ = std::move(message);
    buttons_ = std::move(buttons);
    default_button_ = default_button;
    out_chosen_button_ = out_chosen_button;

    // In case we are cancelled, always set default button.
    *out_chosen_button_ = default_button;

    ModalForm::Show(root_element);

    EnsureFocus();
  }

 protected:
  void BuildUI() override {
    using namespace el::dsl;  // NOLINT(build/namespaces)

    set_text(xe::to_string(title_));

    auto button_bar = LayoutBoxNode().distribution_position(
        LayoutDistributionPosition::kRightBottom);
    for (int32_t i = 0; i < buttons_.size(); ++i) {
      button_bar.child(ButtonNode(xe::to_string(buttons_[i]).c_str())
                           .id(100 + i)
                           .data(100 + i));
    }

    LoadNodeTree(
        LayoutBoxNode()
            .axis(Axis::kY)
            .gravity(Gravity::kAll)
            .position(LayoutPosition::kLeftTop)
            .distribution(LayoutDistribution::kAvailable)
            .distribution_position(LayoutDistributionPosition::kLeftTop)
            .child(LabelNode(xe::to_string(message_).c_str()))
            .child(button_bar));

    auto default_button = GetElementById<el::Button>(100 + default_button_);
    el::Button::set_auto_focus_state(true);
    default_button->set_focus(el::FocusReason::kNavigation);
  }

  bool OnEvent(const el::Event& ev) override {
    if (ev.target->IsOfType<el::Button>() &&
        (ev.type == el::EventType::kClick ||
         ev.special_key == el::SpecialKey::kEnter)) {
      int data_value = ev.target->data.as_integer();
      if (data_value) {
        *out_chosen_button_ = data_value - 100;
      }
      Die();
      return true;
    }
    return ModalForm::OnEvent(ev);
  }

  std::wstring title_;
  std::wstring message_;
  std::vector<std::wstring> buttons_;
  uint32_t default_button_ = 0;
  uint32_t* out_chosen_button_ = nullptr;
};

// http://www.se7ensins.com/forums/threads/working-xshowmessageboxui.844116/?jdfwkey=sb0vm
SHIM_CALL XamShowMessageBoxUI_shim(PPCContext* ppc_context,
                                   KernelState* kernel_state) {
  uint32_t user_index = SHIM_GET_ARG_32(0);
  uint32_t title_ptr = SHIM_GET_ARG_32(1);
  uint32_t text_ptr = SHIM_GET_ARG_32(2);
  uint32_t button_count = SHIM_GET_ARG_32(3);
  uint32_t button_ptrs = SHIM_GET_ARG_32(4);
  uint32_t active_button = SHIM_GET_ARG_32(5);
  uint32_t flags = SHIM_GET_ARG_32(6);
  uint32_t result_ptr = SHIM_GET_ARG_32(7);
  uint32_t overlapped_ptr = SHIM_GET_ARG_32(8);

  auto title = xe::load_and_swap<std::wstring>(SHIM_MEM_ADDR(title_ptr));
  auto text = xe::load_and_swap<std::wstring>(SHIM_MEM_ADDR(text_ptr));
  std::vector<std::wstring> buttons;
  std::wstring all_buttons;
  for (uint32_t j = 0; j < button_count; ++j) {
    uint32_t button_ptr = SHIM_MEM_32(button_ptrs + j * 4);
    auto button = xe::load_and_swap<std::wstring>(SHIM_MEM_ADDR(button_ptr));
    all_buttons.append(button);
    if (j + 1 < button_count) {
      all_buttons.append(L" | ");
    }
    buttons.push_back(button);
  }

  XELOGD(
      "XamShowMessageBoxUI(%d, %.8X(%S), %.8X(%S), %d, %.8X(%S), %d, %X, %.8X, "
      "%.8X)",
      user_index, title_ptr, title.c_str(), text_ptr, text.c_str(),
      button_count, button_ptrs, all_buttons.c_str(), active_button, flags,
      result_ptr, overlapped_ptr);

  uint32_t chosen_button;
  if (FLAGS_headless) {
    // Auto-pick the focused button.
    chosen_button = active_button;
  } else {
    auto display_window = kernel_state->emulator()->display_window();
    xe::threading::Fence fence;
    display_window->loop()->PostSynchronous([&]() {
      auto root_element = display_window->root_element();
      auto window = new MessageBoxWindow(&fence);
      switch (flags & 0xF) {
        case 0:
          // config.pszMainIcon = nullptr;
          break;
        case 1:
          // config.pszMainIcon = TD_ERROR_ICON;
          break;
        case 2:
          // config.pszMainIcon = TD_WARNING_ICON;
          break;
        case 3:
          // config.pszMainIcon = TD_INFORMATION_ICON;
          break;
      }
      window->Show(root_element, title, text, buttons, active_button,
                   &chosen_button);
    });
    fence.Wait();
  }
  SHIM_SET_MEM_32(result_ptr, chosen_button);

  kernel_state->CompleteOverlappedImmediate(overlapped_ptr, X_ERROR_SUCCESS);
  SHIM_SET_RETURN_32(X_ERROR_IO_PENDING);
}

class KeyboardInputWindow : public el::ModalForm {
 public:
  explicit KeyboardInputWindow(xe::threading::Fence* fence)
      : ModalForm([fence]() { fence->Signal(); }) {}
  ~KeyboardInputWindow() override = default;

  // TODO(benvanik): icon.
  void Show(el::Element* root_element, std::wstring title,
            std::wstring description, std::wstring default_text,
            std::wstring* out_text, size_t max_length) {
    title_ = std::move(title);
    description_ = std::move(description);
    default_text_ = std::move(default_text);
    out_text_ = out_text;
    max_length_ = max_length;

    // Incase we're cancelled, set as the default text.
    if (out_text) {
      *out_text = default_text;
    }

    ModalForm::Show(root_element);

    EnsureFocus();
  }

 protected:
  void BuildUI() override {
    using namespace el::dsl;  // NOLINT(build/namespaces)

    set_text(xe::to_string(title_));

    LoadNodeTree(
        LayoutBoxNode()
            .axis(Axis::kY)
            .gravity(Gravity::kAll)
            .position(LayoutPosition::kLeftTop)
            .min_width(300)
            .distribution(LayoutDistribution::kAvailable)
            .distribution_position(LayoutDistributionPosition::kLeftTop)
            .child(LabelNode(xe::to_string(description_).c_str()))
            .child(LayoutBoxNode()
                       .axis(Axis::kX)
                       .distribution(LayoutDistribution::kGravity)
                       .child(TextBoxNode()
                                  .id("text_input")
                                  .gravity(Gravity::kLeftRight)
                                  .placeholder(xe::to_string(default_text_))
                                  .min_width(150)
                                  .autofocus(true)))
            .child(LayoutBoxNode()
                       .distribution_position(
                           LayoutDistributionPosition::kRightBottom)
                       .child(ButtonNode("OK").id("ok_button"))
                       .child(ButtonNode("Cancel").id("cancel_button"))));
  }

  bool OnEvent(const el::Event& ev) override {
    if (ev.special_key == el::SpecialKey::kEnter ||
        (ev.target->id() == TBIDC("ok_button") &&
         ev.type == el::EventType::kClick)) {
      // Pressed enter or clicked OK
      // Grab the text.
      if (out_text_) {
        *out_text_ =
            xe::to_wstring(GetElementById<el::TextBox>("text_input")->text());
      }

      Die();
      return true;
    } else if (ev.target->id() == TBIDC("cancel_button") &&
               ev.type == el::EventType::kClick) {
      // Cancel.
      Die();
      return true;
    }

    return ModalForm::OnEvent(ev);
  }

  std::wstring title_;
  std::wstring description_;
  std::wstring default_text_;
  std::wstring* out_text_ = nullptr;
  size_t max_length_ = 0;
};

// http://www.se7ensins.com/forums/threads/release-how-to-use-xshowkeyboardui-release.906568/
dword_result_t XamShowKeyboardUI(dword_t r3, dword_t flags,
                                 lpwstring_t default_text, lpwstring_t title,
                                 lpwstring_t description, lpwstring_t buffer,
                                 dword_t buffer_length,
                                 pointer_t<XAM_OVERLAPPED> overlapped) {
  // Unknown parameters. I've only seen zero.
  assert_zero(r3);

  if (!buffer) {
    return X_ERROR_INVALID_PARAMETER;
  }

  if (FLAGS_headless) {
    // Redirect default_text back into the buffer.
    std::memset(buffer, 0, buffer_length * 2);
    if (default_text) {
      xe::store_and_swap<std::wstring>(buffer, default_text.value());
    }

    if (overlapped) {
      kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
      return X_ERROR_IO_PENDING;
    } else {
      return X_ERROR_SUCCESS;
    }
  }

  std::wstring out_text;

  auto display_window = kernel_state()->emulator()->display_window();
  xe::threading::Fence fence;
  display_window->loop()->PostSynchronous([&]() {
    auto root_element = display_window->root_element();
    auto window = new KeyboardInputWindow(&fence);
    window->Show(root_element, title ? title.value() : L"",
                 description ? description.value() : L"",
                 default_text ? default_text.value() : L"", &out_text,
                 buffer_length);
  });
  fence.Wait();

  // Zero the output buffer.
  std::memset(buffer, 0, buffer_length * 2);

  // Truncate the string.
  out_text = out_text.substr(0, buffer_length - 1);
  xe::store_and_swap<std::wstring>(buffer, out_text);

  if (overlapped) {
    kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
    return X_ERROR_IO_PENDING;
  } else {
    return X_ERROR_SUCCESS;
  }
}
DECLARE_XAM_EXPORT(XamShowKeyboardUI, ExportTag::kImplemented);

dword_result_t XamShowDeviceSelectorUI(dword_t user_index, dword_t content_type,
                                       dword_t content_flags,
                                       qword_t total_requested,
                                       lpdword_t device_id_ptr,
                                       pointer_t<XAM_OVERLAPPED> overlapped) {
  // NOTE: 0xF00D0000 magic from xam_content.cc
  switch (content_type) {
    case 1:  // save game
      *device_id_ptr = 0xF00D0000 | 0x0001;
      break;
    case 2:  // marketplace
      *device_id_ptr = 0xF00D0000 | 0x0002;
      break;
    case 3:  // title/publisher update?
      *device_id_ptr = 0xF00D0000 | 0x0003;
      break;
  }

  if (overlapped) {
    kernel_state()->CompleteOverlappedImmediate(overlapped, X_ERROR_SUCCESS);
    return X_ERROR_IO_PENDING;
  } else {
    return X_ERROR_SUCCESS;
  }
}
DECLARE_XAM_EXPORT(XamShowDeviceSelectorUI, ExportTag::kImplemented);

class DirtyDiscWindow : public el::ModalForm {
 public:
  explicit DirtyDiscWindow(xe::threading::Fence* fence)
      : ModalForm([fence]() { fence->Signal(); }) {}
  ~DirtyDiscWindow() override = default;

 protected:
  void BuildUI() override {
    using namespace el::dsl;  // NOLINT(build/namespaces)

    set_text("Disc Read Error");

    LoadNodeTree(
        LayoutBoxNode()
            .axis(Axis::kY)
            .gravity(Gravity::kAll)
            .position(LayoutPosition::kLeftTop)
            .distribution(LayoutDistribution::kAvailable)
            .distribution_position(LayoutDistributionPosition::kLeftTop)
            .child(LabelNode(
                "There's been an issue reading content from the game disc."))
            .child(LabelNode(
                "This is likely caused by bad or unimplemented file IO "
                "calls."))
            .child(LayoutBoxNode()
                       .distribution_position(
                           LayoutDistributionPosition::kRightBottom)
                       .child(ButtonNode("Exit").id("exit_button"))));
  }

  bool OnEvent(const el::Event& ev) override {
    if (ev.target->id() == TBIDC("exit_button") &&
        ev.type == el::EventType::kClick) {
      Die();
      return true;
    }
    return ModalForm::OnEvent(ev);
  }
};

SHIM_CALL XamShowDirtyDiscErrorUI_shim(PPCContext* ppc_context,
                                       KernelState* kernel_state) {
  uint32_t user_index = SHIM_GET_ARG_32(0);

  XELOGD("XamShowDirtyDiscErrorUI(%d)", user_index);

  if (FLAGS_headless) {
    assert_always();
    exit(1);
    return;
  }

  auto display_window = kernel_state->emulator()->display_window();
  xe::threading::Fence fence;
  display_window->loop()->PostSynchronous([&]() {
    auto root_element = display_window->root_element();
    auto window = new DirtyDiscWindow(&fence);
    window->Show(root_element);
  });
  fence.Wait();

  // This is death, and should never return.
  // TODO(benvanik): cleaner exit.
  exit(1);
}

void RegisterUIExports(xe::cpu::ExportResolver* export_resolver,
                       KernelState* kernel_state) {
  SHIM_SET_MAPPING("xam.xex", XamIsUIActive, state);
  SHIM_SET_MAPPING("xam.xex", XamShowMessageBoxUI, state);
  SHIM_SET_MAPPING("xam.xex", XamShowDirtyDiscErrorUI, state);
}

}  // namespace xam
}  // namespace kernel
}  // namespace xe
