// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package ucfg

import (
	"reflect"
	"strings"
)

type tagOptions struct {
	squash      bool
	ignore      bool
	cfgHandling configHandling
}

// configHandling configures the operation to execute if we merge into a struct
// field that holds an unpacked config object.
type configHandling uint8

const (
	cfgDefaultHandling configHandling = iota
	cfgMergeValues
	cfgReplaceValue
	cfgArrAppend
	cfgArrPrepend
)

var noTagOpts = tagOptions{}

func parseTags(tag string) (string, tagOptions) {
	s := strings.Split(tag, ",")
	opts := tagOptions{}
	for _, opt := range s[1:] {
		switch opt {
		case "squash", "inline":
			opts.squash = true
		case "ignore":
			opts.ignore = true
		case "merge":
			opts.cfgHandling = cfgMergeValues
		case "replace":
			opts.cfgHandling = cfgReplaceValue
		case "append":
			opts.cfgHandling = cfgArrAppend
		case "prepend":
			opts.cfgHandling = cfgArrPrepend
		}
	}
	return s[0], opts
}

func fieldName(tagName, structName string) string {
	if tagName != "" {
		return tagName
	}
	return strings.ToLower(structName)
}

func chaseValueInterfaces(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Interface && !v.IsNil() {
		v = v.Elem()
	}
	return v
}

func chaseValuePointers(v reflect.Value) reflect.Value {
	for v.Kind() == reflect.Ptr && !v.IsNil() {
		v = v.Elem()
	}
	return v
}

func chaseValue(v reflect.Value) reflect.Value {
	for (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() {
		v = v.Elem()
	}
	return v
}

func chaseTypePointers(t reflect.Type) reflect.Type {
	for t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return t
}

// tryTConfig tries to convert input value into addressable Config by converting
// to *Config first. If value is convertible to Config, but not addressable a new
// value is allocated in order to guarantee returned value of type Config is
// addressable. Returns false if type value is not convertible to TConfig.
func tryTConfig(value reflect.Value) (reflect.Value, bool) {
	v := chaseValue(value)
	t := v.Type()

	if t == tConfig {
		v := pointerize(tConfigPtr, tConfig, v)
		return v.Elem(), true
	}

	if !t.ConvertibleTo(tConfig) {
		return reflect.Value{}, false
	}

	v = pointerize(reflect.PtrTo(v.Type()), v.Type(), v)
	if !v.Type().ConvertibleTo(tConfigPtr) {
		return reflect.Value{}, false
	}

	v = v.Convert(tConfigPtr)
	return v.Elem(), true
}

func pointerize(t, base reflect.Type, v reflect.Value) reflect.Value {
	if t == base {
		return v
	}

	if t.Kind() == reflect.Interface {
		return v
	}

	for t != v.Type() {
		if !v.CanAddr() {
			tmp := reflect.New(v.Type())
			tmp.Elem().Set(v)
			v = tmp
		} else {
			v = v.Addr()
		}
	}
	return v
}

func isInt(k reflect.Kind) bool {
	switch k {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	default:
		return false
	}
}

func isUint(k reflect.Kind) bool {
	switch k {
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return true
	default:
		return false
	}
}

func isFloat(k reflect.Kind) bool {
	switch k {
	case reflect.Float32, reflect.Float64:
		return true
	default:
		return false
	}
}
