package main

import (
    "fmt"
    "runtime"
    "syscall"
    "unsafe"
)

var (
    user32               = syscall.NewLazyDLL("user32.dll")
    kernel32             = syscall.NewLazyDLL("kernel32.dll")
    procRegisterClassExW = user32.NewProc("RegisterClassExW")
    procCreateWindowExW  = user32.NewProc("CreateWindowExW")
    procDefWindowProcW   = user32.NewProc("DefWindowProcW")
    procShowWindow       = user32.NewProc("ShowWindow")
    procUpdateWindow     = user32.NewProc("UpdateWindow")
    procGetMessageW      = user32.NewProc("GetMessageW")
    procTranslateMessage = user32.NewProc("TranslateMessage")
    procDispatchMessageW = user32.NewProc("DispatchMessageW")
    procPostQuitMessage  = user32.NewProc("PostQuitMessage")
    procLoadCursorW      = user32.NewProc("LoadCursorW")
    procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW")
)

const (
    CS_HREDRAW          = 0x0002
    CS_VREDRAW          = 0x0001
    CW_USEDEFAULT       = 0x80000000
    WS_OVERLAPPEDWINDOW = 0x00CF0000
    SW_SHOWDEFAULT      = 10
    WM_DESTROY          = 0x0002
    IDC_ARROW           = 32512
    COLOR_WINDOW        = 5
)

type wndClassEx struct {
    cbSize        uint32
    style         uint32
    lpfnWndProc   uintptr
    cbClsExtra    int32
    cbWndExtra    int32
    hInstance     syscall.Handle
    hIcon         syscall.Handle
    hCursor       syscall.Handle
    hbrBackground syscall.Handle
    lpszMenuName  *uint16
    lpszClassName *uint16
    hIconSm       syscall.Handle
}

type point struct {
    x int32
    y int32
}

type msg struct {
    hwnd    syscall.Handle
    message uint32
    wParam  uintptr
    lParam  uintptr
    time    uint32
    pt      point
}

func wndProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
    switch msg {
    case WM_DESTROY:
        postQuitMessage(0)
        return 0
    }
    ret, _, _ := procDefWindowProcW.Call(uintptr(hwnd), uintptr(msg), wParam, lParam)
    return ret
}

func registerWindowClass(className *uint16, instance syscall.Handle, wndProcPtr uintptr) error {
    wc := wndClassEx{
        cbSize:        uint32(unsafe.Sizeof(wndClassEx{})),
        style:         CS_HREDRAW | CS_VREDRAW,
        lpfnWndProc:   wndProcPtr,
        hInstance:     instance,
        hCursor:       loadCursor(0, IDC_ARROW),
        hbrBackground: syscall.Handle(COLOR_WINDOW + 1),
        lpszClassName: className,
    }

    atom, _, err := procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc)))
    if atom == 0 {
        return err
    }
    return nil
}

func createWindow(className, windowTitle *uint16, instance syscall.Handle) (syscall.Handle, error) {
    hwnd, _, err := procCreateWindowExW.Call(
        0,
        uintptr(unsafe.Pointer(className)),
        uintptr(unsafe.Pointer(windowTitle)),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        800,
        600,
        0,
        0,
        uintptr(instance),
        0,
    )
    if hwnd == 0 {
        return 0, err
    }
    return syscall.Handle(hwnd), nil
}

func showWindow(hwnd syscall.Handle, cmdShow int32) {
    procShowWindow.Call(uintptr(hwnd), uintptr(cmdShow))
    procUpdateWindow.Call(uintptr(hwnd))
}

func loadCursor(instance syscall.Handle, cursorID uintptr) syscall.Handle {
    hCursor, _, _ := procLoadCursorW.Call(uintptr(instance), cursorID)
    return syscall.Handle(hCursor)
}

func postQuitMessage(exitCode int32) {
    procPostQuitMessage.Call(uintptr(exitCode))
}

func getModuleHandle() syscall.Handle {
    handle, _, _ := procGetModuleHandleW.Call(0)
    return syscall.Handle(handle)
}

func messageLoop() {
    var m msg
    for {
        ret, _, err := procGetMessageW.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
        switch int32(ret) {
        case -1:
            fmt.Printf("GetMessage failed: %v\n", err)
            return
        case 0:
            return
        default:
            procTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
            procDispatchMessageW.Call(uintptr(unsafe.Pointer(&m)))
        }
    }
}

func main() {
    runtime.LockOSThread()

    className := syscall.StringToUTF16Ptr("GoWinClass")
    windowTitle := syscall.StringToUTF16Ptr("Go Windows API Window")

    instance := getModuleHandle()

    wndProcPtr := syscall.NewCallback(func(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr {
        return wndProc(hwnd, msg, wParam, lParam)
    })

    if err := registerWindowClass(className, instance, wndProcPtr); err != nil {
        fmt.Printf("RegisterClassEx failed: %v\n", err)
        return
    }

    hwnd, err := createWindow(className, windowTitle, instance)
    if err != nil {
        fmt.Printf("CreateWindowEx failed: %v\n", err)
        return
    }

    showWindow(hwnd, SW_SHOWDEFAULT)
    messageLoop()