package ffmpeg

/*
#include <libavcodec/avcodec.h>

static enum AVMediaType codec_get_type(const AVCodec *c) {
    return c->type;
}
*/
import "C"
import "unsafe"

// CodecType represents the type of codec
type CodecType int

const (
	CodecTypeVideo   CodecType = CodecType(C.AVMEDIA_TYPE_VIDEO)
	CodecTypeAudio   CodecType = CodecType(C.AVMEDIA_TYPE_AUDIO)
	CodecTypeSubtitle CodecType = CodecType(C.AVMEDIA_TYPE_SUBTITLE)
)

// Codec represents an FFmpeg codec
type Codec struct {
	ptr *C.AVCodec
}

// FindDecoder finds a decoder by name
func FindDecoder(name string) (*Codec, error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	ptr := C.avcodec_find_decoder_by_name(cName)
	if ptr == nil {
		return nil, ErrCodecNotFound
	}
	return &Codec{ptr: ptr}, nil
}

// FindEncoder finds an encoder by name
func FindEncoder(name string) (*Codec, error) {
	cName := C.CString(name)
	defer C.free(unsafe.Pointer(cName))

	ptr := C.avcodec_find_encoder_by_name(cName)
	if ptr == nil {
		return nil, ErrCodecNotFound
	}
	return &Codec{ptr: ptr}, nil
}

// CodecFromC converts a C pointer to Codec
func CodecFromC(ptr *C.AVCodec) *Codec {
	return &Codec{ptr: ptr}
}

// CPtr returns the underlying C pointer
func (c *Codec) CPtr() *C.AVCodec {
	return c.ptr
}

// Name returns the codec name
func (c *Codec) Name() string {
	if c.ptr == nil {
		return ""
	}
	return C.GoString(c.ptr.name)
}

// LongName returns the codec long name
func (c *Codec) LongName() string {
	if c.ptr == nil {
		return ""
	}
	return C.GoString(c.ptr.long_name)
}

// Type returns the codec type
func (c *Codec) Type() CodecType {
	if c.ptr == nil {
		return CodecType(0)
	}
	t := C.codec_get_type(c.ptr)
	return CodecType(t)
}

// ID returns the codec ID
func (c *Codec) ID() int {
	if c.ptr == nil {
		return 0
	}
	return int(c.ptr.id)
}

// Context represents a codec context
type Context struct {
	ptr *C.AVCodecContext
}

// AllocContext allocates a codec context
func AllocContext() *Context {
	return &Context{
		ptr: C.avcodec_alloc_context3(nil),
	}
}

// Free frees the codec context
func (c *Context) Free() {
	if c.ptr != nil {
		C.avcodec_free_context(&c.ptr)
		c.ptr = nil
	}
}

// ContextFromC converts a C pointer to Context
func ContextFromC(ptr *C.AVCodecContext) *Context {
	return &Context{ptr: ptr}
}

// CPtr returns the underlying C pointer
func (c *Context) CPtr() *C.AVCodecContext {
	return c.ptr
}

// SetCodec sets the codec for this context
func (c *Context) SetCodec(codec *Codec) error {
	if c.ptr == nil || codec == nil || codec.ptr == nil {
		return ErrInvalidCodec
	}
	C.avcodec_free_context(&c.ptr)
	c.ptr = C.avcodec_alloc_context3(codec.ptr)
	if c.ptr == nil {
		return ErrInvalidCodec
	}
	return nil
}

// Width returns the width
func (c *Context) Width() int {
	if c.ptr == nil {
		return 0
	}
	return int(c.ptr.width)
}

// SetWidth sets the width
func (c *Context) SetWidth(width int) {
	if c.ptr != nil {
		c.ptr.width = C.int(width)
	}
}

// Height returns the height
func (c *Context) Height() int {
	if c.ptr == nil {
		return 0
	}
	return int(c.ptr.height)
}

// SetHeight sets the height
func (c *Context) SetHeight(height int) {
	if c.ptr != nil {
		c.ptr.height = C.int(height)
	}
}

// Format returns the pixel/sample format
func (c *Context) Format() int {
	if c.ptr == nil {
		return -1
	}
	return int(c.ptr.sample_fmt)
}

// SetFormat sets the pixel/sample format
func (c *Context) SetFormat(format int) {
	if c.ptr != nil {
		// Use unsafe.Pointer to assign to the enum field
		*(*C.int)(unsafe.Pointer(&c.ptr.sample_fmt)) = C.int(format)
	}
}

// BitRate returns the bit rate
func (c *Context) BitRate() int64 {
	if c.ptr == nil {
		return 0
	}
	return int64(c.ptr.bit_rate)
}

// SetBitRate sets the bit rate
func (c *Context) SetBitRate(br int64) {
	if c.ptr != nil {
		c.ptr.bit_rate = C.int64_t(br)
	}
}

// TimeBase returns the time base
func (c *Context) TimeBase() Rational {
	if c.ptr == nil {
		return Rational{}
	}
	return Rational{
		num: int(c.ptr.time_base.num),
		den: int(c.ptr.time_base.den),
	}
}

// SetTimeBase sets the time base
func (c *Context) SetTimeBase(r Rational) {
	if c.ptr != nil {
		c.ptr.time_base.num = C.int(r.num)
		c.ptr.time_base.den = C.int(r.den)
	}
}

// SetChannelLayout sets the channel layout
func (c *Context) SetChannelLayout(layout uint64) {
	if c.ptr != nil {
		c.ptr.channel_layout = C.uint64_t(layout)
	}
}

// SetSampleRate sets the sample rate
func (c *Context) SetSampleRate(rate int) {
	if c.ptr != nil {
		c.ptr.sample_rate = C.int(rate)
	}
}

// Channels returns the number of channels
func (c *Context) Channels() int {
	if c.ptr == nil {
		return 0
	}
	return int(c.ptr.channels)
}

// Open opens the codec
func (c *Context) Open(codec *Codec) error {
	if c.ptr == nil || codec == nil || codec.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_open2(c.ptr, codec.ptr, nil)
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to open codec",
			Op:      "Open",
		}
	}
	return nil
}

// CopyParameters copies parameters from CodecParameters to this context
func (c *Context) CopyParameters(cp *CodecParameters) error {
	if c.ptr == nil || cp == nil || cp.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_parameters_to_context(c.ptr, cp.ptr)
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to copy parameters",
			Op:      "CopyParameters",
		}
	}
	return nil
}

// SendPacket sends a packet to the decoder
func (c *Context) SendPacket(pkt *Packet) error {
	if c.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_send_packet(c.ptr, pkt.CPtr())
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to send packet",
			Op:      "SendPacket",
		}
	}
	return nil
}

// ReceiveFrame receives a frame from the decoder
func (c *Context) ReceiveFrame(frame *Frame) error {
	if c.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_receive_frame(c.ptr, frame.CPtr())
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to receive frame",
			Op:      "ReceiveFrame",
		}
	}
	return nil
}

// SendFrame sends a frame to the encoder
func (c *Context) SendFrame(frame *Frame) error {
	if c.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_send_frame(c.ptr, frame.CPtr())
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to send frame",
			Op:      "SendFrame",
		}
	}
	return nil
}

// ReceivePacket receives a packet from the encoder
func (c *Context) ReceivePacket(pkt *Packet) error {
	if c.ptr == nil {
		return ErrInvalidCodec
	}

	ret := C.avcodec_receive_packet(c.ptr, pkt.CPtr())
	if ret < 0 {
		return &FFmpegError{
			Code:    int(ret),
			Message: "failed to receive packet",
			Op:      "ReceivePacket",
		}
	}
	return nil
}

// Close closes the codec
func (c *Context) Close() {
	if c.ptr != nil {
		C.avcodec_close(c.ptr)
	}
}
