//go:build windows

package main

import (
	"fmt"
	"log"
	"os/exec"
	"syscall"
	"unsafe"
)

const (
	wmUser      = 0x0400
	wmTray      = wmUser + 1
	wmCommand   = 0x0111
	wmDestroy    = 0x0002
	wmClose      = 0x0010
	wmRButtonUp  = 0x0205
	wmLButtonUp  = 0x0202
	wmCreate     = 0x0001
	wmQuit       = 0x0012
	niAdd        = 0x00000000
	niModify     = 0x00000001
	niDelete     = 0x00000002
	nifMessage   = 0x00000001
	nifIcon      = 0x00000002
	nifTip       = 0x00000004
	nifInfo      = 0x00000010
	nimPopup     = 0x00000000
	miString     = 0x00000000
	miSeparator  = 0x00000800
	miDefault    = 0x00001000
	swHide       = 0
	idOpen       = 1001
	idExit       = 1002
	maxTip       = 128
	maxClassName  = 64
)

type trayApp struct {
	uiURL string
	open  func()
}

func newTray(uiURL string, open func()) *trayApp {
	return &trayApp{uiURL: uiURL, open: open}
}

func (t *trayApp) Run() error {
	return runTray(t.uiURL, t.open)
}

func openBrowser(url string) {
	_ = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
}

func runTray(uiURL string, open func()) error {
	user32 := syscall.NewLazyDLL("user32.dll")
	shell32 := syscall.NewLazyDLL("shell32.dll")
	kernel32 := syscall.NewLazyDLL("kernel32.dll")

	registerClass := user32.NewProc("RegisterClassW")
	createWindowEx := user32.NewProc("CreateWindowExW")
	defWindowProc := user32.NewProc("DefWindowProcW")
	getMessage := user32.NewProc("GetMessageW")
	translateMessage := user32.NewProc("TranslateMessage")
	dispatchMessage := user32.NewProc("DispatchMessageW")
	postQuitMessage := user32.NewProc("PostQuitMessage")
	createPopupMenu := user32.NewProc("CreatePopupMenu")
	appendMenu := user32.NewProc("AppendMenuW")
	setForegroundWindow := user32.NewProc("SetForegroundWindow")
	trackPopupMenu := user32.NewProc("TrackPopupMenu")
	showWindow := user32.NewProc("ShowWindow")
	loadIcon := user32.NewProc("LoadIconW")
	shellNotifyIcon := shell32.NewProc("Shell_NotifyIconW")
	getModuleHandle := kernel32.NewProc("GetModuleHandleW")

	type wndClassEx struct {
		cbSize        uint32
		style         uint32
		lpfnWndProc   uintptr
		cbClsExtra    int32
		cbWndExtra    int32
		hInstance     uintptr
		hIcon         uintptr
		hCursor       uintptr
		hbrBackground uintptr
		lpszMenuName  *uint16
		lpszClassName *uint16
		hIconSm       uintptr
	}
	type point struct{ X, Y int32 }
	type msg struct {
		hwnd    uintptr
		message uint32
		wParam  uintptr
		lParam  uintptr
		time    uint32
		pt      point
	}
	type notifyIconData struct {
		cbSize           uint32
		hWnd             uintptr
		uID              uint32
		uFlags           uint32
		uCallbackMessage uint32
		hIcon            uintptr
		szTip            [maxTip]uint16
		dwState          uint32
		dwStateMask      uint32
		szInfo           [256]uint16
		uTimeoutVersion  uint32
		szInfoTitle      [64]uint16
		dwInfoFlags      uint32
		guidItem         [16]byte
		hBalloonIcon     uintptr
	}

	className, _ := syscall.UTF16PtrFromString("VRC_OSC_TRAY")
	windowTitle, _ := syscall.UTF16PtrFromString("VRC OSC Tray")
	icon, _, _ := loadIcon.Call(0, 32512) // IDI_APPLICATION

	var hwnd uintptr
	var menu uintptr
	var nid notifyIconData

	wndProc := syscall.NewCallback(func(hwnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
		switch msg {
		case wmCreate:
			return 0
		case wmTray:
			switch lParam {
			case wmLButtonUp:
				if open != nil {
					open()
				}
			case wmRButtonUp:
				if menu == 0 {
					m, _, _ := createPopupMenu.Call()
					menu = m
					openText, _ := syscall.UTF16PtrFromString("Open UI")
					exitText, _ := syscall.UTF16PtrFromString("Exit")
					appendMenu.Call(menu, miString|miDefault, idOpen, uintptr(unsafe.Pointer(openText)))
					appendMenu.Call(menu, miString, idExit, uintptr(unsafe.Pointer(exitText)))
				}
				setForegroundWindow.Call(hwnd)
				var p point
				user32.NewProc("GetCursorPos").Call(uintptr(unsafe.Pointer(&p)))
				trackPopupMenu.Call(menu, nimPopup, uintptr(p.X), uintptr(p.Y), 0, hwnd, 0)
			}
			return 0
		case wmCommand:
			switch uint16(wParam & 0xffff) {
			case idOpen:
				if open != nil {
					open()
				}
			case idExit:
				user32.NewProc("DestroyWindow").Call(hwnd)
			}
			return 0
		case wmDestroy:
			nid.cbSize = uint32(unsafe.Sizeof(nid))
			nid.hWnd = hwnd
			nid.uID = 1
			shellNotifyIcon.Call(niDelete, uintptr(unsafe.Pointer(&nid)))
			postQuitMessage.Call(0)
			return 0
		}
		ret, _, _ := defWindowProc.Call(hwnd, uintptr(msg), wParam, lParam)
		return ret
	})

	hInstance, _, _ := getModuleHandle.Call(0)
	_, _, regErr := registerClass.Call(uintptr(unsafe.Pointer(&wndClassEx{
		cbSize:        uint32(unsafe.Sizeof(wndClassEx{})),
		lpfnWndProc:   wndProc,
		hInstance:     hInstance,
		hIcon:         icon,
		lpszClassName: className,
	})))
	if regErr != nil {
		return fmt.Errorf("RegisterClassW failed: %v", regErr)
	}

	hwnd, _, err := createWindowEx.Call(0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowTitle)), 0, 0, 0, 0, 0, 0, 0, hInstance, 0)
	if hwnd == 0 {
		return fmt.Errorf("CreateWindowExW failed: %v", err)
	}
	showWindow.Call(hwnd, swHide)

	nid.cbSize = uint32(unsafe.Sizeof(nid))
	nid.hWnd = hwnd
	nid.uID = 1
	nid.uFlags = nifMessage | nifIcon | nifTip
	nid.uCallbackMessage = wmTray
	nid.hIcon = icon
	tip := syscall.StringToUTF16("VRC OSC")
	copy(nid.szTip[:], tip)
	if ok, _, _ := shellNotifyIcon.Call(niAdd, uintptr(unsafe.Pointer(&nid))); ok == 0 {
		return fmt.Errorf("Shell_NotifyIconW add failed")
	}
	log.Printf("tray started")

	var m msg
	for {
		r, _, _ := getMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
		if int32(r) <= 0 {
			break
		}
		translateMessage.Call(uintptr(unsafe.Pointer(&m)))
		dispatchMessage.Call(uintptr(unsafe.Pointer(&m)))
	}
	return nil
}
