/* 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/dom/BrowsingContext.h"
#include "mozilla/dom/CustomElementTypes.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/sessionstore/SessionStoreTypes.h"
#include "mozilla/dom/WindowContext.h"
#include "nsISessionStoreRestoreData.h"
#include "nsNetUtil.h"

namespace mozilla {
namespace dom {

bool SessionStoreRestoreData::IsEmpty() {
  return (!mURI && mScroll.IsEmpty() && mInnerHTML.IsEmpty() &&
          mEntries.IsEmpty() && mChildren.IsEmpty());
}

SessionStoreRestoreData* SessionStoreRestoreData::FindDataForChild(
    BrowsingContext* aContext) {
  nsTArray<uint32_t> path;
  if (!aContext->GetOffsetPath(path)) {
    return nullptr;
  }

  SessionStoreRestoreData* data = this;

  for (uint32_t offset : Reversed(path)) {
    if (!data || data->mChildren.Length() <= offset ||
        !data->mChildren[offset] || data->mChildren[offset]->IsEmpty()) {
      return nullptr;
    }
    data = data->mChildren[offset];
  }

  return data;
}

bool SessionStoreRestoreData::CanRestoreInto(nsIURI* aDocumentURI) {
  if (!mURI) {
    // This should mean that we don't have form data. It's fine to restore this
    // data into any document — the worst that will happen is that we restore an
    // incorrect scroll position.
    MOZ_ASSERT(mEntries.IsEmpty());
    MOZ_ASSERT(mInnerHTML.IsEmpty());
    return true;
  }
  bool equalsExceptRef = false;
  return (aDocumentURI &&
          NS_SUCCEEDED(mURI->EqualsExceptRef(aDocumentURI, &equalsExceptRef)) &&
          equalsExceptRef);
}

bool SessionStoreRestoreData::RestoreInto(RefPtr<BrowsingContext> aContext) {
  if (!aContext->IsInProcess()) {
    return false;
  }

  if (WindowContext* window = aContext->GetCurrentWindowContext()) {
    if (!mScroll.IsEmpty()) {
      if (nsGlobalWindowInner* inner = window->GetInnerWindow()) {
        SessionStoreUtils::RestoreScrollPosition(*inner, mScroll);
      }
    }
    if (mURI) {
      if (nsCOMPtr<Document> doc = window->GetExtantDoc()) {
        if (!CanRestoreInto(doc->GetDocumentURI())) {
          return false;
        }
        SessionStoreUtils::RestoreFormData(*doc, mInnerHTML, mEntries);
      }
    }
  }

  return true;
}

void SessionStoreRestoreData::AddFormEntry(
    bool aIsXPath, const nsAString& aIdOrXPath,
    sessionstore::FormEntryValue aValue) {
  mEntries.AppendElement(
      Entry{sessionstore::FormEntry{nsString(aIdOrXPath), aValue}, aIsXPath});
}

NS_IMPL_ISUPPORTS(SessionStoreRestoreData, nsISessionStoreRestoreData,
                  SessionStoreRestoreData)

NS_IMETHODIMP
SessionStoreRestoreData::GetUrl(nsACString& aURL) {
  if (mURI) {
    nsresult rv = mURI->GetSpec(aURL);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::SetUrl(const nsACString& aURL) {
  nsresult rv = NS_NewURI(getter_AddRefs(mURI), aURL);
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::GetInnerHTML(nsAString& aInnerHTML) {
  aInnerHTML = mInnerHTML;
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::SetInnerHTML(const nsAString& aInnerHTML) {
  mInnerHTML = aInnerHTML;
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::GetScroll(nsACString& aScroll) {
  aScroll = mScroll;
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::SetScroll(const nsACString& aScroll) {
  mScroll = aScroll;
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddTextField(bool aIsXPath,
                                      const nsAString& aIdOrXPath,
                                      const nsAString& aValue) {
  AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::TextField{nsString(aValue)});
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddCheckbox(bool aIsXPath, const nsAString& aIdOrXPath,
                                     const bool aValue) {
  AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::Checkbox{aValue});
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddFileList(bool aIsXPath, const nsAString& aIdOrXPath,
                                     const nsAString& aType,
                                     const nsTArray<nsString>& aFileList) {
  AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::FileList{aFileList});
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddSingleSelect(bool aIsXPath,
                                         const nsAString& aIdOrXPath,
                                         uint32_t aSelectedIndex,
                                         const nsAString& aValue) {
  AddFormEntry(aIsXPath, aIdOrXPath,
               sessionstore::SingleSelect{aSelectedIndex, nsString(aValue)});
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddMultipleSelect(bool aIsXPath,
                                           const nsAString& aIdOrXPath,
                                           const nsTArray<nsString>& aValues) {
  AddFormEntry(aIsXPath, aIdOrXPath, sessionstore::MultipleSelect{aValues});
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddCustomElement(bool aIsXPath,
                                          const nsAString& aIdOrXPath,
                                          JS::Handle<JS::Value> aValue,
                                          JS::Handle<JS::Value> aState) {
  AutoJSContext cx;
  Nullable<OwningFileOrUSVStringOrFormData> value;
  if (!aValue.isNull() && !value.SetValue().Init(cx, aValue)) {
    return NS_ERROR_FAILURE;
  }
  Nullable<OwningFileOrUSVStringOrFormData> state;
  if (!aState.isNull() && !state.SetValue().Init(cx, aState)) {
    return NS_ERROR_FAILURE;
  }
  AddFormEntry(aIsXPath, aIdOrXPath,
               CustomElementTuple(
                   nsContentUtils::ConvertToCustomElementFormValue(value),
                   nsContentUtils::ConvertToCustomElementFormValue(state)));
  return NS_OK;
}

NS_IMETHODIMP
SessionStoreRestoreData::AddChild(nsISessionStoreRestoreData* aChild,
                                  uint32_t aIndex) {
  if (nsCOMPtr<SessionStoreRestoreData> child = do_QueryInterface(aChild)) {
    if (aIndex > mChildren.Length()) {
      mChildren.SetLength(aIndex);
    }
    mChildren.InsertElementAt(aIndex, child);
  }
  return NS_OK;
}

}  // namespace dom
}  // namespace mozilla
