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

#include "ui/views/mus/aura_init.h"

#include <utility>

#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "build/build_config.h"
#include "services/catalog/public/cpp/resource_loader.h"
#include "services/catalog/public/interfaces/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/aura/env.h"
#include "ui/base/ime/input_method_initializer.h"
#include "ui/base/material_design/material_design_controller.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/views/layout/layout_provider.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/views_delegate.h"

#if defined(OS_LINUX)
#include "components/font_service/public/cpp/font_loader.h"
#include "ui/gfx/platform_font_linux.h"
#endif

namespace views {

namespace {

class MusViewsDelegate : public ViewsDelegate {
 public:
  MusViewsDelegate() {}
  ~MusViewsDelegate() override {}

 private:
#if defined(OS_WIN)
  HICON GetSmallWindowIcon() const override { return nullptr; }
#endif
  void OnBeforeWidgetInit(
      Widget::InitParams* params,
      internal::NativeWidgetDelegate* delegate) override {}

  LayoutProvider layout_provider_;

  DISALLOW_COPY_AND_ASSIGN(MusViewsDelegate);
};

}  // namespace

AuraInit::AuraInit() {
  if (!ViewsDelegate::GetInstance())
    views_delegate_ = std::make_unique<MusViewsDelegate>();
}

AuraInit::~AuraInit() {
#if defined(OS_LINUX)
  if (font_loader_.get()) {
    SkFontConfigInterface::SetGlobal(nullptr);
    // FontLoader is ref counted. We need to explicitly shutdown the background
    // thread, otherwise the background thread may be shutdown after the app is
    // torn down, when we're in a bad state.
    font_loader_->Shutdown();
  }
#endif
}

std::unique_ptr<AuraInit> AuraInit::Create(
    service_manager::Connector* connector,
    const service_manager::Identity& identity,
    const std::string& resource_file,
    const std::string& resource_file_200,
    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
    Mode mode,
    bool register_path_provider) {
  std::unique_ptr<AuraInit> aura_init = base::WrapUnique(new AuraInit());
  if (!aura_init->Init(connector, identity, resource_file, resource_file_200,
                       io_task_runner, mode, register_path_provider)) {
    aura_init.reset();
  }
  return aura_init;
}

bool AuraInit::Init(service_manager::Connector* connector,
                    const service_manager::Identity& identity,
                    const std::string& resource_file,
                    const std::string& resource_file_200,
                    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
                    Mode mode,
                    bool register_path_provider) {
  env_ = aura::Env::CreateInstance(
      (mode == Mode::AURA_MUS || mode == Mode::AURA_MUS_WINDOW_MANAGER)
          ? aura::Env::Mode::MUS
          : aura::Env::Mode::LOCAL);

  if (mode == Mode::AURA_MUS) {
    mus_client_ =
        base::WrapUnique(new MusClient(connector, identity, io_task_runner));
  }
  // MaterialDesignController may have initialized already (such as happens
  // in the utility process).
  if (!ui::MaterialDesignController::is_mode_initialized())
    ui::MaterialDesignController::Initialize();
  if (!InitializeResources(connector, resource_file, resource_file_200,
                           register_path_provider)) {
    return false;
  }

// Initialize the skia font code to go ask fontconfig underneath.
#if defined(OS_LINUX)
  font_loader_ = sk_make_sp<font_service::FontLoader>(connector);
  SkFontConfigInterface::SetGlobal(font_loader_.get());

  // Initialize static default font, by running this now, before any other apps
  // load, we ensure all the state is set up.
  bool success = gfx::PlatformFontLinux::InitDefaultFont();

  // If a remote service manager has shut down, initializing the font will fail.
  if (!success)
    return false;
#endif  // defined(OS_LINUX)

  ui::InitializeInputMethodForTesting();
  return true;
}

bool AuraInit::InitializeResources(service_manager::Connector* connector,
                                   const std::string& resource_file,
                                   const std::string& resource_file_200,
                                   bool register_path_provider) {
  // Resources may have already been initialized (e.g. when 'chrome --mash' is
  // used to launch the current app).
  if (ui::ResourceBundle::HasSharedInstance())
    return true;

  std::set<std::string> resource_paths({resource_file});
  if (!resource_file_200.empty())
    resource_paths.insert(resource_file_200);

  catalog::ResourceLoader loader;
  filesystem::mojom::DirectoryPtr directory;
  connector->BindInterface(catalog::mojom::kServiceName, &directory);
  // TODO(jonross): if this proves useful in resolving the crash of
  // mash_unittests then switch AuraInit to have an Init method, returning a
  // bool for success. Then update all callsites to use this to determine the
  // shutdown of their ServiceContext.
  // One cause of failure is that the peer has closed, but we have not been
  // notified yet. It is not possible to complete initialization, so exit now.
  // Calling services will shutdown ServiceContext as appropriate.
  if (!loader.OpenFiles(std::move(directory), resource_paths))
    return false;
  if (register_path_provider)
    ui::RegisterPathProvider();
  base::File pak_file = loader.TakeFile(resource_file);
  base::File pak_file_2 = pak_file.Duplicate();
  ui::ResourceBundle::InitSharedInstanceWithPakFileRegion(
      std::move(pak_file), base::MemoryMappedFile::Region::kWholeFile);
  ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile(
      std::move(pak_file_2), ui::SCALE_FACTOR_100P);
  if (!resource_file_200.empty())
    ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile(
        loader.TakeFile(resource_file_200), ui::SCALE_FACTOR_200P);
  return true;
}

}  // namespace views
