package vlc

/*
#cgo LDFLAGS: -lvlc
#include <vlc/vlc.h>
#include <stdlib.h>
*/
import "C"
import (
	"time"
	"unsafe"
)

// NavigationAction defines actions for navigating menus of VCDs, DVDs and BDs.
type NavigationAction uint

// Navigation actions.
const (
	// Activate selected navigation item.
	NavigationActionActivate NavigationAction = iota

	// Move selection up.
	NavigationActionUp

	// Move selection down.
	NavigationActionDown

	// Move selection left.
	NavigationActionLeft

	// Move selection right.
	NavigationActionRight
)

// Player is a media player used to play a single media file.
// For playing media lists (playlists) use ListPlayer instead.
type Player struct {
	player *C.libvlc_media_player_t
}

// NewPlayer creates an instance of a single-media player.
func NewPlayer() (*Player, error) {
	if err := inst.assertInit(); err != nil {
		return nil, err
	}

	player := C.libvlc_media_player_new(inst.handle)
	if player == nil {
		return nil, errOrDefault(getError(), ErrPlayerCreate)
	}

	return &Player{player: player}, nil
}

// Release destroys the media player instance.
func (p *Player) Release() error {
	if err := p.assertInit(); err != nil {
		return nil
	}

	C.libvlc_media_player_release(p.player)
	p.player = nil

	return nil
}

// Play plays the current media.
func (p *Player) Play() error {
	if err := p.assertInit(); err != nil {
		return err
	}
	if p.IsPlaying() {
		return nil
	}

	if C.libvlc_media_player_play(p.player) < 0 {
		return errOrDefault(getError(), ErrPlayerPlay)
	}

	return nil
}

// IsPlaying returns a boolean value specifying if the player is currently
// playing.
func (p *Player) IsPlaying() bool {
	if err := p.assertInit(); err != nil {
		return false
	}

	return C.libvlc_media_player_is_playing(p.player) != 0
}

// WillPlay returns true if the current media is not in a finished or
// error state.
func (p *Player) WillPlay() bool {
	if err := p.assertInit(); err != nil {
		return false
	}

	return C.libvlc_media_player_will_play(p.player) != 0
}

// Stop cancels the currently playing media, if there is one.
func (p *Player) Stop() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_stop(p.player)
	return getError()
}

// SetPause sets the pause state of the media player.
// Pass in `true` to pause the current media, or `false` to resume it.
func (p *Player) SetPause(pause bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_pause(p.player, C.int(boolToInt(pause)))
	return getError()
}

// TogglePause pauses or resumes the player, depending on its current status.
// Calling this method has no effect if there is no media.
func (p *Player) TogglePause() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_pause(p.player)
	return getError()
}

// CanPause returns true if the media player can be paused.
func (p *Player) CanPause() bool {
	if err := p.assertInit(); err != nil {
		return false
	}

	return C.libvlc_media_player_can_pause(p.player) != 0
}

// IsSeekable returns true if the current media is seekable.
func (p *Player) IsSeekable() bool {
	if err := p.assertInit(); err != nil {
		return false
	}

	return C.libvlc_media_player_is_seekable(p.player) != 0
}

// VideoOutputCount returns the number of video outputs the media player has.
func (p *Player) VideoOutputCount() int {
	if err := p.assertInit(); err != nil {
		return 0
	}

	return int(C.libvlc_media_player_has_vout(p.player))
}

// IsScrambled returns true if the media player is in a scrambled state.
func (p *Player) IsScrambled() bool {
	if err := p.assertInit(); err != nil {
		return false
	}

	return C.libvlc_media_player_program_scrambled(p.player) != 0
}

// PlaybackRate returns the playback rate of the media player.
//
//	NOTE: Depending on the underlying media, the returned rate may be
//	different from the real playback rate.
func (p *Player) PlaybackRate() float32 {
	if err := p.assertInit(); err != nil {
		return 0
	}

	return float32(C.libvlc_media_player_get_rate(p.player))
}

// SetPlaybackRate sets the playback rate of the media player.
//
//	NOTE: Depending on the underlying media, changing the playback rate
//	might not be supported.
func (p *Player) SetPlaybackRate(rate float32) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_rate(p.player, C.float(rate))
	return getError()
}

// SetFullScreen sets the fullscreen state of the media player.
// Pass in `true` to enable fullscreen, or `false` to disable it.
func (p *Player) SetFullScreen(fullscreen bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_set_fullscreen(p.player, C.int(boolToInt(fullscreen)))
	return getError()
}

// ToggleFullScreen toggles the fullscreen status of the player,
// on non-embedded video outputs.
func (p *Player) ToggleFullScreen() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_toggle_fullscreen(p.player)
	return getError()
}

// IsFullScreen returns the fullscreen status of the player.
func (p *Player) IsFullScreen() (bool, error) {
	if err := p.assertInit(); err != nil {
		return false, err
	}

	return C.libvlc_get_fullscreen(p.player) != C.int(0), nil
}

// Volume returns the volume of the player.
func (p *Player) Volume() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_audio_get_volume(p.player)), nil
}

// SetVolume sets the volume of the player.
func (p *Player) SetVolume(volume int) error {
	if err := p.assertInit(); err != nil {
		return err
	}
	if volume < 0 {
		return ErrInvalid
	}

	if C.libvlc_audio_set_volume(p.player, C.int(volume)) < 0 {
		return errOrDefault(getError(), ErrPlayerSetVolume)
	}

	return nil
}

// IsMuted returns a boolean value that specifies whether the audio
// output of the player is muted.
func (p *Player) IsMuted() (bool, error) {
	if err := p.assertInit(); err != nil {
		return false, err
	}

	return C.libvlc_audio_get_mute(p.player) > C.int(0), nil
}

// SetMute mutes or unmutes the audio output of the player.
//
//	NOTE: If there is no active audio playback stream, the mute status might
//	not be available. If digital pass-through (S/PDIF, HDMI, etc.) is in use,
//	muting may not be applicable.
//	Some audio output plugins do not support muting.
func (p *Player) SetMute(mute bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_audio_set_mute(p.player, C.int(boolToInt(mute)))
	return nil
}

// ToggleMute mutes or unmutes the audio output of the player, depending on
// the current status.
//
//	NOTE: If there is no active audio playback stream, the mute status might
//	not be available. If digital pass-through (S/PDIF, HDMI, etc.) is in use,
//	muting may not be applicable.
//	Some audio output plugins do not support muting.
func (p *Player) ToggleMute() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_audio_toggle_mute(p.player)
	return nil
}

// Media returns the current media of the player, if one exists.
func (p *Player) Media() (*Media, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	media := C.libvlc_media_player_get_media(p.player)
	if media == nil {
		return nil, nil
	}

	// This call will not release the media. Instead, it will decrement
	// the reference count increased by libvlc_media_player_get_media.
	C.libvlc_media_release(media)

	return &Media{media}, nil
}

// SetMedia sets the provided media as the current media of the player.
func (p *Player) SetMedia(m *Media) error {
	return p.setMedia(m)
}

// LoadMediaFromPath loads the media located at the specified path and sets
// it as the current media of the player.
func (p *Player) LoadMediaFromPath(path string) (*Media, error) {
	return p.loadMedia(path, true)
}

// LoadMediaFromURL loads the media located at the specified URL and sets
// it as the current media of the player.
func (p *Player) LoadMediaFromURL(url string) (*Media, error) {
	return p.loadMedia(url, false)
}

// SetAudioOutput sets the audio output to be used by the player. Any change
// will take effect only after playback is stopped and restarted. The audio
// output cannot be changed while playing.
func (p *Player) SetAudioOutput(output string) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	cOutput := C.CString(output)
	defer C.free(unsafe.Pointer(cOutput))

	if C.libvlc_audio_output_set(p.player, cOutput) != 0 {
		return errOrDefault(getError(), ErrAudioOutputSet)
	}

	return nil
}

// AudioOutputDevices returns the list of available devices for the
// audio output used by the media player.
//
//	NOTE: Not all audio outputs support this. An empty list of devices does
//	not imply that the audio output used by the player does not work.
//	Some audio output devices in the list might not work in some circumstances.
//	By default, it is recommended to not specify any explicit audio device.
func (p *Player) AudioOutputDevices() ([]*AudioOutputDevice, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	return parseAudioOutputDeviceList(C.libvlc_audio_output_device_enum(p.player))
}

// SetAudioOutputDevice sets the audio output device to be used by the
// media player. The list of available devices can be obtained using the
// Player.AudioOutputDevices method. Pass in an empty string as the `output`
// parameter in order to move the current audio output to the specified
// device immediately. This is the recommended usage.
//
//	NOTE: Passing an empty string as the `output` parameter is only supported
//	for libVLC 2.2.0 or later.
//	The syntax for the `device` parameter depends on the audio output.
//	Some audio output modules require further parameters.
//	Due to a design bug in libVLC, the method does not return an error if the
//	passed in device cannot be set.
func (p *Player) SetAudioOutputDevice(device, output string) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	var cOutput *C.char
	if output != "" {
		cOutput = C.CString(output)
		defer C.free(unsafe.Pointer(cOutput))
	}

	cDevice := C.CString(device)
	defer C.free(unsafe.Pointer(cDevice))

	C.libvlc_audio_output_device_set(p.player, cOutput, cDevice)
	return getError()
}

// StereoMode returns the stereo mode of the audio output used by the player.
func (p *Player) StereoMode() (StereoMode, error) {
	if err := p.assertInit(); err != nil {
		return StereoModeError, err
	}

	return StereoMode(C.libvlc_audio_get_channel(p.player)), nil
}

// SetStereoMode sets the stereo mode of the audio output used by the player.
//
//	NOTE: The audio output might not support all stereo modes.
func (p *Player) SetStereoMode(mode StereoMode) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_audio_set_channel(p.player, C.int(mode)) != 0 {
		return errOrDefault(getError(), ErrStereoModeSet)
	}

	return nil
}

// MediaLength returns media length in milliseconds.
func (p *Player) MediaLength() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_length(p.player)), nil
}

// MediaState returns the state of the current media.
func (p *Player) MediaState() (MediaState, error) {
	if err := p.assertInit(); err != nil {
		return MediaNothingSpecial, err
	}

	state := int(C.libvlc_media_player_get_state(p.player))
	return MediaState(state), nil
}

// MediaPosition returns media position as a
// float percentage between 0.0 and 1.0.
func (p *Player) MediaPosition() (float32, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	position := float32(C.libvlc_media_player_get_position(p.player))
	if position < 0 {
		return 0, errOrDefault(getError(), ErrMediaNotFound)
	}

	return position, nil
}

// SetMediaPosition sets media position as percentage between 0.0 and 1.0.
// Some formats and protocols do not support this.
func (p *Player) SetMediaPosition(pos float32) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_position(p.player, C.float(pos))
	return getError()
}

// MediaTime returns media time in milliseconds.
func (p *Player) MediaTime() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_time(p.player)), nil
}

// SetMediaTime sets the media time in milliseconds. Some formats and
// protocols do not support this.
func (p *Player) SetMediaTime(t int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_time(p.player, C.libvlc_time_t(int64(t)))
	return getError()
}

// NextFrame displays the next video frame, if supported.
func (p *Player) NextFrame() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_next_frame(p.player)
	return getError()
}

// Scale returns the scaling factor of the current video. A scaling factor
// of zero means the video is configured to fit in the available space.
func (p *Player) Scale() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_scale(p.player)), nil
}

// SetScale sets the scaling factor of the current video. The scaling factor
// is the ratio of the number of pixels displayed on the screen to the number
// of pixels in the original decoded video. A scaling factor of zero adjusts
// the video to fit in the available space.
//
//	NOTE: Not all video outputs support scaling.
func (p *Player) SetScale(scale float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_scale(p.player, C.float(scale))
	return getError()
}

// AspectRatio returns the aspect ratio of the current video.
func (p *Player) AspectRatio() (string, error) {
	if err := p.assertInit(); err != nil {
		return "", err
	}

	aspectRatio := C.libvlc_video_get_aspect_ratio(p.player)
	if aspectRatio == nil {
		return "", getError()
	}
	defer C.free(unsafe.Pointer(aspectRatio))

	return C.GoString(aspectRatio), nil
}

// SetAspectRatio sets the aspect ratio of the current video (e.g. `16:9`).
//
//	NOTE: Invalid aspect ratios are ignored.
func (p *Player) SetAspectRatio(aspectRatio string) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	cAspectRatio := C.CString(aspectRatio)
	C.libvlc_video_set_aspect_ratio(p.player, cAspectRatio)
	C.free(unsafe.Pointer(cAspectRatio))
	return getError()
}

// SetDeinterlaceMode sets the deinterlace mode to use when rendering videos.
//
//	NOTE: pass in `vlc.DeinterlaceModeDisable` to disable deinterlacing.
func (p *Player) SetDeinterlaceMode(mode DeinterlaceMode) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	cMode := C.CString(string(mode))
	C.libvlc_video_set_deinterlace(p.player, cMode)
	C.free(unsafe.Pointer(cMode))
	return nil
}

// VideoAdjustmentsEnabled returns true if video adjustments are enabled.
// By default, video adjustments are not enabled.
func (p *Player) VideoAdjustmentsEnabled(enable bool) (bool, error) {
	if err := p.assertInit(); err != nil {
		return false, err
	}

	return C.libvlc_video_get_adjust_int(p.player, C.libvlc_adjust_Enable) != 0, nil
}

// EnableVideoAdjustments enables or disables video adjustments. By default,
// video adjustments are not enabled.
func (p *Player) EnableVideoAdjustments(enable bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_int(p.player, C.libvlc_adjust_Enable, C.int(boolToInt(enable)))
	return nil
}

// Contrast returns the contrast set to be used when rendering videos.
// The returned contrast is a value between 0.0 and 2.0.
// Default: 1.0.
func (p *Player) Contrast() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_adjust_float(p.player, C.libvlc_adjust_Contrast)), nil
}

// SetContrast sets the contrast to be used when rendering videos. The specified
// contrast must be a value between 0.0 and 2.0.
//
//	NOTE: this method has no effect if video adjustments are not enabled. The
//	adjustments can be enabled using the Player.EnableVideoAdjustments method.
func (p *Player) SetContrast(contrast float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_float(p.player, C.libvlc_adjust_Contrast, C.float(contrast))
	return nil
}

// Brightness returns the brightness set to be used when rendering videos.
// The returned brightness is a value between 0.0 and 2.0.
// Default: 1.0.
func (p *Player) Brightness() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_adjust_float(p.player, C.libvlc_adjust_Brightness)), nil
}

// SetBrightness sets the brightness to be used when rendering videos. The
// specified brightness must be a value between 0.0 and 2.0.
//
//	NOTE: this method has no effect if video adjustments are not enabled. The
//	adjustments can be enabled using the Player.EnableVideoAdjustments method.
func (p *Player) SetBrightness(brightness float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_float(p.player, C.libvlc_adjust_Brightness, C.float(brightness))
	return nil
}

// Hue returns the hue set to be used when rendering videos.
// The returned hue is a value between -180.0 and 180.0.
// Default: 0.0.
func (p *Player) Hue() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_adjust_float(p.player, C.libvlc_adjust_Hue)), nil
}

// SetHue sets the hue to be used when rendering videos. The specified hue
// must be a value between -180.0 and 180.0.
//
//	NOTE: this method has no effect if video adjustments are not enabled. The
//	adjustments can be enabled using the Player.EnableVideoAdjustments method.
func (p *Player) SetHue(hue float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_float(p.player, C.libvlc_adjust_Hue, C.float(hue))
	return nil
}

// Saturation returns the saturation set to be used when rendering videos.
// The returned saturation is a value between 0.0 and 3.0.
// Default: 1.0.
func (p *Player) Saturation() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_adjust_float(p.player, C.libvlc_adjust_Saturation)), nil
}

// SetSaturation sets the saturation to be used when rendering videos. The
// specified saturation must be a value between 0.0 and 3.0.
//
//	NOTE: this method has no effect if video adjustments are not enabled. The
//	adjustments can be enabled using the Player.EnableVideoAdjustments method.
func (p *Player) SetSaturation(saturation float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_float(p.player, C.libvlc_adjust_Saturation, C.float(saturation))
	return nil
}

// Gamma returns the gamma set to be used when rendering videos.
// The returned gamma is a value between 0.01 and 10.0.
// Default: 1.0.
func (p *Player) Gamma() (float64, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return float64(C.libvlc_video_get_adjust_float(p.player, C.libvlc_adjust_Gamma)), nil
}

// SetGamma sets the gamma to be used when rendering videos. The specified
// gamma must be a value between 0.01 and 10.0.
//
//	NOTE: this method has no effect if video adjustments are not enabled. The
//	adjustments can be enabled using the Player.EnableVideoAdjustments method.
func (p *Player) SetGamma(gamma float64) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_adjust_float(p.player, C.libvlc_adjust_Gamma, C.float(gamma))
	return nil
}

// AudioDelay returns the delay of the current audio track,
// with microsecond precision.
func (p *Player) AudioDelay() (time.Duration, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	delay := C.libvlc_audio_get_delay(p.player)
	return time.Duration(delay) * time.Microsecond, nil
}

// SetAudioDelay delays the current audio track according to the
// specified duration, with microsecond precision.
// The delay can be either positive (the audio track is played later) or
// negative (the audio track is played earlier), and it defaults to zero.
//
//	NOTE: The audio delay is set to zero each time the player media changes.
func (p *Player) SetAudioDelay(d time.Duration) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_audio_set_delay(p.player, C.int64_t(d.Microseconds())) != 0 {
		return errOrDefault(getError(), ErrMediaTrackNotFound)
	}

	return nil
}

// SubtitleDelay returns the delay of the current subtitle track,
// with microsecond precision.
func (p *Player) SubtitleDelay() (time.Duration, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	delay := C.libvlc_video_get_spu_delay(p.player)
	return time.Duration(delay) * time.Microsecond, nil
}

// SetSubtitleDelay delays the current subtitle track according to the
// specified duration, with microsecond precision.
// The delay can be either positive (the subtitle track is displayed later) or
// negative (the subtitle track is displayed earlier), and it defaults to zero.
//
//	NOTE: The subtitle delay is set to zero each time the player media changes.
func (p *Player) SetSubtitleDelay(d time.Duration) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_video_set_spu_delay(p.player, C.int64_t(d.Microseconds())) != 0 {
		return errOrDefault(getError(), ErrMediaTrackNotFound)
	}

	return nil
}

// VideoTrackCount returns the number of video tracks available
// in the current media of the player.
func (p *Player) VideoTrackCount() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	count := int(C.libvlc_video_get_track_count(p.player))
	if count < 0 {
		return 0, errOrDefault(getError(), ErrMediaNotInitialized)
	}

	return count, nil
}

// VideoTrackDescriptors returns a descriptor list of the available
// video tracks for the current player media.
func (p *Player) VideoTrackDescriptors() ([]*MediaTrackDescriptor, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	cDescriptors := C.libvlc_video_get_track_description(p.player)
	return parseMediaTrackDescriptorList(cDescriptors)
}

// VideoTrackID returns the ID of the current video track of the player.
//
//	NOTE: The method returns -1 if there is no active video track.
func (p *Player) VideoTrackID() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_video_get_track(p.player)), nil
}

// SetVideoTrack sets the track identified by the specified ID as the
// current video track of the player.
func (p *Player) SetVideoTrack(trackID int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_video_set_track(p.player, C.int(trackID)) != 0 {
		return errOrDefault(getError(), ErrInvalidMediaTrack)
	}

	return nil
}

// AudioTrackCount returns the number of audio tracks available
// in the current media of the player.
func (p *Player) AudioTrackCount() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	count := int(C.libvlc_audio_get_track_count(p.player))
	if count < 0 {
		return 0, errOrDefault(getError(), ErrMediaNotInitialized)
	}

	return count, nil
}

// AudioTrackDescriptors returns a descriptor list of the available
// audio tracks for the current player media.
func (p *Player) AudioTrackDescriptors() ([]*MediaTrackDescriptor, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	cDescriptors := C.libvlc_audio_get_track_description(p.player)
	return parseMediaTrackDescriptorList(cDescriptors)
}

// AudioTrackID returns the ID of the current audio track of the player.
//
//	NOTE: The method returns -1 if there is no active audio track.
func (p *Player) AudioTrackID() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_audio_get_track(p.player)), nil
}

// SetAudioTrack sets the track identified by the specified ID as the
// current audio track of the player.
func (p *Player) SetAudioTrack(trackID int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_audio_set_track(p.player, C.int(trackID)) != 0 {
		return errOrDefault(getError(), ErrInvalidMediaTrack)
	}

	return nil
}

// SubtitleTrackCount returns the number of subtitle tracks available
// in the current media of the player.
func (p *Player) SubtitleTrackCount() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	count := int(C.libvlc_video_get_spu_count(p.player))
	if count < 0 {
		return 0, errOrDefault(getError(), ErrMediaNotInitialized)
	}

	return count, nil
}

// SubtitleTrackDescriptors returns a descriptor list of the available
// subtitle tracks for the current player media.
func (p *Player) SubtitleTrackDescriptors() ([]*MediaTrackDescriptor, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	cDescriptors := C.libvlc_video_get_spu_description(p.player)
	return parseMediaTrackDescriptorList(cDescriptors)
}

// SubtitleTrackID returns the ID of the current subtitle track of the player.
//
//	NOTE: The method returns -1 if there is no active subtitle track.
func (p *Player) SubtitleTrackID() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_video_get_spu(p.player)), nil
}

// SetSubtitleTrack sets the track identified by the specified ID as the
// current subtitle track of the player.
func (p *Player) SetSubtitleTrack(trackID int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	if C.libvlc_video_set_spu(p.player, C.int(trackID)) != 0 {
		return errOrDefault(getError(), ErrInvalidMediaTrack)
	}

	return nil
}

// SetEqualizer sets an equalizer for the player. The equalizer can be applied
// at any time (whether media playback is started or not) and it will be used
// for subsequently played media instances as well. In order to revert to the
// default equalizer, pass in `nil` as the equalizer parameter.
func (p *Player) SetEqualizer(e *Equalizer) error {
	if err := p.assertInit(); err != nil {
		return err
	}
	if e == nil {
		e = &Equalizer{}
	}

	if C.libvlc_media_player_set_equalizer(p.player, e.equalizer) != 0 {
		return errOrDefault(getError(), ErrPlayerSetEqualizer)
	}

	return nil
}

// VideoDimensions returns the width and height of the current media of
// the player, in pixels.
//
//	NOTE: The dimensions can only be obtained for parsed media instances.
//	Either play the media or call one of the media parsing methods first.
func (p *Player) VideoDimensions() (uint, uint, error) {
	if err := p.assertInit(); err != nil {
		return 0, 0, err
	}

	var w, h C.uint
	if C.libvlc_video_get_size(p.player, 0, &w, &h) != 0 {
		return 0, 0, errOrDefault(getError(), ErrMissingMediaDimensions)
	}

	return uint(w), uint(h), nil
}

// CursorPosition returns the X and Y coordinates of the mouse cursor
// relative to the rendered area of the currently playing video.
//
//	NOTE: The coordinates are expressed in terms of the decoded video
//	resolution, not in terms of pixels on the screen. Either coordinate may
//	be negative or larger than the corresponding dimension of the video, if
//	the cursor is outside the rendering area.
//	The coordinates may be out of date if the pointer is not located on the
//	video rendering area. libVLC does not track the pointer if it is outside
//	of the video widget. Also, libVLC does not support multiple cursors.
func (p *Player) CursorPosition() (int, int, error) {
	if err := p.assertInit(); err != nil {
		return 0, 0, err
	}

	var x, y C.int
	if C.libvlc_video_get_cursor(p.player, 0, &x, &y) != 0 {
		return 0, 0, errOrDefault(getError(), ErrCursorPositionMissing)
	}

	return int(x), int(y), nil
}

// TakeSnapshot takes a snapshot of the current video and saves it at the
// specified output path. If the specified width is 0, the snapshot width
// will be calculated based on the specified height in order to preserve the
// original aspect ratio. Similarly, if the specified height is 0, the snapshot
// height will be calculated based on the specified width in order to preserve
// the original aspect ratio. If both the width and height values are 0, the
// original video size dimensions are used for the snapshot.
func (p *Player) TakeSnapshot(outputPath string, width, height uint) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	cOutputPath := C.CString(outputPath)
	defer C.free(unsafe.Pointer(cOutputPath))

	if C.libvlc_video_take_snapshot(p.player, 0, cOutputPath, C.uint(width), C.uint(height)) != 0 {
		return errOrDefault(getError(), ErrVideoSnapshot)
	}

	return nil
}

// TitleCount returns the number of titles in the currently playing media.
//
//	NOTE: The method returns -1 if the player does not have a media instance.
func (p *Player) TitleCount() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_title_count(p.player)), nil
}

// TitleIndex returns the index of the currently playing media title.
//
//	NOTE: The method returns -1 if the player does not have a media instance.
func (p *Player) TitleIndex() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_title(p.player)), nil
}

// SetTitle sets the title with the specified index to be played,
// if applicable to the current player media instance.
//
//	NOTE: The method has no effect if the current player media has no titles.
func (p *Player) SetTitle(titleIndex int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_title(p.player, C.int(titleIndex))
	return getError()
}

// ChapterIndex returns the index of the currently playing media chapter.
//
//	NOTE: The method returns -1 if the player does not have a media instance.
func (p *Player) ChapterIndex() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_chapter(p.player)), nil
}

// ChapterCount returns the number of chapters in the currently playing media.
//
//	NOTE: The method returns -1 if the player does not have a media instance.
func (p *Player) ChapterCount() (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_chapter_count(p.player)), nil
}

// SetChapter sets the chapter with the specified index to be played,
// if applicable to the current player media instance.
//
//	NOTE: The method has no effect if the current player media has no chapters.
func (p *Player) SetChapter(chapterIndex int) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_chapter(p.player, C.int(chapterIndex))
	return getError()
}

// NextChapter sets the next chapter to be played, if applicable to the
// current player media instance.
//
//	NOTE: The method has no effect if the current player media has no chapters.
func (p *Player) NextChapter() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_next_chapter(p.player)
	return getError()
}

// PreviousChapter sets the previous chapter to be played, if applicable to
// the current player media instance.
//
//	NOTE: The method has no effect if the current player media has no chapters.
func (p *Player) PreviousChapter() error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_previous_chapter(p.player)
	return getError()
}

// TitleChapterCount returns the number of chapters available within the media
// title with the specified index.
//
//	NOTE: The method returns -1 if the player does not have a media instance.
func (p *Player) TitleChapterCount(titleIndex int) (int, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return int(C.libvlc_media_player_get_chapter_count_for_title(p.player, C.int(titleIndex))), nil
}

// Navigate executes the specified action in order to navigate
// menus of VCDs, DVDs and BDs.
func (p *Player) Navigate(action NavigationAction) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_navigate(p.player, C.uint(action))
	return getError()
}

// SetTitleDisplayMode configures if and how the video title will be displayed.
// Pass in `vlc.PositionDisable` in order to prevent the video title from being
// displayed. The title is displayed after the specified `timeout`.
func (p *Player) SetTitleDisplayMode(position Position, timeout time.Duration) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_video_title_display(p.player, C.libvlc_position_t(position), C.uint(timeout.Milliseconds()))
	return getError()
}

// Marquee returns the marquee of the player.
func (p *Player) Marquee() *Marquee {
	return newMarquee(p)
}

// Logo returns the logo of the player.
func (p *Player) Logo() *Logo {
	return newLogo(p)
}

// XWindow returns the identifier of the X window the media player is
// configured to render its video output to, or 0 if no window is set.
// The window can be set using the SetXWindow method.
//
//	NOTE: The window identifier is returned even if the player is not
//	currently using it (for instance if it is playing an audio-only input).
func (p *Player) XWindow() (uint32, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return uint32(C.libvlc_media_player_get_xwindow(p.player)), getError()
}

// SetXWindow sets an X Window System drawable where the media player can
// render its video output. The call takes effect when the playback starts.
// If it is already started, it might need to be stopped before changes apply.
// If libVLC was built without X11 output support, calling this method has no
// effect.
//
//	NOTE: By default, libVLC captures input events on the video rendering area.
//	Use the SetMouseInput and SetKeyInput methods if you want to handle input
//	events in your application. By design, the X11 protocol delivers input
//	events to only one recipient.
func (p *Player) SetXWindow(windowID uint32) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_xwindow(p.player, C.uint(windowID))
	return getError()
}

// HWND returns the handle of the Windows API window the media player is
// configured to render its video output to, or 0 if no window is set.
// The window can be set using the SetHWND method.
//
//	NOTE: The window handle is returned even if the player is not currently
//	using it (for instance if it is playing an audio-only input).
func (p *Player) HWND() (uintptr, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return uintptr(C.libvlc_media_player_get_hwnd(p.player)), getError()
}

// SetHWND sets a Windows API window handle where the media player can render
// its video output. If libVLC was built without Win32/Win64 API output
// support, calling this method has no effect.
//
//	NOTE: By default, libVLC captures input events on the video rendering area.
//	Use the SetMouseInput and SetKeyInput methods if you want to handle input
//	events in your application.
func (p *Player) SetHWND(hwnd uintptr) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_hwnd(p.player, unsafe.Pointer(hwnd)) //nolint:govet
	return getError()
}

// NSObject returns the handler of the NSView the media player is configured
// to render its video output to, or 0 if no view is set. See SetNSObject.
func (p *Player) NSObject() (uintptr, error) {
	if err := p.assertInit(); err != nil {
		return 0, err
	}

	return uintptr(C.libvlc_media_player_get_nsobject(p.player)), getError()
}

// SetNSObject sets a NSObject handler where the media player can render
// its video output. Use the vout called "macosx". The object can be a NSView
// or a NSObject following the VLCVideoViewEmbedding protocol.
//
//	@protocol VLCVideoViewEmbedding <NSObject>
//	- (void)addVoutSubview:(NSView *)view;
//	- (void)removeVoutSubview:(NSView *)view;
//	@end
func (p *Player) SetNSObject(drawable uintptr) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_nsobject(p.player, unsafe.Pointer(drawable)) //nolint:govet
	return getError()
}

// SetKeyInput enables or disables key press event handling, according to the
// libVLC hotkeys configuration. By default, keyboard events are handled by
// the libVLC video widget.
//
//	NOTE: This method works only for X11 and Win32 at the moment.
//	NOTE: On X11, there can be only one subscriber for key press and mouse
//	click events per window. If your application has subscribed to these
//	events for the X window ID of the video widget, then libVLC will not be
//	able to handle key presses and mouse clicks.
func (p *Player) SetKeyInput(enable bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_key_input(p.player, C.uint(boolToInt(enable)))
	return getError()
}

// SetMouseInput enables or disables mouse click event handling. By default,
// mouse events are handled by the libVLC video widget. This is needed for DVD
// menus to work, as well as for a few video filters, such as "puzzle".
//
//	NOTE: This method works only for X11 and Win32 at the moment.
//	NOTE: On X11, there can be only one subscriber for key press and mouse
//	click events per window. If your application has subscribed to these
//	events for the X window ID of the video widget, then libVLC will not be
//	able to handle key presses and mouse clicks.
func (p *Player) SetMouseInput(enable bool) error {
	if err := p.assertInit(); err != nil {
		return err
	}

	C.libvlc_video_set_mouse_input(p.player, C.uint(boolToInt(enable)))
	return getError()
}

// EventManager returns the event manager responsible for the media player.
func (p *Player) EventManager() (*EventManager, error) {
	if err := p.assertInit(); err != nil {
		return nil, err
	}

	manager := C.libvlc_media_player_event_manager(p.player)
	if manager == nil {
		return nil, ErrMissingEventManager
	}

	return newEventManager(manager), nil
}

func (p *Player) loadMedia(path string, local bool) (*Media, error) {
	m, err := newMedia(path, local)
	if err != nil {
		return nil, err
	}

	if err = p.setMedia(m); err != nil {
		m.release()
		return nil, err
	}

	return m, nil
}

func (p *Player) setMedia(m *Media) error {
	if err := p.assertInit(); err != nil {
		return err
	}
	if err := m.assertInit(); err != nil {
		return err
	}

	C.libvlc_media_player_set_media(p.player, m.media)
	return getError()
}

func (p *Player) assertInit() error {
	if p == nil || p.player == nil {
		return ErrPlayerNotInitialized
	}

	return nil
}
