/*
 * GStreamer
 * Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
 * Copyright (C) 2012 Matthew Waters <ystreet00@gmail.com>
 * Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

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

#include "gstd3d11window_win32.h"
#include <wrl.h>

/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */

GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_window_debug);
#define GST_CAT_DEFAULT gst_d3d11_window_debug

G_LOCK_DEFINE_STATIC (create_lock);
G_LOCK_DEFINE_STATIC (get_instance_lock);

#define EXTERNAL_PROC_PROP_NAME "d3d11_window_external_proc"
#define D3D11_WINDOW_PROP_NAME "gst_d3d11_window_win32_object"

#define WM_GST_D3D11_FULLSCREEN (WM_USER + 1)
#define WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2)
#define WM_GST_D3D11_DESTROY_INTERNAL_WINDOW (WM_USER + 3)
#define WM_GST_D3D11_MOVE_WINDOW (WM_USER + 4)
#define WM_GST_D3D11_SHOW_WINDOW (WM_USER + 5)
#define WS_GST_D3D11 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW)

static LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam);
static LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
    LPARAM lParam);

/* windowsx.h */
#ifndef GET_X_LPARAM
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#endif

#ifndef GET_Y_LPARAM
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
#endif

typedef enum
{
  GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE = 0,
  GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED,
  GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED,
} GstD3D11WindowWin32OverlayState;

struct _GstD3D11WindowWin32
{
  GstD3D11Window parent;

  SRWLOCK lock;
  CONDITION_VARIABLE cond;

  GMainContext *main_context;
  GMainLoop *loop;

  gboolean visible;

  GSource *msg_source;
  GIOChannel *msg_io_channel;

  GThread *thread;

  GThread *internal_hwnd_thread;

  GRecMutex hwnds_lock;
  HWND internal_hwnd;
  HWND external_hwnd;
  GstD3D11WindowWin32OverlayState overlay_state;

  gboolean have_swapchain1;

  /* atomic */
  gint pending_fullscreen_count;
  gint pending_move_window;

  /* fullscreen related */
  LONG restore_style;
  WINDOWPLACEMENT restore_placement;

  /* Handle set_render_rectangle */
  GstVideoRectangle render_rect;

  gboolean flushing;
  gboolean setup_external_hwnd;
};

#define gst_d3d11_window_win32_parent_class parent_class
G_DEFINE_TYPE (GstD3D11WindowWin32, gst_d3d11_window_win32,
    GST_TYPE_D3D11_WINDOW);

static void gst_d3d11_window_win32_constructed (GObject * object);
static void gst_d3d11_window_win32_finalize (GObject * object);

static void gst_d3d11_window_win32_show (GstD3D11Window * window);
static void gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window);
static void
gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window);
static gboolean
gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window,
    DXGI_FORMAT format, guint width, guint height,
    guint swapchain_flags, IDXGISwapChain ** swap_chain);
static GstFlowReturn gst_d3d11_window_win32_present (GstD3D11Window * window,
    guint present_flags);

static gpointer gst_d3d11_window_win32_thread_func (gpointer data);
static gboolean
gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self);
static void gst_d3d11_window_win32_destroy_internal_window (HWND hwnd);
static void
gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self);
static void
gst_d3d11_window_win32_on_resize (GstD3D11Window * window,
    guint width, guint height);
static GstFlowReturn gst_d3d11_window_win32_prepare (GstD3D11Window * window,
    guint display_width, guint display_height, GstCaps * caps,
    GstStructure * config, DXGI_FORMAT display_format, GError ** error);
static void gst_d3d11_window_win32_unprepare (GstD3D11Window * window);
static void
gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window,
    const GstVideoRectangle * rect);
static void gst_d3d11_window_win32_set_title (GstD3D11Window * window,
    const gchar * title);
static gboolean gst_d3d11_window_win32_unlock (GstD3D11Window * window);
static gboolean gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window);
static GstFlowReturn
gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self,
    HWND hwnd);

static void
gst_d3d11_window_win32_class_init (GstD3D11WindowWin32Class * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstD3D11WindowClass *window_class = GST_D3D11_WINDOW_CLASS (klass);

  gobject_class->constructed = gst_d3d11_window_win32_constructed;
  gobject_class->finalize = gst_d3d11_window_win32_finalize;

  window_class->show = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_show);
  window_class->update_swap_chain =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_update_swap_chain);
  window_class->change_fullscreen_mode =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_change_fullscreen_mode);
  window_class->create_swap_chain =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_create_swap_chain);
  window_class->present = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_present);
  window_class->on_resize =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_on_resize);
  window_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_prepare);
  window_class->unprepare =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unprepare);
  window_class->set_render_rectangle =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_render_rectangle);
  window_class->set_title =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_title);
  window_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock);
  window_class->unlock_stop =
      GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock_stop);
}

static void
gst_d3d11_window_win32_init (GstD3D11WindowWin32 * self)
{
  self->main_context = g_main_context_new ();
  self->restore_placement.length = sizeof (WINDOWPLACEMENT);
  g_rec_mutex_init (&self->hwnds_lock);
}

static void
gst_d3d11_window_win32_constructed (GObject * object)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (object);
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (object);

  if (window->external_handle) {
    /* Will setup internal child window on ::prepare() */
    self->setup_external_hwnd = TRUE;
    window->initialized = TRUE;
    goto done;
  }

  AcquireSRWLockExclusive (&self->lock);
  self->loop = g_main_loop_new (self->main_context, FALSE);
  self->thread = g_thread_new ("GstD3D11WindowWin32",
      (GThreadFunc) gst_d3d11_window_win32_thread_func, self);
  while (!g_main_loop_is_running (self->loop))
    SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0);
  ReleaseSRWLockExclusive (&self->lock);

done:
  G_OBJECT_CLASS (parent_class)->constructed (object);
}

static void
gst_d3d11_window_win32_finalize (GObject * object)
{
  GstD3D11WindowWin32 *self = (GstD3D11WindowWin32 *) object;

  GST_DEBUG_OBJECT (object, "finalize");
  gst_d3d11_window_win32_unprepare (GST_D3D11_WINDOW (object));
  g_rec_mutex_clear (&self->hwnds_lock);
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static GstFlowReturn
gst_d3d11_window_win32_prepare (GstD3D11Window * window, guint display_width,
    guint display_height, GstCaps * caps, GstStructure * config,
    DXGI_FORMAT display_format, GError ** error)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  HWND hwnd;
  GstFlowReturn ret;

  if (!self->setup_external_hwnd)
    goto done;

  hwnd = (HWND) window->external_handle;
  if (!IsWindow (hwnd)) {
    gst_structure_free (config);
    GST_ERROR_OBJECT (self, "Invalid window handle");
    g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
        "Invalid window handle");
    return GST_FLOW_ERROR;
  }

  GST_DEBUG_OBJECT (self, "Preparing external handle");
  ret = gst_d3d11_window_win32_set_external_handle (self, hwnd);
  if (ret != GST_FLOW_OK) {
    gst_structure_free (config);
    if (ret == GST_FLOW_FLUSHING) {
      GST_WARNING_OBJECT (self, "Flushing");
      return GST_FLOW_FLUSHING;
    }

    GST_ERROR_OBJECT (self, "Couldn't configure internal window");
    g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
        "Window handle configuration failed");
    return GST_FLOW_ERROR;
  }

  GST_DEBUG_OBJECT (self, "External handle got prepared");
  self->setup_external_hwnd = FALSE;

done:
  return GST_D3D11_WINDOW_CLASS (parent_class)->prepare (window, display_width,
      display_height, caps, config, display_format, error);
}

static void
gst_d3d11_window_win32_unprepare (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);

  GST_DEBUG_OBJECT (self, "unprepare");

  if (self->external_hwnd) {
    G_LOCK (get_instance_lock);
    gst_d3d11_window_win32_release_external_handle (self);
    RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
    G_UNLOCK (get_instance_lock);

    if (self->internal_hwnd_thread == g_thread_self ()) {
      /* State changing thread is identical to internal window thread.
       * window can be closed here */

      GST_INFO_OBJECT (self, "Closing internal window immediately");
      gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
    } else if (self->internal_hwnd) {
      /* We cannot destroy internal window from non-window thread.
       * and we cannot use synchronously SendMessage() method at this point
       * since window thread might be waiting for current thread and SendMessage()
       * will be blocked until it's called from window thread.
       * Instead, posts message so that it can be closed from window thread
       * asynchronously */
      GST_INFO_OBJECT (self, "Posting custom destory message");
      PostMessageA (self->internal_hwnd, WM_GST_D3D11_DESTROY_INTERNAL_WINDOW,
          0, 0);
    }

    /* Hold hwnds lock to allow the window thread finish processing what it could
     * already have in the queue at this moment, before we reset the handlers to null */
    g_rec_mutex_lock (&self->hwnds_lock);
    self->external_hwnd = NULL;
    self->internal_hwnd = NULL;
    self->internal_hwnd_thread = NULL;
    g_rec_mutex_unlock (&self->hwnds_lock);
  }

  if (self->loop) {
    g_main_loop_quit (self->loop);
  }

  if (self->thread) {
    g_thread_join (self->thread);
    self->thread = NULL;
  }

  if (self->loop) {
    g_main_loop_unref (self->loop);
    self->loop = NULL;
  }

  if (self->main_context) {
    g_main_context_unref (self->main_context);
    self->main_context = NULL;
  }
}

static GstD3D11WindowWin32 *
gst_d3d11_window_win32_hwnd_get_instance (HWND hwnd)
{
  HANDLE handle;
  G_LOCK (get_instance_lock);
  handle = GetPropA (hwnd, D3D11_WINDOW_PROP_NAME);
  if (handle)
    handle = gst_object_ref (handle);
  G_UNLOCK (get_instance_lock);

  return (GstD3D11WindowWin32 *) handle;
}

static void
gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window,
    const GstVideoRectangle * rect)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);

  self->render_rect = *rect;

  if (self->external_hwnd && self->internal_hwnd) {
    g_atomic_int_add (&self->pending_move_window, 1);

    if (self->internal_hwnd_thread == g_thread_self ()) {
      /* We are on message pumping thread already, handle this synchroniously */
      SendMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0);
    } else {
      /* Post message to message pumping thread. Handling HWND specific message
       * on message pumping thread is not a worst idea in generall */
      PostMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0);
    }
  } else if (!window->external_handle && self->internal_hwnd) {
    MoveWindow (self->internal_hwnd,
        self->render_rect.x, self->render_rect.y, self->render_rect.w,
        self->render_rect.h, TRUE);
  }
}

static void
gst_d3d11_window_win32_set_title (GstD3D11Window * window, const gchar * title)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);

  /* Do this only when we are rendring on our own HWND */
  if (!self->external_hwnd && self->internal_hwnd) {
    gunichar2 *str = g_utf8_to_utf16 (title, -1, nullptr, nullptr, nullptr);

    if (str) {
      SetWindowTextW (self->internal_hwnd, (LPCWSTR) str);
      g_free (str);
    }
  }
}

static gboolean
gst_d3d11_window_win32_unlock (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  GstD3D11SRWLockGuard lk (&self->lock);

  GST_DEBUG_OBJECT (self, "Unlock");

  self->flushing = TRUE;
  WakeAllConditionVariable (&self->cond);

  return TRUE;
}

static gboolean
gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  GstD3D11SRWLockGuard lk (&self->lock);

  GST_DEBUG_OBJECT (self, "Unlock stop");

  self->flushing = FALSE;
  WakeAllConditionVariable (&self->cond);

  return TRUE;
}

static gboolean
running_cb (gpointer user_data)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (user_data);

  GST_TRACE_OBJECT (self, "Main loop running now");

  AcquireSRWLockExclusive (&self->lock);
  WakeConditionVariable (&self->cond);
  ReleaseSRWLockExclusive (&self->lock);

  return G_SOURCE_REMOVE;
}

static gboolean
msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
{
  MSG msg;

  if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
    return G_SOURCE_CONTINUE;

  TranslateMessage (&msg);
  DispatchMessage (&msg);

  return G_SOURCE_CONTINUE;
}

static gpointer
gst_d3d11_window_win32_thread_func (gpointer data)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (data);
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (data);
  GSource *source;

  GST_DEBUG_OBJECT (self, "Enter loop");
  g_main_context_push_thread_default (self->main_context);

  window->initialized = gst_d3d11_window_win32_create_internal_window (self);

  /* Watching and dispatching all messages on this thread */
  self->msg_io_channel = g_io_channel_win32_new_messages (0);
  self->msg_source = g_io_create_watch (self->msg_io_channel, G_IO_IN);
  g_source_set_callback (self->msg_source, (GSourceFunc) msg_cb, self, NULL);
  g_source_attach (self->msg_source, self->main_context);

  source = g_idle_source_new ();
  g_source_set_callback (source, (GSourceFunc) running_cb, self, NULL);
  g_source_attach (source, self->main_context);
  g_source_unref (source);

  g_main_loop_run (self->loop);

  RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
  gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
  self->internal_hwnd = NULL;
  self->internal_hwnd_thread = NULL;

  if (self->msg_source) {
    g_source_destroy (self->msg_source);
    g_source_unref (self->msg_source);
    self->msg_source = NULL;
  }

  if (self->msg_io_channel) {
    g_io_channel_unref (self->msg_io_channel);
    self->msg_io_channel = NULL;
  }

  g_main_context_pop_thread_default (self->main_context);

  GST_DEBUG_OBJECT (self, "Exit loop");

  return NULL;
}

static void
gst_d3d11_window_win32_destroy_internal_window (HWND hwnd)
{
  if (!hwnd)
    return;

  ShowWindow (hwnd, SW_HIDE);
  SetParent (hwnd, NULL);

  GST_INFO ("Destroying internal window %" G_GUINTPTR_FORMAT, (guintptr) hwnd);

  if (!DestroyWindow (hwnd))
    g_critical ("failed to destroy window %" G_GUINTPTR_FORMAT
        ", 0x%x", (guintptr) hwnd, (guint) GetLastError ());
}

static GstFlowReturn
gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self,
    HWND hwnd)
{
  WNDPROC external_window_proc;
  GstFlowReturn ret = GST_FLOW_OK;

  GstD3D11SRWLockGuard lk (&self->lock);
  self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE;
  self->external_hwnd = hwnd;

  G_LOCK (get_instance_lock);
  external_window_proc = (WNDPROC) GetWindowLongPtrA (hwnd, GWLP_WNDPROC);

  GST_DEBUG_OBJECT (self, "set external window %" G_GUINTPTR_FORMAT
      ", original window procedure %p", (guintptr) hwnd, external_window_proc);

  g_assert (external_window_proc != sub_class_proc);
  g_warn_if_fail (GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME) == NULL);
  g_warn_if_fail (GetPropA (hwnd, D3D11_WINDOW_PROP_NAME) == NULL);

  SetPropA (hwnd, EXTERNAL_PROC_PROP_NAME, (HANDLE) external_window_proc);
  SetPropA (hwnd, D3D11_WINDOW_PROP_NAME, self);

  SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) sub_class_proc);
  G_UNLOCK (get_instance_lock);

  /* SendMessage() may cause deadlock if parent window thread is busy
   * for changing pipeline's state. Post our message instead, and wait for
   * the parent window's thread or flushing */
  PostMessageA (hwnd, WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW, 0, 0);
  while (self->external_hwnd &&
      self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE &&
      !self->flushing) {
    SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0);
  }

  if (self->overlay_state != GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED) {
    if (self->flushing)
      ret = GST_FLOW_FLUSHING;
    else
      ret = GST_FLOW_ERROR;
  }

  return ret;
}

static void
gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self)
{
  WNDPROC external_proc;
  HWND hwnd = self->external_hwnd;

  if (!hwnd)
    return;

  self->external_hwnd = NULL;
  external_proc = (WNDPROC) GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME);
  if (!external_proc) {
    GST_WARNING_OBJECT (self, "Failed to get original window procedure");
    return;
  }

  GST_DEBUG_OBJECT (self, "release external window %" G_GUINTPTR_FORMAT
      ", original window procedure %p", (guintptr) hwnd, external_proc);

  RemovePropA (hwnd, EXTERNAL_PROC_PROP_NAME);
  RemovePropA (hwnd, D3D11_WINDOW_PROP_NAME);

  if (!SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) external_proc))
    GST_WARNING_OBJECT (self, "Couldn't restore original window procedure");
}

static gboolean
gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self)
{
  WNDCLASSEXA wc;
  ATOM atom = 0;
  HINSTANCE hinstance = GetModuleHandleA (NULL);

  GST_LOG_OBJECT (self, "Attempting to create a win32 window");

  G_LOCK (create_lock);
  atom = GetClassInfoExA (hinstance, "GSTD3D11", &wc);
  if (atom == 0) {
    GST_LOG_OBJECT (self, "Register internal window class");
    ZeroMemory (&wc, sizeof (WNDCLASSEXA));

    wc.cbSize = sizeof (WNDCLASSEXA);
    wc.lpfnWndProc = window_proc;
    wc.hInstance = hinstance;
    wc.hIcon = LoadIcon (NULL, IDI_WINLOGO);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hCursor = LoadCursor (NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
    wc.lpszClassName = "GSTD3D11";

    atom = RegisterClassExA (&wc);

    if (atom == 0) {
      G_UNLOCK (create_lock);
      GST_ERROR_OBJECT (self, "Failed to register window class 0x%x",
          (unsigned int) GetLastError ());
      return FALSE;
    }
  } else {
    GST_LOG_OBJECT (self, "window class was already registered");
  }

  self->internal_hwnd = 0;
  self->visible = FALSE;

  self->internal_hwnd = CreateWindowExA (0,
      "GSTD3D11",
      "Direct3D11 renderer", WS_GST_D3D11,
      CW_USEDEFAULT, CW_USEDEFAULT,
      0, 0, (HWND) NULL, (HMENU) NULL, hinstance, self);

  G_UNLOCK (create_lock);

  if (!self->internal_hwnd) {
    GST_ERROR_OBJECT (self, "Failed to create d3d11 window");
    return FALSE;
  }

  GST_DEBUG_OBJECT (self, "d3d11 window created: %" G_GUINTPTR_FORMAT,
      (guintptr) self->internal_hwnd);

  GST_LOG_OBJECT (self,
      "Created a internal d3d11 window %p", self->internal_hwnd);

  self->internal_hwnd_thread = g_thread_self ();

  return TRUE;
}

/* always called from window thread */
static void
gst_d3d11_window_win32_change_fullscreen_mode_internal (GstD3D11WindowWin32 *
    self)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (self);
  HWND hwnd = self->external_hwnd ? self->external_hwnd : self->internal_hwnd;

  if (!window->swap_chain)
    return;

  if (window->requested_fullscreen == window->fullscreen)
    return;

  GST_DEBUG_OBJECT (self, "Change mode to %s",
      window->requested_fullscreen ? "fullscreen" : "windowed");

  window->fullscreen = !window->fullscreen;

  if (!window->fullscreen) {
    /* Restore the window's attributes and size */
    SetWindowLongA (hwnd, GWL_STYLE, self->restore_style);

    SetWindowPlacement (hwnd, &self->restore_placement);
  } else {
    ComPtr < IDXGIOutput > output;
    DXGI_OUTPUT_DESC output_desc;
    IDXGISwapChain *swap_chain = window->swap_chain;

    /* remember current placement to restore window later */
    GetWindowPlacement (hwnd, &self->restore_placement);

    /* show window before change style */
    ShowWindow (hwnd, SW_SHOW);

    self->restore_style = GetWindowLong (hwnd, GWL_STYLE);

    /* Make the window borderless so that the client area can fill the screen */
    SetWindowLongA (hwnd, GWL_STYLE,
        self->restore_style &
        ~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
            WS_THICKFRAME | WS_MAXIMIZE));

    swap_chain->GetContainingOutput (&output);
    output->GetDesc (&output_desc);

    SetWindowPos (hwnd, HWND_TOP,
        output_desc.DesktopCoordinates.left,
        output_desc.DesktopCoordinates.top,
        output_desc.DesktopCoordinates.right,
        output_desc.DesktopCoordinates.bottom,
        SWP_FRAMECHANGED | SWP_NOACTIVATE);

    ShowWindow (hwnd, SW_MAXIMIZE);
  }

  GST_DEBUG_OBJECT (self, "Fullscreen mode change done");
}

static void
gst_d3d11_window_win32_on_key_event (GstD3D11WindowWin32 * self,
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (self);
  gunichar2 wcrep[128];
  const gchar *event;

  if (!window->enable_navigation_events)
    return;

  if (GetKeyNameTextW (lParam, (LPWSTR) wcrep, 128)) {
    gchar *utfrep = g_utf16_to_utf8 (wcrep, 128, NULL, NULL, NULL);
    if (utfrep) {
      if (uMsg == WM_KEYDOWN)
        event = "key-press";
      else
        event = "key-release";

      gst_d3d11_window_on_key_event (window, event, utfrep);
      g_free (utfrep);
    }
  }
}

static void
gst_d3d11_window_win32_on_mouse_event (GstD3D11WindowWin32 * self,
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (self);
  gint button;
  const gchar *event = nullptr;
  guint modifier = 0;
  gint delta_x = 0, delta_y = 0;
  POINT screen_point = { 0, 0 };

  if (!window->enable_navigation_events)
    return;

  switch (uMsg) {
    case WM_MOUSEMOVE:
      button = 0;
      event = "mouse-move";
      break;
    case WM_LBUTTONDOWN:
      button = 1;
      event = "mouse-button-press";
      break;
    case WM_LBUTTONUP:
      button = 1;
      event = "mouse-button-release";
      break;
    case WM_LBUTTONDBLCLK:
      button = 1;
      event = "mouse-double-click";
      break;
    case WM_RBUTTONDOWN:
      button = 2;
      event = "mouse-button-press";
      break;
    case WM_RBUTTONUP:
      button = 2;
      event = "mouse-button-release";
      break;
    case WM_RBUTTONDBLCLK:
      button = 2;
      event = "mouse-double-click";
      break;
    case WM_MBUTTONDOWN:
      button = 3;
      event = "mouse-button-press";
      break;
    case WM_MBUTTONUP:
      button = 3;
      event = "mouse-button-release";
      break;
    case WM_MBUTTONDBLCLK:
      button = 3;
      event = "mouse-double-click";
      break;
    case WM_MOUSEHWHEEL:
      button = 0;
      event = "mouse-scroll";
      delta_x = GET_WHEEL_DELTA_WPARAM (wParam);
      break;
    case WM_MOUSEWHEEL:
      button = 0;
      event = "mouse-scroll";
      delta_y = GET_WHEEL_DELTA_WPARAM (wParam);
      break;
    default:
      return;
  }

  if ((wParam & MK_CONTROL) != 0)
    modifier |= GST_NAVIGATION_MODIFIER_CONTROL_MASK;
  if ((wParam & MK_LBUTTON) != 0)
    modifier |= GST_NAVIGATION_MODIFIER_BUTTON1_MASK;
  if ((wParam & MK_RBUTTON) != 0)
    modifier |= GST_NAVIGATION_MODIFIER_BUTTON2_MASK;
  if ((wParam & MK_MBUTTON) != 0)
    modifier |= GST_NAVIGATION_MODIFIER_BUTTON3_MASK;
  if ((wParam & MK_SHIFT) != 0)
    modifier |= GST_NAVIGATION_MODIFIER_SHIFT_MASK;

  if (uMsg == WM_MOUSEHWHEEL || uMsg == WM_MOUSEWHEEL) {
    screen_point.x = GET_X_LPARAM (lParam);
    screen_point.y = GET_Y_LPARAM (lParam);
    ScreenToClient (hWnd, &screen_point);

    gst_d3d11_window_on_mouse_scroll_event (window, event,
        (gdouble) screen_point.x, (gdouble) screen_point.y,
        delta_x, delta_y, modifier);
  } else {
    gst_d3d11_window_on_mouse_event (window,
        event, button, (gdouble) GET_X_LPARAM (lParam),
        (gdouble) GET_Y_LPARAM (lParam), modifier);
  }
}

static void
gst_d3d11_window_win32_handle_window_proc (GstD3D11WindowWin32 * self,
    HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  GstD3D11Window *window = GST_D3D11_WINDOW (self);

  switch (uMsg) {
    case WM_SIZE:
      gst_d3d11_window_win32_on_resize (window, 0, 0);
      break;
    case WM_CLOSE:
      if (self->internal_hwnd) {
        RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
        gst_d3d11_window_win32_destroy_internal_window (hWnd);
        self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED;
        self->internal_hwnd = NULL;
        self->internal_hwnd_thread = NULL;
      }
      break;
    case WM_KEYDOWN:
    case WM_KEYUP:
      gst_d3d11_window_win32_on_key_event (self, hWnd, uMsg, wParam, lParam);
      break;
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_MOUSEMOVE:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDBLCLK:
    case WM_MOUSEWHEEL:
    case WM_MOUSEHWHEEL:
      gst_d3d11_window_win32_on_mouse_event (self, hWnd, uMsg, wParam, lParam);
      break;
    case WM_SYSKEYDOWN:
      if ((window->fullscreen_toggle_mode &
              GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER)
          == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER) {
        WORD state = GetKeyState (VK_RETURN);
        BYTE high = HIBYTE (state);

        if (high & 0x1) {
          window->requested_fullscreen = !window->fullscreen;
          gst_d3d11_window_win32_change_fullscreen_mode_internal (self);
        }
      }
      break;
    case WM_GST_D3D11_FULLSCREEN:
      if (g_atomic_int_get (&self->pending_fullscreen_count)) {
        g_atomic_int_dec_and_test (&self->pending_fullscreen_count);
        if ((window->fullscreen_toggle_mode &
                GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
            == GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
          gst_d3d11_window_win32_change_fullscreen_mode_internal (self);
      }
      break;
    case WM_GST_D3D11_MOVE_WINDOW:
      if (g_atomic_int_get (&self->pending_move_window)) {
        g_atomic_int_set (&self->pending_move_window, 0);

        if (self->internal_hwnd && self->external_hwnd) {
          if (self->render_rect.w < 0 || self->render_rect.h < 0) {
            RECT rect;

            /* Reset render rect and back to full-size window */
            if (GetClientRect (self->external_hwnd, &rect)) {
              MoveWindow (self->internal_hwnd, 0, 0,
                  rect.right - rect.left, rect.bottom - rect.top, FALSE);
            }
          } else {
            MoveWindow (self->internal_hwnd, self->render_rect.x,
                self->render_rect.y, self->render_rect.w, self->render_rect.h,
                FALSE);
          }
        }
      }
      break;
    case WM_GST_D3D11_SHOW_WINDOW:
      ShowWindow (self->internal_hwnd, SW_SHOW);
      break;
    default:
      break;
  }

  return;
}

static LRESULT CALLBACK
window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  GstD3D11WindowWin32 *self;

  if (uMsg == WM_GST_D3D11_DESTROY_INTERNAL_WINDOW) {
    GST_INFO ("Handle destroy window message");
    gst_d3d11_window_win32_destroy_internal_window (hWnd);
    return 0;
  }

  if (uMsg == WM_CREATE) {
    self = GST_D3D11_WINDOW_WIN32 (((LPCREATESTRUCT) lParam)->lpCreateParams);

    GST_LOG_OBJECT (self, "WM_CREATE");

    SetPropA (hWnd, D3D11_WINDOW_PROP_NAME, self);
  } else if ((self = gst_d3d11_window_win32_hwnd_get_instance (hWnd))) {
    g_rec_mutex_lock (&self->hwnds_lock);
    if (self->internal_hwnd != hWnd) {
      g_warn_if_fail (self->internal_hwnd == NULL);
      /* Most likely the instance is already in the destruction proccess,
       * just let it process WM_GST_D3D11_DESTROY_INTERNAL_WINDOW */
      g_rec_mutex_unlock (&self->hwnds_lock);
      gst_object_unref (self);
      return DefWindowProcA (hWnd, uMsg, wParam, lParam);
    }

    gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam,
        lParam);
    g_rec_mutex_unlock (&self->hwnds_lock);

    switch (uMsg) {
      case WM_SIZE:
        /* We handled this event already */
        gst_object_unref (self);
        return 0;
      case WM_NCHITTEST:
        /* To passthrough mouse event if external window is used.
         * Only hit-test succeeded window can receive/handle some mouse events
         * and we want such events to be handled by parent (application) window
         */
        if (self->external_hwnd) {
          gst_object_unref (self);
          return (LRESULT) HTTRANSPARENT;
        }
        break;
      default:
        break;
    }
    gst_object_unref (self);
  }

  return DefWindowProcA (hWnd, uMsg, wParam, lParam);
}

static LRESULT FAR PASCAL
sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  WNDPROC external_window_proc =
      (WNDPROC) GetPropA (hWnd, EXTERNAL_PROC_PROP_NAME);
  GstD3D11WindowWin32 *self = gst_d3d11_window_win32_hwnd_get_instance (hWnd);

  if (self == NULL || self->flushing) {
    GST_DEBUG ("No object attached to the window, chain up to default");
    gst_clear_object (&self);
    return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam);
  }

  switch (uMsg) {
    case WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW:{
      GstD3D11Window *window = GST_D3D11_WINDOW (self);
      RECT rect;

      GST_DEBUG_OBJECT (self, "Create internal window");

      GstD3D11SRWLockGuard lk (&self->lock);
      if (self->internal_hwnd) {
        GST_WARNING_OBJECT (self,
            "Window already created, probably we have received 2 creation messages");
        g_warn_if_fail (self->overlay_state ==
            GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED);
        gst_object_unref (self);
        return 0;
      }

      if (self->flushing) {
        GST_DEBUG_OBJECT (self, "Flushing");
        gst_object_unref (self);
        return 0;
      }

      window->initialized =
          gst_d3d11_window_win32_create_internal_window (self);

      SetWindowLongPtrA (self->internal_hwnd, GWL_STYLE,
          WS_CHILD | WS_MAXIMIZE);
      SetParent (self->internal_hwnd, self->external_hwnd);

      /* take changes into account: SWP_FRAMECHANGED */
      GetClientRect (self->external_hwnd, &rect);

      if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
          self->render_rect.w != 0 || self->render_rect.h != 0) {
        rect.left = self->render_rect.x;
        rect.top = self->render_rect.y;
        rect.right = self->render_rect.x + self->render_rect.w;
        rect.bottom = self->render_rect.y + self->render_rect.h;
      }

      SetWindowPos (self->internal_hwnd, HWND_TOP, rect.left, rect.top,
          rect.right - rect.left, rect.bottom - rect.top,
          SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
          SWP_FRAMECHANGED | SWP_NOACTIVATE);
      MoveWindow (self->internal_hwnd, rect.left, rect.top,
          rect.right - rect.left, rect.bottom - rect.top, FALSE);

      self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED;
      WakeAllConditionVariable (&self->cond);

      /* don't need to be chained up to parent window procedure,
       * as this is our custom message */
      gst_object_unref (self);
      return 0;
    }
    case WM_SIZE:
      if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
          self->render_rect.w != 0 || self->render_rect.h != 0) {
        MoveWindow (self->internal_hwnd,
            self->render_rect.x, self->render_rect.y,
            self->render_rect.w, self->render_rect.h, FALSE);
      } else {
        MoveWindow (self->internal_hwnd, 0, 0, LOWORD (lParam), HIWORD (lParam),
            FALSE);
      }
      break;
    case WM_CLOSE:
    case WM_DESTROY:{
      GstD3D11SRWLockGuard lk (&self->lock);
      GST_WARNING_OBJECT (self, "external window is closing");
      gst_d3d11_window_win32_release_external_handle (self);

      if (self->internal_hwnd) {
        RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
        gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
      }
      self->internal_hwnd = NULL;
      self->internal_hwnd_thread = NULL;

      self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED;
      WakeAllConditionVariable (&self->cond);
      break;
    }
    default:
      gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam,
          lParam);
      break;
  }

  gst_object_unref (self);
  return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam);
}

static void
gst_d3d11_window_win32_disable_alt_enter (GstD3D11WindowWin32 * self,
    GstD3D11Device * device, IDXGISwapChain * swap_chain, HWND hwnd)
{
  ComPtr < IDXGIFactory1 > factory;
  HRESULT hr;

  hr = swap_chain->GetParent (IID_PPV_ARGS (&factory));
  if (!gst_d3d11_result (hr, device) || !factory) {
    GST_WARNING_OBJECT (self,
        "Cannot get parent dxgi factory for swapchain %p, hr: 0x%x",
        swap_chain, (guint) hr);
    return;
  }

  hr = factory->MakeWindowAssociation (hwnd, DXGI_MWA_NO_ALT_ENTER);
  if (!gst_d3d11_result (hr, device)) {
    GST_WARNING_OBJECT (self,
        "MakeWindowAssociation failure, hr: 0x%x", (guint) hr);
  }
}

static IDXGISwapChain *
create_swap_chain (GstD3D11WindowWin32 * self, GstD3D11Device * device,
    DXGI_SWAP_CHAIN_DESC * desc)
{
  HRESULT hr;
  IDXGISwapChain *swap_chain = NULL;
  ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
  IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device);

  GstD3D11DeviceLockGuard lk (device);
  hr = factory->CreateSwapChain (device_handle, desc, &swap_chain);

  if (!gst_d3d11_result (hr, device)) {
    GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x",
        (guint) hr);
    swap_chain = NULL;
  }

  return swap_chain;
}

static IDXGISwapChain1 *
create_swap_chain_for_hwnd (GstD3D11WindowWin32 * self, GstD3D11Device * device,
    HWND hwnd, DXGI_SWAP_CHAIN_DESC1 * desc,
    DXGI_SWAP_CHAIN_FULLSCREEN_DESC * fullscreen_desc, IDXGIOutput * output)
{
  HRESULT hr;
  IDXGISwapChain1 *swap_chain = NULL;
  ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
  IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device);
  ComPtr < IDXGIFactory2 > factory2;

  hr = factory->QueryInterface (IID_PPV_ARGS (&factory2));
  if (!gst_d3d11_result (hr, device)) {
    GST_WARNING_OBJECT (self, "IDXGIFactory2 interface is unavailable");
    return NULL;
  }

  GstD3D11DeviceLockGuard lk (device);
  hr = factory2->CreateSwapChainForHwnd (device_handle, hwnd, desc,
      fullscreen_desc, output, &swap_chain);

  if (!gst_d3d11_result (hr, device)) {
    GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x",
        (guint) hr);
    swap_chain = NULL;
  }

  return swap_chain;
}

static gboolean
gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window,
    DXGI_FORMAT format, guint width, guint height,
    guint swapchain_flags, IDXGISwapChain ** swap_chain)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  DXGI_SWAP_CHAIN_DESC desc = { 0, };
  IDXGISwapChain *new_swapchain = NULL;
  GstD3D11Device *device = window->device;

  self->have_swapchain1 = FALSE;

  {
    DXGI_SWAP_CHAIN_DESC1 desc1 = { 0, };
    desc1.Width = 0;
    desc1.Height = 0;
    desc1.Format = format;
    /* FIXME: add support stereo */
    desc1.Stereo = FALSE;
    desc1.SampleDesc.Count = 1;
    desc1.SampleDesc.Quality = 0;
    desc1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc1.BufferCount = 2;
    desc1.Scaling = DXGI_SCALING_STRETCH;

    /* scaling-stretch would break aspect-ratio so we prefer to use scaling-none,
     * but Windows7 does not support this method */
    if (gst_d3d11_is_windows_8_or_greater ())
      desc1.Scaling = DXGI_SCALING_NONE;
    desc1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    desc1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    desc1.Flags = swapchain_flags;

    new_swapchain = create_swap_chain_for_hwnd (self, device,
        self->internal_hwnd, &desc1, NULL, NULL);

    if (!new_swapchain) {
      GST_WARNING_OBJECT (self, "Failed to create swapchain1");
    } else {
      self->have_swapchain1 = TRUE;
    }
  }

  if (!new_swapchain) {
    DXGI_SWAP_EFFECT swap_effect = DXGI_SWAP_EFFECT_DISCARD;

    if (gst_d3d11_is_windows_8_or_greater ())
      swap_effect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;

    /* we will get client area at on_resize */
    desc.BufferDesc.Width = 0;
    desc.BufferDesc.Height = 0;
    /* don't care refresh rate */
    desc.BufferDesc.RefreshRate.Numerator = 0;
    desc.BufferDesc.RefreshRate.Denominator = 1;
    desc.BufferDesc.Format = format;
    desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;
    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 2;
    desc.SwapEffect = swap_effect;
    desc.OutputWindow = self->internal_hwnd;
    desc.Windowed = TRUE;
    desc.Flags = swapchain_flags;

    new_swapchain = create_swap_chain (self, device, &desc);
  }

  if (!new_swapchain) {
    GST_ERROR_OBJECT (self, "Cannot create swapchain");
    return FALSE;
  }

  /* disable alt+enter here. It should be manually handled */
  GstD3D11DeviceLockGuard lk (device);
  gst_d3d11_window_win32_disable_alt_enter (self,
      device, new_swapchain, desc.OutputWindow);

  *swap_chain = new_swapchain;

  return TRUE;
}

static void
gst_d3d11_window_win32_show (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  gint width, height;

  switch (window->method) {
    case GST_VIDEO_ORIENTATION_90R:
    case GST_VIDEO_ORIENTATION_90L:
    case GST_VIDEO_ORIENTATION_UL_LR:
    case GST_VIDEO_ORIENTATION_UR_LL:
      width = GST_VIDEO_INFO_HEIGHT (&window->render_info);
      height = GST_VIDEO_INFO_WIDTH (&window->render_info);
      break;
    default:
      width = GST_VIDEO_INFO_WIDTH (&window->render_info);
      height = GST_VIDEO_INFO_HEIGHT (&window->render_info);
      break;
  }

  if (!self->visible) {
    /* if no parent the real size has to be set now because this has not been done
     * when at window creation */
    if (!self->external_hwnd) {
      if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
          self->render_rect.w != 0 || self->render_rect.h != 0) {
        MoveWindow (self->internal_hwnd,
            self->render_rect.x, self->render_rect.y, self->render_rect.w,
            self->render_rect.h, FALSE);
      } else {
        RECT rect = { 0, };

        rect.right = width;
        rect.bottom = height;

        if (AdjustWindowRect (&rect, WS_GST_D3D11, FALSE)) {
          width = rect.right - rect.left;
          height = rect.bottom - rect.top;
        } else {
          width += 2 * GetSystemMetrics (SM_CXSIZEFRAME);
          height +=
              2 * GetSystemMetrics (SM_CYSIZEFRAME) +
              GetSystemMetrics (SM_CYCAPTION);
        }

        MoveWindow (self->internal_hwnd, 0, 0, width, height, FALSE);
      }

      ShowWindow (self->internal_hwnd, SW_SHOW);
    } else if (self->internal_hwnd) {
      /* ShowWindow will throw message to message pumping thread (app thread)
       * synchroniously, which can be blocked at the moment.
       * Post message to internal hwnd and do that from message pumping thread
       */
      PostMessageA (self->internal_hwnd, WM_GST_D3D11_SHOW_WINDOW, 0, 0);
    }

    if (g_atomic_int_get (&self->pending_fullscreen_count) > 0)
      PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0);

    self->visible = TRUE;
  }
}

static GstFlowReturn
gst_d3d11_window_win32_present (GstD3D11Window * window, guint present_flags)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  HRESULT hr;

  if ((!self->external_hwnd &&
          self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED)
      || !self->internal_hwnd) {
    GST_ERROR_OBJECT (self, "Output window was closed");

    return GST_D3D11_WINDOW_FLOW_CLOSED;
  }

  if (self->have_swapchain1) {
    IDXGISwapChain1 *swap_chain1 = (IDXGISwapChain1 *) window->swap_chain;
    DXGI_PRESENT_PARAMETERS present_params = { 0, };

    /* the first present should not specify dirty-rect */
    if (!window->first_present && !window->emit_present) {
      present_params.DirtyRectsCount = 1;
      present_params.pDirtyRects = &window->render_rect;
    }

    hr = swap_chain1->Present1 (0, present_flags, &present_params);
  } else {
    hr = window->swap_chain->Present (0, present_flags);
  }

  if (!gst_d3d11_result (hr, window->device)) {
    GST_WARNING_OBJECT (self, "Direct3D cannot present texture, hr: 0x%x",
        (guint) hr);
  }

  return GST_FLOW_OK;
}

static void
gst_d3d11_window_win32_on_resize (GstD3D11Window * window,
    guint width, guint height)
{
  /* Set zero width and height here. dxgi will decide client area by itself */
  GST_D3D11_WINDOW_CLASS (parent_class)->on_resize (window, 0, 0);
}

static void
gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);

  if (self->internal_hwnd)
    PostMessageA (self->internal_hwnd, WM_SIZE, 0, 0);

  return;
}

static void
gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);

  if (self->internal_hwnd) {
    g_atomic_int_add (&self->pending_fullscreen_count, 1);
    if (self->visible)
      PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0);
  }
}

HWND
gst_d3d11_window_win32_get_internal_hwnd (GstD3D11Window * window)
{
  GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
  return self->internal_hwnd;
}

GstD3D11Window *
gst_d3d11_window_win32_new (GstD3D11Device * device, guintptr handle)
{
  GstD3D11Window *window;

  g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);

  window = (GstD3D11Window *) g_object_new (GST_TYPE_D3D11_WINDOW_WIN32,
      "d3d11device", device, "window-handle", handle, NULL);
  if (!window->initialized) {
    gst_object_unref (window);
    return NULL;
  }

  gst_object_ref_sink (window);

  return window;
}
