// Package cps provides the command processing system.
package cps

import (
	"context"
	"errors"
	"fmt"
	"strings"
)

type Command struct {
	Context context.Context
	Raw     string            // The original command passed to the agent.
	Command string            // Which command was invoked?
	Arg     string            // Input string sans the leading command.
	Args    []string          // Tokenised list of arguments.
	Meta    map[string]string // Sometimes you need additional information.
}

func (cmd *Command) Handle() (*Response, error) {
	return handle(cmd)
}

// Parse takes a command string from an input string and parent context.
func Parse(ctx context.Context, line string) (*Command, error) {
	line = strings.TrimSpace(line)
	args := strings.Fields(line)
	if len(args) == 0 {
		return nil, errors.New("cps: no command line provided")
	}

	cmd := &Command{
		Context: ctx,
		Raw:     line,
		Command: strings.ToLower(args[0]),
		Args:    args[1:],
		Meta:    map[string]string{},
	}
	cmd.Arg = strings.TrimSpace(strings.TrimPrefix(cmd.Raw, cmd.Command))
	return cmd, nil
}

// ParseBackground calls Parse(context.Background, line).
func ParseBackground(line string) (*Command, error) {
	return Parse(context.Background(), line)
}

type Response struct {
	Message string
	Error   error
}

func (r *Response) String() string {
	if r == nil {
		return "carrier signal lost"
	}

	out := ""
	if r.Message != "" {
		out += r.Message
	} else if r.Error != nil {
		out = fmt.Sprintf("Error: %s", r.Error)
	}

	return out
}

var registry = map[string]func(*Command) (*Response, error){}

func Register(command string, handler func(*Command) (*Response, error)) {
	if _, ok := registry[command]; ok {
		panic("handler for " + command + " already registered!")
	}

	registry[command] = handler
}

func handle(cmd *Command) (*Response, error) {
	handler, ok := registry[cmd.Command]
	if !ok {
		err := fmt.Errorf("cps: no handler for command '%s'", cmd.Command)
		return &Response{
			Message: fmt.Sprintf("%s is not a recognized command", cmd.Command),
			Error:   err,
		}, err
	}

	return handler(cmd)
}

func Handle(ctx context.Context, line string) (*Response, error) {
	cmd, err := Parse(ctx, line)
	if err != nil {
		return &Response{
			Message: "Unable to process message",
			Error:   err,
		}, err
	}

	return cmd.Handle()
}