// Package drm is the kernel mode-setting ABI.
package drm

import (
	"unsafe"

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

const ioctlBase = 0x64

func IO(nr uintptr) uintptr         { return ioctlBase<<8 | nr }
func IOWR(nr, size uintptr) uintptr { return 3<<30 | size<<16 | ioctlBase<<8 | nr }

func Ioctl(fd int, req uintptr, arg unsafe.Pointer) error {
	if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), req, uintptr(arg)); errno != 0 {
		return errno
	}
	return nil
}

const (
	Connected         = 1
	TypePreferred     = 1 << 3
	PageFlipEvent     = 0x01
	EventFlipComplete = 0x02

	VblankRelative      = 0x1
	VblankHighCrtcMask  = 0x3e
	VblankHighCrtcShift = 1
)

type ModeInfo struct {
	Clock                                         uint32
	Hdisplay, HsyncStart, HsyncEnd, Htotal, Hskew uint16
	Vdisplay, VsyncStart, VsyncEnd, Vtotal, Vscan uint16
	Vrefresh, Flags, Type                         uint32
	Name                                          [32]byte
}

type ModeCardRes struct {
	FBIDPtr, CrtcIDPtr, ConnIDPtr, EncIDPtr     uint64
	CountFBs, CountCRTCs, CountConns, CountEncs uint32
	MinWidth, MaxWidth, MinHeight, MaxHeight    uint32
}

type ModeGetConnector struct {
	EncodersPtr, ModesPtr, PropsPtr, PropValuesPtr uint64
	CountModes, CountProps, CountEncoders          uint32
	EncoderID, ConnectorID, ConnectorType          uint32
	ConnectorTypeID, Connection                    uint32
	MmWidth, MmHeight, Subpixel, Pad               uint32
}

type ModeGetEncoder struct {
	EncoderID, EncoderType        uint32
	CrtcID                        uint32
	PossibleCRTCs, PossibleClones uint32
}

type ModeCrtc struct {
	SetConnectorsPtr uint64
	CountConnectors  uint32
	CrtcID           uint32
	FBID             uint32
	X, Y             uint32
	GammaSize        uint32
	ModeValid        uint32
	Mode             ModeInfo
}

type ModeFBCmd struct {
	FBID, Width, Height, Pitch, Bpp, Depth uint32
	Handle                                 uint32
}

type ModeCreateDumb struct {
	Height, Width, Bpp, Flags uint32
	Handle, Pitch             uint32
	Size                      uint64
}

type ModeMapDumb struct {
	Handle, Pad uint32
	Offset      uint64
}

type ModeCrtcPageFlip struct {
	CrtcID, FBID, Flags, Reserved uint32
	UserData                      uint64
}

// WaitVblank is larger than the kernel union it mirrors, so WaitVblankIO
// carries the union's size and only Type and Sequence are at valid offsets.
type WaitVblank struct {
	Type     uint32
	Sequence uint32
	Signal   uint64
	TvSec    int64
	TvUsec   int64
}

const waitVblankUnionSize = 24

var (
	SetMaster    = IO(0x1e)
	DropMaster   = IO(0x1f)
	WaitVblankIO = IOWR(0x3a, waitVblankUnionSize)
	GetResources = IOWR(0xa0, unsafe.Sizeof(ModeCardRes{}))
	GetCrtc      = IOWR(0xa1, unsafe.Sizeof(ModeCrtc{}))
	SetCrtc      = IOWR(0xa2, unsafe.Sizeof(ModeCrtc{}))
	GetEncoder   = IOWR(0xa6, unsafe.Sizeof(ModeGetEncoder{}))
	GetConnector = IOWR(0xa7, unsafe.Sizeof(ModeGetConnector{}))
	GetFB        = IOWR(0xad, unsafe.Sizeof(ModeFBCmd{}))
	AddFB        = IOWR(0xae, unsafe.Sizeof(ModeFBCmd{}))
	PageFlip     = IOWR(0xb0, unsafe.Sizeof(ModeCrtcPageFlip{}))
	CreateDumb   = IOWR(0xb2, unsafe.Sizeof(ModeCreateDumb{}))
	MapDumb      = IOWR(0xb3, unsafe.Sizeof(ModeMapDumb{}))
)

const (
	redShift   = 16
	greenShift = 8
	blueShift  = 0
)

func Pack(r, g, b uint8) uint32 {
	return uint32(r)<<redShift | uint32(g)<<greenShift | uint32(b)<<blueShift
}

func Unpack(v uint32) (r, g, b uint8) {
	return uint8(v >> redShift), uint8(v >> greenShift), uint8(v >> blueShift)
}

// Offset maps the portrait canvas onto the landscape panel, a quarter turn that
// puts logical top on the panel's right edge.
func Offset(x, y, stride, panelW int) int {
	return x*stride + (panelW-1-y)*4
}
