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

// +build windows

package sspi

import (
	"syscall"
	"time"
	"unsafe"
)

// TODO: add documentation

type PackageInfo struct {
	Capabilities uint32
	Version      uint16
	RPCID        uint16
	MaxToken     uint32
	Name         string
	Comment      string
}

func QueryPackageInfo(pkgname string) (*PackageInfo, error) {
	name, err := syscall.UTF16PtrFromString(pkgname)
	if err != nil {
		return nil, err
	}
	var pi *SecPkgInfo
	ret := QuerySecurityPackageInfo(name, &pi)
	if ret != SEC_E_OK {
		return nil, ret
	}
	defer FreeContextBuffer((*byte)(unsafe.Pointer(pi)))

	return &PackageInfo{
		Capabilities: pi.Capabilities,
		Version:      pi.Version,
		RPCID:        pi.RPCID,
		MaxToken:     pi.MaxToken,
		Name:         syscall.UTF16ToString((*[2 << 12]uint16)(unsafe.Pointer(pi.Name))[:]),
		Comment:      syscall.UTF16ToString((*[2 << 12]uint16)(unsafe.Pointer(pi.Comment))[:]),
	}, nil
}

type Credentials struct {
	Handle CredHandle
	expiry syscall.Filetime
}

func AcquireCredentials(pkgname string, creduse uint32, authdata *byte) (*Credentials, error) {
	name, err := syscall.UTF16PtrFromString(pkgname)
	if err != nil {
		return nil, err
	}
	var c Credentials
	ret := AcquireCredentialsHandle(nil, name, creduse, nil, authdata, 0, 0, &c.Handle, &c.expiry)
	if ret != SEC_E_OK {
		return nil, ret
	}
	return &c, nil
}

func (c *Credentials) Release() error {
	ret := FreeCredentialsHandle(&c.Handle)
	if ret != SEC_E_OK {
		return ret
	}
	return nil
}

func (c *Credentials) Expiry() time.Time {
	return time.Unix(0, c.expiry.Nanoseconds())
}

// TODO: add functions to display and manage RequestedFlags and EstablishedFlags fields.
// TODO: maybe get rid of RequestedFlags and EstablishedFlags fields, and replace them with input parameter for New...Context and return value of Update (instead of current bool parameter).

type updateFunc func(c *Context, targname *uint16, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno

type Context struct {
	Cred             *Credentials
	Handle           *CtxtHandle
	handle           CtxtHandle
	updFn            updateFunc
	expiry           syscall.Filetime
	RequestedFlags   uint32
	EstablishedFlags uint32
}

func NewClientContext(cred *Credentials, flags uint32) *Context {
	return &Context{
		Cred:           cred,
		updFn:          initialize,
		RequestedFlags: flags,
	}
}

func NewServerContext(cred *Credentials, flags uint32) *Context {
	return &Context{
		Cred:           cred,
		updFn:          accept,
		RequestedFlags: flags,
	}
}

func initialize(c *Context, targname *uint16, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno {
	return InitializeSecurityContext(&c.Cred.Handle, h, targname, c.RequestedFlags,
		0, SECURITY_NATIVE_DREP, in, 0, newh, out, &c.EstablishedFlags, &c.expiry)
}

func accept(c *Context, targname *uint16, h, newh *CtxtHandle, out, in *SecBufferDesc) syscall.Errno {
	return AcceptSecurityContext(&c.Cred.Handle, h, in, c.RequestedFlags,
		SECURITY_NATIVE_DREP, newh, out, &c.EstablishedFlags, &c.expiry)
}

func (c *Context) Update(targname *uint16, out, in *SecBufferDesc) syscall.Errno {
	h := c.Handle
	if c.Handle == nil {
		c.Handle = &c.handle
	}
	return c.updFn(c, targname, h, c.Handle, out, in)
}

func (c *Context) Release() error {
	ret := DeleteSecurityContext(c.Handle)
	if ret != SEC_E_OK {
		return ret
	}
	return nil
}

func (c *Context) Expiry() time.Time {
	return time.Unix(0, c.expiry.Nanoseconds())
}

// TODO: add comment to function doco that this "impersonation" is applied to current OS thread.
func (c *Context) ImpersonateUser() error {
	ret := ImpersonateSecurityContext(c.Handle)
	if ret != SEC_E_OK {
		return ret
	}
	return nil
}

func (c *Context) RevertToSelf() error {
	ret := RevertSecurityContext(c.Handle)
	if ret != SEC_E_OK {
		return ret
	}
	return nil
}

// Sizes queries the context for the sizes used in per-message functions.
// It returns the maximum token size used in authentication exchanges, the
// maximum signature size, the preferred integral size of messages, the
// size of any security trailer, and any error.
func (c *Context) Sizes() (uint32, uint32, uint32, uint32, error) {
	var s _SecPkgContext_Sizes
	ret := QueryContextAttributes(c.Handle, _SECPKG_ATTR_SIZES, (*byte)(unsafe.Pointer(&s)))
	if ret != SEC_E_OK {
		return 0, 0, 0, 0, ret
	}
	return s.MaxToken, s.MaxSignature, s.BlockSize, s.SecurityTrailer, nil
}

// NewSecBufferDesc returns an initialized SecBufferDesc describing the
// provided SecBuffer.
func NewSecBufferDesc(b []SecBuffer) *SecBufferDesc {
	return &SecBufferDesc{
		Version:      SECBUFFER_VERSION,
		BuffersCount: uint32(len(b)),
		Buffers:      &b[0],
	}
}
