// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//	http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 firecracker

import (
	"context"
	"io"
	"os"
	"os/exec"
)

var defaultFirecrackerVMMCommandBuilder = VMCommandBuilder{}.
	WithBin("firecracker").
	WithStdin(os.Stdin).
	WithStdout(os.Stdout).
	WithStderr(os.Stderr)

// VMCommandBuilder is a utility for building an exec.Cmd that represents how to
// start the Firecracker VMM.
type VMCommandBuilder struct {
	bin        string
	args       []string
	socketPath string
	stdin      io.Reader
	stdout     io.Writer
	stderr     io.Writer
}

// Args returns all args that will be passed to exec.Command
func (b VMCommandBuilder) Args() []string {
	return b.args
}

// WithArgs specifies which arguments to pass through to the
// firecracker exec.Command
func (b VMCommandBuilder) WithArgs(args []string) VMCommandBuilder {
	b.args = args
	return b
}

// AddArgs will append the provided args to the given command
func (b VMCommandBuilder) AddArgs(args ...string) VMCommandBuilder {
	b.args = append(b.args, args...)
	return b
}

// Bin return the bin that was set
func (b VMCommandBuilder) Bin() string {
	return b.bin
}

// WithBin specifies which binary for firecracker to use
func (b VMCommandBuilder) WithBin(bin string) VMCommandBuilder {
	b.bin = bin
	return b
}

// SocketPath returns the specified socket path
func (b VMCommandBuilder) SocketPath() []string {
	if len(b.socketPath) == 0 {
		return nil
	}

	return []string{
		"--api-sock",
		b.socketPath,
	}
}

// WithSocketPath specifies the socket path to be used when
// creating the firecracker exec.Command
func (b VMCommandBuilder) WithSocketPath(path string) VMCommandBuilder {
	b.socketPath = path
	return b
}

// Stdout will return the stdout that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stdout() io.Writer {
	return b.stdout
}

// WithStdout specifies which io.Writer to use in place of the
// os.Stdout in the firecracker exec.Command.
func (b VMCommandBuilder) WithStdout(stdout io.Writer) VMCommandBuilder {
	b.stdout = stdout
	return b
}

// Stderr will return the stderr that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stderr() io.Writer {
	return b.stderr
}

// WithStderr specifies which io.Writer to use in place of the
// os.Stderr in the firecracker exec.Command.
func (b VMCommandBuilder) WithStderr(stderr io.Writer) VMCommandBuilder {
	b.stderr = stderr
	return b
}

// Stdin will return the stdin that will be used when creating
// the firecracker exec.Command
func (b VMCommandBuilder) Stdin() io.Reader {
	return b.stdin
}

// WithStdin specifies which io.Reader to use in place of the
// os.Stdin in the firecracker exec.Command.
func (b VMCommandBuilder) WithStdin(stdin io.Reader) VMCommandBuilder {
	b.stdin = stdin
	return b
}

// Build will build a firecracker command using the specific arguments
// specified in the builder.
func (b VMCommandBuilder) Build(ctx context.Context) *exec.Cmd {
	args := []string{}
	if socketPath := b.SocketPath(); socketPath != nil {
		args = append(args, socketPath...)
	}
	if v := b.Args(); v != nil {
		args = append(args, v...)
	}

	cmd := exec.CommandContext(
		ctx,
		b.Bin(),
		args...,
	)

	if stdout := b.Stdout(); stdout != nil {
		cmd.Stdout = stdout
	}
	if stderr := b.Stderr(); stderr != nil {
		cmd.Stderr = stderr
	}
	if stdin := b.Stdin(); stdin != nil {
		cmd.Stdin = stdin
	}

	return cmd
}
