/* 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 "nsThebesFontEnumerator.h"
#include <stdint.h>              // for uint32_t
#include "gfxPlatform.h"         // for gfxPlatform
#include "mozilla/Assertions.h"  // for MOZ_ASSERT_HELPER2
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/Promise.h"  // for mozilla::dom::Promise
#include "nsCOMPtr.h"             // for nsCOMPtr
#include "nsDebug.h"              // for NS_ENSURE_ARG_POINTER
#include "nsError.h"              // for NS_OK, NS_FAILED, nsresult
#include "nsAtom.h"               // for nsAtom, NS_Atomize
#include "nsID.h"
#include "nsString.h"  // for nsAutoCString, nsAutoString, etc
#include "nsTArray.h"  // for nsTArray, nsTArray_Impl, etc
#include "nscore.h"    // for char16_t, NS_IMETHODIMP

using mozilla::MakeUnique;
using mozilla::Runnable;
using mozilla::UniquePtr;

NS_IMPL_ISUPPORTS(nsThebesFontEnumerator, nsIFontEnumerator)

nsThebesFontEnumerator::nsThebesFontEnumerator() = default;

NS_IMETHODIMP
nsThebesFontEnumerator::EnumerateAllFonts(nsTArray<nsString>& aResult) {
  return EnumerateFonts(nullptr, nullptr, aResult);
}

NS_IMETHODIMP
nsThebesFontEnumerator::EnumerateFonts(const char* aLangGroup,
                                       const char* aGeneric,
                                       nsTArray<nsString>& aResult) {
  nsAutoCString generic;
  if (aGeneric)
    generic.Assign(aGeneric);
  else
    generic.SetIsVoid(true);

  RefPtr<nsAtom> langGroupAtom;
  if (aLangGroup) {
    nsAutoCString lowered;
    lowered.Assign(aLangGroup);
    ToLowerCase(lowered);
    langGroupAtom = NS_Atomize(lowered);
  }

  return gfxPlatform::GetPlatform()->GetFontList(langGroupAtom, generic,
                                                 aResult);
}

struct EnumerateFontsPromise final {
  explicit EnumerateFontsPromise(mozilla::dom::Promise* aPromise)
      : mPromise(aPromise) {
    MOZ_ASSERT(aPromise);
    MOZ_ASSERT(NS_IsMainThread());
  }

  RefPtr<mozilla::dom::Promise> mPromise;
};

class EnumerateFontsResult final : public Runnable {
 public:
  EnumerateFontsResult(nsresult aRv,
                       UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
                       nsTArray<nsString> aFontList)
      : Runnable("EnumerateFontsResult"),
        mRv(aRv),
        mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)),
        mFontList(std::move(aFontList)),
        mWorkerThread(do_GetCurrentThread()) {
    MOZ_ASSERT(!NS_IsMainThread());
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(NS_IsMainThread());

    if (NS_FAILED(mRv)) {
      mEnumerateFontsPromise->mPromise->MaybeReject(mRv);
    } else {
      mEnumerateFontsPromise->mPromise->MaybeResolve(mFontList);
    }

    mWorkerThread->Shutdown();

    return NS_OK;
  }

 private:
  nsresult mRv;
  UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
  nsTArray<nsString> mFontList;
  nsCOMPtr<nsIThread> mWorkerThread;
};

class EnumerateFontsTask final : public Runnable {
 public:
  EnumerateFontsTask(nsAtom* aLangGroupAtom, const nsAutoCString& aGeneric,
                     UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
                     nsIEventTarget* aMainThreadTarget)
      : Runnable("EnumerateFontsTask"),
        mLangGroupAtom(aLangGroupAtom),
        mGeneric(aGeneric),
        mEnumerateFontsPromise(std::move(aEnumerateFontsPromise)),
        mMainThreadTarget(aMainThreadTarget) {
    MOZ_ASSERT(NS_IsMainThread());
  }

  NS_IMETHOD Run() override {
    MOZ_ASSERT(!NS_IsMainThread());

    nsTArray<nsString> fontList;

    nsresult rv = gfxPlatform::GetPlatform()->GetFontList(mLangGroupAtom,
                                                          mGeneric, fontList);
    nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsResult(
        rv, std::move(mEnumerateFontsPromise), std::move(fontList));
    mMainThreadTarget->Dispatch(runnable.forget());

    return NS_OK;
  }

 private:
  RefPtr<nsAtom> mLangGroupAtom;
  nsAutoCStringN<16> mGeneric;
  UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
  RefPtr<nsIEventTarget> mMainThreadTarget;
};

NS_IMETHODIMP
nsThebesFontEnumerator::EnumerateAllFontsAsync(
    JSContext* aCx, JS::MutableHandle<JS::Value> aRval) {
  return EnumerateFontsAsync(nullptr, nullptr, aCx, aRval);
}

NS_IMETHODIMP
nsThebesFontEnumerator::EnumerateFontsAsync(
    const char* aLangGroup, const char* aGeneric, JSContext* aCx,
    JS::MutableHandle<JS::Value> aRval) {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
  NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);

  mozilla::ErrorResult errv;
  RefPtr<mozilla::dom::Promise> promise =
      mozilla::dom::Promise::Create(global, errv);
  if (errv.Failed()) {
    return errv.StealNSResult();
  }

  auto enumerateFontsPromise = MakeUnique<EnumerateFontsPromise>(promise);

  nsCOMPtr<nsIThread> thread;
  nsresult rv = NS_NewNamedThread("FontEnumThread", getter_AddRefs(thread));
  NS_ENSURE_SUCCESS(rv, rv);

  RefPtr<nsAtom> langGroupAtom;
  if (aLangGroup) {
    nsAutoCStringN<16> lowered;
    lowered.Assign(aLangGroup);
    ToLowerCase(lowered);
    langGroupAtom = NS_Atomize(lowered);
  }

  nsAutoCString generic;
  if (aGeneric) {
    generic.Assign(aGeneric);
  } else {
    generic.SetIsVoid(true);
  }

  nsCOMPtr<nsIEventTarget> target = global->SerialEventTarget();
  nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsTask(
      langGroupAtom, generic, std::move(enumerateFontsPromise), target);
  thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);

  if (!ToJSValue(aCx, promise, aRval)) {
    return NS_ERROR_FAILURE;
  }

  return NS_OK;
}

NS_IMETHODIMP
nsThebesFontEnumerator::HaveFontFor(const char* aLangGroup, bool* aResult) {
  NS_ENSURE_ARG_POINTER(aResult);

  *aResult = true;
  return NS_OK;
}

NS_IMETHODIMP
nsThebesFontEnumerator::GetDefaultFont(const char* aLangGroup,
                                       const char* aGeneric,
                                       char16_t** aResult) {
  if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aLangGroup) ||
      NS_WARN_IF(!aGeneric)) {
    return NS_ERROR_INVALID_ARG;
  }

  *aResult = nullptr;
  nsAutoCString defaultFontName(gfxPlatform::GetPlatform()->GetDefaultFontName(
      nsDependentCString(aLangGroup), nsDependentCString(aGeneric)));
  if (!defaultFontName.IsEmpty()) {
    *aResult = UTF8ToNewUnicode(defaultFontName);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsThebesFontEnumerator::GetStandardFamilyName(const char16_t* aName,
                                              char16_t** aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  NS_ENSURE_ARG_POINTER(aName);

  nsAutoString name(aName);
  if (name.IsEmpty()) {
    *aResult = nullptr;
    return NS_OK;
  }

  nsAutoCString family;
  gfxPlatform::GetPlatform()->GetStandardFamilyName(
      NS_ConvertUTF16toUTF8(aName), family);
  if (family.IsEmpty()) {
    *aResult = nullptr;
    return NS_OK;
  }
  *aResult = UTF8ToNewUnicode(family);
  return NS_OK;
}
