/*
Copyright 2018 the Velero contributors.

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 process

import (
	plugin "github.com/hashicorp/go-plugin"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
)

type Factory interface {
	newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error)
}

type processFactory struct {
}

func newProcessFactory() Factory {
	return &processFactory{}
}

func (pf *processFactory) newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error) {
	return newProcess(command, logger, logLevel)
}

type Process interface {
	dispense(key KindAndName) (any, error)
	exited() bool
	kill()
}

type process struct {
	client         *plugin.Client
	protocolClient plugin.ClientProtocol
}

func newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error) {
	builder := newClientBuilder(command, logger.WithField("cmd", command), logLevel)

	// This creates a new go-plugin Client that has its own unique exec.Cmd for launching the plugin process.
	client := builder.client()

	// This launches the plugin process.
	protocolClient, err := client.Client()
	if err != nil {
		return nil, err
	}

	p := &process{
		client:         client,
		protocolClient: protocolClient,
	}

	return p, nil
}

// removeFeaturesFlag looks for and removes the '--features' arg
// as well as the arg immediately following it (the flag value).
func removeFeaturesFlag(args []string) []string {
	var commandArgs []string
	var featureFlag bool

	for _, arg := range args {
		// if this arg is the flag name, skip it
		if arg == "--features" {
			featureFlag = true
			continue
		}

		// if the last arg we saw was the flag name, then
		// this arg is the value for the flag, so skip it
		if featureFlag {
			featureFlag = false
			continue
		}

		// otherwise, keep the arg
		commandArgs = append(commandArgs, arg)
	}

	return commandArgs
}

func (r *process) dispense(key KindAndName) (any, error) {
	// This calls GRPCClient(clientConn) on the plugin instance registered for key.name.
	dispensed, err := r.protocolClient.Dispense(key.Kind.String())
	if err != nil {
		return nil, errors.WithStack(err)
	}

	// Currently all plugins except for PluginLister dispense clientDispenser instances.
	if clientDispenser, ok := dispensed.(common.ClientDispenser); ok {
		if key.Name == "" {
			return nil, errors.Errorf("%s plugin requested but name is missing", key.Kind.String())
		}
		// Get the instance that implements our plugin interface (e.g. ObjectStore) that is a gRPC-based
		// client
		dispensed = clientDispenser.ClientFor(key.Name)
	}

	return dispensed, nil
}

func (r *process) exited() bool {
	return r.client.Exited()
}

func (r *process) kill() {
	r.client.Kill()
}
