/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "FileSource.h"
#include "mozilla/dom/Promise.h"

using namespace mozilla::dom;

namespace mozilla::intl {

NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(L10nFileSource, mGlobal)

L10nFileSource::L10nFileSource(RefPtr<const ffi::FileSource> aRaw,
                               nsIGlobalObject* aGlobal)
    : mGlobal(aGlobal), mRaw(std::move(aRaw)) {}

/* static */
already_AddRefed<L10nFileSource> L10nFileSource::Constructor(
    const GlobalObject& aGlobal, const nsACString& aName,
    const nsACString& aMetaSource, const nsTArray<nsCString>& aLocales,
    const nsACString& aPrePath, const dom::FileSourceOptions& aOptions,
    const Optional<Sequence<nsCString>>& aIndex, ErrorResult& aRv) {
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());

  ffi::L10nFileSourceStatus status;

  bool allowOverrides = aOptions.mAddResourceOptions.mAllowOverrides;

  RefPtr<const ffi::FileSource> raw;
  if (aIndex.WasPassed()) {
    raw = dont_AddRef(ffi::l10nfilesource_new_with_index(
        &aName, &aMetaSource, &aLocales, &aPrePath, aIndex.Value().Elements(),
        aIndex.Value().Length(), allowOverrides, &status));
  } else {
    raw = dont_AddRef(ffi::l10nfilesource_new(
        &aName, &aMetaSource, &aLocales, &aPrePath, allowOverrides, &status));
  }

  if (PopulateError(aRv, status)) {
    return nullptr;
  }
  return MakeAndAddRef<L10nFileSource>(std::move(raw), global);
}

/* static */
already_AddRefed<L10nFileSource> L10nFileSource::CreateMock(
    const GlobalObject& aGlobal, const nsACString& aName,
    const nsACString& aMetaSource, const nsTArray<nsCString>& aLocales,
    const nsACString& aPrePath, const nsTArray<L10nFileSourceMockFile>& aFS,
    ErrorResult& aRv) {
  nsTArray<ffi::L10nFileSourceMockFile> fs(aFS.Length());
  for (const auto& file : aFS) {
    auto f = fs.AppendElement();
    f->path = file.mPath;
    f->source = file.mSource;
  }
  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());

  ffi::L10nFileSourceStatus status;

  RefPtr<const ffi::FileSource> raw(dont_AddRef(ffi::l10nfilesource_new_mock(
      &aName, &aMetaSource, &aLocales, &aPrePath, &fs, &status)));

  if (PopulateError(aRv, status)) {
    return nullptr;
  }
  return MakeAndAddRef<L10nFileSource>(std::move(raw), global);
}

JSObject* L10nFileSource::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
  return L10nFileSource_Binding::Wrap(aCx, this, aGivenProto);
}

void L10nFileSource::GetName(nsCString& aRetVal) {
  ffi::l10nfilesource_get_name(mRaw.get(), &aRetVal);
}

void L10nFileSource::GetMetaSource(nsCString& aRetVal) {
  ffi::l10nfilesource_get_metasource(mRaw.get(), &aRetVal);
}

void L10nFileSource::GetLocales(nsTArray<nsCString>& aRetVal) {
  ffi::l10nfilesource_get_locales(mRaw.get(), &aRetVal);
}

void L10nFileSource::GetPrePath(nsCString& aRetVal) {
  ffi::l10nfilesource_get_prepath(mRaw.get(), &aRetVal);
}

void L10nFileSource::GetIndex(Nullable<nsTArray<nsCString>>& aRetVal) {
  bool hasIndex =
      ffi::l10nfilesource_get_index(mRaw.get(), &aRetVal.SetValue());
  if (!hasIndex) {
    aRetVal.SetNull();
  }
}

L10nFileSourceHasFileStatus L10nFileSource::HasFile(const nsACString& aLocale,
                                                    const nsACString& aPath,
                                                    ErrorResult& aRv) {
  ffi::L10nFileSourceStatus status;

  bool isPresent = false;
  bool hasValue = ffi::l10nfilesource_has_file(mRaw.get(), &aLocale, &aPath,
                                               &status, &isPresent);

  if (!PopulateError(aRv, status) && hasValue) {
    if (isPresent) {
      return L10nFileSourceHasFileStatus::Present;
    }

    return L10nFileSourceHasFileStatus::Missing;
  }
  return L10nFileSourceHasFileStatus::Unknown;
}

already_AddRefed<FluentResource> L10nFileSource::FetchFileSync(
    const nsACString& aLocale, const nsACString& aPath, ErrorResult& aRv) {
  ffi::L10nFileSourceStatus status;

  RefPtr<const ffi::FluentResource> raw =
      dont_AddRef(ffi::l10nfilesource_fetch_file_sync(mRaw.get(), &aLocale,
                                                      &aPath, &status));

  if (!PopulateError(aRv, status) && raw) {
    return MakeAndAddRef<FluentResource>(mGlobal, raw);
  }

  return nullptr;
}

already_AddRefed<Promise> L10nFileSource::FetchFile(const nsACString& aLocale,
                                                    const nsACString& aPath,
                                                    ErrorResult& aRv) {
  RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
  if (aRv.Failed()) {
    return nullptr;
  }

  ffi::L10nFileSourceStatus status;

  ffi::l10nfilesource_fetch_file(
      mRaw.get(), &aLocale, &aPath, promise,
      [](const Promise* aPromise, const ffi::FluentResource* aRes) {
        Promise* promise = const_cast<Promise*>(aPromise);

        if (aRes) {
          nsIGlobalObject* global = promise->GetGlobalObject();
          RefPtr<FluentResource> res = new FluentResource(global, aRes);
          promise->MaybeResolve(res);
        } else {
          promise->MaybeResolve(JS::NullHandleValue);
        }
      },
      &status);

  if (PopulateError(aRv, status)) {
    return nullptr;
  }

  return promise.forget();
}

/* static */
bool L10nFileSource::PopulateError(ErrorResult& aError,
                                   ffi::L10nFileSourceStatus& aStatus) {
  switch (aStatus) {
    case ffi::L10nFileSourceStatus::InvalidLocaleCode:
      aError.ThrowTypeError("Invalid locale code");
      return true;
    case ffi::L10nFileSourceStatus::EmptyName:
      aError.ThrowTypeError("Name cannot be empty.");
      return true;
    case ffi::L10nFileSourceStatus::EmptyPrePath:
      aError.ThrowTypeError("prePath cannot be empty.");
      return true;
    case ffi::L10nFileSourceStatus::EmptyResId:
      aError.ThrowTypeError("resId cannot be empty.");
      return true;

    case ffi::L10nFileSourceStatus::None:
      return false;
  }
  MOZ_ASSERT_UNREACHABLE("Unknown status");
  return false;
}

}  // namespace mozilla::intl