// Aseprite UI Library
// Copyright (C) 2001-2016  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ui/system.h"

#include "gfx/point.h"
#include "she/display.h"
#include "she/surface.h"
#include "she/system.h"
#include "ui/cursor.h"
#include "ui/intern.h"
#include "ui/intern.h"
#include "ui/manager.h"
#include "ui/overlay.h"
#include "ui/overlay_manager.h"
#include "ui/theme.h"
#include "ui/widget.h"

namespace ui {

// Current mouse cursor type.

static CursorType mouse_cursor_type = kOutsideDisplay;
static Cursor* mouse_cursor = NULL;
static she::Display* mouse_display = NULL;
static Overlay* mouse_cursor_overlay = NULL;
static bool use_native_mouse_cursor = false;
static bool support_native_custom_cursor = false;

// Mouse information (button and position).

static volatile MouseButtons m_buttons;
static gfx::Point m_mouse_pos;
static int mouse_cursor_scale = 1;

static int mouse_scares = 0;

static void update_mouse_overlay(Cursor* cursor)
{
  mouse_cursor = cursor;

  if (mouse_cursor && mouse_scares == 0) {
    if (!mouse_cursor_overlay) {
      mouse_cursor_overlay = new Overlay(
        mouse_cursor->getSurface(),
        get_mouse_position(),
        Overlay::MouseZOrder);

      OverlayManager::instance()->addOverlay(mouse_cursor_overlay);
    }
    else {
      mouse_cursor_overlay->setSurface(mouse_cursor->getSurface());
      update_cursor_overlay();
    }
  }
  else if (mouse_cursor_overlay) {
    OverlayManager::instance()->removeOverlay(mouse_cursor_overlay);
    mouse_cursor_overlay->setSurface(NULL);
    delete mouse_cursor_overlay;
    mouse_cursor_overlay = NULL;
  }
}

static bool update_custom_native_cursor(Cursor* cursor)
{
  bool result = false;

  // Check if we can use a custom native mouse in this platform
  if (!use_native_mouse_cursor &&
      support_native_custom_cursor &&
      mouse_display) {
    if (cursor) {
      result = mouse_display->setNativeMouseCursor(
        // The surface is already scaled by guiscale()
        cursor->getSurface(),
        cursor->getFocus(),
        // We scale the cursor by the she::Display scale
        mouse_display->scale() * mouse_cursor_scale);
    }
    else if (mouse_cursor_type == kOutsideDisplay) {
      result = mouse_display->setNativeMouseCursor(she::kArrowCursor);
    }
    else {
      result = mouse_display->setNativeMouseCursor(she::kNoCursor);
    }
  }

  return result;
}

static void update_mouse_cursor()
{
  she::NativeCursor nativeCursor = she::kNoCursor;
  Cursor* cursor = nullptr;

  if (use_native_mouse_cursor ||
      mouse_cursor_type == kOutsideDisplay) {
    switch (mouse_cursor_type) {
      case ui::kOutsideDisplay:
        nativeCursor = she::kArrowCursor;
        break;
      case ui::kNoCursor: break;
      case ui::kArrowCursor:
      case ui::kArrowPlusCursor:
        nativeCursor = she::kArrowCursor;
        break;
      case ui::kCrosshairCursor:
        nativeCursor = she::kCrosshairCursor;
        break;
      case ui::kForbiddenCursor:
        nativeCursor = she::kForbiddenCursor;
        break;
      case ui::kHandCursor:
        nativeCursor = she::kLinkCursor;
        break;
      case ui::kScrollCursor:
      case ui::kMoveCursor:
        nativeCursor = she::kMoveCursor;
        break;
      case ui::kSizeNSCursor: nativeCursor = she::kSizeNSCursor; break;
      case ui::kSizeWECursor: nativeCursor = she::kSizeWECursor; break;
      case ui::kSizeNCursor: nativeCursor = she::kSizeNCursor; break;
      case ui::kSizeNECursor: nativeCursor = she::kSizeNECursor; break;
      case ui::kSizeECursor: nativeCursor = she::kSizeECursor; break;
      case ui::kSizeSECursor: nativeCursor = she::kSizeSECursor; break;
      case ui::kSizeSCursor: nativeCursor = she::kSizeSCursor; break;
      case ui::kSizeSWCursor: nativeCursor = she::kSizeSWCursor; break;
      case ui::kSizeWCursor: nativeCursor = she::kSizeWCursor; break;
      case ui::kSizeNWCursor: nativeCursor = she::kSizeNWCursor; break;
    }
  }

  // Set native cursor
  if (mouse_display) {
    bool ok = mouse_display->setNativeMouseCursor(nativeCursor);

    // It looks like the specific native cursor is not supported,
    // so we can should use the internal overlay (even when we
    // have use_native_mouse_cursor flag enabled).
    if (!ok)
      nativeCursor = she::kNoCursor;
  }

  // Use a custom cursor
  if (nativeCursor == she::kNoCursor &&
      mouse_cursor_type != ui::kOutsideDisplay &&
      CurrentTheme::get()) {
    cursor = CurrentTheme::get()->getCursor(mouse_cursor_type);
  }

  // Try to use a custom native cursor if it's possible
  if (!update_custom_native_cursor(cursor)) {
    // Or an overlay as last resource
    update_mouse_overlay(cursor);
  }
}

UISystem::UISystem()
{
  mouse_cursor_type = kOutsideDisplay;
  support_native_custom_cursor =
    ((she::instance() &&
      (int(she::instance()->capabilities()) &
       int(she::Capabilities::CustomNativeMouseCursor))) ?
     true: false);

  details::initWidgets();
}

UISystem::~UISystem()
{
  OverlayManager::destroyInstance();

  // finish theme
  CurrentTheme::set(NULL);

  details::exitWidgets();

  _internal_set_mouse_display(nullptr);
  if (!update_custom_native_cursor(nullptr))
    update_mouse_overlay(nullptr);
}

void _internal_set_mouse_display(she::Display* display)
{
  CursorType cursor = get_mouse_cursor();
  set_mouse_cursor(kNoCursor);
  mouse_display = display;
  if (display)
    set_mouse_cursor(cursor);  // Restore mouse cursor
}

int display_w()
{
  if (mouse_display)
    return mouse_display->width() / mouse_display->scale();
  else
    return 1;
}

int display_h()
{
  if (mouse_display)
    return mouse_display->height() / mouse_display->scale();
  else
    return 1;
}

void update_cursor_overlay()
{
  if (mouse_cursor_overlay != NULL && mouse_scares == 0) {
    gfx::Point newPos =
      get_mouse_position() - mouse_cursor->getFocus();

    if (newPos != mouse_cursor_overlay->position()) {
      mouse_cursor_overlay->moveOverlay(newPos);
    }
  }
}

void set_use_native_cursors(bool state)
{
  use_native_mouse_cursor = state;
  update_mouse_cursor();
}

CursorType get_mouse_cursor()
{
  return mouse_cursor_type;
}

void set_mouse_cursor(CursorType type)
{
  if (mouse_cursor_type == type)
    return;

  mouse_cursor_type = type;
  update_mouse_cursor();
}

void set_mouse_cursor_scale(const int newScale)
{
  mouse_cursor_scale = newScale;
  update_mouse_cursor();
}

void hide_mouse_cursor()
{
  ASSERT(mouse_scares >= 0);
  mouse_scares++;
}

void show_mouse_cursor()
{
  ASSERT(mouse_scares > 0);
  mouse_scares--;

  if (mouse_scares == 0)
    update_mouse_cursor();
}

void _internal_no_mouse_position()
{
  if (!update_custom_native_cursor(nullptr))
    update_mouse_overlay(nullptr);
}

void _internal_set_mouse_position(const gfx::Point& newPos)
{
  m_mouse_pos = newPos;
}

void _internal_set_mouse_buttons(MouseButtons buttons)
{
  m_buttons = buttons;
}

MouseButtons _internal_get_mouse_buttons()
{
  return m_buttons;
}

const gfx::Point& get_mouse_position()
{
  return m_mouse_pos;
}

void set_mouse_position(const gfx::Point& newPos)
{
  if (mouse_display)
    mouse_display->setMousePosition(newPos);

  _internal_set_mouse_position(newPos);
}

} // namespace ui
