/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "TextureDIB.h"
#include "gfx2DGlue.h"
#include "mozilla/gfx/DataSurfaceHelpers.h" // For BufferSizeFromDimensions
#include "mozilla/layers/ISurfaceAllocator.h"
#include "mozilla/ipc/ProtocolUtils.h"

namespace mozilla {

using namespace gfx;

namespace layers {

/**
  * Can only be drawn into through Cairo.
  * The coresponding TextureHost depends on the compositor
  */
class MemoryDIBTextureData : public DIBTextureData
{
public:
  virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;

  virtual TextureData*
  CreateSimilar(LayersIPCChannel* aAllocator,
                LayersBackend aLayersBackend,
                TextureFlags aFlags = TextureFlags::DEFAULT,
                TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override;

  virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override;

  static
  DIBTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat);

  virtual void Deallocate(LayersIPCChannel* aAllocator) override
  {
    mSurface = nullptr;
  }

  MemoryDIBTextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                       gfxWindowsSurface* aSurface)
  : DIBTextureData(aSize, aFormat, aSurface)
  {
    MOZ_COUNT_CTOR(MemoryDIBTextureData);
  }

  virtual ~MemoryDIBTextureData()
  {
    MOZ_COUNT_DTOR(MemoryDIBTextureData);
  }
};

/**
  * Can only be drawn into through Cairo.
  * The coresponding TextureHost depends on the compositor
  */
class ShmemDIBTextureData : public DIBTextureData
{
public:
  virtual bool Serialize(SurfaceDescriptor& aOutDescriptor) override;

  virtual TextureData*
  CreateSimilar(LayersIPCChannel* aAllocator,
                LayersBackend aLayersBackend,
                TextureFlags aFlags = TextureFlags::DEFAULT,
                TextureAllocationFlags aAllocFlags = ALLOC_DEFAULT) const override;

  virtual bool UpdateFromSurface(gfx::SourceSurface* aSurface) override;

  static
  DIBTextureData* Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                         LayersIPCChannel* aAllocator);

  void DeallocateData()
  {
    if (mSurface) {
      ::DeleteObject(mBitmap);
      ::DeleteDC(mDC);
      ::CloseHandle(mFileMapping);
      mBitmap = NULL;
      mDC = NULL;
      mFileMapping = NULL;
      mSurface = nullptr;
    }
  }

  virtual void Deallocate(LayersIPCChannel* aAllocator) override
  {
    DeallocateData();
  }

  ShmemDIBTextureData(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                      gfxWindowsSurface* aSurface,
                      HANDLE aFileMapping, HANDLE aHostHandle,
                      HDC aDC, HBITMAP aBitmap)
  : DIBTextureData(aSize, aFormat, aSurface)
  , mFileMapping(aFileMapping)
  , mHostHandle(aHostHandle)
  , mDC(aDC)
  , mBitmap(aBitmap)
  {
    MOZ_COUNT_CTOR(ShmemDIBTextureData);
  }

  virtual ~ShmemDIBTextureData() 
  {
    MOZ_COUNT_DTOR(ShmemDIBTextureData);

    // The host side has its own references and handles to this data, we can
    // safely clear ours.
    DeallocateData();
  }

  HANDLE mFileMapping;
  HANDLE mHostHandle;
  HDC mDC;
  HBITMAP mBitmap;
};

void
DIBTextureData::FillInfo(TextureData::Info& aInfo) const
{
  aInfo.size = mSize;
  aInfo.format = mFormat;
  aInfo.hasIntermediateBuffer = true;
  aInfo.hasSynchronization = false;
  aInfo.supportsMoz2D = true;
  aInfo.canExposeMappedData = false;
}

already_AddRefed<gfx::DrawTarget>
DIBTextureData::BorrowDrawTarget()
{
  return gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mSurface, mSize);
}

DIBTextureData*
DIBTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                       LayersIPCChannel* aAllocator)
{
  if (!aAllocator) {
    return nullptr;
  }
  if (aFormat == gfx::SurfaceFormat::UNKNOWN) {
    return nullptr;
  }
  if (aAllocator->IsSameProcess()) {
    return MemoryDIBTextureData::Create(aSize, aFormat);
  } else {
    return ShmemDIBTextureData::Create(aSize, aFormat, aAllocator);
  }
}

TextureData*
MemoryDIBTextureData::CreateSimilar(LayersIPCChannel* aAllocator,
                                    LayersBackend aLayersBackend,
                                    TextureFlags aFlags,
                                    TextureAllocationFlags aAllocFlags) const
{
  if (!aAllocator) {
    return nullptr;
  }
  return MemoryDIBTextureData::Create(mSize, mFormat);
}

bool
MemoryDIBTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
{
  MOZ_ASSERT(mSurface);
  // The host will release this ref when it receives the surface descriptor.
  // We AddRef in case we die before the host receives the pointer.
  aOutDescriptor = SurfaceDescriptorDIB(reinterpret_cast<uintptr_t>(mSurface.get()));
  mSurface->AddRef();
  return true;
}

DIBTextureData*
MemoryDIBTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat)
{
  RefPtr<gfxWindowsSurface> surface
    = new gfxWindowsSurface(aSize, SurfaceFormatToImageFormat(aFormat));
  if (!surface || surface->CairoStatus()) {
    NS_WARNING("Could not create DIB surface");
    return nullptr;
  }

  return new MemoryDIBTextureData(aSize, aFormat, surface);
}

bool
MemoryDIBTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface)
{
  RefPtr<gfxImageSurface> imgSurf = mSurface->GetAsImageSurface();

  RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface();

  if (!srcSurf) {
    gfxCriticalError() << "Failed to GetDataSurface in UpdateFromSurface (DIB).";
    return false;
  }

  DataSourceSurface::MappedSurface sourceMap;
  if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) {
    gfxCriticalError() << "Failed to map source surface for UpdateFromSurface.";
    return false;
  }

  for (int y = 0; y < srcSurf->GetSize().height; y++) {
    memcpy(imgSurf->Data() + imgSurf->Stride() * y,
           sourceMap.mData + sourceMap.mStride * y,
           srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat()));
  }

  srcSurf->Unmap();
  return true;
}

TextureData*
ShmemDIBTextureData::CreateSimilar(LayersIPCChannel* aAllocator,
                                   LayersBackend aLayersBackend,
                                   TextureFlags aFlags,
                                   TextureAllocationFlags aAllocFlags) const
{
  if (!aAllocator) {
    return nullptr;
  }
  return ShmemDIBTextureData::Create(mSize, mFormat, aAllocator);
}

bool
ShmemDIBTextureData::UpdateFromSurface(gfx::SourceSurface* aSurface)
{

  RefPtr<DataSourceSurface> srcSurf = aSurface->GetDataSurface();

  if (!srcSurf) {
    gfxCriticalError() << "Failed to GetDataSurface in UpdateFromSurface (DTD).";
    return false;
  }

  DataSourceSurface::MappedSurface sourceMap;
  if (!srcSurf->Map(gfx::DataSourceSurface::READ, &sourceMap)) {
    gfxCriticalError() << "Failed to map source surface for UpdateFromSurface.";
    return false;
  }

  GdiFlush();

  uint32_t stride = mSize.width * BytesPerPixel(mFormat);
  uint8_t* data = (uint8_t*)::MapViewOfFile(mFileMapping, FILE_MAP_WRITE, 0, 0, stride * mSize.height);

  if (!data) {
    gfxCriticalError() << "Failed to map view of file for UpdateFromSurface.";
    srcSurf->Unmap();
    return false;
  }

  for (int y = 0; y < srcSurf->GetSize().height; y++) {
    memcpy(data + stride * y,
           sourceMap.mData + sourceMap.mStride * y,
           srcSurf->GetSize().width * BytesPerPixel(srcSurf->GetFormat()));
  }

  ::UnmapViewOfFile(data);

  srcSurf->Unmap();
  return true;
}

bool
ShmemDIBTextureData::Serialize(SurfaceDescriptor& aOutDescriptor)
{
  if (mFormat == gfx::SurfaceFormat::UNKNOWN) {
    return false;
  }

  ::GdiFlush();
  aOutDescriptor = SurfaceDescriptorFileMapping((WindowsHandle)mHostHandle, mFormat, mSize);
  return true;
}

DIBTextureData*
ShmemDIBTextureData::Create(gfx::IntSize aSize, gfx::SurfaceFormat aFormat,
                            LayersIPCChannel* aAllocator)
{
  MOZ_ASSERT(aAllocator->GetParentPid() != base::ProcessId());

  DWORD mapSize = aSize.width * aSize.height * BytesPerPixel(aFormat);
  HANDLE fileMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, mapSize, NULL);

  if (!fileMapping) {
    gfxCriticalError() << "Failed to create memory file mapping for " << mapSize << " bytes.";
    return nullptr;
  }

  BITMAPV4HEADER header;
  memset(&header, 0, sizeof(BITMAPV4HEADER));
  header.bV4Size          = sizeof(BITMAPV4HEADER);
  header.bV4Width         = aSize.width;
  header.bV4Height        = -LONG(aSize.height); // top-to-buttom DIB
  header.bV4Planes        = 1;
  header.bV4BitCount      = 32;
  header.bV4V4Compression = BI_BITFIELDS;
  header.bV4RedMask       = 0x00FF0000;
  header.bV4GreenMask     = 0x0000FF00;
  header.bV4BlueMask      = 0x000000FF;

  HDC nulldc = ::GetDC(NULL);

  HDC dc = ::CreateCompatibleDC(nulldc);

  ::ReleaseDC(nullptr, nulldc);

  if (!dc) {
    ::CloseHandle(fileMapping);
    gfxCriticalError() << "Failed to create DC for bitmap.";
    return nullptr;
  }

  void* bits;
  HBITMAP bitmap = ::CreateDIBSection(dc, (BITMAPINFO*)&header,
                                      DIB_RGB_COLORS, &bits,
                                      fileMapping, 0);

  if (!bitmap) {
    gfxCriticalError() << "Failed to create DIB section for a bitmap of size "
                       << aSize << " and mapSize " << mapSize;
    ::CloseHandle(fileMapping);
    ::DeleteDC(dc);
    return nullptr;
  }

  ::SelectObject(dc, bitmap);

  RefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(dc, 0);
  if (surface->CairoStatus())
  {
    ::DeleteObject(bitmap);
    ::DeleteDC(dc);
    ::CloseHandle(fileMapping);
    gfxCriticalError() << "Could not create surface, status: "
                       << surface->CairoStatus();
    return nullptr;
  }

  HANDLE hostHandle = NULL;

  if (!ipc::DuplicateHandle(fileMapping, aAllocator->GetParentPid(),
                            &hostHandle, 0, DUPLICATE_SAME_ACCESS)) {
    gfxCriticalError() << "Failed to duplicate handle to parent process for surface.";
    ::DeleteObject(bitmap);
    ::DeleteDC(dc);
    ::CloseHandle(fileMapping);
    return nullptr;
  }

  return new ShmemDIBTextureData(aSize, aFormat, surface,
                                 fileMapping, hostHandle,
                                 dc, bitmap);
}


bool
TextureHostDirectUpload::Lock()
{
  MOZ_ASSERT(!mIsLocked);
  mIsLocked = true;
  return true;
}

void
TextureHostDirectUpload::Unlock()
{
  MOZ_ASSERT(mIsLocked);
  mIsLocked = false;
}

void
TextureHostDirectUpload::SetCompositor(Compositor* aCompositor)
{
  mCompositor = aCompositor;
}

void
TextureHostDirectUpload::DeallocateDeviceData()
{
  if (mTextureSource) {
    mTextureSource->DeallocateDeviceData();
  }
}

bool
TextureHostDirectUpload::BindTextureSource(CompositableTextureSourceRef& aTexture)
{
  if (!mTextureSource) {
    Updated();
  }

  aTexture = mTextureSource;
  return !!aTexture;
}

DIBTextureHost::DIBTextureHost(TextureFlags aFlags,
                               const SurfaceDescriptorDIB& aDescriptor)
  : TextureHostDirectUpload(aFlags, SurfaceFormat::B8G8R8X8, IntSize())
{
  // We added an extra ref for transport, so we shouldn't AddRef now.
  mSurface =
    dont_AddRef(reinterpret_cast<gfxWindowsSurface*>(aDescriptor.surface()));
  MOZ_ASSERT(mSurface);

  mSize = mSurface->GetSize();
  mFormat = mSurface->GetSurfaceFormat();
}

void
DIBTextureHost::UpdatedInternal(const nsIntRegion* aRegion)
{
  if (!mCompositor) {
    // This can happen if we send textures to a compositable that isn't yet
    // attached to a layer.
    return;
  }

  if (!mTextureSource) {
    mTextureSource = mCompositor->CreateDataTextureSource(mFlags);
  }

  if (mSurface->CairoStatus()) {
      gfxWarning() << "Bad Cairo surface internal update " << mSurface->CairoStatus();
      mTextureSource = nullptr;
      return;
  }
  RefPtr<gfxImageSurface> imgSurf = mSurface->GetAsImageSurface();

  RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface(imgSurf->Data(), imgSurf->Stride(), mSize, mFormat);

  if (!surf || !mTextureSource->Update(surf, const_cast<nsIntRegion*>(aRegion))) {
    mTextureSource = nullptr;
  }

  ReadUnlock();
}

TextureHostFileMapping::TextureHostFileMapping(TextureFlags aFlags,
                                               const SurfaceDescriptorFileMapping& aDescriptor)
  : TextureHostDirectUpload(aFlags, aDescriptor.format(), aDescriptor.size())
  , mFileMapping((HANDLE)aDescriptor.handle())
{
}

TextureHostFileMapping::~TextureHostFileMapping()
{
  ::CloseHandle(mFileMapping);
}

UserDataKey kFileMappingKey;

static void UnmapFileData(void* aData)
{
  MOZ_ASSERT(aData);
  ::UnmapViewOfFile(aData);
}

void
TextureHostFileMapping::UpdatedInternal(const nsIntRegion* aRegion)
{
  if (!mCompositor) {
    // This can happen if we send textures to a compositable that isn't yet
    // attached to a layer.
    return;
  }

  if (!mTextureSource) {
    mTextureSource = mCompositor->CreateDataTextureSource(mFlags);
  }

  uint8_t* data = nullptr;
  int32_t totalBytes = BufferSizeFromDimensions(mSize.width, mSize.height, BytesPerPixel(mFormat));
  if (totalBytes > 0) {
    data = (uint8_t*)::MapViewOfFile(mFileMapping, FILE_MAP_READ, 0, 0, totalBytes);
  }

  if (data) {
    RefPtr<DataSourceSurface> surf = Factory::CreateWrappingDataSourceSurface(data, mSize.width * BytesPerPixel(mFormat), mSize, mFormat);
    if (surf) {
        surf->AddUserData(&kFileMappingKey, data, UnmapFileData);
        if (!mTextureSource->Update(surf, const_cast<nsIntRegion*>(aRegion))) {
          mTextureSource = nullptr;
        }
    } else {
      mTextureSource = nullptr;
    }
  } else {
    mTextureSource = nullptr;
  }

  ReadUnlock();
}

}
}
