// Copyright (c) 2013 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.

#ifndef TOOLS_GN_LOADER_H_
#define TOOLS_GN_LOADER_H_

#include <functional>
#include <map>
#include <memory>
#include <set>

#include "base/memory/ref_counted.h"
#include "gn/label.h"
#include "gn/scope.h"
#include "util/msg_loop.h"

class BuildSettings;
class LocationRange;
class Settings;
class SourceFile;
class Toolchain;

// The loader manages execution of the different build files. It receives
// requests (normally from the Builder) when new references are found, and also
// manages loading the build config files.
//
// This loader class is abstract so it can be mocked out for testing the
// Builder.
class Loader : public base::RefCountedThreadSafe<Loader> {
 public:
  Loader();

  // Loads the given file in the conext of the given toolchain. The initial
  // call to this (the one that actually starts the generation) should have an
  // empty toolchain name, which will trigger the load of the default build
  // config.
  virtual void Load(const SourceFile& file,
                    const LocationRange& origin,
                    const Label& toolchain_name) = 0;

  // Notification that the given toolchain has loaded. This will unblock files
  // waiting on this definition.
  virtual void ToolchainLoaded(const Toolchain* toolchain) = 0;

  // Returns the label of the default toolchain.
  virtual Label GetDefaultToolchain() const = 0;

  // Returns information about the toolchain with the given label. Will return
  // false if we haven't processed this toolchain yet.
  virtual const Settings* GetToolchainSettings(const Label& label) const = 0;

  // Returns the build file that the given label references.
  virtual SourceFile BuildFileForLabel(const Label& label) const = 0;

  // Helper function that extracts the file and toolchain name from the given
  // label, and calls Load().
  void Load(const Label& label, const LocationRange& origin);

  // When processing the default build config, we want to capture the argument
  // of set_default_build_config. The implementation of that function uses this
  // constant as a property key to get the Label* out of the scope where the
  // label should be stored.
  static const void* const kDefaultToolchainKey;

 protected:
  friend class base::RefCountedThreadSafe<Loader>;
  virtual ~Loader();
};

class LoaderImpl : public Loader {
 public:
  // Callback to emulate InputFileManager::AsyncLoadFile.
  using AsyncLoadFileCallback =
      std::function<bool(const LocationRange&,
                         const BuildSettings*,
                         const SourceFile&,
                         std::function<void(const ParseNode*)>,
                         Err*)>;

  explicit LoaderImpl(const BuildSettings* build_settings);

  // Loader implementation.
  void Load(const SourceFile& file,
            const LocationRange& origin,
            const Label& toolchain_name) override;
  void ToolchainLoaded(const Toolchain* toolchain) override;
  Label GetDefaultToolchain() const override;
  const Settings* GetToolchainSettings(const Label& label) const override;
  SourceFile BuildFileForLabel(const Label& label) const override;

  // Sets the task runner corresponding to the main thread. By default this
  // class will use the thread active during construction, but there is not
  // a task runner active during construction all the time.
  void set_task_runner(MsgLoop* task_runner) { task_runner_ = task_runner; }

  // The complete callback is called whenever there are no more pending loads.
  // Called on the main thread only. This may be called more than once if the
  // queue is drained, but then more stuff gets added.
  void set_complete_callback(std::function<void()> cb) {
    complete_callback_ = std::move(cb);
  }

  // This callback is used when the loader finds it wants to load a file.
  void set_async_load_file(AsyncLoadFileCallback cb) {
    async_load_file_ = std::move(cb);
  }

  // Sets the additional extension for build files in this build.
  // The resulting file name will be "BUILD.<extension>.gn".
  void set_build_file_extension(std::string extension) {
    build_file_extension_ = "." + extension;
  }

  const Label& default_toolchain_label() const {
    return default_toolchain_label_;
  }

 private:
  struct LoadID;
  struct ToolchainRecord;

  ~LoaderImpl() override;

  // Schedules the input file manager to load the given file.
  void ScheduleLoadFile(const Settings* settings,
                        const LocationRange& origin,
                        const SourceFile& file);
  void ScheduleLoadBuildConfig(Settings* settings,
                               const Scope::KeyValueMap& toolchain_overrides);

  // Runs the given file on the background thread. These are called by the
  // input file manager.
  void BackgroundLoadFile(const Settings* settings,
                          const SourceFile& file_name,
                          const LocationRange& origin,
                          const ParseNode* root);
  void BackgroundLoadBuildConfig(Settings* settings,
                                 const Scope::KeyValueMap& toolchain_overrides,
                                 const ParseNode* root);

  // Posted to the main thread when any file other than a build config file
  // file has completed running.
  void DidLoadFile();

  // Posted to the main thread when any build config file has completed
  // running. The label should be the name of the toolchain.
  //
  // If there is no defauled toolchain loaded yet, we'll assume that the first
  // call to this indicates to the default toolchain, and this function will
  // set the default toolchain name to the given label.
  void DidLoadBuildConfig(const Label& label);

  // Decrements the pending_loads_ variable and issues the complete callback if
  // necessary.
  void DecrementPendingLoads();

  // Forwards to the appropriate location to load the file.
  bool AsyncLoadFile(const LocationRange& origin,
                     const BuildSettings* build_settings,
                     const SourceFile& file_name,
                     std::function<void(const ParseNode*)> callback,
                     Err* err);

  MsgLoop* task_runner_;

  int pending_loads_;
  std::function<void()> complete_callback_;

  // When non-null, use this callback instead of the InputFileManager for
  // mocking purposes.
  AsyncLoadFileCallback async_load_file_;

  using LoadIDSet = std::set<LoadID>;
  LoadIDSet invocations_;

  const BuildSettings* build_settings_;
  Label default_toolchain_label_;

  // Records for the build config file loads.
  using ToolchainRecordMap = std::map<Label, std::unique_ptr<ToolchainRecord>>;
  ToolchainRecordMap toolchain_records_;

  std::string build_file_extension_;
};

#endif  // TOOLS_GN_LOADER_H_
