/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "NativeFontResourceDWrite.h"

#include <unordered_map>

#include "DrawTargetD2D1.h"
#include "Logging.h"
#include "mozilla/RefPtr.h"

namespace mozilla {
namespace gfx {

static Atomic<uint64_t> sNextFontFileKey;
static std::unordered_map<uint64_t, IDWriteFontFileStream*> sFontFileStreams;

class DWriteFontFileLoader : public IDWriteFontFileLoader
{
public:
  DWriteFontFileLoader()
  {
  }

  // IUnknown interface
  IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject)
  {
    if (iid == __uuidof(IDWriteFontFileLoader)) {
      *ppObject = static_cast<IDWriteFontFileLoader*>(this);
      return S_OK;
    } else if (iid == __uuidof(IUnknown)) {
      *ppObject = static_cast<IUnknown*>(this);
      return S_OK;
    } else {
      return E_NOINTERFACE;
    }
  }

  IFACEMETHOD_(ULONG, AddRef)()
  {
    return 1;
  }

  IFACEMETHOD_(ULONG, Release)()
  {
    return 1;
  }

  // IDWriteFontFileLoader methods
  /**
    * Important! Note the key here has to be a uint64_t that will have been
    * generated by incrementing sNextFontFileKey.
    */
  virtual HRESULT STDMETHODCALLTYPE
    CreateStreamFromKey(void const* fontFileReferenceKey,
                        UINT32 fontFileReferenceKeySize,
                        OUT IDWriteFontFileStream** fontFileStream);

  /**
    * Gets the singleton loader instance. Note that when using this font
    * loader, the key must be a uint64_t that has been generated by incrementing
    * sNextFontFileKey.
    * Also note that this is _not_ threadsafe.
    */
  static IDWriteFontFileLoader* Instance()
  {
    if (!mInstance) {
      mInstance = new DWriteFontFileLoader();
      DrawTargetD2D1::GetDWriteFactory()->
          RegisterFontFileLoader(mInstance);
    }
    return mInstance;
  }

private:
  static IDWriteFontFileLoader* mInstance;
};

class DWriteFontFileStream : public IDWriteFontFileStream
{
public:
  /**
    * Used by the FontFileLoader to create a new font stream,
    * this font stream is created from data in memory. The memory
    * passed may be released after object creation, it will be
    * copied internally.
    *
    * @param aData Font data
    */
  DWriteFontFileStream(uint8_t *aData, uint32_t aSize, uint64_t aFontFileKey);
  ~DWriteFontFileStream();

  // IUnknown interface
  IFACEMETHOD(QueryInterface)(IID const& iid, OUT void** ppObject)
  {
    if (iid == __uuidof(IDWriteFontFileStream)) {
      *ppObject = static_cast<IDWriteFontFileStream*>(this);
      return S_OK;
    } else if (iid == __uuidof(IUnknown)) {
      *ppObject = static_cast<IUnknown*>(this);
      return S_OK;
    } else {
      return E_NOINTERFACE;
    }
  }

  IFACEMETHOD_(ULONG, AddRef)()
  {
    ++mRefCnt;
    return mRefCnt;
  }

  IFACEMETHOD_(ULONG, Release)()
  {
    --mRefCnt;
    if (mRefCnt == 0) {
      delete this;
      return 0;
    }
    return mRefCnt;
  }

  // IDWriteFontFileStream methods
  virtual HRESULT STDMETHODCALLTYPE ReadFileFragment(void const** fragmentStart,
                                                     UINT64 fileOffset,
                                                     UINT64 fragmentSize,
                                                     OUT void** fragmentContext);

  virtual void STDMETHODCALLTYPE ReleaseFileFragment(void* fragmentContext);

  virtual HRESULT STDMETHODCALLTYPE GetFileSize(OUT UINT64* fileSize);

  virtual HRESULT STDMETHODCALLTYPE GetLastWriteTime(OUT UINT64* lastWriteTime);

private:
  std::vector<uint8_t> mData;
  uint32_t mRefCnt;
  uint64_t mFontFileKey;
};

IDWriteFontFileLoader* DWriteFontFileLoader::mInstance = nullptr;

HRESULT STDMETHODCALLTYPE
DWriteFontFileLoader::CreateStreamFromKey(const void *fontFileReferenceKey,
                                          UINT32 fontFileReferenceKeySize,
                                          IDWriteFontFileStream **fontFileStream)
{
  if (!fontFileReferenceKey || !fontFileStream) {
    return E_POINTER;
  }

  uint64_t fontFileKey = *static_cast<const uint64_t*>(fontFileReferenceKey);
  auto found = sFontFileStreams.find(fontFileKey);
  if (found == sFontFileStreams.end()) {
    *fontFileStream = nullptr;
    return E_FAIL;
  }

  found->second->AddRef();
  *fontFileStream = found->second;
  return S_OK;
}

DWriteFontFileStream::DWriteFontFileStream(uint8_t *aData, uint32_t aSize,
                                           uint64_t aFontFileKey)
  : mRefCnt(0)
  , mFontFileKey(aFontFileKey)
{
  mData.resize(aSize);
  memcpy(&mData.front(), aData, aSize);
}

DWriteFontFileStream::~DWriteFontFileStream()
{
  sFontFileStreams.erase(mFontFileKey);
}

HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::GetFileSize(UINT64 *fileSize)
{
  *fileSize = mData.size();
  return S_OK;
}

HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::GetLastWriteTime(UINT64 *lastWriteTime)
{
  return E_NOTIMPL;
}

HRESULT STDMETHODCALLTYPE
DWriteFontFileStream::ReadFileFragment(const void **fragmentStart,
                                       UINT64 fileOffset,
                                       UINT64 fragmentSize,
                                       void **fragmentContext)
{
  // We are required to do bounds checking.
  if (fileOffset + fragmentSize > mData.size()) {
    return E_FAIL;
  }

  // truncate the 64 bit fileOffset to size_t sized index into mData
  size_t index = static_cast<size_t>(fileOffset);

  // We should be alive for the duration of this.
  *fragmentStart = &mData[index];
  *fragmentContext = nullptr;
  return S_OK;
}

void STDMETHODCALLTYPE
DWriteFontFileStream::ReleaseFileFragment(void *fragmentContext)
{
}

/* static */
already_AddRefed<NativeFontResourceDWrite>
NativeFontResourceDWrite::Create(uint8_t *aFontData, uint32_t aDataLength,
                                 bool aNeedsCairo)
{
  IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory();
  if (!factory) {
    gfxWarning() << "Failed to get DWrite Factory.";
    return nullptr;
  }

  uint64_t fontFileKey = sNextFontFileKey++;
  RefPtr<IDWriteFontFileStream> ffsRef =
    new DWriteFontFileStream(aFontData, aDataLength, fontFileKey);
  sFontFileStreams[fontFileKey] = ffsRef;

  RefPtr<IDWriteFontFile> fontFile;
  HRESULT hr =
    factory->CreateCustomFontFileReference(&fontFileKey, sizeof(fontFileKey),
                                           DWriteFontFileLoader::Instance(),
                                           getter_AddRefs(fontFile));
  if (FAILED(hr)) {
    gfxWarning() << "Failed to load font file from data!";
    return nullptr;
  }

  BOOL isSupported;
  DWRITE_FONT_FILE_TYPE fileType;
  DWRITE_FONT_FACE_TYPE faceType;
  UINT32 numberOfFaces;
  hr = fontFile->Analyze(&isSupported, &fileType, &faceType, &numberOfFaces);
  if (FAILED(hr) || !isSupported) {
    gfxWarning() << "Font file is not supported.";
    return nullptr;
  }

  RefPtr<NativeFontResourceDWrite> fontResource =
    new NativeFontResourceDWrite(factory, fontFile.forget(), faceType,
                                 numberOfFaces, aNeedsCairo);
  return fontResource.forget();
}

already_AddRefed<ScaledFont>
NativeFontResourceDWrite::CreateScaledFont(uint32_t aIndex, Float aGlyphSize,
                                           const uint8_t* aInstanceData, uint32_t aInstanceDataLength)
{
  if (aIndex >= mNumberOfFaces) {
    gfxWarning() << "Font face index is too high for font resource.";
    return nullptr;
  }

  IDWriteFontFile *fontFile = mFontFile;
  RefPtr<IDWriteFontFace> fontFace;
  if (FAILED(mFactory->CreateFontFace(mFaceType, 1, &fontFile, aIndex,
    DWRITE_FONT_SIMULATIONS_NONE, getter_AddRefs(fontFace)))) {
    gfxWarning() << "Failed to create font face from font file data.";
    return nullptr;
  }

  RefPtr<ScaledFontBase> scaledFont = new ScaledFontDWrite(fontFace, aGlyphSize);
  if (mNeedsCairo && !scaledFont->PopulateCairoScaledFont()) {
    gfxWarning() << "Unable to create cairo scaled font DWrite font.";
    return nullptr;
  }

  return scaledFont.forget();
}

} // gfx
} // mozilla
