#include <ddraw.h>
#undef interface

static LRESULT CALLBACK VideoDirectDraw7_WindowProcedure(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
  if(msg == WM_SYSKEYDOWN && wparam == VK_F4) return false;
  return DefWindowProc(hwnd, msg, wparam, lparam);
}

struct VideoDirectDraw : VideoDriver {
  VideoDirectDraw& self = *this;
  VideoDirectDraw(Video& super) : VideoDriver(super) { construct(); }
  ~VideoDirectDraw() { destruct(); }

  auto create() -> bool override {
    VideoDriver::shader = "Blur";
    return initialize();
  }

  auto driver() -> string override { return "DirectDraw 7.0"; }
  auto ready() -> bool override { return _ready; }

  auto hasFullScreen() -> bool override { return true; }
  auto hasMonitor() -> bool override { return true; }
  auto hasContext() -> bool override { return true; }
  auto hasBlocking() -> bool override { return true; }

  auto setFullScreen(bool fullScreen) -> bool override {
    return initialize();
  }

  auto setMonitor(string monitor) -> bool override {
    return initialize();
  }

  auto setContext(uintptr context) -> bool override {
    return initialize();
  }

  auto setBlocking(bool blocking) -> bool override {
    return true;
  }

  auto focused() -> bool override {
    if(self.fullScreen && self.exclusive) return true;
    auto focused = GetFocus();
    return _context == focused || IsChild(_context, focused);
  }

  auto clear() -> void override {
    DDBLTFX fx{};
    fx.dwSize = sizeof(DDBLTFX);
    fx.dwFillColor = 0x00000000;
    _screen->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
    _raster->Blt(0, 0, 0, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
  }

  auto size(uint& width, uint& height) -> void override {
    RECT rectangle;
    GetClientRect(_context, &rectangle);
    width = rectangle.right - rectangle.left;
    height = rectangle.bottom - rectangle.top;
  }

  auto acquire(uint32_t*& data, uint& pitch, uint width, uint height) -> bool override {
    if(width != _width || height != _height) resize(_width = width, _height = height);
    DDSURFACEDESC2 description = {};
    description.dwSize = sizeof(DDSURFACEDESC2);
    if(_raster->Lock(0, &description, DDLOCK_WAIT, 0) != DD_OK) {
      _raster->Restore();
      if(_raster->Lock(0, &description, DDLOCK_WAIT, 0) != DD_OK) return false;
    }
    pitch = description.lPitch;
    return data = (uint32_t*)description.lpSurface;
  }

  auto release() -> void override {
    _raster->Unlock(0);
  }

  auto output(uint width, uint height) -> void override {
    uint windowWidth, windowHeight;
    size(windowWidth, windowHeight);

    if(self.blocking) while(true) {
      BOOL vblank;
      _interface->GetVerticalBlankStatus(&vblank);
      if(vblank) break;
    }

    RECT source;
    SetRect(&source, 0, 0, _width, _height);

    POINT point{0, 0};
    ClientToScreen(_context, &point);

    RECT target;
    GetClientRect(_context, &target);
    OffsetRect(&target, point.x, point.y);

    target.left += ((int)windowWidth - (int)width) / 2;
    target.top += ((int)windowHeight - (int)height) / 2;
    target.right = target.left + width;
    target.bottom = target.top + height;

    if(_screen->Blt(&target, _raster, &source, DDBLT_WAIT, 0) == DDERR_SURFACELOST) {
      _screen->Restore();
      _raster->Restore();
    }
  }

private:
  auto construct() -> void {
    WNDCLASS windowClass{};
    windowClass.cbClsExtra = 0;
    windowClass.cbWndExtra = 0;
    windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    windowClass.hCursor = LoadCursor(0, IDC_ARROW);
    windowClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
    windowClass.hInstance = GetModuleHandle(0);
    windowClass.lpfnWndProc = VideoDirect3D9_WindowProcedure;
    windowClass.lpszClassName = L"VideoDirectDraw7_Window";
    windowClass.lpszMenuName = 0;
    windowClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&windowClass);
  }

  auto destruct() -> void {
    terminate();
  }

  auto initialize() -> bool {
    terminate();
    if(!self.fullScreen && !self.context) return false;

    auto monitor = Video::monitor(self.monitor);
    _monitorX = monitor.x;
    _monitorY = monitor.y;
    _monitorWidth = monitor.width;
    _monitorHeight = monitor.height;

    if(self.fullScreen) {
      _context = _window = CreateWindowEx(WS_EX_TOPMOST, L"VideoDirectDraw7_Window", L"", WS_VISIBLE | WS_POPUP,
        _monitorX, _monitorY, _monitorWidth, _monitorHeight,
        nullptr, nullptr, GetModuleHandle(0), nullptr);
    } else {
      _context = (HWND)self.context;
    }

    LPDIRECTDRAW interface = nullptr;
    DirectDrawCreate(0, &interface, 0);
    interface->QueryInterface(IID_IDirectDraw7, (void**)&_interface);
    interface->Release();

    _interface->SetCooperativeLevel(_context, DDSCL_NORMAL);

    DDSURFACEDESC2 description{};
    description.dwSize = sizeof(DDSURFACEDESC2);
    description.dwFlags = DDSD_CAPS;
    description.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
    _interface->CreateSurface(&description, &_screen, 0);

    _interface->CreateClipper(0, &_clipper, 0);
    _clipper->SetHWnd(0, _context);
    _screen->SetClipper(_clipper);

    _raster = nullptr;
    _surfaceWidth = 0;
    _surfaceHeight = 0;
    resize(_width = 256, _height = 256);
    return _ready = true;
  }

  auto terminate() -> void {
    _ready = false;
    if(_clipper) { _clipper->Release(); _clipper = nullptr; }
    if(_raster) { _raster->Release(); _raster = nullptr; }
    if(_screen) { _screen->Release(); _screen = nullptr; }
    if(_interface) { _interface->Release(); _interface = nullptr; }
    if(_window) { DestroyWindow(_window); _window = nullptr; }
    _context = nullptr;
  }

  auto resize(uint width, uint height) -> void {
    if(_surfaceWidth >= width && _surfaceHeight >= height) return;

    _surfaceWidth = max(width, _surfaceWidth);
    _surfaceHeight = max(height, _surfaceHeight);

    if(_raster) _raster->Release();

    DDSURFACEDESC2 description{};
    description.dwSize = sizeof(DDSURFACEDESC2);
    _screen->GetSurfaceDesc(&description);
    int depth = description.ddpfPixelFormat.dwRGBBitCount;
    if(depth == 32) goto tryNativeSurface;

    memory::fill(&description, sizeof(DDSURFACEDESC2));
    description.dwSize = sizeof(DDSURFACEDESC2);
    description.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT;
    description.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;  //DDSCAPS_SYSTEMMEMORY
    description.dwWidth = _surfaceWidth;
    description.dwHeight = _surfaceHeight;

    description.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
    description.ddpfPixelFormat.dwFlags = DDPF_RGB;
    description.ddpfPixelFormat.dwRGBBitCount = 32;
    description.ddpfPixelFormat.dwRBitMask = 0xff0000;
    description.ddpfPixelFormat.dwGBitMask = 0x00ff00;
    description.ddpfPixelFormat.dwBBitMask = 0x0000ff;

    if(_interface->CreateSurface(&description, &_raster, 0) == DD_OK) return clear();

  tryNativeSurface:
    memory::fill(&description, sizeof(DDSURFACEDESC2));
    description.dwSize = sizeof(DDSURFACEDESC2);
    description.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
    description.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY;  //DDSCAPS_SYSTEMMEMORY
    description.dwWidth = _surfaceWidth;
    description.dwHeight = _surfaceHeight;

    if(_interface->CreateSurface(&description, &_raster, 0) == DD_OK) return clear();
  }

  bool _ready = false;

  int _monitorX = 0;
  int _monitorY = 0;
  int _monitorWidth = 0;
  int _monitorHeight = 0;

  uint _width = 0;
  uint _height = 0;

  HWND _context = nullptr;
  HWND _window = nullptr;
  LPDIRECTDRAW7 _interface = nullptr;
  LPDIRECTDRAWSURFACE7 _screen = nullptr;
  LPDIRECTDRAWSURFACE7 _raster = nullptr;
  LPDIRECTDRAWCLIPPER _clipper = nullptr;
  uint _surfaceWidth = 0;
  uint _surfaceHeight = 0;
};
