// yt - A fully featured command line YouTube client
//
// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of Yt.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.

//! This crate provides abstractions for
//! [libmpv](https://github.com/mpv-player/mpv/tree/master/libmpv) of the
//! [mpv media player](https://github.com/mpv-player/mpv).
//!
//! Libmpv requires `LC_NUMERIC` to be `C`, which should be the default value.
//!
//! Most of the documentation is paraphrased or even copied from the
//! [mpv manual](https://mpv.io/manual/master/),
//! if any questions arise it will probably answer them in much more depth than this documentation.
//!
//! # Examples
//!
//! See the 'examples' directory in the crate root.

// Procedure for updating to new libmpv:
// - make any nessecary API change (if so, bump crate version)
// - update MPV_CLIENT_API consts in lib.rs
// - run tests and examples to test whether they still work

#![allow(non_upper_case_globals)]

use std::fmt::Display;
use std::os::raw as ctype;

pub const MPV_CLIENT_API_MAJOR: ctype::c_ulong = 2;
pub const MPV_CLIENT_API_MINOR: ctype::c_ulong = 2;
pub const MPV_CLIENT_API_VERSION: ctype::c_ulong =
    MPV_CLIENT_API_MAJOR << 16 | MPV_CLIENT_API_MINOR;

mod mpv;
#[cfg(test)]
mod tests;

pub use crate::mpv::*;

/// A format mpv can use.
pub use libmpv2_sys::mpv_format as MpvFormat;
pub mod mpv_format {
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_DOUBLE as Double;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_FLAG as Flag;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_INT64 as Int64;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_NODE as Node;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_NODE_ARRAY as Array;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_NODE_MAP as Map;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_NONE as None;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_OSD_STRING as OsdString;
    pub use libmpv2_sys::mpv_format_MPV_FORMAT_STRING as String;
}

/// An libmpv2_sys mpv error.
pub use libmpv2_sys::mpv_error as MpvError;
pub mod mpv_error {
    pub use libmpv2_sys::mpv_error_MPV_ERROR_AO_INIT_FAILED as AoInitFailed;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_COMMAND as Command;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_EVENT_QUEUE_FULL as EventQueueFull;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_GENERIC as Generic;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_INVALID_PARAMETER as InvalidParameter;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_LOADING_FAILED as LoadingFailed;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_NOMEM as NoMem;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_NOTHING_TO_PLAY as NothingToPlay;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_NOT_IMPLEMENTED as NotImplemented;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_OPTION_ERROR as OptionError;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_OPTION_FORMAT as OptionFormat;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_OPTION_NOT_FOUND as OptionNotFound;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_PROPERTY_ERROR as PropertyError;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_PROPERTY_FORMAT as PropertyFormat;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_PROPERTY_NOT_FOUND as PropertyNotFound;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_PROPERTY_UNAVAILABLE as PropertyUnavailable;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_SUCCESS as Success;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_UNINITIALIZED as Uninitialized;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_UNKNOWN_FORMAT as UnknownFormat;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_UNSUPPORTED as Unsupported;
    pub use libmpv2_sys::mpv_error_MPV_ERROR_VO_INIT_FAILED as VoInitFailed;
}

/// Log verbosity level.
pub use libmpv2_sys::mpv_log_level as LogLevel;
pub mod mpv_log_level {
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_DEBUG as Debug;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_ERROR as Error;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_FATAL as Fatal;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_INFO as Info;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_NONE as None;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_TRACE as Trace;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_V as V;
    pub use libmpv2_sys::mpv_log_level_MPV_LOG_LEVEL_WARN as Warn;
}

/// The reason a file stopped.
#[derive(Debug, Clone, Copy)]
pub enum EndFileReason {
    /**
     * The end of file was reached. Sometimes this may also happen on
     * incomplete or corrupted files, or if the network connection was
     * interrupted when playing a remote file. It also happens if the
     * playback range was restricted with --end or --frames or similar.
     */
    Eof,

    /**
     * Playback was stopped by an external action (e.g. playlist controls).
     */
    Stop,

    /**
     * Playback was stopped by the quit command or player shutdown.
     */
    Quit,

    /**
     * Some kind of error happened that lead to playback abort. Does not
     * necessarily happen on incomplete or broken files (in these cases, both
     * MPV_END_FILE_REASON_ERROR or MPV_END_FILE_REASON_EOF are possible).
     *
     * mpv_event_end_file.error will be set.
     */
    Error,

    /**
     * The file was a playlist or similar. When the playlist is read, its
     * entries will be appended to the playlist after the entry of the current
     * file, the entry of the current file is removed, and a MPV_EVENT_END_FILE
     * event is sent with reason set to MPV_END_FILE_REASON_REDIRECT. Then
     * playback continues with the playlist contents.
     * Since API version 1.18.
     */
    Redirect,
}

impl From<libmpv2_sys::mpv_end_file_reason> for EndFileReason {
    fn from(value: libmpv2_sys::mpv_end_file_reason) -> Self {
        match value {
            0 => Self::Eof,
            2 => Self::Stop,
            3 => Self::Quit,
            4 => Self::Error,
            5 => Self::Redirect,
            _ => unreachable!("Other enum variants do not exist yet"),
        }
    }
}

impl Display for EndFileReason {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EndFileReason::Eof => f.write_str("The end of file was reached.")?,
            EndFileReason::Error => {
                f.write_str(
                    "Playback was stopped by an external action (e.g. playlist controls).",
                )?;
            }
            EndFileReason::Quit => {
                f.write_str("Playback was stopped by the quit command or player shutdown.")?;
            }
            EndFileReason::Redirect => {
                f.write_str("Some kind of error happened that lead to playback abort.")?;
            }
            EndFileReason::Stop => {
                f.write_str("The file was a playlist or similar.")?;
            }
        }

        Ok(())
    }
}