package winapi

import (
	"errors"
	"reflect"
	"syscall"
	"unsafe"

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

// Uint16BufferToSlice wraps a uint16 pointer-and-length into a slice
// for easier interop with Go APIs
func Uint16BufferToSlice(buffer *uint16, bufferLength int) (result []uint16) {
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&result))
	hdr.Data = uintptr(unsafe.Pointer(buffer))
	hdr.Cap = bufferLength
	hdr.Len = bufferLength

	return
}

// UnicodeString corresponds to UNICODE_STRING win32 struct defined here
// https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_unicode_string
type UnicodeString struct {
	Length        uint16
	MaximumLength uint16
	Buffer        *uint16
}

// NTSTRSAFE_UNICODE_STRING_MAX_CCH is a constant defined in ntstrsafe.h. This value
// denotes the maximum number of wide chars a path can have.
const NTSTRSAFE_UNICODE_STRING_MAX_CCH = 32767

//String converts a UnicodeString to a golang string
func (uni UnicodeString) String() string {
	// UnicodeString is not guaranteed to be null terminated, therefore
	// use the UnicodeString's Length field
	return windows.UTF16ToString(Uint16BufferToSlice(uni.Buffer, int(uni.Length/2)))
}

// NewUnicodeString allocates a new UnicodeString and copies `s` into
// the buffer of the new UnicodeString.
func NewUnicodeString(s string) (*UnicodeString, error) {
	buf, err := windows.UTF16FromString(s)
	if err != nil {
		return nil, err
	}

	if len(buf) > NTSTRSAFE_UNICODE_STRING_MAX_CCH {
		return nil, syscall.ENAMETOOLONG
	}

	uni := &UnicodeString{
		// The length is in bytes and should not include the trailing null character.
		Length:        uint16((len(buf) - 1) * 2),
		MaximumLength: uint16((len(buf) - 1) * 2),
		Buffer:        &buf[0],
	}
	return uni, nil
}

// ConvertStringSetToSlice is a helper function used to convert the contents of
// `buf` into a string slice. `buf` contains a set of null terminated strings
// with an additional null at the end to indicate the end of the set.
func ConvertStringSetToSlice(buf []byte) ([]string, error) {
	var results []string
	prev := 0
	for i := range buf {
		if buf[i] == 0 {
			if prev == i {
				// found two null characters in a row, return result
				return results, nil
			}
			results = append(results, string(buf[prev:i]))
			prev = i + 1
		}
	}
	return nil, errors.New("string set malformed: missing null terminator at end of buffer")
}
