// Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.

package common

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"
)

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

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

// Int64 returns a pointer to the provided int64
func Int64(value int64) *int64 {
	return &value
}

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

//Float32 returns a pointer to the provided float32
func Float32(value float32) *float32 {
	return &value
}

//Float64 returns a pointer to the provided float64
func Float64(value float64) *float64 {
	return &value
}

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

//PointerString prints the values of pointers in a struct
//Producing a human friendly string for an struct with pointers.
//useful when debugging the values of a struct
func PointerString(datastruct interface{}) (representation string) {
	val := reflect.ValueOf(datastruct)
	typ := reflect.TypeOf(datastruct)
	all := make([]string, 2)
	all = append(all, "{")
	for i := 0; i < typ.NumField(); i++ {
		sf := typ.Field(i)

		//unexported
		if sf.PkgPath != "" && !sf.Anonymous {
			continue
		}

		sv := val.Field(i)
		stringValue := ""
		if isNil(sv) {
			stringValue = fmt.Sprintf("%s=<nil>", sf.Name)
		} else {
			if sv.Type().Kind() == reflect.Ptr {
				sv = sv.Elem()
			}
			stringValue = fmt.Sprintf("%s=%v", sf.Name, sv)
		}
		all = append(all, stringValue)
	}
	all = append(all, "}")
	representation = strings.TrimSpace(strings.Join(all, " "))
	return
}

// SDKTime a struct that parses/renders to/from json using RFC339 date-time information
type SDKTime struct {
	time.Time
}

// SDKDate a struct that parses/renders to/from json using only date information
type SDKDate struct {
	//Date date information
	Date time.Time
}

func sdkTimeFromTime(t time.Time) SDKTime {
	return SDKTime{t}
}

func sdkDateFromTime(t time.Time) SDKDate {
	return SDKDate{Date: t}
}

func formatTime(t SDKTime) string {
	return t.Format(sdkTimeFormat)
}

func formatDate(t SDKDate) string {
	return t.Date.Format(sdkDateFormat)
}

func now() *SDKTime {
	t := SDKTime{time.Now()}
	return &t
}

var timeType = reflect.TypeOf(SDKTime{})
var timeTypePtr = reflect.TypeOf(&SDKTime{})

var sdkDateType = reflect.TypeOf(SDKDate{})
var sdkDateTypePtr = reflect.TypeOf(&SDKDate{})

//Formats for sdk supported time representations
const sdkTimeFormat = time.RFC3339Nano
const rfc1123OptionalLeadingDigitsInDay = "Mon, _2 Jan 2006 15:04:05 MST"
const sdkDateFormat = "2006-01-02"

func tryParsingTimeWithValidFormatsForHeaders(data []byte, headerName string) (t time.Time, err error) {
	header := strings.ToLower(headerName)
	switch header {
	case "lastmodified", "date":
		t, err = tryParsing(data, time.RFC3339Nano, time.RFC3339, time.RFC1123, rfc1123OptionalLeadingDigitsInDay, time.RFC850, time.ANSIC)
		return
	default: //By default we parse with RFC3339
		t, err = time.Parse(sdkTimeFormat, string(data))
		return
	}
}

func tryParsing(data []byte, layouts ...string) (tm time.Time, err error) {
	datestring := string(data)
	for _, l := range layouts {
		tm, err = time.Parse(l, datestring)
		if err == nil {
			return
		}
	}
	err = fmt.Errorf("Could not parse time: %s with formats: %s", datestring, layouts[:])
	return
}

// String returns string representation of SDKDate
func (t *SDKDate) String() string {
	return t.Date.Format(sdkDateFormat)
}

// NewSDKDateFromString parses the dateString into SDKDate
func NewSDKDateFromString(dateString string) (*SDKDate, error) {
	parsedTime, err := time.Parse(sdkDateFormat, dateString)
	if err != nil {
		return nil, err
	}

	return &SDKDate{Date: parsedTime}, nil
}

// UnmarshalJSON unmarshals from json
func (t *SDKTime) UnmarshalJSON(data []byte) (e error) {
	s := string(data)
	if s == "null" {
		t.Time = time.Time{}
	} else {
		//Try parsing with RFC3339
		t.Time, e = time.Parse(`"`+sdkTimeFormat+`"`, string(data))
	}
	return
}

// MarshalJSON marshals to JSON
func (t *SDKTime) MarshalJSON() (buff []byte, e error) {
	s := t.Format(sdkTimeFormat)
	buff = []byte(`"` + s + `"`)
	return
}

// UnmarshalJSON unmarshals from json
func (t *SDKDate) UnmarshalJSON(data []byte) (e error) {
	if string(data) == `"null"` {
		t.Date = time.Time{}
		return
	}

	t.Date, e = tryParsing(data,
		strconv.Quote(sdkDateFormat),
	)
	return
}

// MarshalJSON marshals to JSON
func (t *SDKDate) MarshalJSON() (buff []byte, e error) {
	s := t.Date.Format(sdkDateFormat)
	buff = []byte(strconv.Quote(s))
	return
}

// PrivateKeyFromBytes is a helper function that will produce a RSA private
// key from bytes.
func PrivateKeyFromBytes(pemData []byte, password *string) (key *rsa.PrivateKey, e error) {
	if pemBlock, _ := pem.Decode(pemData); pemBlock != nil {
		decrypted := pemBlock.Bytes
		if x509.IsEncryptedPEMBlock(pemBlock) {
			if password == nil {
				e = fmt.Errorf("private_key_password is required for encrypted private keys")
				return
			}
			if decrypted, e = x509.DecryptPEMBlock(pemBlock, []byte(*password)); e != nil {
				return
			}
		}

		key, e = x509.ParsePKCS1PrivateKey(decrypted)

	} else {
		e = fmt.Errorf("PEM data was not found in buffer")
		return
	}
	return
}

func generateRandUUID() (string, error) {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	uuid := fmt.Sprintf("%x%x%x%x%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

	return uuid, nil
}

func makeACopy(original []string) []string {
	tmp := make([]string, len(original))
	copy(tmp, original)
	return tmp
}