/**
 * Copyright 2016 IBM Corp.
 *
 * Licensed 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 sl has convenience functions for returning pointers to values
package sl

import (
	"reflect"
	"strings"
	"time"

	"github.com/softlayer/softlayer-go/datatypes"
)

// Int returns a pointer to the int value provided
func Int(v int) *int {
	return &v
}

// Uint returns a pointer to the uint value provided
func Uint(v uint) *uint {
	return &v
}

// String returns a pointer to the string value provided
func String(v string) *string {
	return &v
}

// Bool returns a pointer to the bool value provided
func Bool(v bool) *bool {
	return &v
}

// Time converts the time.Time value provided to a datatypes.Time value,
// and returns a pointer to it
func Time(v time.Time) *datatypes.Time {
	r := datatypes.Time{Time: v}
	return &r
}

// Float converts the float value provided to a datatypes.Float64 value,
// and returns a pointer to it
func Float(v float64) *datatypes.Float64 {
	r := datatypes.Float64(v)
	return &r
}

// Convenience functions to simplify dereference of datatype properties

// Get returns the value of p, either p itself, or, if p is a pointer, the
// value that p points to. d is an optional default value to be returned
// in the event that p is nil. If d is not specified, and p is nil, a
// type-appropriate zero-value is returned instead.
func Get(p interface{}, d ...interface{}) interface{} {
	var (
		val interface{}
		ok  bool
	)

	if val, ok = GetOk(p); ok {
		return val
	}

	if len(d) > 0 {
		return d[0]
	}

	return val
}

// GetOk returns the value of p, either p itself, or, if p is a pointer, the
// value that p points to. If p is nil, a type-appropriate zero-value is
// returned instead. If p is a value or non-nil pointer, the second return
// value will be true.  Otherwise, it will be false.
func GetOk(p interface{}) (interface{}, bool) {
	t := reflect.TypeOf(p)

	// if p is a non-pointer, just return it
	if t.Kind() != reflect.Ptr {
		return p, true
	}

	// p is a pointer.  If non-nil, return the value pointed to
	v := reflect.Indirect(reflect.ValueOf(p))
	if v.IsValid() {
		return v.Interface(), true
	}

	// p is a nil pointer.  Return the zero value for the pointed-to type
	return reflect.Zero(t.Elem()).Interface(), false
}

// Grab returns the value specified by the path given,
// starting from the struct s.
// If at any point in the path the lookup falls short
// (i.e. a field is not found), or if the last field in the path is nil
// itself, a type-appropriate zero-value is returned.
// This behavior can be overidden by providing a default value.
//
// This is useful for getting values our of deeply nested structures
// Example: val := sl.Grab(virtualGuest, "Datacenter.Name")
func Grab(s interface{}, path string, d ...interface{}) interface{} {
	var (
		val interface{}
		ok  bool
	)

	if val, ok = GrabOk(s, path); ok {
		return val
	}

	if len(d) > 0 {
		return d[0]
	}

	return val
}

// GrabOk returns the value specified by the path given,
// starting from the struct s.
// If at any point in the path the lookup falls short
// (i.e. a field is not found), or if the last field in the path is nil
// itself, a type-appropriate zero-value is returned.
// It returns a second value, a boolean, which will be false if it failed
// to lookup the value, including if the last field in the path was nil.
//
// This is useful for getting values our of deeply nested structures
// Example: val, ok := sl.GrabOk(virtualGuest, "Datacenter.Name")
func GrabOk(s interface{}, path string) (interface{}, bool) {
	t := reflect.TypeOf(s)
	if t.Kind() != reflect.Struct {
		return nil, false
	}

	dotIndex := strings.Index(path, ".")
	if dotIndex == -1 {
		dotIndex = len(path)
	}

	fieldName := path[0:dotIndex]
	val := reflect.ValueOf(s)
	fieldVal := val.FieldByName(fieldName)
	if fieldVal.Kind() == reflect.Ptr {
		if fieldVal.IsNil() {
			return reflect.Zero(fieldVal.Type().Elem()).Interface(), false
		}

		fieldVal = reflect.Indirect(fieldVal)
	}

	result, ok := GetOk(fieldVal.Interface())
	if !ok {
		return result, ok
	}

	if dotIndex == len(path) {
		return result, ok
	}

	return GrabOk(result, path[dotIndex+1:len(path)])
}
