// Library for working with webcams and other video capturing devices.
// It depends entirely on v4l2 framework, thus will compile and work
// only on Linux machine
package webcam

import (
	"errors"
	"fmt"
	"reflect"
	"unsafe"

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

// Webcam object
type Webcam struct {
	fd        uintptr
	bufcount  uint32
	buffers   [][]byte
	streaming bool
	pollFds   []unix.PollFd
}

type ControlID uint32

type Control struct {
	Name string
	Min  int32
	Max  int32
	Type int32
	Step int32
}

// Open a webcam with a given path
// Checks if device is a v4l2 device and if it is
// capable to stream video
func Open(path string) (*Webcam, error) {
	handle, err := unix.Open(path, unix.O_RDWR|unix.O_NONBLOCK, 0666)
	if err != nil {
		return nil, err
	}
	if handle < 0 {
		return nil, fmt.Errorf("failed to open %v", path)
	}
	// At this point the handle is valid and must be return or closed
	success := false // If this is not set true prior to function exit we assume error and close the handle
	defer func() {
		if !success {
			// Since the handle is not returned on error we must close it or leak
			unix.Close(handle)
		}
	}()
	fd := uintptr(handle)

	supportsVideoCapture, supportsVideoStreaming, err := checkCapabilities(fd)

	if err != nil {
		return nil, err
	}

	if !supportsVideoCapture {
		return nil, errors.New("Not a video capture device")
	}

	if !supportsVideoStreaming {
		return nil, errors.New("Device does not support the streaming I/O method")
	}

	w := new(Webcam)
	w.fd = fd
	w.bufcount = 256
	w.pollFds = []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN}}
	success = true
	return w, nil
}

// Returns image formats supported by the device alongside with
// their text description
// Note that this function is somewhat experimental. Frames are not ordered in
// any meaning, also duplicates can occur so it's up to developer to clean it up.
// See http://linuxtv.org/downloads/v4l-dvb-apis/vidioc-enum-framesizes.html
// for more information
func (w *Webcam) GetSupportedFormats() map[PixelFormat]string {

	result := make(map[PixelFormat]string)
	var err error
	var code uint32
	var desc string
	var index uint32

	for index = 0; err == nil; index++ {
		code, desc, err = getPixelFormat(w.fd, index)

		if err != nil {
			break
		}

		result[PixelFormat(code)] = desc
	}

	return result
}

// GetName returns the human-readable name of the device
func (w *Webcam) GetName() (string, error) {
	return getName(w.fd)
}

// GetBusInfo returns the location of the device in the system
func (w *Webcam) GetBusInfo() (string, error) {
	return getBusInfo(w.fd)
}

// SelectInput selects the current video input.
func (w *Webcam) SelectInput(index uint32) error {
	return selectInput(w.fd, index)
}

// GetInput queries the current video input.
func (w *Webcam) GetInput() (int32, error) {
	return getInput(w.fd)
}

// Returns supported frame sizes for a given image format
func (w *Webcam) GetSupportedFrameSizes(f PixelFormat) []FrameSize {
	result := make([]FrameSize, 0)

	var index uint32
	var err error

	for index = 0; err == nil; index++ {
		s, err := getFrameSize(w.fd, index, uint32(f))

		if err != nil {
			break
		}

		result = append(result, s)
	}

	return result
}

// GetSupportedFramerates returns supported frame rates for a given image format and frame size.
func (w *Webcam) GetSupportedFramerates(fp PixelFormat, width uint32, height uint32) []FrameRate {
	var result []FrameRate
	var index uint32
	var err error

	// keep incrementing the index value until we get an EINVAL error
	index = 0
	for err == nil {
		r, err := getFrameInterval(w.fd, index, uint32(fp), width, height)
		if err != nil {
			break
		}
		result = append(result, r)
		index++
	}

	return result
}

// Sets desired image format and frame size
// Note, that device driver can change that values.
// Resulting values are returned by a function
// alongside with an error if any
func (w *Webcam) SetImageFormat(f PixelFormat, width, height uint32) (PixelFormat, uint32, uint32, error) {

	code := uint32(f)
	cw := width
	ch := height

	err := setImageFormat(w.fd, &code, &width, &height)

	if err != nil {
		return 0, 0, 0, err
	} else {
		return PixelFormat(code), cw, ch, nil
	}
}

// Set the number of frames to be buffered.
// Not allowed if streaming is already on.
func (w *Webcam) SetBufferCount(count uint32) error {
	if w.streaming {
		return errors.New("Cannot set buffer count when streaming")
	}
	w.bufcount = count
	return nil
}

// Get a map of available controls.
func (w *Webcam) GetControls() map[ControlID]Control {
	cmap := make(map[ControlID]Control)
	for _, c := range queryControls(w.fd) {
		cmap[ControlID(c.id)] = Control{
			Name: c.name,
			Min:  c.min,
			Max:  c.max,
			Type: int32(c.c_type),
			Step: c.step,
		}
	}
	return cmap
}

// Get the value of a control.
func (w *Webcam) GetControl(id ControlID) (int32, error) {
	return getControl(w.fd, uint32(id))
}

// Set a control.
func (w *Webcam) SetControl(id ControlID, value int32) error {
	return setControl(w.fd, uint32(id), value)
}

// Get the framerate.
func (w *Webcam) GetFramerate() (float32, error) {
	return getFramerate(w.fd)
}

// Set FPS
func (w *Webcam) SetFramerate(fps float32) error {
	return setFramerate(w.fd, 1000, uint32(1000*(fps)))
}

// Start streaming process
func (w *Webcam) StartStreaming() error {
	if w.streaming {
		return errors.New("Already streaming")
	}

	err := mmapRequestBuffers(w.fd, &w.bufcount)

	if err != nil {
		return errors.New("Failed to map request buffers: " + string(err.Error()))
	}

	w.buffers = make([][]byte, w.bufcount, w.bufcount)
	for index, _ := range w.buffers {
		var length uint32

		buffer, err := mmapQueryBuffer(w.fd, uint32(index), &length)

		if err != nil {
			return errors.New("Failed to map memory: " + string(err.Error()))
		}

		w.buffers[index] = buffer
	}

	for index, _ := range w.buffers {

		err := mmapEnqueueBuffer(w.fd, uint32(index))

		if err != nil {
			return errors.New("Failed to enqueue buffer: " + string(err.Error()))
		}

	}

	err = startStreaming(w.fd)

	if err != nil {
		return errors.New("Failed to start streaming: " + string(err.Error()))
	}
	w.streaming = true

	return nil
}

// Read a single frame from the webcam
// If frame cannot be read at the moment
// function will return empty slice
func (w *Webcam) ReadFrame() ([]byte, error) {
	result, index, err := w.GetFrame()
	if err == nil {
		w.ReleaseFrame(index)
	}
	return result, err
}

// Get a single frame from the webcam and return the frame and
// the buffer index. To return the buffer, ReleaseFrame must be called.
// If frame cannot be read at the moment
// function will return empty slice
func (w *Webcam) GetFrame() ([]byte, uint32, error) {
	var index uint32
	var length uint32

	err := mmapDequeueBuffer(w.fd, &index, &length)

	if err != nil {
		return nil, 0, err
	}

	return w.buffers[int(index)][:length], index, nil

}

// Release the frame buffer that was obtained via GetFrame
func (w *Webcam) ReleaseFrame(index uint32) error {
	return mmapEnqueueBuffer(w.fd, index)
}

// Wait until frame could be read
func (w *Webcam) WaitForFrame(timeout uint32) error {

	count, err := waitForFrame(w.pollFds, timeout)

	if count < 0 || err != nil {
		return err
	} else if count == 0 {
		return new(Timeout)
	} else {
		return nil
	}
}

func (w *Webcam) StopStreaming() error {
	if !w.streaming {
		return errors.New("Request to stop streaming when not streaming")
	}
	w.streaming = false
	for _, buffer := range w.buffers {
		err := mmapReleaseBuffer(buffer)
		if err != nil {
			return err
		}
	}

	return stopStreaming(w.fd)
}

// Close the device
func (w *Webcam) Close() error {
	if w.streaming {
		w.StopStreaming()
	}

	err := unix.Close(int(w.fd))

	return err
}

// Sets automatic white balance correction
func (w *Webcam) SetAutoWhiteBalance(val bool) error {
	v := int32(0)
	if val {
		v = 1
	}
	return setControl(w.fd, V4L2_CID_AUTO_WHITE_BALANCE, v)
}

func gobytes(p unsafe.Pointer, n int) []byte {

	h := reflect.SliceHeader{uintptr(p), n, n}
	s := *(*[]byte)(unsafe.Pointer(&h))

	return s
}
