package shm

import (
	"fmt"

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

type PixelFormat uint32

const (
	FormatARGB8888 PixelFormat = 0
	FormatXRGB8888 PixelFormat = 1
	FormatABGR8888 PixelFormat = 0x34324241
	FormatXBGR8888 PixelFormat = 0x34324258
	FormatRGB888   PixelFormat = 0x34324752
	FormatBGR888   PixelFormat = 0x34324742
)

func (f PixelFormat) BytesPerPixel() int {
	switch f {
	case FormatRGB888, FormatBGR888:
		return 3
	default:
		return 4
	}
}

func (f PixelFormat) Is24Bit() bool {
	return f == FormatRGB888 || f == FormatBGR888
}

type Buffer struct {
	fd     int
	data   []byte
	size   int
	Width  int
	Height int
	Stride int
	Format PixelFormat
}

func CreateBuffer(width, height, stride int) (*Buffer, error) {
	size := stride * height

	fd, err := CreateAnonFd("dms-shm")
	if err != nil {
		return nil, fmt.Errorf("create shm fd: %w", err)
	}

	if err := unix.Ftruncate(fd, int64(size)); err != nil {
		unix.Close(fd)
		return nil, fmt.Errorf("ftruncate: %w", err)
	}

	data, err := unix.Mmap(fd, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
	if err != nil {
		unix.Close(fd)
		return nil, fmt.Errorf("mmap: %w", err)
	}

	return &Buffer{
		fd:     fd,
		data:   data,
		size:   size,
		Width:  width,
		Height: height,
		Stride: stride,
	}, nil
}

func (b *Buffer) Fd() int      { return b.fd }
func (b *Buffer) Size() int    { return b.size }
func (b *Buffer) Data() []byte { return b.data }

func (b *Buffer) Close() error {
	var firstErr error

	if b.data != nil {
		if err := unix.Munmap(b.data); err != nil && firstErr == nil {
			firstErr = fmt.Errorf("munmap: %w", err)
		}
		b.data = nil
	}

	if b.fd >= 0 {
		if err := unix.Close(b.fd); err != nil && firstErr == nil {
			firstErr = fmt.Errorf("close: %w", err)
		}
		b.fd = -1
	}

	return firstErr
}

func (b *Buffer) ConvertTo32Bit(srcFormat PixelFormat) (*Buffer, PixelFormat, error) {
	if !srcFormat.Is24Bit() {
		return b, srcFormat, nil
	}

	dstFormat := FormatXRGB8888
	dstStride := b.Width * 4

	dst, err := CreateBuffer(b.Width, b.Height, dstStride)
	if err != nil {
		return nil, srcFormat, err
	}
	dst.Format = dstFormat

	srcData := b.data
	dstData := dst.data

	// DRM format names are counterintuitive on little-endian:
	// RGB888 memory layout: B, G, R (name is logical order, not memory)
	// BGR888 memory layout: R, G, B
	isBGRMemory := srcFormat == FormatRGB888

	for y := 0; y < b.Height; y++ {
		srcRow := y * b.Stride
		dstRow := y * dstStride
		for x := 0; x < b.Width; x++ {
			si := srcRow + x*3
			di := dstRow + x*4
			if isBGRMemory {
				// RGB888: src memory is B,G,R -> dst XRGB8888 memory B,G,R,X
				dstData[di+0] = srcData[si+0]
				dstData[di+1] = srcData[si+1]
				dstData[di+2] = srcData[si+2]
			} else {
				// BGR888: src memory is R,G,B -> dst XRGB8888 memory B,G,R,X
				dstData[di+0] = srcData[si+2]
				dstData[di+1] = srcData[si+1]
				dstData[di+2] = srcData[si+0]
			}
			dstData[di+3] = 0xFF
		}
	}

	return dst, dstFormat, nil
}

func (b *Buffer) GetPixelRGBA(x, y int) (r, g, b2, a uint8) {
	if x < 0 || x >= b.Width || y < 0 || y >= b.Height {
		return
	}

	off := y*b.Stride + x*4
	if off+3 >= len(b.data) {
		return
	}

	switch b.Format {
	case FormatXBGR8888, FormatABGR8888:
		return b.data[off], b.data[off+1], b.data[off+2], 0xFF
	default:
		return b.data[off+2], b.data[off+1], b.data[off], 0xFF
	}
}

func (b *Buffer) GetPixelBGRA(x, y int) (b2, g, r, a uint8) {
	if x < 0 || x >= b.Width || y < 0 || y >= b.Height {
		return
	}

	off := y*b.Stride + x*4
	if off+3 >= len(b.data) {
		return
	}

	return b.data[off], b.data[off+1], b.data[off+2], b.data[off+3]
}

func (b *Buffer) ConvertBGRAtoRGBA() {
	for y := 0; y < b.Height; y++ {
		rowOff := y * b.Stride
		for x := 0; x < b.Width; x++ {
			off := rowOff + x*4
			if off+3 >= len(b.data) {
				continue
			}
			b.data[off], b.data[off+2] = b.data[off+2], b.data[off]
		}
	}
}

func (b *Buffer) FlipVertical() {
	tmp := make([]byte, b.Stride)
	for y := 0; y < b.Height/2; y++ {
		topOff := y * b.Stride
		botOff := (b.Height - 1 - y) * b.Stride
		copy(tmp, b.data[topOff:topOff+b.Stride])
		copy(b.data[topOff:topOff+b.Stride], b.data[botOff:botOff+b.Stride])
		copy(b.data[botOff:botOff+b.Stride], tmp)
	}
}

func (b *Buffer) Clear() {
	for i := range b.data {
		b.data[i] = 0
	}
}

func (b *Buffer) CopyFrom(src *Buffer) {
	copy(b.data, src.data)
}

const (
	TransformNormal     = 0
	Transform90         = 1
	Transform180        = 2
	Transform270        = 3
	TransformFlipped    = 4
	TransformFlipped90  = 5
	TransformFlipped180 = 6
	TransformFlipped270 = 7
)

func (b *Buffer) ApplyTransform(transform int32) (*Buffer, error) {
	if transform == TransformNormal {
		return b, nil
	}

	var newW, newH int
	switch transform {
	case Transform90, Transform270, TransformFlipped90, TransformFlipped270:
		newW, newH = b.Height, b.Width
	default:
		newW, newH = b.Width, b.Height
	}

	newBuf, err := CreateBuffer(newW, newH, newW*4)
	if err != nil {
		return nil, err
	}
	newBuf.Format = b.Format

	srcData := b.data
	dstData := newBuf.data

	for sy := 0; sy < b.Height; sy++ {
		for sx := 0; sx < b.Width; sx++ {
			var dx, dy int

			switch transform {
			case Transform90: // 90° CCW
				dx = sy
				dy = b.Width - 1 - sx
			case Transform180:
				dx = b.Width - 1 - sx
				dy = b.Height - 1 - sy
			case Transform270: // 270° CCW = 90° CW
				dx = b.Height - 1 - sy
				dy = sx
			case TransformFlipped:
				dx = b.Width - 1 - sx
				dy = sy
			case TransformFlipped90:
				dx = sy
				dy = sx
			case TransformFlipped180:
				dx = sx
				dy = b.Height - 1 - sy
			case TransformFlipped270:
				dx = b.Height - 1 - sy
				dy = b.Width - 1 - sx
			default:
				dx, dy = sx, sy
			}

			si := sy*b.Stride + sx*4
			di := dy*newBuf.Stride + dx*4

			if si+3 < len(srcData) && di+3 < len(dstData) {
				dstData[di+0] = srcData[si+0]
				dstData[di+1] = srcData[si+1]
				dstData[di+2] = srcData[si+2]
				dstData[di+3] = srcData[si+3]
			}
		}
	}

	return newBuf, nil
}

func InverseTransform(transform int32) int32 {
	switch transform {
	case Transform90:
		return Transform270
	case Transform270:
		return Transform90
	case TransformFlipped90:
		return TransformFlipped270
	case TransformFlipped270:
		return TransformFlipped90
	default:
		return transform
	}
}
