package args

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

type Arg struct {
	Env     string
	Flag    string
	Help    string
	Default interface{}
	Value   interface{}
	ArgType Type
}

func NewArg(argType Type, key, help string, def interface{}) *Arg {
	return &Arg{
		Env:     strings.ToUpper(key),
		Flag:    strings.ToLower(key),
		Help:    help,
		Default: def,
		Value:   def,
		ArgType: argType,
	}
}

func (a *Arg) String() string {
	return fmt.Sprintf("[%s $%s/-%s, %v from %v, help %v]",
		a.ArgType,
		a.Env,
		a.Flag,
		a.Value,
		a.Default,
		a.Help,
	)
}

func (a *Arg) GetInt() int {
	if a.ArgType != INT {
		return -1
	}
	if _, ok := a.Value.(int); !ok {
		return -1
	}
	return a.Value.(int)
}

func (a *Arg) GetString() string {
	if a.ArgType != STRING {
		return ""
	}
	if _, ok := a.Value.(string); !ok {
		return ""
	}
	return a.Value.(string)
}

func (a *Arg) GetBool() bool {
	if a.ArgType != BOOL {
		return false
	}
	if _, ok := a.Value.(bool); !ok {
		return false
	}
	return a.Value.(bool)
}

func (a *Arg) GetDuration() time.Duration {
	if a.ArgType != DURATION {
		return time.Duration(-1)
	}
	if _, ok := a.Value.(time.Duration); !ok {
		return time.Duration(-1)
	}
	return a.Value.(time.Duration)
}

func (a *Arg) GetTime() time.Time {
	if a.ArgType != TIME {
		return time.Date(1995, 1, 1, 0, 0, 0, 0, time.UTC)
	}
	if _, ok := a.Value.(time.Time); !ok {
		return time.Date(1995, 1, 1, 0, 0, 0, 0, time.UTC)
	}
	return a.Value.(time.Time)
}

func (a *Arg) GetFloat() float32 {
	if a.ArgType != FLOAT {
		return float32(-1)
	}
	if _, ok := a.Value.(float32); !ok {
		return float32(-1)
	}
	return a.Value.(float32)
}

func (a *Arg) Set(value interface{}) error {
	err := fmt.Errorf("incompatible set of type %T for arg $%v/-%v type %v", value, a.Env, a.Flag, a.ArgType)
	switch a.ArgType {
	case INT:
		switch value.(type) {
		case int:
			a.Value = value.(int)
			err = nil
		case string:
			s := value.(string)
			var i int
			if i, err = strconv.Atoi(s); err == nil {
				a.Value = i
				err = nil
			}
		case float64:
			f := value.(float64)
			if f-float64(int(f)) == 0 {
				a.Value = int(f)
				err = nil
			}
		}
	case STRING:
		i, ok := value.(string)
		if ok {
			a.Value = i
			err = nil
		}
	case BOOL:
		i, ok := value.(bool)
		if ok {
			a.Value = i
			err = nil
		} else if s, ok := value.(string); !ok {
		} else if i, err = strconv.ParseBool(s); err == nil {
			a.Value = i
			err = nil
		}
	case DURATION:
		i, ok := value.(time.Duration)
		if ok {
			a.Value = i
			err = nil
		} else if s, ok := value.(string); !ok {
		} else if i, err = time.ParseDuration(s); err == nil {
			a.Value = i
			err = nil
		}
	case TIME:
		if i, ok := value.(time.Duration); ok {
			a.Value = time.Now().Add(i)
			err = nil
		} else if j, ok := value.(time.Time); ok {
			a.Value = j
			err = nil
		} else if s, ok := value.(string); !ok {
		} else if i, err = time.ParseDuration(s); err == nil {
			a.Value = time.Now().Add(i)
			err = nil
		} else {
			layouts := []string{
				"01-02-2006",
				"01/02/2006",
				"01-02-06",
				"01/02/06",
				"2006-01-02",
				"2006/01/02",
				"06-01-02",
				"06/01/02",
				"2006-01-02 15:04:05.999999999 -0700 MST",
			}
			for k := range layouts {
				if j, err = time.Parse(layouts[k], s); err == nil {
					a.Value = j
					err = nil
					break
				}
			}
		}
	case FLOAT:
		if i, ok := value.(float32); ok {
			a.Value = i
			err = nil
		} else if j, ok := value.(float64); ok {
			a.Value = float32(j)
			err = nil
		} else if k, ok := value.(int); ok {
			a.Value = float32(k)
			err = nil
		} else if s, ok := value.(string); !ok {
		} else if j, err = strconv.ParseFloat(s, 32); err == nil {
			a.Value = float32(j)
			err = nil
		} else if k, err = strconv.Atoi(s); err == nil {
			a.Value = float32(k)
			err = nil
		}
	}
	return err
}
