/* -*- 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 "ContentHandlerService.h"
#include "HandlerServiceChild.h"
#include "ContentChild.h"
#include "nsIMutableArray.h"
#include "nsIMIMEInfo.h"
#include "nsIStringEnumerator.h"
#include "nsReadableUtils.h"
#include "nsMIMEInfoImpl.h"
#include "nsMIMEInfoChild.h"

using mozilla::dom::ContentChild;
using mozilla::dom::HandlerInfo;
using mozilla::dom::PHandlerServiceChild;

namespace mozilla {
namespace dom {

NS_IMPL_ISUPPORTS(ContentHandlerService, nsIHandlerService)

ContentHandlerService::ContentHandlerService() {}

/* static */ already_AddRefed<nsIHandlerService>
ContentHandlerService::Create() {
  if (XRE_IsContentProcess()) {
    RefPtr service = new ContentHandlerService();
    if (NS_SUCCEEDED(service->Init())) {
      return service.forget();
    }
    return nullptr;
  }

  nsCOMPtr<nsIHandlerService> service =
      do_GetService("@mozilla.org/uriloader/handler-service-parent;1");
  return service.forget();
}

nsresult ContentHandlerService::Init() {
  if (!XRE_IsContentProcess()) {
    return NS_ERROR_FAILURE;
  }
  ContentChild* cpc = ContentChild::GetSingleton();

  mHandlerServiceChild = new HandlerServiceChild();
  if (!cpc->SendPHandlerServiceConstructor(mHandlerServiceChild)) {
    mHandlerServiceChild = nullptr;
    return NS_ERROR_UNEXPECTED;
  }
  return NS_OK;
}

void ContentHandlerService::nsIHandlerInfoToHandlerInfo(
    nsIHandlerInfo* aInfo, HandlerInfo* aHandlerInfo) {
  nsCString type;
  aInfo->GetType(type);
  nsCOMPtr<nsIMIMEInfo> mimeInfo = do_QueryInterface(aInfo);
  bool isMIMEInfo = !!mimeInfo;
  nsString description;
  aInfo->GetDescription(description);
  bool alwaysAskBeforeHandling;
  aInfo->GetAlwaysAskBeforeHandling(&alwaysAskBeforeHandling);
  nsCOMPtr<nsIHandlerApp> app;
  aInfo->GetPreferredApplicationHandler(getter_AddRefs(app));
  nsString name;
  nsString detailedDescription;
  if (app) {
    app->GetName(name);
    app->GetDetailedDescription(detailedDescription);
  }
  HandlerApp happ(name, detailedDescription);
  nsTArray<HandlerApp> happs;
  nsCOMPtr<nsIMutableArray> apps;
  aInfo->GetPossibleApplicationHandlers(getter_AddRefs(apps));
  if (apps) {
    unsigned int length;
    apps->GetLength(&length);
    for (unsigned int i = 0; i < length; i++) {
      apps->QueryElementAt(i, NS_GET_IID(nsIHandlerApp), getter_AddRefs(app));
      app->GetName(name);
      app->GetDetailedDescription(detailedDescription);
      happs.AppendElement(HandlerApp(name, detailedDescription));
    }
  }

  nsTArray<nsCString> extensions;

  if (isMIMEInfo) {
    nsCOMPtr<nsIUTF8StringEnumerator> extensionsIter;
    mimeInfo->GetFileExtensions(getter_AddRefs(extensionsIter));
    if (extensionsIter) {
      bool hasMore = false;
      while (NS_SUCCEEDED(extensionsIter->HasMore(&hasMore)) && hasMore) {
        nsAutoCString extension;
        if (NS_SUCCEEDED(extensionsIter->GetNext(extension))) {
          extensions.AppendElement(std::move(extension));
        }
      }
    }
  }

  nsHandlerInfoAction action;
  aInfo->GetPreferredAction(&action);
  HandlerInfo info(type, isMIMEInfo, description, alwaysAskBeforeHandling,
                   std::move(extensions), happ, happs, action);
  *aHandlerInfo = info;
}

NS_IMETHODIMP RemoteHandlerApp::GetName(nsAString& aName) {
  aName.Assign(mAppChild.name());
  return NS_OK;
}

NS_IMETHODIMP RemoteHandlerApp::SetName(const nsAString& aName) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP RemoteHandlerApp::GetDetailedDescription(
    nsAString& aDetailedDescription) {
  aDetailedDescription.Assign(mAppChild.detailedDescription());
  return NS_OK;
}

NS_IMETHODIMP RemoteHandlerApp::SetDetailedDescription(
    const nsAString& aDetailedDescription) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP RemoteHandlerApp::Equals(nsIHandlerApp* aHandlerApp,
                                       bool* _retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP RemoteHandlerApp::LaunchWithURI(
    nsIURI* aURI, BrowsingContext* aBrowsingContext) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMPL_ISUPPORTS(RemoteHandlerApp, nsIHandlerApp)

static inline void CopyHandlerInfoTonsIHandlerInfo(
    const HandlerInfo& info, nsIHandlerInfo* aHandlerInfo) {
  HandlerApp preferredApplicationHandler = info.preferredApplicationHandler();
  nsCOMPtr<nsIHandlerApp> preferredApp(
      new RemoteHandlerApp(preferredApplicationHandler));
  aHandlerInfo->SetPreferredApplicationHandler(preferredApp);
  nsCOMPtr<nsIMutableArray> possibleHandlers;
  aHandlerInfo->GetPossibleApplicationHandlers(
      getter_AddRefs(possibleHandlers));
  possibleHandlers->AppendElement(preferredApp);

  aHandlerInfo->SetPreferredAction(info.preferredAction());
  aHandlerInfo->SetAlwaysAskBeforeHandling(info.alwaysAskBeforeHandling());

  if (info.isMIMEInfo()) {
    nsCOMPtr<nsIMIMEInfo> mimeInfo(do_QueryInterface(aHandlerInfo));
    MOZ_ASSERT(mimeInfo,
               "parent and child don't agree on whether this is a MIME info");
    mimeInfo->SetFileExtensions(StringJoin(","_ns, info.extensions()));
  }
}

ContentHandlerService::~ContentHandlerService() {}

NS_IMETHODIMP ContentHandlerService::AsyncInit() {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP ContentHandlerService::Enumerate(nsISimpleEnumerator** _retval) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP ContentHandlerService::FillHandlerInfo(
    nsIHandlerInfo* aHandlerInfo, const nsACString& aOverrideType) {
  HandlerInfo info, returnedInfo;
  nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
  mHandlerServiceChild->SendFillHandlerInfo(info, aOverrideType, &returnedInfo);
  CopyHandlerInfoTonsIHandlerInfo(returnedInfo, aHandlerInfo);
  return NS_OK;
}

NS_IMETHODIMP ContentHandlerService::GetMIMEInfoFromOS(
    const nsACString& aMIMEType, const nsACString& aFileExt, bool* aFound,
    nsIMIMEInfo** aMIMEInfo) {
  nsresult rv = NS_ERROR_FAILURE;
  HandlerInfo returnedInfo;
  if (!mHandlerServiceChild->SendGetMIMEInfoFromOS(aMIMEType, aFileExt, &rv,
                                                   &returnedInfo, aFound)) {
    return NS_ERROR_FAILURE;
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  RefPtr<nsChildProcessMIMEInfo> mimeInfo =
      new nsChildProcessMIMEInfo(returnedInfo.type());
  CopyHandlerInfoTonsIHandlerInfo(returnedInfo, mimeInfo);
  mimeInfo.forget(aMIMEInfo);
  return NS_OK;
}

NS_IMETHODIMP ContentHandlerService::Store(nsIHandlerInfo* aHandlerInfo) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP ContentHandlerService::Exists(nsIHandlerInfo* aHandlerInfo,
                                            bool* _retval) {
  HandlerInfo info;
  nsIHandlerInfoToHandlerInfo(aHandlerInfo, &info);
  mHandlerServiceChild->SendExists(info, _retval);
  return NS_OK;
}

NS_IMETHODIMP ContentHandlerService::Remove(nsIHandlerInfo* aHandlerInfo) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
ContentHandlerService::ExistsForProtocolOS(const nsACString& aProtocolScheme,
                                           bool* aRetval) {
  if (!mHandlerServiceChild->SendExistsForProtocolOS(aProtocolScheme,
                                                     aRetval)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP
ContentHandlerService::ExistsForProtocol(const nsACString& aProtocolScheme,
                                         bool* aRetval) {
  if (!mHandlerServiceChild->SendExistsForProtocol(aProtocolScheme, aRetval)) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

NS_IMETHODIMP ContentHandlerService::GetTypeFromExtension(
    const nsACString& aFileExtension, nsACString& _retval) {
  _retval.Assign(*mExtToTypeMap.LookupOrInsertWith(aFileExtension, [&] {
    nsCString type;
    mHandlerServiceChild->SendGetTypeFromExtension(aFileExtension, &type);
    return MakeUnique<nsCString>(type);
  }));
  return NS_OK;
}

NS_IMETHODIMP ContentHandlerService::GetApplicationDescription(
    const nsACString& aProtocolScheme, nsAString& aRetVal) {
  nsresult rv = NS_ERROR_FAILURE;
  nsAutoCString scheme(aProtocolScheme);
  nsAutoString desc;
  mHandlerServiceChild->SendGetApplicationDescription(scheme, &rv, &desc);
  aRetVal.Assign(desc);
  return rv;
}

}  // namespace dom
}  // namespace mozilla