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

#include "imgIContainer.h"
#include "imgINotificationObserver.h"
#include "imgIRequest.h"
#include "imgLoader.h"
#include "nsAlertsUtils.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsDirectoryServiceUtils.h"
#include "nsNetUtil.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/BasePrincipal.h"

namespace mozilla {

NS_IMPL_ISUPPORTS(AlertNotification, nsIAlertNotification)

NS_IMETHODIMP
AlertNotification::Init(const nsAString& aName, const nsAString& aImageURL,
                        const nsAString& aTitle, const nsAString& aText,
                        bool aTextClickable, const nsAString& aCookie,
                        const nsAString& aDir, const nsAString& aLang,
                        const nsAString& aData, nsIPrincipal* aPrincipal,
                        bool aInPrivateBrowsing, bool aRequireInteraction,
                        bool aSilent, const nsTArray<uint32_t>& aVibrate) {
  if (!mId.IsEmpty()) {
    return NS_ERROR_ALREADY_INITIALIZED;
  }

  mName = aName;
  mImageURL = aImageURL;
  mTitle = aTitle;
  mText = aText;
  mTextClickable = aTextClickable;
  mCookie = aCookie;
  mDir = aDir;
  mLang = aLang;
  mData = aData;
  mPrincipal = aPrincipal;
  mInPrivateBrowsing = aInPrivateBrowsing;
  mRequireInteraction = aRequireInteraction;
  mSilent = aSilent;
  mVibrate = aVibrate.Clone();

  return InitId();
}

NS_IMETHODIMP
AlertNotification::InitWithObject(nsIAlertNotification* aAlertNotification) {
  MOZ_TRY(aAlertNotification->GetName(mName));
  MOZ_TRY(aAlertNotification->GetImageURL(mImageURL));
  MOZ_TRY(aAlertNotification->GetImage(getter_AddRefs(mImage)));
  MOZ_TRY(aAlertNotification->GetTitle(mTitle));
  MOZ_TRY(aAlertNotification->GetText(mText));
  MOZ_TRY(aAlertNotification->GetTextClickable(&mTextClickable));
  MOZ_TRY(aAlertNotification->GetCookie(mCookie));
  MOZ_TRY(aAlertNotification->GetDir(mDir));
  MOZ_TRY(aAlertNotification->GetLang(mLang));
  MOZ_TRY(aAlertNotification->GetData(mData));
  MOZ_TRY(aAlertNotification->GetPrincipal(getter_AddRefs(mPrincipal)));
  MOZ_TRY(aAlertNotification->GetInPrivateBrowsing(&mInPrivateBrowsing));
  MOZ_TRY(aAlertNotification->GetRequireInteraction(&mRequireInteraction));
  MOZ_TRY(aAlertNotification->GetSilent(&mSilent));
  if (NS_FAILED(aAlertNotification->GetVibrate(mVibrate))) {
    mVibrate.Clear();
  };
  nsTArray<RefPtr<nsIAlertAction>> actions;
  if (NS_SUCCEEDED(aAlertNotification->GetActions(actions))) {
    for (auto& action : actions) {
      if (RefPtr<nsIAlertAction> copied =
              AlertAction::Copy(*action).unwrapOr(nullptr)) {
        mActions.AppendElement(copied);
      }
    }
  };
  return InitId();
}

nsresult AlertNotification::InitId() {
  nsAutoString id;

  // Multiple profiles might overwrite each other's toast messages when a
  // common name is used for a given origin. We prevent this by including
  // the profile directory as part of the toast hash.
  nsCOMPtr<nsIFile> profDir;
  MOZ_TRY(NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                                 getter_AddRefs(profDir)));
  MOZ_TRY(profDir->Normalize());
  MOZ_TRY(profDir->GetPath(id));

  if (mPrincipal && mPrincipal->GetIsContentPrincipal()) {
    // Notification originated from a web notification.
    nsAutoCString origin;
    MOZ_TRY(mPrincipal->GetOrigin(origin));
    id += NS_ConvertUTF8toUTF16(origin);
  } else {
    id += u"chrome";
  }

  if (mName.IsEmpty()) {
    // No associated name, append a UUID to prevent reuse of the same tag.
    nsIDToCString uuidString(nsID::GenerateUUID());
    size_t len = strlen(uuidString.get());
    MOZ_ASSERT(len == NSID_LENGTH - 1);
    nsAutoString uuid;
    CopyASCIItoUTF16(nsDependentCSubstring(uuidString.get(), len), uuid);

    id += u"#notag:"_ns;
    id += uuid;
  } else {
    id += u"#tag:"_ns;
    id += mName;
  }

  // Windows notification tags are limited to 16 characters, or 64 characters
  // after the Creators Update; therefore we hash the tag to fit the minimum
  // range.
  HashNumber hash = HashString(id);
  mId.AppendPrintf("%010u", hash);
  return NS_OK;
}

NS_IMETHODIMP AlertNotification::GetId(nsAString& aId) {
  if (mId.IsEmpty()) {
    return NS_ERROR_NOT_INITIALIZED;
  }
  aId = mId;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::SetActions(
    const nsTArray<RefPtr<nsIAlertAction>>& aActions) {
  mActions = aActions.Clone();
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetName(nsAString& aName) {
  if (mPrincipal && mPrincipal->GetIsContentPrincipal()) {
    // mName is no longer unique, but there has been a long assumption
    // throughout the codebase that GetName will be unique. So we return mId for
    // GetName for web triggered notifications to keep uniqueness without
    // accidentially causing subtle breakage in other modules.
    aName = mId;
  } else {
    // System callers has always been expected to provide unique names
    // themselves, so it's fine to return mName as is.
    aName = mName;
  }
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetImageURL(nsAString& aImageURL) {
  aImageURL = mImageURL;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::SetImage(imgIContainer* aImage) {
  mImage = aImage;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetImage(imgIContainer** aImage) {
  nsCOMPtr<imgIContainer> image = mImage;
  image.forget(aImage);
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetTitle(nsAString& aTitle) {
  aTitle = mTitle;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetText(nsAString& aText) {
  aText = mText;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetTextClickable(bool* aTextClickable) {
  *aTextClickable = mTextClickable;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetCookie(nsAString& aCookie) {
  aCookie = mCookie;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetDir(nsAString& aDir) {
  aDir = mDir;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetLang(nsAString& aLang) {
  aLang = mLang;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetRequireInteraction(bool* aRequireInteraction) {
  *aRequireInteraction = mRequireInteraction;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetData(nsAString& aData) {
  aData = mData;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal) {
  NS_IF_ADDREF(*aPrincipal = mPrincipal);
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetURI(nsIURI** aURI) {
  if (!nsAlertsUtils::IsActionablePrincipal(mPrincipal)) {
    *aURI = nullptr;
    return NS_OK;
  }
  auto* basePrin = BasePrincipal::Cast(mPrincipal);
  return basePrin->GetURI(aURI);
}

NS_IMETHODIMP
AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing) {
  *aInPrivateBrowsing = mInPrivateBrowsing;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetActionable(bool* aActionable) {
  *aActionable = nsAlertsUtils::IsActionablePrincipal(mPrincipal);
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetSilent(bool* aSilent) {
  *aSilent = mSilent;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetVibrate(nsTArray<uint32_t>& aVibrate) {
  aVibrate = mVibrate.Clone();
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetActions(nsTArray<RefPtr<nsIAlertAction>>& aActions) {
  aActions = mActions.Clone();
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetSource(nsAString& aSource) {
  nsAlertsUtils::GetSourceHostPort(mPrincipal, aSource);
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetOrigin(nsACString& aOrigin) {
  return nsAlertsUtils::GetOrigin(mPrincipal, aOrigin);
}

NS_IMETHODIMP
AlertNotification::GetOpaqueRelaunchData(nsAString& aOpaqueRelaunchData) {
  aOpaqueRelaunchData = mOpaqueRelaunchData;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::SetOpaqueRelaunchData(const nsAString& aOpaqueRelaunchData) {
  mOpaqueRelaunchData = aOpaqueRelaunchData;
  return NS_OK;
}

NS_IMETHODIMP
AlertNotification::GetAction(const nsAString& aName,
                             nsIAlertAction** aAlertAction) {
  NS_ENSURE_ARG_POINTER(aAlertAction);
  for (const auto& action : mActions) {
    nsString name;
    MOZ_TRY(action->GetAction(name));
    if (name.Equals(aName)) {
      RefPtr<nsIAlertAction> match = action;
      match.forget(aAlertAction);
      return NS_OK;
    }
  }
  *aAlertAction = nullptr;
  return NS_OK;
}

NS_IMPL_ISUPPORTS(AlertAction, nsIAlertAction)

AlertAction::AlertAction(const nsAString& aAction, const nsAString& aTitle)
    : mAction(aAction), mTitle(aTitle) {}

Result<already_AddRefed<AlertAction>, nsresult> AlertAction::Copy(
    nsIAlertAction& aAction) {
  nsAutoString action;
  nsAutoString title;
  MOZ_TRY(aAction.GetAction(action));
  MOZ_TRY(aAction.GetTitle(title));
  return do_AddRef(new AlertAction(action, title));
}

NS_IMETHODIMP
AlertAction::GetAction(nsAString& aAction) {
  aAction = mAction;
  return NS_OK;
}

NS_IMETHODIMP
AlertAction::GetTitle(nsAString& aTitle) {
  aTitle = mTitle;
  return NS_OK;
}

NS_IMETHODIMP
AlertAction::GetIconURL(nsAString& aTitle) {
  aTitle.Truncate();
  return NS_OK;
}

NS_IMETHODIMP
AlertAction::GetWindowsSystemActivationType(bool* aType) {
  *aType = false;
  return NS_OK;
}

NS_IMETHODIMP
AlertAction::GetOpaqueRelaunchData(nsAString& aData) {
  aData.Truncate();
  return NS_OK;
}

}  // namespace mozilla
