/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "cuttlefish/host/commands/cvd/cli/commands/help.h"

#include <iostream>
#include <memory>
#include <ostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "absl/strings/str_join.h"

#include "cuttlefish/host/commands/cvd/cli/command_request.h"
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
#include "cuttlefish/host/commands/cvd/cli/request_context.h"
#include "cuttlefish/host/commands/cvd/cli/types.h"
#include "cuttlefish/result/result.h"

namespace cuttlefish {
namespace {

constexpr char kHelpIntroText[] = R"(Cuttlefish Virtual Device (CVD) CLI.

usage: cvd <selector/driver options> <command> <args>

Driver Options:
  -help                  Print this message
  -verbosity=<LEVEL>     Adjust Cvd verbosity level. LEVEL is Android log
                         severity. (Required: cvd >= v1.3)

Commands (cvd help <command> for more information):

)";

constexpr char kSelectorOptionsText[] = R"(
Selector Options:
  -group_name <name>     Specify the name of the instance group created
                         or selected.
  -instance_name <name>  Selects the device of the given name to perform the
                         commands for.
  -instance_name <names> Takes the names of the devices to create within an
                         instance group. The 'names' is comma-separated.
)";

constexpr char kSummaryHelpText[] =
    "Used to display help information for other commands";

constexpr char kDetailedHelpText[] =
    R"(cvd help - used to display help text for cvd and its commands

Example usage:
  cvd help - displays summary help for available commands

  cvd help <command> - displays more detailed help for the specific command
)";

void PrintHandler(std::stringstream& help_message,
                  const CvdCommandHandler& handler) {
  help_message << "\t" << absl::StrJoin(handler.CmdList(), ", ") << " - ";
  help_message << handler.SummaryHelp() << "\n\n";
}

Result<CommandRequest> GetLookupRequest(const std::string& arg) {
  auto builder = CommandRequestBuilder().AddArguments({"cvd", arg});
  return CF_EXPECT(std::move(builder).Build());
}

}  // namespace

CvdHelpHandler::CvdHelpHandler(
    const std::vector<std::unique_ptr<CvdCommandHandler>>& request_handlers)
    : request_handlers_(request_handlers) {}

Result<void> CvdHelpHandler::Handle(const CommandRequest& request) {
  std::vector<std::string> args = request.SubcommandArguments();
  if (args.empty()) {
    std::cout << TopLevelHelp();
  } else {
    std::cout << CF_EXPECT(SubCommandHelp(request));
  }

  return {};
}

cvd_common::Args CvdHelpHandler::CmdList() const { return {"help"}; }

std::string CvdHelpHandler::SummaryHelp() const { return kSummaryHelpText; }

bool CvdHelpHandler::RequiresHostConfiguration() const { return false; }

Result<std::string> CvdHelpHandler::DetailedHelp(
    const CommandRequest& request) {
  return kDetailedHelpText;
}

std::string CvdHelpHandler::TopLevelHelp() {
  std::stringstream help_message;
  help_message << kHelpIntroText;

  for (const auto& handler : request_handlers_) {
    if (!handler->RequiresDeviceExists()) {
      PrintHandler(help_message, *handler);
    }
  }

  help_message << kSelectorOptionsText;
  help_message << "\nDevice-Specific Commands (cvd help <command> for more "
                  "information):\n";
  for (const auto& handler : request_handlers_) {
    if (handler->RequiresDeviceExists()) {
      PrintHandler(help_message, *handler);
    }
  }

  return help_message.str();
}

Result<std::string> CvdHelpHandler::SubCommandHelp(
    const CommandRequest& request) {
  const std::vector<std::string>& args = request.SubcommandArguments();
  CF_EXPECT(!args.empty(),
            "Cannot process subcommand help without valid subcommand argument");
  CommandRequest lookup_request = CF_EXPECT(GetLookupRequest(args.front()));
  auto handler = CF_EXPECT(RequestHandler(lookup_request, request_handlers_));

  // Create new command with "<subcmd> --help" instead of "help <subcmd>"
  CommandRequest subcmd_request =
      CF_EXPECT(CommandRequestBuilder()
                    .AddArguments(args)
                    .AddArguments({"--help"})
                    .SetEnv(request.Env())
                    .SetSelectorOptions(request.Selectors())
                    .Build());

  std::stringstream help_message;
  help_message << CF_EXPECT(handler->DetailedHelp(subcmd_request)) << std::endl;
  return help_message.str();
}

std::unique_ptr<CvdCommandHandler> NewCvdHelpHandler(
    const std::vector<std::unique_ptr<CvdCommandHandler>>& server_handlers) {
  return std::unique_ptr<CvdCommandHandler>(
      new CvdHelpHandler(server_handlers));
}

}  // namespace cuttlefish
