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

#include "mozIDOMWindow.h"
#include "nsArray.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsICommandLine.h"
#include "nsISupportsPrimitives.h"
#include "nsIURI.h"
#include "nsIWindowWatcher.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"

nsLayoutDebugCLH::nsLayoutDebugCLH() = default;

nsLayoutDebugCLH::~nsLayoutDebugCLH() = default;

NS_IMPL_ISUPPORTS(nsLayoutDebugCLH, ICOMMANDLINEHANDLER)

static nsresult HandleFlagWithOptionalArgument(nsICommandLine* aCmdLine,
                                               const nsAString& aName,
                                               const nsAString& aDefaultValue,
                                               nsAString& aValue,
                                               bool& aFlagPresent) {
  aValue.Truncate();
  aFlagPresent = false;

  nsresult rv;
  int32_t idx;

  rv = aCmdLine->FindFlag(aName, false, &idx);
  NS_ENSURE_SUCCESS(rv, rv);
  if (idx < 0) {
    return NS_OK;
  }

  aFlagPresent = true;

  int32_t length;
  aCmdLine->GetLength(&length);

  bool argPresent = false;

  if (idx + 1 < length) {
    rv = aCmdLine->GetArgument(idx + 1, aValue);
    NS_ENSURE_SUCCESS(rv, rv);

    if (!aValue.IsEmpty() && aValue.CharAt(0) == '-') {
      aValue.Truncate();
    } else {
      argPresent = true;
    }
  }

  if (!argPresent) {
    aValue = aDefaultValue;
  }

  return aCmdLine->RemoveArguments(idx, idx + argPresent);
}

static nsresult HandleFlagWithOptionalArgument(nsICommandLine* aCmdLine,
                                               const nsAString& aName,
                                               double aDefaultValue,
                                               double& aValue,
                                               bool& aFlagPresent) {
  nsresult rv;
  nsString s;

  rv =
      HandleFlagWithOptionalArgument(aCmdLine, aName, u"0"_ns, s, aFlagPresent);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!aFlagPresent) {
    aValue = 0.0;
    return NS_OK;
  }

  aValue = s.ToDouble(&rv);
  return rv;
}

static nsresult AppendArg(nsIMutableArray* aArray, const nsAString& aString) {
  nsCOMPtr<nsISupportsString> s =
      do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
  NS_ENSURE_TRUE(s, NS_ERROR_FAILURE);
  s->SetData(aString);
  return aArray->AppendElement(s);
}

NS_IMETHODIMP
nsLayoutDebugCLH::Handle(nsICommandLine* aCmdLine) {
  nsresult rv;
  bool flagPresent;

  nsString url;
  bool autoclose = false;
  double delay = 0.0;
  bool captureProfile = false;
  nsString profileFilename;
  bool paged = false;
  bool anonymousSubtreeDumping = false;
  bool deterministicFrameDumping = false;
  bool dumpContent = false;
  bool dumpFrames = false;
  bool dumpFramesCSSPixels = false;
  bool dumpRetainedDisplayList = false;

  rv = HandleFlagWithOptionalArgument(aCmdLine, u"layoutdebug"_ns,
                                      u"about:blank"_ns, url, flagPresent);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!flagPresent) {
    return NS_OK;
  }

  rv = HandleFlagWithOptionalArgument(aCmdLine, u"autoclose"_ns, 0.0, delay,
                                      autoclose);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = HandleFlagWithOptionalArgument(aCmdLine, u"capture-profile"_ns,
                                      u"profile.json"_ns, profileFilename,
                                      captureProfile);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"paged"_ns, false, &paged);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"anonymous-subtree-dumping"_ns, false,
                            &anonymousSubtreeDumping);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"deterministic-frame-dumping"_ns, false,
                            &deterministicFrameDumping);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"dump-content"_ns, false, &dumpContent);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"dump-frames"_ns, false, &dumpFrames);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"dump-frames-css-pixels"_ns, false,
                            &dumpFramesCSSPixels);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = aCmdLine->HandleFlag(u"dump-retained-display-list"_ns, false,
                            &dumpRetainedDisplayList);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMutableArray> argsArray = nsArray::Create();

  nsCOMPtr<nsIURI> uri;
  nsAutoCString resolvedSpec;

  rv = aCmdLine->ResolveURI(url, getter_AddRefs(uri));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = uri->GetSpec(resolvedSpec);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = AppendArg(argsArray, NS_ConvertUTF8toUTF16(resolvedSpec));
  NS_ENSURE_SUCCESS(rv, rv);

  if (autoclose) {
    nsString arg;
    arg.AppendPrintf("autoclose=%f", delay);

    rv = AppendArg(argsArray, arg);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (captureProfile) {
    nsString arg;
    arg.AppendLiteral("profile=");
    arg.Append(profileFilename);

    rv = AppendArg(argsArray, arg);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (paged) {
    rv = AppendArg(argsArray, u"paged"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (anonymousSubtreeDumping) {
    rv = AppendArg(argsArray, u"anonymous-subtree-dumping"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (deterministicFrameDumping) {
    rv = AppendArg(argsArray, u"deterministic-frame-dumping"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (dumpContent) {
    rv = AppendArg(argsArray, u"dump-content"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (dumpFrames) {
    rv = AppendArg(argsArray, u"dump-frames"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (dumpFramesCSSPixels) {
    rv = AppendArg(argsArray, u"dump-frames-css-pixels"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (dumpRetainedDisplayList) {
    rv = AppendArg(argsArray, u"dump-retained-display-list"_ns);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  nsCOMPtr<nsIWindowWatcher> wwatch =
      do_GetService(NS_WINDOWWATCHER_CONTRACTID);
  NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE);

  nsCOMPtr<mozIDOMWindowProxy> opened;
  wwatch->OpenWindow(
      nullptr, "chrome://layoutdebug/content/layoutdebug.xhtml"_ns, "_blank"_ns,
      "chrome,dialog=no,all"_ns, argsArray, getter_AddRefs(opened));
  aCmdLine->SetPreventDefault(true);
  return NS_OK;
}

NS_IMETHODIMP
nsLayoutDebugCLH::GetHelpInfo(nsACString& aResult) {
  aResult.AssignLiteral(
      "  --layoutdebug [<url>] Start with Layout Debugger\n"
      "  --autoclose [<seconds>] Automatically close the Layout Debugger once\n"
      "                     the page has loaded, after delaying the specified\n"
      "                     number of seconds (which defaults to 0).\n"
      "  --capture-profile [<filename>] Capture a profile of the Layout\n"
      "                     Debugger using the Gecko Profiler, and save the\n"
      "                     profile to the specified file (which defaults to\n"
      "                     profile.json).\n"
      "  --paged Layout the page in paginated mode.\n"
      "  --anonymous-subtree-dumping Toggle option to include anonymous\n"
      "                              subtrees in content dumps.\n"
      "  --deterministic-frame-dumping Toggle option to only include\n"
      "                                deterministic information in frame\n"
      "                                dumps, for ease of diffing.\n"
      "  --dump-content Dump the content tree after page load.\n"
      "  --dump-frames Dump the frame tree (in app units) after page load.\n"
      "  --dump-frames-css-pixels Dump the frame tree (in CSS pixels) after\n"
      "                           page load.\n"
      "  --dump-retained-display-list Dump the retained display list after\n"
      "                               page load.\n");
  return NS_OK;
}
