// Package mmal implements a hardware accelerated H264 encoder for raspberry pi.
// This package requires libmmal headers and libraries to be built.
// Reference: https://github.com/raspberrypi/userland/tree/master/interface/mmal
package mmal

// #cgo CFLAGS: -I/opt/vc/include
// #cgo LDFLAGS: -L/opt/vc/lib -lmmal -lmmal_core -lmmal_util -lmmal_vc_client -lbcm_host -lvcsm -lvcos
// #include "bridge.h"
import "C"
import (
	"fmt"
	"image"
	"io"
	"sync"
	"unsafe"

	"github.com/pion/mediadevices/pkg/codec"
	"github.com/pion/mediadevices/pkg/io/video"
	"github.com/pion/mediadevices/pkg/prop"
)

type encoder struct {
	engine C.Encoder
	r      video.Reader
	mu     sync.Mutex
	closed bool
	cntr   int
}

func statusToErr(status *C.Status) error {
	return fmt.Errorf("(status = %d) %s", int(status.code), C.GoString(status.msg))
}

func newEncoder(r video.Reader, p prop.Media, params Params) (codec.ReadCloser, error) {
	if params.KeyFrameInterval == 0 {
		params.KeyFrameInterval = 60
	}

	if params.BitRate == 0 {
		params.BitRate = 300000
	}

	e := encoder{
		r: video.ToI420(r),
	}
	status := C.enc_new(C.Params{
		width:              C.int(p.Width),
		height:             C.int(p.Height),
		bitrate:            C.uint(params.BitRate),
		key_frame_interval: C.uint(params.KeyFrameInterval),
	}, &e.engine)
	if status.code != 0 {
		return nil, statusToErr(&status)
	}

	return &e, nil
}

func (e *encoder) Read() ([]byte, func(), error) {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.closed {
		return nil, func() {}, io.EOF
	}

	img, release, err := e.r.Read()
	if err != nil {
		return nil, func() {}, err
	}
	defer release()
	imgReal := img.(*image.YCbCr)
	var y, cb, cr C.Slice
	y.data = (*C.uchar)(&imgReal.Y[0])
	y.len = C.int(len(imgReal.Y))
	cb.data = (*C.uchar)(&imgReal.Cb[0])
	cb.len = C.int(len(imgReal.Cb))
	cr.data = (*C.uchar)(&imgReal.Cr[0])
	cr.len = C.int(len(imgReal.Cr))

	var encodedBuffer *C.MMAL_BUFFER_HEADER_T
	status := C.enc_encode(&e.engine, y, cb, cr, &encodedBuffer)
	if status.code != 0 {
		return nil, func() {}, statusToErr(&status)
	}

	// GoBytes copies the C array to Go slice. After this, it's safe to release the C array
	encoded := C.GoBytes(unsafe.Pointer(encodedBuffer.data), C.int(encodedBuffer.length))
	// Release the buffer so that mmal can reuse this memory
	C.mmal_buffer_header_release(encodedBuffer)

	return encoded, func() {}, err
}

func (e *encoder) Controller() codec.EncoderController {
	return e
}

func (e *encoder) Close() error {
	e.mu.Lock()
	defer e.mu.Unlock()

	if e.closed {
		return nil
	}

	e.closed = true
	C.enc_close(&e.engine)
	return nil
}
