// Package opt implements command-line flag parsing.
package opt // import "modernc.org/opt"

import (
	"fmt"
	"strings"
)

type opt struct {
	handler func(opt, arg string) error
	name    string

	arg bool // Enable argument, e.g. `-I foo` or `-I=foo`
}

// A Set represents a set of defined options.
type Set struct {
	cfg map[string]*opt
	imm []*opt
}

// NewSet returns a new, empty option set.
func NewSet() *Set { return &Set{cfg: map[string]*opt{}} }

// Opt defines a simple option, e.g. `-f`. When the option is found during
// Parse, the handler is called with the value of the option, e.g. "-f".
func (p *Set) Opt(name string, handler func(opt string) error) {
	p.cfg[name] = &opt{
		handler: func(opt, arg string) error { return handler(opt) },
	}
}

// Arg defines a simple option with an argument, e.g. `-I foo` or `-I=foo`.
// Setting imm argument enables additionally `-Ifoo`. When the option is found
// during Parse, the handler is called with the values of the option and the
// argument, e.g. "-I" and "foo" for all of the variants.
func (p *Set) Arg(name string, imm bool, handler func(opt, arg string) error) {
	switch {
	case imm:
		p.imm = append(p.imm, &opt{
			handler: handler,
			name:    name,
		})
	default:
		p.cfg[name] = &opt{
			arg:     true,
			handler: handler,
			name:    name,
		}
	}
}

// Parse parses opts. Must be called after all options are defined. The handler
// is called for all items in opts that were not defined before using Opt or
// Arg.
//
// If any handler returns a non-nil error, Parse will stop.  If the error is of
// type Skip, the error returned by Parse will contain all the unprocessed
// items of opts.
//
// The opts slice must not be modified by any handler while Parser is
// executing.
func (p *Set) Parse(opts []string, handler func(string) error) (err error) {
	defer func() {
		switch err.(type) {
		case Skip:
			err = Skip(opts)
		}
	}()

	for len(opts) != 0 {
		opt := opts[0]
		opts = opts[1:]
		var arg string
	out:
		switch {
		case strings.HasPrefix(opt, "-"):
			name := opt[1:]
			for _, cfg := range p.imm {
				if strings.HasPrefix(name, cfg.name) {
					switch {
					case name == cfg.name:
						if len(opts) == 0 {
							return fmt.Errorf("missing argument of %s", opt)
						}

						if err = cfg.handler(opt, opts[0]); err != nil {
							return err
						}

						opts = opts[1:]
					default:
						if err = cfg.handler(opt[:len(cfg.name)+1], name[len(cfg.name):]); err != nil {
							return err
						}
					}
					break out
				}
			}

			if n := strings.IndexByte(opt, '='); n > 0 {
				arg = opt[n+1:]
				name = opt[1:n]
				opt = opt[:n]
			}
			switch cfg := p.cfg[name]; {
			case cfg == nil:
				if err = handler(opt); err != nil {
					return err
				}
			default:
				switch {
				case cfg.arg:
					switch {
					case arg != "":
						if err = cfg.handler(opt, arg); err != nil {
							return err
						}
					default:
						if len(opts) == 0 {
							return fmt.Errorf("missing argument of %s", opt)
						}

						if err = cfg.handler(opt, opts[0]); err != nil {
							return err
						}

						opts = opts[1:]
					}
				default:
					if err = cfg.handler(opt, ""); err != nil {
						return err
					}
				}
			}
		default:
			if opt == "" {
				break
			}

			if err = handler(opt); err != nil {
				return err
			}
		}
	}
	return nil
}

// Skip is an error that contains all unprocessed items passed to Parse.
type Skip []string

func (s Skip) Error() string { return fmt.Sprint([]string(s)) }