// +build windows

package wincred

import (
	"encoding/binary"
	"reflect"
	"time"
	"unsafe"

	syscall "golang.org/x/sys/windows"
)

// utf16ToByte creates a byte array from a given UTF 16 char array.
func utf16ToByte(wstr []uint16) (result []byte) {
	result = make([]byte, len(wstr)*2)
	for i := range wstr {
		binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i])
	}
	return
}

// utf16FromString creates a UTF16 char array from a string.
func utf16FromString(str string) []uint16 {
	res, err := syscall.UTF16FromString(str)
	if err != nil {
		return []uint16{}
	}
	return res
}

// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`).
// This function avoids having cgo as dependency.
func goBytes(src *byte, len uint32) []byte {
	if src == nil || len == 0 {
		return []byte{}
	}
	rv := make([]byte, len)
	copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(src)),
		Len:  int(len),
		Cap:  int(len),
	})))
	return rv
}

// Convert the given CREDENTIAL struct to a more usable structure
func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
	if cred == nil {
		return nil
	}
	result = new(Credential)
	result.Comment = syscall.UTF16PtrToString(cred.Comment)
	result.TargetName = syscall.UTF16PtrToString(cred.TargetName)
	result.TargetAlias = syscall.UTF16PtrToString(cred.TargetAlias)
	result.UserName = syscall.UTF16PtrToString(cred.UserName)
	result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds())
	result.Persist = CredentialPersistence(cred.Persist)
	result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize)
	result.Attributes = make([]CredentialAttribute, cred.AttributeCount)
	attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(cred.Attributes)),
		Len:  int(cred.AttributeCount),
		Cap:  int(cred.AttributeCount),
	}))
	for i, attr := range attrSlice {
		resultAttr := &result.Attributes[i]
		resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword)
		resultAttr.Value = goBytes(attr.Value, attr.ValueSize)
	}
	return result
}

// Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the
// Windows APIs
func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) {
	if cred == nil {
		return nil
	}
	result = new(sysCREDENTIAL)
	result.Flags = 0
	result.Type = 0
	result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName)
	result.Comment, _ = syscall.UTF16PtrFromString(cred.Comment)
	result.LastWritten = syscall.NsecToFiletime(cred.LastWritten.UnixNano())
	result.CredentialBlobSize = uint32(len(cred.CredentialBlob))
	if len(cred.CredentialBlob) > 0 {
		result.CredentialBlob = &cred.CredentialBlob[0]
	}
	result.Persist = uint32(cred.Persist)
	result.AttributeCount = uint32(len(cred.Attributes))
	attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
	if len(attributes) > 0 {
		result.Attributes = &attributes[0]
	}
	for i := range cred.Attributes {
		inAttr := &cred.Attributes[i]
		outAttr := &attributes[i]
		outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword)
		outAttr.Flags = 0
		outAttr.ValueSize = uint32(len(inAttr.Value))
		if len(inAttr.Value) > 0 {
			outAttr.Value = &inAttr.Value[0]
		}
	}
	result.TargetAlias, _ = syscall.UTF16PtrFromString(cred.TargetAlias)
	result.UserName, _ = syscall.UTF16PtrFromString(cred.UserName)

	return
}
