package lt

import (
	"fmt"
	"log"
	"net"
	"net/url"
	"os"
	"path"
	"unsafe"

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

// ---------------------------------------------------------------------------
// Connection
// ---------------------------------------------------------------------------

func dial(addr string) (net.Conn, error) {
	u, err := url.Parse(addr)
	if err != nil {
		return nil, err
	}

	switch u.Host {
	// Windows: Unix Domain Socket
	case "unix":
		return net.Dial("unix", path.Join(os.TempDir(), u.Scheme+".sock"))
	// Windows: Named Pipe (default)
	case "pipe", "":
		return pipeDial("\\\\.\\pipe\\" + u.Scheme)
	default:
		return nil, fmt.Errorf("unknown port: %s", u.Port())
	}
}

// ---------------------------------------------------------------------------
// Shared buffer
// ---------------------------------------------------------------------------

type buffer struct {
	Data   []byte
	ref    int
	handle windows.Handle
	ptr    uintptr
}

func mapBuffer(handle string, size int) (*buffer, error) {
	b := &buffer{}
	key, err := windows.UTF16PtrFromString(handle)
	if err != nil {
		return nil, err
	}

	// Open system handle
	b.handle, err = windows.CreateFileMapping(
		windows.InvalidHandle,
		nil,
		windows.PAGE_READONLY,
		0,
		uint32(size),
		key)
	if err != nil && err != windows.ERROR_ALREADY_EXISTS {
		return nil, os.NewSyscallError("CreateFileMapping", err)
	}

	// Map buffer
	b.ptr, err = windows.MapViewOfFile(
		b.handle,
		windows.FILE_MAP_READ,
		0,
		0,
		uintptr(size))
	if err != nil {
		windows.CloseHandle(b.handle)
		return nil, os.NewSyscallError("MapViewOfFile", err)
	}
	b.Data = unsafe.Slice((*byte)(unsafe.Pointer(b.ptr)), size)

	// Done
	return b, nil
}

func (b *buffer) Close() error {
	if b.ptr != uintptr(0) {
		windows.UnmapViewOfFile(b.ptr)
		b.ptr = uintptr(0)
	}
	if b.handle != windows.InvalidHandle {
		windows.CloseHandle(b.handle)
		b.handle = windows.InvalidHandle
	}
	return nil
}

// ---------------------------------------------------------------------------
// Name pipe
// ---------------------------------------------------------------------------

type pipeConn struct {
	net.Conn
	handle windows.Handle
}

var (
	modkernel32        = windows.NewLazySystemDLL("kernel32.dll")
	procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
)

func waitNamedPipe(name *uint16, timeout uint32) error {
	r1, _, e1 := procWaitNamedPipeW.Call(uintptr(unsafe.Pointer(name)), uintptr(timeout))
	if r1 == 0 {
		if e1 != nil {
			return e1
		}
		return windows.ERROR_SEM_TIMEOUT
	}
	return nil
}

func pipeDial(addr string) (net.Conn, error) {
	name, err := windows.UTF16PtrFromString(addr)
	if err != nil {
		return nil, err
	}
	// Open named pipe
	c := &pipeConn{}
	for {
		c.handle, err = windows.CreateFile(
			name,
			windows.GENERIC_READ|windows.GENERIC_WRITE,
			0,                     // mode
			nil,                   // default security attributes
			windows.OPEN_EXISTING, // opens existing pipe
			0,                     // default attributes
			0,                     // no template file
		)
		if err == nil {
			break
		}
		if err == windows.ERROR_PIPE_BUSY {
			if waitNamedPipe(name, 2000) == nil {
				continue
			}
		}
		log.Println("CreateFile", err)
		return nil, ErrClosed
	}
	// Done
	return c, nil
}

func (c *pipeConn) Read(p []byte) (int, error) {
	var n uint32
	err := windows.ReadFile(c.handle, p, &n, nil)
	return int(n), err
}

func (c *pipeConn) Write(p []byte) (int, error) {
	var n uint32
	err := windows.WriteFile(c.handle, p, &n, nil)
	return int(n), err
}

func (c *pipeConn) Close() error {
	return windows.CloseHandle(c.handle)
}
