// Package env provides a convenient way to convert environment
// variables into Go data. It is similar in design to package
// flag.
package env

import (
	"log"
	"net/url"
	"os"
	"strconv"
	"strings"
	"time"
)

var funcs []func() bool

// Int returns a new int pointer.
// When Parse is called,
// env var name will be parsed
// and the resulting value
// will be assigned to the returned location.
func Int(name string, value int) *int {
	p := new(int)
	IntVar(p, name, value)
	return p
}

// IntVar defines an int var with the specified
// name and default value. The argument p points
// to an int variable in which to store the
// value of the environment var.
func IntVar(p *int, name string, value int) {
	*p = value
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			v, err := strconv.Atoi(s)
			if err != nil {
				log.Println(name, err)
				return false
			}
			*p = v
		}
		return true
	})
}

// Bool returns a new bool pointer.
// When Parse is called,
// env var name will be parsed
// and the resulting value
// will be assigned to the returned location.
// Parsing uses strconv.ParseBool.
func Bool(name string, value bool) *bool {
	p := new(bool)
	BoolVar(p, name, value)
	return p
}

// BoolVar defines a bool var with the specified
// name and default value. The argument p points
// to a bool variable in which to store the value
// of the environment variable.
func BoolVar(p *bool, name string, value bool) {
	*p = value
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			v, err := strconv.ParseBool(s)
			if err != nil {
				log.Println(name, err)
				return false
			}
			*p = v
		}
		return true
	})
}

// Duration returns a new time.Duration pointer.
// When Parse is called,
// env var name will be parsed
// and the resulting value
// will be assigned to the returned location.
func Duration(name string, value time.Duration) *time.Duration {
	p := new(time.Duration)
	DurationVar(p, name, value)
	return p
}

// DurationVar defines a time.Duration var with
// the specified name and default value. The
// argument p points to a time.Duration variable
// in which to store the value of the environment
// variable.
func DurationVar(p *time.Duration, name string, value time.Duration) {
	*p = value
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			v, err := time.ParseDuration(s)
			if err != nil {
				log.Println(name, err)
				return false
			}
			*p = v
		}
		return true
	})
}

// URL returns a new url.URL pointer.
// When Parse is called,
// env var name will be parsed
// and the resulting value
// will be assigned to the returned location.
// URL panics if there is an error parsing
// the given default value.
func URL(name string, value string) *url.URL {
	p := new(url.URL)
	URLVar(p, name, value)
	return p
}

// URLVar defines a url.URL variable with
// the specified name ande default value.
// The argument p points to a url.URL variable
// in which to store the value of the environment
// variable.
func URLVar(p *url.URL, name string, value string) {
	v, err := url.Parse(value)
	if err != nil {
		panic(err)
	}
	*p = *v
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			v, err := url.Parse(s)
			if err != nil {
				log.Println(name, err)
				return false
			}
			*p = *v
		}
		return true
	})
}

// String returns a new string pointer.
// When Parse is called,
// env var name will be assigned
// to the returned location.
func String(name string, value string) *string {
	p := new(string)
	StringVar(p, name, value)
	return p
}

// StringVar defines a string with the
// specified name and default value. The
// argument p points to a string variable in
// which to store the value of the environment
// var.
func StringVar(p *string, name string, value string) {
	*p = value
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			*p = s
		}
		return true
	})
}

// StringSlice returns a pointer to a slice
// of strings. It expects env var name to
// be a list of items delimited by commas.
// If env var name is missing, StringSlice
// returns a pointer to a slice of the value
// strings.
func StringSlice(name string, value ...string) *[]string {
	p := new([]string)
	StringSliceVar(p, name, value...)
	return p
}

// StringSliceVar defines a new string slice
// with the specified name. The argument p
// points to a string slice variable in which
// to store the value of the environment var.
func StringSliceVar(p *[]string, name string, value ...string) {
	*p = value
	funcs = append(funcs, func() bool {
		if s := os.Getenv(name); s != "" {
			a := strings.Split(s, ",")
			*p = a
		}
		return true
	})
}

// Parse parses known env vars
// and assigns the values to the variables
// that were previously registered.
// If any values cannot be parsed,
// Parse prints an error message for each one
// and exits the process with status 1.
func Parse() {
	ok := true
	for _, f := range funcs {
		ok = f() && ok
	}
	if !ok {
		os.Exit(1)
	}
}
