// Copyright 2018 The gVisor Authors.
//
// 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.

package cmd

import (
	"context"
	"fmt"

	"github.com/google/subcommands"
	"gvisor.dev/gvisor/runsc/flag"
)

// NewHelp returns a help command for the given commander.
func NewHelp(cdr *subcommands.Commander) *Help {
	return &Help{
		cdr: cdr,
	}
}

// Help implements subcommands.Command for the "help" command. The 'help'
// command prints help for commands registered to a Commander but also allows for
// registering additional help commands that print other documentation.
type Help struct {
	cdr      *subcommands.Commander
	commands []subcommands.Command
	help     bool
}

// Name implements subcommands.Command.Name.
func (*Help) Name() string {
	return "help"
}

// Synopsis implements subcommands.Command.Synopsis.
func (*Help) Synopsis() string {
	return "Print help documentation."
}

// Usage implements subcommands.Command.Usage.
func (*Help) Usage() string {
	return `help [<subcommand>]:
	With an argument, prints detailed information on the use of
	the specified topic or subcommand. With no argument, print a list of
	all commands and a brief description of each.
`
}

// SetFlags implements subcommands.Command.SetFlags.
func (h *Help) SetFlags(f *flag.FlagSet) {}

// Execute implements subcommands.Command.Execute.
func (h *Help) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	switch f.NArg() {
	case 0:
		fmt.Fprintf(h.cdr.Output, "Usage: %s <flags> <subcommand> <subcommand args>\n\n", h.cdr.Name())
		fmt.Fprintf(h.cdr.Output, `runsc is a command line client for running applications packaged in the Open
Container Initiative (OCI) format. Applications run by runsc are run in an
isolated gVisor sandbox that emulates a Linux environment.

gVisor is a user-space kernel, written in Go, that implements a substantial
portion of the Linux system call interface. It provides an additional layer
of isolation between running applications and the host operating system.

Functionality is provided by subcommands. For additonal help on individual
subcommands use "%s %s <subcommand>".

`, h.cdr.Name(), h.Name())
		h.cdr.VisitGroups(func(g *subcommands.CommandGroup) {
			h.cdr.ExplainGroup(h.cdr.Output, g)
		})

		fmt.Fprintf(h.cdr.Output, "Additional help topics (Use \"%s %s <topic>\" to see help on the topic):\n", h.cdr.Name(), h.Name())
		for _, cmd := range h.commands {
			fmt.Fprintf(h.cdr.Output, "\t%-15s  %s\n", cmd.Name(), cmd.Synopsis())
		}
		fmt.Fprintf(h.cdr.Output, "\nUse \"%s flags\" for a list of top-level flags\n", h.cdr.Name())
		return subcommands.ExitSuccess
	default:
		// Look for commands registered to the commander and print help explanation if found.
		found := false
		h.cdr.VisitCommands(func(g *subcommands.CommandGroup, cmd subcommands.Command) {
			if f.Arg(0) == cmd.Name() {
				h.cdr.ExplainCommand(h.cdr.Output, cmd)
				found = true
			}
		})
		if found {
			return subcommands.ExitSuccess
		}

		// Next check commands registered to the help command.
		for _, cmd := range h.commands {
			if f.Arg(0) == cmd.Name() {
				fs := flag.NewFlagSet(f.Arg(0), flag.ContinueOnError)
				fs.Usage = func() { h.cdr.ExplainCommand(h.cdr.Error, cmd) }
				cmd.SetFlags(fs)
				if fs.Parse(f.Args()[1:]) != nil {
					return subcommands.ExitUsageError
				}
				return cmd.Execute(ctx, f, args...)
			}
		}

		fmt.Fprintf(h.cdr.Error, "Subcommand %s not understood\n", f.Arg(0))
	}

	f.Usage()
	return subcommands.ExitUsageError
}

// Register registers a new help command.
func (h *Help) Register(cmd subcommands.Command) {
	h.commands = append(h.commands, cmd)
}