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

// The entrypoint for the starboard cast runtime. If loggy is enabled (typically
// used for partner builds), this code forks a separate process to run loggy for
// logging.
//
// Also contains logic for parsing chromium args passed as JSON (used by some
// partner platforms).

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "chromecast/app/cast_main_delegate.h"
#include "chromecast/cast_core/child_log_process.h"
#include "content/public/app/content_main.h"

namespace {

constexpr char kHomeEnvOverride[] = "home-env-override";
constexpr char kRuntimeHomeSubdirUsedFile[] = ".dirty";
constexpr char kParametersKey[] = "parameters";
constexpr char kArgvKey[] = "argv";

// JSONArgsParser determines whether command line arguments were delivered
// as JSON or the standard format and, if necessary, parses the JSON
// arguments.
class JSONArgsParser {
 public:
  JSONArgsParser(base::span<const char*> argv) {
    if (!TryParseJson(argv)) {
      // If args were not provided as JSON, use defaults.
      argv_.assign(argv.begin(), argv.end());
      argv_.push_back(nullptr);
    }
  }

  int argc() { return argv_.size() - 1; }

  const char** argv() { return argv_.data(); }

 private:
  // If the command is of the format `<command> <json>`, tries to parse
  // parameters from the JSON blob in |argv[1] instead of directly from
  // |argv|. Returns true if successful; otherwise, returns false.
  bool TryParseJson(base::span<const char*> argv) {
    // Required format is `<command> <json>`
    if (argv.size() != 2) {
      return false;
    }

    // JSON must be the following format. All keys and values are strings.
    // {"parameters":{"argv":["arg1", ...]}}
    std::optional<base::DictValue> root =
        base::JSONReader::ReadDict(argv[1], base::JSON_PARSE_CHROMIUM_EXTENSIONS);

    if (!root) {
      return false;
    }

    base::DictValue* v = root->FindDict(kParametersKey);
    if (!v) {
      return false;
    }

    base::ListValue* argv_list = v->FindList(kArgvKey);
    if (!argv_list) {
      return false;
    }

    for (const auto& val : *argv_list) {
      // All values must be strings.
      if (!val.is_string()) {
        return false;
      }
      argv_parsed_.push_back(val.GetString());
    }

    // Parsing is successful, so load up |argv_| and return |true|.
    argv_.push_back(argv[0]);
    for (const std::string& arg : argv_parsed_) {
      argv_.push_back(arg.c_str());
    }
    argv_.push_back(nullptr);
    return true;
  }

  // Persists the backing memory of |argv|.
  std::vector<std::string> argv_parsed_;
  std::vector<const char*> argv_;
};

}  // namespace

int main(int argc, const char** argv) {
  // SAFETY: required from caller.
  JSONArgsParser args(
      UNSAFE_BUFFERS(base::span<const char*>(argv, static_cast<size_t>(argc))));

  chromecast::ForkAndRunLogProcessIfSpecified(args.argc(), args.argv());

  chromecast::shell::CastMainDelegate delegate;
  content::ContentMainParams params(&delegate);

  base::CommandLine temp_cmd(args.argc(), args.argv());
  std::string home_override = temp_cmd.GetSwitchValueASCII(kHomeEnvOverride);
  if (!home_override.empty()) {
    const char * maybe_home = getenv("HOME");
    LOG(INFO) << "HOME variable was previously \"" << (maybe_home ? maybe_home : "[UNSET]")
              << "\"; overriding to \"" << home_override << "\".";
    setenv("HOME", home_override.c_str(), 1);

    base::FilePath home_directory(home_override);
    if (!base::DirectoryExists(home_directory)) {
      CHECK(base::CreateDirectory(home_directory));
    }

    base::FilePath home_directory_used_file =
        home_directory.AppendASCII(kRuntimeHomeSubdirUsedFile);
    if (!base::PathExists(home_directory_used_file)) {
      base::File(home_directory_used_file,
                 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    }
  }

  params.argc = args.argc();
  params.argv = args.argv();

  return content::ContentMain(std::move(params));
}
