package rdp

import "sync"

// PixelBuffer holds a shared RGBA pixel buffer that is updated by the RDP
// backend (via partial region updates) and read by the frame-serving path.
type PixelBuffer struct {
	Width  int
	Height int
	Data   []byte // RGBA pixel data, len = Width * Height * 4
	mu     sync.RWMutex
	dirty  bool
}

// NewPixelBuffer allocates a buffer for the given resolution.
// The buffer is initialized to all zeros (transparent black).
func NewPixelBuffer(width, height int) *PixelBuffer {
	return &PixelBuffer{
		Width:  width,
		Height: height,
		Data:   make([]byte, width*height*4),
	}
}

// Update applies a partial region update to the pixel buffer.
// The data slice must contain w*h*4 bytes of RGBA pixel data.
// Pixels outside the buffer bounds are silently clipped.
func (p *PixelBuffer) Update(x, y, w, h int, data []byte) {
	p.mu.Lock()
	defer p.mu.Unlock()

	for row := 0; row < h; row++ {
		destY := y + row
		if destY < 0 || destY >= p.Height {
			continue
		}

		srcOffset := row * w * 4
		destOffset := (destY*p.Width + x) * 4

		// Calculate how many pixels we can copy on this row
		copyWidth := w
		if x < 0 {
			srcOffset += (-x) * 4
			copyWidth += x // reduce by the clipped amount
			destOffset = destY * p.Width * 4
		}
		if x+copyWidth > p.Width {
			copyWidth = p.Width - x
			if x < 0 {
				copyWidth = p.Width
			}
		}
		if copyWidth <= 0 {
			continue
		}

		srcEnd := srcOffset + copyWidth*4
		if srcEnd > len(data) {
			srcEnd = len(data)
		}
		if srcOffset >= len(data) {
			continue
		}

		destEnd := destOffset + copyWidth*4
		if destEnd > len(p.Data) {
			destEnd = len(p.Data)
		}

		copy(p.Data[destOffset:destEnd], data[srcOffset:srcEnd])
	}

	p.dirty = true
}

// GetFrame returns a copy of the full pixel buffer.
// The caller receives an independent copy that will not be affected
// by subsequent updates.
func (p *PixelBuffer) GetFrame() []byte {
	p.mu.RLock()
	defer p.mu.RUnlock()

	frame := make([]byte, len(p.Data))
	copy(frame, p.Data)
	return frame
}

// IsDirty reports whether the buffer has been updated since the last
// ClearDirty call.
func (p *PixelBuffer) IsDirty() bool {
	p.mu.RLock()
	defer p.mu.RUnlock()
	return p.dirty
}

// ClearDirty resets the dirty flag. Typically called after a frame has
// been sent to the frontend.
func (p *PixelBuffer) ClearDirty() {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.dirty = false
}
