// Package posflag implements a koanf.Provider that reads commandline
// parameters as conf maps using spf13/pflag, a POSIX compliant
// alternative to Go's stdlib flag package.
package posflag

import (
	"errors"

	"github.com/knadh/koanf"
	"github.com/knadh/koanf/maps"
	"github.com/spf13/pflag"
)

// Posflag implements a pflag command line provider.
type Posflag struct {
	delim   string
	flagset *pflag.FlagSet
	ko      *koanf.Koanf
}

// Provider returns a commandline flags provider that returns
// a nested map[string]interface{} of environment variable where the
// nesting hierarchy of keys are defined by delim. For instance, the
// delim "." will convert the key `parent.child.key: 1`
// to `{parent: {child: {key: 1}}}`.
//
// It takes an optional (but recommended) Koanf instance to see if the
// the flags defined have been set from other providers, for instance,
// a config file. If they are not, then the default values of the flags
// are merged. If they do exist, the flag values are not merged but only
// the values that have been explicitly set in the command line are merged.
func Provider(f *pflag.FlagSet, delim string, ko *koanf.Koanf) *Posflag {
	return &Posflag{
		flagset: f,
		delim:   delim,
		ko:      ko,
	}
}

// Read reads the flag variables and returns a nested conf map.
func (p *Posflag) Read() (map[string]interface{}, error) {
	mp := make(map[string]interface{})
	p.flagset.VisitAll(func(f *pflag.Flag) {
		// If no value was explicitly set in the command line,
		// check if the default value should be used.
		if !f.Changed {
			if p.ko != nil {
				if p.ko.Exists(f.Name) {
					return
				}
			} else {
				return
			}
		}

		var v interface{}
		switch f.Value.Type() {
		case "int":
			i, _ := p.flagset.GetInt(f.Name)
			v = int64(i)
		case "int8":
			i, _ := p.flagset.GetInt8(f.Name)
			v = int64(i)
		case "int16":
			i, _ := p.flagset.GetInt16(f.Name)
			v = int64(i)
		case "int32":
			i, _ := p.flagset.GetInt32(f.Name)
			v = int64(i)
		case "int64":
			i, _ := p.flagset.GetInt64(f.Name)
			v = int64(i)
		case "float32":
			v, _ = p.flagset.GetFloat32(f.Name)
		case "float":
			v, _ = p.flagset.GetFloat64(f.Name)
		case "bool":
			v, _ = p.flagset.GetBool(f.Name)
		case "stringSlice":
			v, _ = p.flagset.GetStringSlice(f.Name)
		case "intSlice":
			v, _ = p.flagset.GetIntSlice(f.Name)
		default:
			v = f.Value.String()
		}

		mp[f.Name] = v
	})
	return maps.Unflatten(mp, p.delim), nil
}

// ReadBytes is not supported by the env koanf.
func (p *Posflag) ReadBytes() ([]byte, error) {
	return nil, errors.New("pflag provider does not support this method")
}

// Watch is not supported.
func (p *Posflag) Watch(cb func(event interface{}, err error)) error {
	return errors.New("posflag provider does not support this method")
}
