package goptions

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

type optionFunc func(f *Flag, option, value string) error
type optionMap map[string]optionFunc

var (
	typeOptionMap = map[reflect.Type]optionMap{
		// Global options
		nil: optionMap{
			"description": description,
			"obligatory":  obligatory,
			"mutexgroup":  mutexgroup,
		},
		reflect.TypeOf(new(time.Time)).Elem(): optionMap{
			"format": time_format,
		},
		reflect.TypeOf(new(*os.File)).Elem(): optionMap{
			"create": initOptionMeta(file_create, "file_mode", 0),
			"append": initOptionMeta(file_append, "file_mode", 0),
			"rdonly": initOptionMeta(file_rdonly, "file_mode", 0),
			"wronly": initOptionMeta(file_wronly, "file_mode", 0),
			"rdwr":   initOptionMeta(file_rdwr, "file_mode", 0),
			"excl":   initOptionMeta(file_excl, "file_mode", 0),
			"sync":   initOptionMeta(file_sync, "file_mode", 0),
			"trunc":  initOptionMeta(file_trunc, "file_mode", 0),
			"perm":   file_perm,
		},
	}
)

// Wraps another optionFunc and inits optionMeta[field] with value if it does
// not have one already.
func initOptionMeta(fn optionFunc, field string, init_value interface{}) optionFunc {
	return func(f *Flag, option, value string) error {
		if _, ok := f.optionMeta[field]; !ok {
			f.optionMeta[field] = init_value
		}
		return fn(f, option, value)
	}
}

func description(f *Flag, option, value string) error {
	f.Description = strings.Replace(value, `\`, ``, -1)
	return nil
}

func obligatory(f *Flag, option, value string) error {
	f.Obligatory = true
	return nil
}

func mutexgroup(f *Flag, option, value string) error {
	if len(value) <= 0 {
		return fmt.Errorf("Mutexgroup option needs a value")
	}
	for _, group := range strings.Split(value, ",") {
		f.MutexGroups = append(f.MutexGroups, group)
	}
	return nil
}

func file_create(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_CREATE
	return nil
}

func file_append(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_APPEND
	return nil
}

func file_rdonly(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_RDONLY
	return nil
}

func file_wronly(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_WRONLY
	return nil
}

func file_rdwr(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_RDWR
	return nil
}

func file_excl(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_EXCL
	return nil
}

func file_sync(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_SYNC
	return nil
}

func file_trunc(f *Flag, option, value string) error {
	f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_TRUNC
	return nil
}

func file_perm(f *Flag, option, value string) error {
	perm, err := strconv.ParseInt(value, 8, 32)
	if err != nil {
		return err
	}
	f.optionMeta["file_perm"] = uint32(perm)
	return nil
}

func time_format(f *Flag, option, value string) error {
	f.optionMeta["format"] = value
	return nil
}

func optionMapForType(t reflect.Type) optionMap {
	g := typeOptionMap[nil]
	m, _ := typeOptionMap[t]
	r := make(optionMap)
	for k, v := range g {
		r[k] = v
	}
	for k, v := range m {
		r[k] = v
	}
	return r
}
