// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/browser_window_state.h"

#include <stddef.h>

#include <string_view>
#include <utility>

#include "base/command_line.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/buildflags.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/sessions/session_service_base.h"
#include "chrome/browser/sessions/session_service_lookup.h"
#include "chrome/browser/ui/browser_init_state.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/ui/window_feature_controller/window_feature_controller.h"
#include "chrome/browser/ui/window_sizer/window_sizer.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "ui/base/mojom/window_show_state.mojom.h"

namespace chrome {
namespace {

// Parse two comma-separated integers from str. Return true on success.
bool ParseCommaSeparatedIntegers(const std::string& str,
                                 int* ret_num1,
                                 int* ret_num2) {
  const size_t comma = str.find(',');
  if (comma == std::string::npos) {
    return false;
  }
  auto view = std::string_view(str);
  return base::StringToInt(view.substr(0, comma), ret_num1) &&
         base::StringToInt(view.substr(comma + 1), ret_num2);
}

}  // namespace

std::string GetWindowName(const BrowserWindowInterface* browser) {
  switch (browser->GetType()) {
    case BrowserWindowInterface::Type::TYPE_NORMAL:
      return prefs::kBrowserWindowPlacement;
    case BrowserWindowInterface::Type::TYPE_POPUP:
    case BrowserWindowInterface::Type::TYPE_PICTURE_IN_PICTURE:
      return prefs::kBrowserWindowPlacementPopup;
    case BrowserWindowInterface::Type::TYPE_APP:
    case BrowserWindowInterface::Type::TYPE_DEVTOOLS:
      return BrowserInitState::From(browser)->create_params().app_name;
    case BrowserWindowInterface::Type::TYPE_APP_POPUP:
      return BrowserInitState::From(browser)->create_params().app_name +
             "_popup";
  }
}

base::DictValue& GetWindowPlacementDictionaryReadWrite(
    const std::string& window_name,
    PrefService* prefs,
    std::unique_ptr<ScopedDictPrefUpdate>& scoped_update) {
  DCHECK(!window_name.empty());
  // Non-app window placements each use their own per-window-name dictionary
  // preference, so can make a ScopedDictPrefUpdate for the relevant preference,
  // and return its dictionary directly.
  if (prefs->FindPreference(window_name)) {
    scoped_update = std::make_unique<ScopedDictPrefUpdate>(prefs, window_name);
    return scoped_update->Get();
  }

  // The window placements for all apps are stored in a single dictionary
  // preference, with per-window-name nested dictionaries, so need to make
  // ScopedDictPrefUpdate and then find the relevant dictionary within it, based
  // on window name.
  scoped_update =
      std::make_unique<ScopedDictPrefUpdate>(prefs, prefs::kAppWindowPlacement);
  base::DictValue* this_app_dict =
      (*scoped_update)->FindDictByDottedPath(window_name);
  if (this_app_dict) {
    return *this_app_dict;
  }
  return (*scoped_update)
      ->SetByDottedPath(window_name, base::DictValue())
      ->GetDict();
}

const base::DictValue* GetWindowPlacementDictionaryReadOnly(
    const std::string& window_name,
    PrefService* prefs) {
  DCHECK(!window_name.empty());
  if (prefs->FindPreference(window_name)) {
    return &prefs->GetDict(window_name);
  }

  const base::DictValue& app_windows =
      prefs->GetDict(prefs::kAppWindowPlacement);
  return app_windows.FindDict(window_name);
}

bool ShouldSaveWindowPlacement(const BrowserWindowInterface* browser) {
  // Never track app windows that do not have a trusted source (i.e. windows
  // spawned by an app).  See similar code in
  // SessionServiceBase::ShouldTrackBrowser().
  return !(browser->GetType() == BrowserWindowInterface::Type::TYPE_APP ||
           browser->GetType() ==
               BrowserWindowInterface::Type::TYPE_APP_POPUP) ||
         WindowFeatureController::From(browser)->IsTrustedSource();
}

bool SavedBoundsAreContentBounds(const BrowserWindowInterface* browser) {
  // Applications other than web apps (such as devtools) save their window size.
  // Web apps, on the other hand, have the same behavior as popups, and save
  // their content bounds.
  return browser->GetType() != BrowserWindowInterface::Type::TYPE_NORMAL &&
         browser->GetType() != BrowserWindowInterface::Type::TYPE_DEVTOOLS &&
         !WindowFeatureController::From(browser)->IsTrustedSource();
}

void SaveWindowPlacement(BrowserWindowInterface* browser,
                         const gfx::Rect& bounds,
                         ui::mojom::WindowShowState show_state) {
  // Save to the session storage service, used when reloading a past session.
  // Note that we don't want to be the ones who cause lazy initialization of
  // the session service. This function gets called during initial window
  // showing, and we don't want to bring in the session service this early.
  SessionServiceBase* service = GetAppropriateSessionServiceIfExisting(browser);
  if (service) {
    service->SetWindowBounds(browser->GetSessionID(), bounds, show_state);
  }
}

void SaveWindowWorkspace(BrowserWindowInterface* browser,
                         const std::string& workspace) {
  SessionServiceBase* service = GetAppropriateSessionServiceIfExisting(browser);
  if (service) {
    service->SetWindowWorkspace(browser->GetSessionID(), workspace);
  }
}

void SaveWindowVisibleOnAllWorkspaces(BrowserWindowInterface* browser,
                                      bool visible_on_all_workspaces) {
  SessionServiceBase* service = GetAppropriateSessionServiceIfExisting(browser);
  if (service) {
    service->SetWindowVisibleOnAllWorkspaces(browser->GetSessionID(),
                                             visible_on_all_workspaces);
  }
}

void GetSavedWindowBoundsAndShowState(BrowserWindowInterface* browser,
                                      gfx::Rect* bounds,
                                      ui::mojom::WindowShowState* show_state) {
  DCHECK(browser);
  DCHECK(bounds);
  DCHECK(show_state);
  *bounds = BrowserInitState::From(browser)->override_bounds();
  WindowSizer::GetBrowserWindowBoundsAndShowState(*bounds, browser, bounds,
                                                  show_state);

  const base::CommandLine& parsed_command_line =
      *base::CommandLine::ForCurrentProcess();

  internal::UpdateWindowBoundsAndShowStateFromCommandLine(parsed_command_line,
                                                          bounds, show_state);
}

namespace internal {

void UpdateWindowBoundsAndShowStateFromCommandLine(
    const base::CommandLine& command_line,
    gfx::Rect* bounds,
    ui::mojom::WindowShowState* show_state) {
  // Allow command-line flags to override the window size and position. If
  // either of these is specified then set the show state to NORMAL so that
  // they are immediately respected.
  if (command_line.HasSwitch(switches::kWindowSize)) {
    std::string str = command_line.GetSwitchValueASCII(switches::kWindowSize);
    int width, height;
    if (ParseCommaSeparatedIntegers(str, &width, &height)) {
      bounds->set_size(gfx::Size(width, height));
      *show_state = ui::mojom::WindowShowState::kNormal;
    }
  }
  if (command_line.HasSwitch(switches::kWindowPosition)) {
    std::string str =
        command_line.GetSwitchValueASCII(switches::kWindowPosition);
    int x, y;
    if (ParseCommaSeparatedIntegers(str, &x, &y)) {
      bounds->set_origin(gfx::Point(x, y));
      *show_state = ui::mojom::WindowShowState::kNormal;
    }
  }
}

}  // namespace internal

}  // namespace chrome
