//go:build windows

package pid

import (
	"errors"
	"os"
	"skapp/pkg/logger"
	"syscall"
	"unsafe"
)

var pidFile = "./.pid"

var (
	kernel32         = syscall.NewLazyDLL("kernel32.dll")
	procLockFileEx   = kernel32.NewProc("LockFileEx")
	procUnlockFileEx = kernel32.NewProc("UnlockFileEx")
)

const (
	fileFlagNormal          = 0x00000080
	lockfileExclusiveLock   = 0x00000002
	lockfileFailImmediately = 0x00000001
)

func (p *PidLock) Lock() error {
	_, err := os.Stat(p.file)
	if err == nil {
		os.Exit(0)
	}

	f, err := os.OpenFile(pidFile, os.O_RDONLY|os.O_CREATE, os.ModePerm)
	if err != nil {
		logger.Log.Fatalf("启动失败 进程锁文件 .pid 创建失败")
		return LockFail
	}

	p.lock = f
	_ = f.Sync()

	err = TryLockEX(f)

	if err != nil {
		logger.Log.Fatal(err.Error())
		return LockFail
	}

	return nil
}

// LockSH places a shared lock on the file. If the file is already locked, waits until the file is released.
func LockSH(fp *os.File) error {
	r1, errNo := wlock(fp, 0x0)
	return isWError(r1, errNo)
}

// LockEX places an exclusive lock on the file. If the file is already locked, waits until the file is released.
func LockEX(fp *os.File) error {
	r1, errNo := wlock(fp, lockfileExclusiveLock)
	return isWError(r1, errNo)
}

// TryLockSH places a shared lock on the file. If the file is already locked, returns an error immediately.
func TryLockSH(fp *os.File) error {
	r1, errNo := wlock(fp, lockfileFailImmediately)
	return isWError(r1, errNo)
}

// TryLockEX places an exclusive lock on the file. If the file is already locked, returns an error immediately.
func TryLockEX(fp *os.File) error {
	r1, errNo := wlock(fp, lockfileExclusiveLock|lockfileFailImmediately)
	return isWError(r1, errNo)
}

// Unlock the file.
func Unlock(fp *os.File) error {
	r1, _, errNo := syscall.SyscallN(
		procUnlockFileEx.Addr(),
		fp.Fd(),
		uintptr(0),
		uintptr(1),
		uintptr(0),
		uintptr(unsafe.Pointer(&syscall.Overlapped{})),
		0,
	)
	return isWError(r1, errNo)
}

func wlock(fp *os.File, flags uintptr) (uintptr, syscall.Errno) {
	r1, _, errNo := syscall.SyscallN(
		procLockFileEx.Addr(),
		fp.Fd(),
		flags,
		uintptr(0),
		uintptr(1),
		uintptr(0),
		uintptr(unsafe.Pointer(&syscall.Overlapped{})),
	)
	return r1, errNo
}

func isWError(r1 uintptr, errNo syscall.Errno) error {
	if r1 != 1 {
		if errNo != 0 {
			return errors.New(errNo.Error())
		} else {
			return syscall.EINVAL
		}
	}

	return nil
}

func (p *PidLock) UnLock() {
	_ = Unlock(p.lock)
	_ = p.lock.Close()
	_ = os.Remove(p.file)
}
