// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package schema

import (
	"reflect"
	"strconv"

	utils "github.com/gofiber/utils/v2"
)

type Converter func(string) reflect.Value

var (
	invalidValue = reflect.Value{}
	boolType     = reflect.Bool
	float32Type  = reflect.Float32
	float64Type  = reflect.Float64
	intType      = reflect.Int
	int8Type     = reflect.Int8
	int16Type    = reflect.Int16
	int32Type    = reflect.Int32
	int64Type    = reflect.Int64
	stringType   = reflect.String
	uintType     = reflect.Uint
	uint8Type    = reflect.Uint8
	uint16Type   = reflect.Uint16
	uint32Type   = reflect.Uint32
	uint64Type   = reflect.Uint64
)

// builtinConverters is the single source of truth for type converters.
var builtinConverters = map[reflect.Kind]Converter{
	boolType:    convertBool,
	float32Type: convertFloat32,
	float64Type: convertFloat64,
	intType:     convertInt,
	int8Type:    convertInt8,
	int16Type:   convertInt16,
	int32Type:   convertInt32,
	int64Type:   convertInt64,
	stringType:  convertString,
	uintType:    convertUint,
	uint8Type:   convertUint8,
	uint16Type:  convertUint16,
	uint32Type:  convertUint32,
	uint64Type:  convertUint64,
}

// builtinConvertersArray is an array indexed by reflect.Kind for O(1) lookup.
var builtinConvertersArray [reflect.UnsafePointer + 1]Converter

func init() {
	for k, conv := range builtinConverters {
		builtinConvertersArray[k] = conv
	}
}

// getBuiltinConverter returns the converter for a kind using O(1) array lookup.
func getBuiltinConverter(k reflect.Kind) Converter {
	if k <= reflect.UnsafePointer {
		return builtinConvertersArray[k]
	}
	return nil
}

func convertBool(value string) reflect.Value {
	if value == "on" {
		return reflect.ValueOf(true)
	} else if v, err := strconv.ParseBool(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertFloat32(value string) reflect.Value {
	if v, err := utils.ParseFloat32(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertFloat64(value string) reflect.Value {
	if v, err := utils.ParseFloat64(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertInt(value string) reflect.Value {
	if v, err := utils.ParseInt(value); err == nil {
		return reflect.ValueOf(int(v))
	}
	return invalidValue
}

func convertInt8(value string) reflect.Value {
	if v, err := utils.ParseInt8(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertInt16(value string) reflect.Value {
	if v, err := utils.ParseInt16(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertInt32(value string) reflect.Value {
	if v, err := utils.ParseInt32(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertInt64(value string) reflect.Value {
	if v, err := utils.ParseInt(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertString(value string) reflect.Value {
	return reflect.ValueOf(value)
}

func convertUint(value string) reflect.Value {
	if v, err := utils.ParseUint(value); err == nil {
		return reflect.ValueOf(uint(v))
	}
	return invalidValue
}

func convertUint8(value string) reflect.Value {
	if v, err := utils.ParseUint8(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertUint16(value string) reflect.Value {
	if v, err := utils.ParseUint16(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertUint32(value string) reflect.Value {
	if v, err := utils.ParseUint32(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertUint64(value string) reflect.Value {
	if v, err := utils.ParseUint(value); err == nil {
		return reflect.ValueOf(v)
	}
	return invalidValue
}

func convertPointer(k reflect.Kind, value string) reflect.Value {
	conv := getBuiltinConverter(k)
	if conv == nil {
		return invalidValue
	}
	if v := conv(value); v.IsValid() {
		switch k {
		case boolType:
			converted := v.Bool()
			return reflect.ValueOf(&converted)
		case float32Type:
			converted := float32(v.Float())
			return reflect.ValueOf(&converted)
		case float64Type:
			converted := v.Float()
			return reflect.ValueOf(&converted)
		case intType:
			converted := int(v.Int())
			return reflect.ValueOf(&converted)
		case int8Type:
			converted := int8(v.Int())
			return reflect.ValueOf(&converted)
		case int16Type:
			converted := int16(v.Int())
			return reflect.ValueOf(&converted)
		case int32Type:
			converted := int32(v.Int())
			return reflect.ValueOf(&converted)
		case int64Type:
			converted := v.Int()
			return reflect.ValueOf(&converted)
		case stringType:
			converted := v.String()
			return reflect.ValueOf(&converted)
		case uintType:
			converted := uint(v.Uint())
			return reflect.ValueOf(&converted)
		case uint8Type:
			converted := uint8(v.Uint())
			return reflect.ValueOf(&converted)
		case uint16Type:
			converted := uint16(v.Uint())
			return reflect.ValueOf(&converted)
		case uint32Type:
			converted := uint32(v.Uint())
			return reflect.ValueOf(&converted)
		case uint64Type:
			converted := v.Uint()
			return reflect.ValueOf(&converted)
		}
	}
	return invalidValue
}
