#include <cstdlib>
#include <cstring>

#include <sstream>
#include <string>

#include "dxgi_adapter.h"
#include "dxgi_output.h"

namespace dxvk {
  
  DxgiOutput::DxgiOutput(
              DxgiAdapter*  adapter,
              UINT          display)
  : m_adapter (adapter),
    m_display (display) {
    TRACE(this, adapter);
  }
  
  
  DxgiOutput::~DxgiOutput() {
    TRACE(this);
  }
  
  
  HRESULT DxgiOutput::QueryInterface(
          REFIID riid,
          void **ppvObject) {
    COM_QUERY_IFACE(riid, ppvObject, IDXGIOutput);
    
    Logger::warn("DxgiOutput::QueryInterface: Unknown interface query");
    return E_NOINTERFACE;
  }
  
  
  HRESULT DxgiOutput::GetParent(
          REFIID riid,
          void   **ppParent) {
    return m_adapter->QueryInterface(riid, ppParent);
  }
  
  
  HRESULT DxgiOutput::FindClosestMatchingMode(
    const DXGI_MODE_DESC *pModeToMatch,
          DXGI_MODE_DESC *pClosestMatch,
          IUnknown       *pConcernedDevice) {
    Logger::err("DxgiOutput::FindClosestMatchingMode: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::GetDesc(DXGI_OUTPUT_DESC *pDesc) {
    if (pDesc == nullptr)
      return DXGI_ERROR_INVALID_CALL;
    
    // Display name, Windows requires wide chars
    const char* displayName = SDL_GetDisplayName(m_display);
    
    if (displayName == nullptr) {
      Logger::err("DxgiOutput::GetDesc: Failed to get display name");
      return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
    }
    
    std::memset(pDesc->DeviceName, 0, sizeof(pDesc->DeviceName));
    std::mbstowcs(pDesc->DeviceName, displayName, _countof(pDesc->DeviceName) - 1);
    
    // Current desktop rect of the display
    SDL_Rect rect;
    
    if (SDL_GetDisplayBounds(m_display, &rect)) {
      Logger::err("DxgiOutput::GetDesc: Failed to get display bounds");
      return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
    }
    
    pDesc->DesktopCoordinates.left    = rect.x;
    pDesc->DesktopCoordinates.top     = rect.y;
    pDesc->DesktopCoordinates.right   = rect.x + rect.w;
    pDesc->DesktopCoordinates.bottom  = rect.y + rect.h;
    
    // We don't have any info for these
    pDesc->AttachedToDesktop  = 1;
    pDesc->Rotation           = DXGI_MODE_ROTATION_UNSPECIFIED;
    pDesc->Monitor            = nullptr;
    return S_OK;
  }
  
  
  HRESULT DxgiOutput::GetDisplayModeList(
          DXGI_FORMAT    EnumFormat,
          UINT           Flags,
          UINT           *pNumModes,
          DXGI_MODE_DESC *pDesc) {
    TRACE(this, EnumFormat, Flags, pNumModes, pDesc);
    
    if (pNumModes == nullptr)
      return DXGI_ERROR_INVALID_CALL;
      
    // In order to check whether a display mode is 'centered' or
    // 'streched' in DXGI terms, we compare its size to the desktop
    // 'mode. If they are the same, we consider the mode to be
    // 'centered', which most games will prefer over 'streched'. 
    SDL_DisplayMode desktopMode;
    
    if (SDL_GetDesktopDisplayMode(m_display, &desktopMode)) {
      Logger::err("DxgiOutput::GetDisplayModeList: Failed to list display modes");
      return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
    }
    
    // Create a list of suitable display modes. Because of the way DXGI
    // swapchains are handled by DXVK, we can ignore the format constraints
    // here and just pick whatever modes SDL returns for the current display.
    std::vector<DXGI_MODE_DESC> modes;
    
    int numDisplayModes = SDL_GetNumDisplayModes(m_display);
    
    if (numDisplayModes < 0) {
      Logger::err("DxgiOutput::GetDisplayModeList: Failed to list display modes");
      return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
    }
    
    for (int i = 0; i < numDisplayModes; i++) {
      SDL_DisplayMode currMode;
      
      if (SDL_GetDisplayMode(m_display, i, &currMode)) {
        Logger::err("DxgiOutput::GetDisplayModeList: Failed to list display modes");
        return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
      }
      
      // We don't want duplicates, so we'll filter out modes
      // with matching resolution and refresh rate.
      bool hasMode = false;
      
      for (int j = 0; j < i && !hasMode; j++) {
        SDL_DisplayMode testMode;
        
        if (SDL_GetDisplayMode(m_display, j, &testMode)) {
          Logger::err("DxgiOutput::GetDisplayModeList: Failed to list display modes");
          return DXGI_ERROR_DRIVER_INTERNAL_ERROR;
        }
        
        hasMode = testMode.w            == currMode.w
                && testMode.h            == currMode.h
                && testMode.refresh_rate == currMode.refresh_rate;
      }
      
      // Convert the SDL display mode to a DXGI display mode info
      // structure and filter out any unwanted modes based on the
      // supplied flags.
      if (!hasMode) {
        bool isNativeMode = (currMode.w == desktopMode.w)
                          && (currMode.h == desktopMode.h);
        
        if (isNativeMode || (Flags & DXGI_ENUM_MODES_SCALING)) {
          DXGI_MODE_DESC mode;
          mode.Width                      = currMode.w;
          mode.Height                     = currMode.h;
          mode.RefreshRate.Numerator      = currMode.refresh_rate;
          mode.RefreshRate.Denominator    = 1;
          mode.Format                     = EnumFormat;
          mode.ScanlineOrdering           = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
          mode.Scaling                    = isNativeMode
            ? DXGI_MODE_SCALING_CENTERED
            : DXGI_MODE_SCALING_STRETCHED;
          modes.push_back(mode);
        }
      }
    }
    
    // Copy list of display modes to the application-provided
    // destination buffer. The buffer may not be appropriately
    // sized by the time this is called.
    if (pDesc != nullptr) {
      for (uint32_t i = 0; i < modes.size() && i < *pNumModes; i++)
        pDesc[i] = modes.at(i);
    }
    
    // If the buffer is too small, we shall ask the application
    // to query the display mode list again by returning the
    // appropriate DXGI error code.
    if ((pDesc == nullptr) || (modes.size() <= *pNumModes)) {
      *pNumModes = modes.size();
      return S_OK;
    } else {
      return DXGI_ERROR_MORE_DATA;
    }
      
  }
  
  
  HRESULT DxgiOutput::GetDisplaySurfaceData(IDXGISurface *pDestination) {
    Logger::err("DxgiOutput::GetDisplaySurfaceData: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::GetFrameStatistics(DXGI_FRAME_STATISTICS *pStats) {
    Logger::err("DxgiOutput::GetFrameStatistics: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::GetGammaControl(DXGI_GAMMA_CONTROL *pArray) {
    Logger::err("DxgiOutput::GetGammaControl: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::GetGammaControlCapabilities(DXGI_GAMMA_CONTROL_CAPABILITIES *pGammaCaps) {
    Logger::err("DxgiOutput::GetGammaControlCapabilities: Not implemented");
    return E_NOTIMPL;
  }
  
  
  void DxgiOutput::ReleaseOwnership() {
    Logger::warn("DxgiOutput::ReleaseOwnership: Stub");
  }
  
  
  HRESULT DxgiOutput::SetDisplaySurface(IDXGISurface *pScanoutSurface) {
    Logger::err("DxgiOutput::SetDisplaySurface: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::SetGammaControl(const DXGI_GAMMA_CONTROL *pArray) {
    Logger::err("DxgiOutput::SetGammaControl: Not implemented");
    return E_NOTIMPL;
  }
  
  
  HRESULT DxgiOutput::TakeOwnership(
          IUnknown *pDevice,
          BOOL     Exclusive) {
    Logger::warn("DxgiOutput::TakeOwnership: Stub");
    return S_OK;
  }
  
  
  HRESULT DxgiOutput::WaitForVBlank() {
    Logger::warn("DxgiOutput::WaitForVBlank: Stub");
    return S_OK;
  }
  
}