/* 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 "nsMIMEInfoAndroid.h"

#include "mozilla/java/GeckoAppShellWrappers.h"
#include "nsArrayUtils.h"
#include "nsComponentManagerUtils.h"
#include "nsISupportsUtils.h"
#include "nsNetUtil.h"
#include "nsStringEnumerator.h"

using namespace mozilla;

NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid, nsIMIMEInfo, nsIHandlerInfo)

NS_IMETHODIMP
nsMIMEInfoAndroid::LaunchDefaultWithFile(nsIFile* aFile) {
  return LaunchWithFile(aFile);
}

NS_IMETHODIMP
nsMIMEInfoAndroid::LoadUriInternal(nsIURI* aURI) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetType(nsACString& aType) {
  aType.Assign(mType);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetDescription(nsAString& aDesc) {
  aDesc.Assign(mDescription);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetDescription(const nsAString& aDesc) {
  mDescription.Assign(aDesc);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetPreferredApplicationHandler(nsIHandlerApp** aApp) {
  *aApp = mPrefApp;
  NS_IF_ADDREF(*aApp);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetPreferredApplicationHandler(nsIHandlerApp* aApp) {
  mPrefApp = aApp;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetPossibleApplicationHandlers(
    nsIMutableArray** aHandlerApps) {
  if (!mHandlerApps) mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID);

  if (!mHandlerApps) return NS_ERROR_OUT_OF_MEMORY;

  *aHandlerApps = mHandlerApps;
  NS_IF_ADDREF(*aHandlerApps);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetHasDefaultHandler(bool* aHasDefault) {
  uint32_t len;
  *aHasDefault = false;
  if (!mHandlerApps) return NS_OK;

  if (NS_FAILED(mHandlerApps->GetLength(&len))) return NS_OK;

  if (len == 0) return NS_OK;

  *aHasDefault = true;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetDefaultDescription(nsAString& aDesc) {
  aDesc.Assign(u""_ns);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetDefaultExecutable(nsIFile** aExecutable) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::LaunchWithURI(
    nsIURI* aURI, mozilla::dom::BrowsingContext* aBrowsingContext) {
  return mPrefApp->LaunchWithURI(aURI, aBrowsingContext);
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetPreferredAction(nsHandlerInfoAction* aPrefAction) {
  *aPrefAction = mPrefAction;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetPreferredAction(nsHandlerInfoAction aPrefAction) {
  mPrefAction = aPrefAction;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetAlwaysAskBeforeHandling(bool* aAlwaysAsk) {
  *aAlwaysAsk = mAlwaysAsk;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetAlwaysAskBeforeHandling(bool aAlwaysAsk) {
  mAlwaysAsk = aAlwaysAsk;
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetFileExtensions(nsIUTF8StringEnumerator** aResult) {
  return NS_NewUTF8StringEnumerator(aResult, &mExtensions, this);
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetFileExtensions(const nsACString& aExtensions) {
  mExtensions.Clear();
  nsACString::const_iterator start, end;
  aExtensions.BeginReading(start);
  aExtensions.EndReading(end);
  while (start != end) {
    nsACString::const_iterator cursor = start;
    (void)FindCharInReadable(',', cursor, end);
    AddUniqueExtension(Substring(start, cursor));
    // If a comma was found, skip it for the next search.
    start = cursor != end ? ++cursor : cursor;
  }
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::ExtensionExists(const nsACString& aExtension,
                                   bool* aRetVal) {
  NS_ASSERTION(!aExtension.IsEmpty(), "no extension");

  nsCString mimeType;
  if (jni::IsAvailable()) {
    auto javaString =
        java::GeckoAppShell::GetMimeTypeFromExtensions(aExtension);
    if (javaString) {
      mimeType = javaString->ToCString();
    }
  }

  // "*/*" means the bridge didn't find anything (i.e., extension doesn't
  // exist).
  *aRetVal = !mimeType.Equals(nsDependentCString("*/*"),
                              nsCaseInsensitiveCStringComparator);
  return NS_OK;
}

void nsMIMEInfoAndroid::AddUniqueExtension(const nsACString& aExtension) {
  if (!aExtension.IsEmpty() &&
      !mExtensions.Contains(aExtension,
                            nsCaseInsensitiveCStringArrayComparator())) {
    mExtensions.AppendElement(aExtension);
  }
}

NS_IMETHODIMP
nsMIMEInfoAndroid::AppendExtension(const nsACString& aExtension) {
  MOZ_ASSERT(!aExtension.IsEmpty(), "No extension");
  AddUniqueExtension(aExtension);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetPrimaryExtension(nsACString& aPrimaryExtension) {
  if (!mExtensions.Length()) {
    aPrimaryExtension.Truncate();
    return NS_ERROR_NOT_INITIALIZED;
  }
  aPrimaryExtension = mExtensions[0];
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::SetPrimaryExtension(const nsACString& aExtension) {
  if (MOZ_UNLIKELY(aExtension.IsEmpty())) {
    // Don't assert since Java may return an empty extension for unknown types.
    return NS_ERROR_INVALID_ARG;
  }
  int32_t i = mExtensions.IndexOf(aExtension, 0,
                                  nsCaseInsensitiveCStringArrayComparator());
  if (i != -1) {
    mExtensions.RemoveElementAt(i);
  }
  mExtensions.InsertElementAt(0, aExtension);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetMIMEType(nsACString& aMIMEType) {
  aMIMEType.Assign(mType);
  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::Equals(nsIMIMEInfo* aMIMEInfo, bool* aRetVal) {
  if (!aMIMEInfo) return NS_ERROR_NULL_POINTER;

  nsAutoCString type;
  nsresult rv = aMIMEInfo->GetMIMEType(type);
  if (NS_FAILED(rv)) return rv;

  *aRetVal = mType.Equals(type);

  return NS_OK;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::GetPossibleLocalHandlers(nsIArray** aPossibleLocalHandlers) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
nsMIMEInfoAndroid::LaunchWithFile(nsIFile* aFile) {
  nsCOMPtr<nsIURI> uri;
  NS_NewFileURI(getter_AddRefs(uri), aFile);
  return LoadUriInternal(uri);
}

NS_IMETHODIMP
nsMIMEInfoAndroid::IsCurrentAppOSDefault(bool* aRetVal) {
  // FIXME: this should in theory be meaningfully implemented. However, android
  // implements its own version of nsIHandlerApp instances which internally
  // have package and class names - but do not expose those. So to meaningfully
  // compare the handler app would require access to those and knowing what
  // our own package/class names are, and it's not clear how to do that.
  // It also seems less important to do this right on Android, given that
  // Android UI normally limits what apps you can associate with what files, so
  // it shouldn't be possible to get into the same kind of loop as on desktop.
  *aRetVal = false;
  return NS_OK;
}

nsMIMEInfoAndroid::nsMIMEInfoAndroid(const nsACString& aMIMEType)
    : mType(aMIMEType),
      mAlwaysAsk(true),
      mPrefAction(nsIMIMEInfo::useHelperApp) {
  mPrefApp = new nsMIMEInfoAndroid::SystemChooser(this);
  nsresult rv;
  mHandlerApps = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
  mHandlerApps->AppendElement(mPrefApp);
}

#define SYSTEMCHOOSER_NAME u"Android chooser"
#define SYSTEMCHOOSER_DESCRIPTION \
  u"Android's default handler application chooser"

NS_IMPL_ISUPPORTS(nsMIMEInfoAndroid::SystemChooser, nsIHandlerApp)

nsresult nsMIMEInfoAndroid::SystemChooser::GetName(nsAString& aName) {
  aName.AssignLiteral(SYSTEMCHOOSER_NAME);
  return NS_OK;
}

nsresult nsMIMEInfoAndroid::SystemChooser::SetName(const nsAString&) {
  return NS_OK;
}

nsresult nsMIMEInfoAndroid::SystemChooser::GetDetailedDescription(
    nsAString& aDesc) {
  aDesc.AssignLiteral(SYSTEMCHOOSER_DESCRIPTION);
  return NS_OK;
}

nsresult nsMIMEInfoAndroid::SystemChooser::SetDetailedDescription(
    const nsAString&) {
  return NS_OK;
}

nsresult nsMIMEInfoAndroid::SystemChooser::Equals(nsIHandlerApp* aHandlerApp,
                                                  bool* aRetVal) {
  *aRetVal = false;
  if (!aHandlerApp) {
    return NS_OK;
  }

  nsAutoString name;
  nsAutoString detailedDescription;
  aHandlerApp->GetName(name);
  aHandlerApp->GetDetailedDescription(detailedDescription);

  *aRetVal = name.Equals(SYSTEMCHOOSER_NAME) &&
             detailedDescription.Equals(SYSTEMCHOOSER_DESCRIPTION);
  return NS_OK;
}

nsresult nsMIMEInfoAndroid::SystemChooser::LaunchWithURI(
    nsIURI* aURI, mozilla::dom::BrowsingContext*) {
  return mOuter->LoadUriInternal(aURI);
}
