// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxge/include/fx_ge.h"

#if _FX_OS_ == _FX_WIN32_DESKTOP_ || _FX_OS_ == _FX_WIN64_DESKTOP_
#include <windows.h>

#include "core/fxge/include/fx_ge_win32.h"
#include "core/fxge/win32/win32_int.h"

CFX_ByteString CFX_WindowsDIB::GetBitmapInfo(const CFX_DIBitmap* pBitmap) {
  CFX_ByteString result;
  int len = sizeof(BITMAPINFOHEADER);
  if (pBitmap->GetBPP() == 1 || pBitmap->GetBPP() == 8) {
    len += sizeof(DWORD) * (int)(1 << pBitmap->GetBPP());
  }
  BITMAPINFOHEADER* pbmih = (BITMAPINFOHEADER*)result.GetBuffer(len);
  FXSYS_memset(pbmih, 0, sizeof(BITMAPINFOHEADER));
  pbmih->biSize = sizeof(BITMAPINFOHEADER);
  pbmih->biBitCount = pBitmap->GetBPP();
  pbmih->biCompression = BI_RGB;
  pbmih->biHeight = -(int)pBitmap->GetHeight();
  pbmih->biPlanes = 1;
  pbmih->biWidth = pBitmap->GetWidth();
  if (pBitmap->GetBPP() == 8) {
    uint32_t* pPalette = (uint32_t*)(pbmih + 1);
    if (pBitmap->GetPalette()) {
      for (int i = 0; i < 256; i++) {
        pPalette[i] = pBitmap->GetPalette()[i];
      }
    } else {
      for (int i = 0; i < 256; i++) {
        pPalette[i] = i * 0x010101;
      }
    }
  }
  if (pBitmap->GetBPP() == 1) {
    uint32_t* pPalette = (uint32_t*)(pbmih + 1);
    if (pBitmap->GetPalette()) {
      pPalette[0] = pBitmap->GetPalette()[0];
      pPalette[1] = pBitmap->GetPalette()[1];
    } else {
      pPalette[0] = 0;
      pPalette[1] = 0xffffff;
    }
  }
  result.ReleaseBuffer(len);
  return result;
}

CFX_DIBitmap* _FX_WindowsDIB_LoadFromBuf(BITMAPINFO* pbmi,
                                         LPVOID pData,
                                         FX_BOOL bAlpha) {
  int width = pbmi->bmiHeader.biWidth;
  int height = pbmi->bmiHeader.biHeight;
  BOOL bBottomUp = TRUE;
  if (height < 0) {
    height = -height;
    bBottomUp = FALSE;
  }
  int pitch = (width * pbmi->bmiHeader.biBitCount + 31) / 32 * 4;
  CFX_DIBitmap* pBitmap = new CFX_DIBitmap;
  FXDIB_Format format = bAlpha
                            ? (FXDIB_Format)(pbmi->bmiHeader.biBitCount + 0x200)
                            : (FXDIB_Format)pbmi->bmiHeader.biBitCount;
  FX_BOOL ret = pBitmap->Create(width, height, format);
  if (!ret) {
    delete pBitmap;
    return nullptr;
  }
  FXSYS_memcpy(pBitmap->GetBuffer(), pData, pitch * height);
  if (bBottomUp) {
    uint8_t* temp_buf = FX_Alloc(uint8_t, pitch);
    int top = 0, bottom = height - 1;
    while (top < bottom) {
      FXSYS_memcpy(temp_buf, pBitmap->GetBuffer() + top * pitch, pitch);
      FXSYS_memcpy(pBitmap->GetBuffer() + top * pitch,
                   pBitmap->GetBuffer() + bottom * pitch, pitch);
      FXSYS_memcpy(pBitmap->GetBuffer() + bottom * pitch, temp_buf, pitch);
      top++;
      bottom--;
    }
    FX_Free(temp_buf);
    temp_buf = nullptr;
  }
  if (pbmi->bmiHeader.biBitCount == 1) {
    for (int i = 0; i < 2; i++) {
      pBitmap->SetPaletteEntry(i, ((uint32_t*)pbmi->bmiColors)[i] | 0xff000000);
    }
  } else if (pbmi->bmiHeader.biBitCount == 8) {
    for (int i = 0; i < 256; i++) {
      pBitmap->SetPaletteEntry(i, ((uint32_t*)pbmi->bmiColors)[i] | 0xff000000);
    }
  }
  return pBitmap;
}

CFX_DIBitmap* CFX_WindowsDIB::LoadFromBuf(BITMAPINFO* pbmi, LPVOID pData) {
  return _FX_WindowsDIB_LoadFromBuf(pbmi, pData, FALSE);
}

HBITMAP CFX_WindowsDIB::GetDDBitmap(const CFX_DIBitmap* pBitmap, HDC hDC) {
  CFX_ByteString info = GetBitmapInfo(pBitmap);
  return CreateDIBitmap(hDC, (BITMAPINFOHEADER*)info.c_str(), CBM_INIT,
                        pBitmap->GetBuffer(), (BITMAPINFO*)info.c_str(),
                        DIB_RGB_COLORS);
}

void GetBitmapSize(HBITMAP hBitmap, int& w, int& h) {
  BITMAP bmp;
  GetObject(hBitmap, sizeof bmp, &bmp);
  w = bmp.bmWidth;
  h = bmp.bmHeight;
}

CFX_DIBitmap* CFX_WindowsDIB::LoadFromFile(const FX_WCHAR* filename) {
  CWin32Platform* pPlatform =
      (CWin32Platform*)CFX_GEModule::Get()->GetPlatformData();
  if (pPlatform->m_GdiplusExt.IsAvailable()) {
    WINDIB_Open_Args_ args;
    args.flags = WINDIB_OPEN_PATHNAME;
    args.path_name = filename;
    return pPlatform->m_GdiplusExt.LoadDIBitmap(args);
  }
  HBITMAP hBitmap = (HBITMAP)LoadImageW(nullptr, (wchar_t*)filename,
                                        IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
  if (!hBitmap) {
    return nullptr;
  }
  HDC hDC = CreateCompatibleDC(nullptr);
  int width, height;
  GetBitmapSize(hBitmap, width, height);
  CFX_DIBitmap* pDIBitmap = new CFX_DIBitmap;
  if (!pDIBitmap->Create(width, height, FXDIB_Rgb)) {
    delete pDIBitmap;
    DeleteDC(hDC);
    return nullptr;
  }
  CFX_ByteString info = GetBitmapInfo(pDIBitmap);
  int ret = GetDIBits(hDC, hBitmap, 0, height, pDIBitmap->GetBuffer(),
                      (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS);
  if (!ret) {
    delete pDIBitmap;
    pDIBitmap = nullptr;
  }
  DeleteDC(hDC);
  return pDIBitmap;
}

CFX_DIBitmap* CFX_WindowsDIB::LoadFromFile(const FX_CHAR* filename) {
  return LoadFromFile(CFX_WideString::FromLocal(filename).c_str());
}

CFX_DIBitmap* CFX_WindowsDIB::LoadDIBitmap(WINDIB_Open_Args_ args) {
  CWin32Platform* pPlatform =
      (CWin32Platform*)CFX_GEModule::Get()->GetPlatformData();
  if (pPlatform->m_GdiplusExt.IsAvailable()) {
    return pPlatform->m_GdiplusExt.LoadDIBitmap(args);
  }
  if (args.flags == WINDIB_OPEN_MEMORY) {
    return nullptr;
  }
  HBITMAP hBitmap = (HBITMAP)LoadImageW(nullptr, (wchar_t*)args.path_name,
                                        IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
  if (!hBitmap) {
    return nullptr;
  }
  HDC hDC = CreateCompatibleDC(nullptr);
  int width, height;
  GetBitmapSize(hBitmap, width, height);
  CFX_DIBitmap* pDIBitmap = new CFX_DIBitmap;
  if (!pDIBitmap->Create(width, height, FXDIB_Rgb)) {
    delete pDIBitmap;
    DeleteDC(hDC);
    return nullptr;
  }
  CFX_ByteString info = GetBitmapInfo(pDIBitmap);
  int ret = GetDIBits(hDC, hBitmap, 0, height, pDIBitmap->GetBuffer(),
                      (BITMAPINFO*)info.c_str(), DIB_RGB_COLORS);
  if (!ret) {
    delete pDIBitmap;
    pDIBitmap = nullptr;
  }
  DeleteDC(hDC);
  return pDIBitmap;
}

CFX_DIBitmap* CFX_WindowsDIB::LoadFromDDB(HDC hDC,
                                          HBITMAP hBitmap,
                                          uint32_t* pPalette,
                                          uint32_t palsize) {
  FX_BOOL bCreatedDC = !hDC;
  if (bCreatedDC) {
    hDC = CreateCompatibleDC(nullptr);
  }
  BITMAPINFOHEADER bmih;
  FXSYS_memset(&bmih, 0, sizeof bmih);
  bmih.biSize = sizeof bmih;
  GetDIBits(hDC, hBitmap, 0, 0, nullptr, (BITMAPINFO*)&bmih, DIB_RGB_COLORS);
  int width = bmih.biWidth;
  int height = abs(bmih.biHeight);
  bmih.biHeight = -height;
  bmih.biCompression = BI_RGB;
  CFX_DIBitmap* pDIBitmap = new CFX_DIBitmap;
  int ret = 0;
  if (bmih.biBitCount == 1 || bmih.biBitCount == 8) {
    int size = sizeof(BITMAPINFOHEADER) + 8;
    if (bmih.biBitCount == 8) {
      size += sizeof(uint32_t) * 254;
    }
    BITMAPINFO* pbmih = (BITMAPINFO*)FX_Alloc(uint8_t, size);
    pbmih->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmih->bmiHeader.biBitCount = bmih.biBitCount;
    pbmih->bmiHeader.biCompression = BI_RGB;
    pbmih->bmiHeader.biHeight = -height;
    pbmih->bmiHeader.biPlanes = 1;
    pbmih->bmiHeader.biWidth = bmih.biWidth;
    if (!pDIBitmap->Create(bmih.biWidth, height, bmih.biBitCount == 1
                                                     ? FXDIB_1bppRgb
                                                     : FXDIB_8bppRgb)) {
      delete pDIBitmap;
      FX_Free(pbmih);
      if (bCreatedDC) {
        DeleteDC(hDC);
      }
      return nullptr;
    }
    ret = GetDIBits(hDC, hBitmap, 0, height, pDIBitmap->GetBuffer(),
                    (BITMAPINFO*)pbmih, DIB_RGB_COLORS);
    FX_Free(pbmih);
    pbmih = nullptr;
    pDIBitmap->CopyPalette(pPalette, palsize);
  } else {
    if (bmih.biBitCount <= 24) {
      bmih.biBitCount = 24;
    } else {
      bmih.biBitCount = 32;
    }
    if (!pDIBitmap->Create(bmih.biWidth, height,
                           bmih.biBitCount == 24 ? FXDIB_Rgb : FXDIB_Rgb32)) {
      delete pDIBitmap;
      if (bCreatedDC) {
        DeleteDC(hDC);
      }
      return nullptr;
    }
    ret = GetDIBits(hDC, hBitmap, 0, height, pDIBitmap->GetBuffer(),
                    (BITMAPINFO*)&bmih, DIB_RGB_COLORS);
    if (ret != 0 && bmih.biBitCount == 32) {
      int pitch = pDIBitmap->GetPitch();
      for (int row = 0; row < height; row++) {
        uint8_t* dest_scan = (uint8_t*)(pDIBitmap->GetBuffer() + row * pitch);
        for (int col = 0; col < width; col++) {
          dest_scan[3] = 255;
          dest_scan += 4;
        }
      }
    }
  }
  if (ret == 0) {
    delete pDIBitmap;
    pDIBitmap = nullptr;
  }
  if (bCreatedDC) {
    DeleteDC(hDC);
  }
  return pDIBitmap;
}

CFX_WindowsDIB::CFX_WindowsDIB(HDC hDC, int width, int height) {
  Create(width, height, FXDIB_Rgb, (uint8_t*)1);
  BITMAPINFOHEADER bmih;
  FXSYS_memset(&bmih, 0, sizeof bmih);
  bmih.biSize = sizeof bmih;
  bmih.biBitCount = 24;
  bmih.biHeight = -height;
  bmih.biPlanes = 1;
  bmih.biWidth = width;
  m_hBitmap = CreateDIBSection(hDC, (BITMAPINFO*)&bmih, DIB_RGB_COLORS,
                               (LPVOID*)&m_pBuffer, nullptr, 0);
  m_hMemDC = CreateCompatibleDC(hDC);
  m_hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmap);
}

CFX_WindowsDIB::~CFX_WindowsDIB() {
  SelectObject(m_hMemDC, m_hOldBitmap);
  DeleteDC(m_hMemDC);
  DeleteObject(m_hBitmap);
}

void CFX_WindowsDIB::LoadFromDevice(HDC hDC, int left, int top) {
  ::BitBlt(m_hMemDC, 0, 0, m_Width, m_Height, hDC, left, top, SRCCOPY);
}

void CFX_WindowsDIB::SetToDevice(HDC hDC, int left, int top) {
  ::BitBlt(hDC, left, top, m_Width, m_Height, m_hMemDC, 0, 0, SRCCOPY);
}
#endif