// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cmp::min;
use std::io;
use std::marker::PhantomData;
use std::{error, fmt};

use audio_streams::{
    capture::{CaptureBuffer, CaptureBufferStream},
    BufferDrop, PlaybackBuffer, PlaybackBufferStream,
};
use cras_sys::gen::{snd_pcm_format_t, CRAS_AUDIO_MESSAGE_ID, CRAS_STREAM_DIRECTION};
use sys_util::error;

use crate::audio_socket::{AudioMessage, AudioSocket};
use crate::cras_server_socket::CrasServerSocket;
use crate::cras_shm::*;

#[derive(Debug)]
pub enum Error {
    IoError(io::Error),
    MessageTypeError,
}

impl error::Error for Error {}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::IoError(ref err) => err.fmt(f),
            Error::MessageTypeError => write!(f, "Message type error"),
        }
    }
}

impl From<io::Error> for Error {
    fn from(io_err: io::Error) -> Error {
        Error::IoError(io_err)
    }
}

/// A trait controls the state of `CrasAudioHeader` and
/// interacts with server's audio thread through `AudioSocket`.
pub trait CrasStreamData<'a>: Send {
    // Creates `CrasStreamData` with only `AudioSocket`.
    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self;
    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a>;
    fn audio_sock_mut(&mut self) -> &mut AudioSocket;
}

/// `CrasStreamData` implementation for `PlaybackBufferStream`.
pub struct CrasPlaybackData<'a> {
    audio_sock: AudioSocket,
    header: CrasAudioHeader<'a>,
}

impl<'a> CrasStreamData<'a> for CrasPlaybackData<'a> {
    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self {
        Self { audio_sock, header }
    }

    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a> {
        &mut self.header
    }

    fn audio_sock_mut(&mut self) -> &mut AudioSocket {
        &mut self.audio_sock
    }
}

impl<'a> BufferDrop for CrasPlaybackData<'a> {
    fn trigger(&mut self, nframes: usize) {
        let log_err = |e| error!("BufferDrop error: {}", e);
        if let Err(e) = self.header.commit_written_frames(nframes as u32) {
            log_err(e);
        }
        if let Err(e) = self.audio_sock.data_ready(nframes as u32) {
            log_err(e);
        }
    }
}

/// `CrasStreamData` implementation for `CaptureBufferStream`.
pub struct CrasCaptureData<'a> {
    audio_sock: AudioSocket,
    header: CrasAudioHeader<'a>,
}

impl<'a> CrasStreamData<'a> for CrasCaptureData<'a> {
    fn new(audio_sock: AudioSocket, header: CrasAudioHeader<'a>) -> Self {
        Self { audio_sock, header }
    }

    fn header_mut(&mut self) -> &mut CrasAudioHeader<'a> {
        &mut self.header
    }

    fn audio_sock_mut(&mut self) -> &mut AudioSocket {
        &mut self.audio_sock
    }
}

impl<'a> BufferDrop for CrasCaptureData<'a> {
    fn trigger(&mut self, nframes: usize) {
        let log_err = |e| error!("BufferDrop error: {}", e);
        if let Err(e) = self.header.commit_read_frames(nframes as u32) {
            log_err(e);
        }
        if let Err(e) = self.audio_sock.capture_ready(nframes as u32) {
            log_err(e);
        }
    }
}

#[allow(dead_code)]
pub struct CrasStream<'a, T: CrasStreamData<'a> + BufferDrop> {
    stream_id: u32,
    server_socket: CrasServerSocket,
    block_size: u32,
    direction: CRAS_STREAM_DIRECTION,
    rate: usize,
    num_channels: usize,
    format: snd_pcm_format_t,
    /// A structure for stream to interact with server audio thread.
    controls: T,
    /// The `PhantomData` is used by `controls: T`
    phantom: PhantomData<CrasAudioHeader<'a>>,
    audio_buffer: CrasAudioBuffer,
}

impl<'a, T: CrasStreamData<'a> + BufferDrop> CrasStream<'a, T> {
    /// Creates a CrasStream by given arguments.
    ///
    /// # Returns
    /// `CrasStream` - CRAS client stream.
    pub fn try_new(
        stream_id: u32,
        server_socket: CrasServerSocket,
        block_size: u32,
        direction: CRAS_STREAM_DIRECTION,
        rate: usize,
        num_channels: usize,
        format: snd_pcm_format_t,
        audio_sock: AudioSocket,
        header_fd: CrasAudioShmHeaderFd,
        samples_fd: CrasShmFd,
    ) -> Result<Self, Error> {
        let (header, audio_buffer) = create_header_and_buffers(header_fd, samples_fd)?;

        Ok(Self {
            stream_id,
            server_socket,
            block_size,
            direction,
            rate,
            num_channels,
            format,
            controls: T::new(audio_sock, header),
            phantom: PhantomData,
            audio_buffer,
        })
    }

    fn wait_request_data(&mut self) -> Result<(), Error> {
        match self.controls.audio_sock_mut().read_audio_message()? {
            AudioMessage::Success { id, .. } => match id {
                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_REQUEST_DATA => Ok(()),
                _ => Err(Error::MessageTypeError),
            },
            _ => Err(Error::MessageTypeError),
        }
    }

    fn wait_data_ready(&mut self) -> Result<u32, Error> {
        match self.controls.audio_sock_mut().read_audio_message()? {
            AudioMessage::Success { id, frames } => match id {
                CRAS_AUDIO_MESSAGE_ID::AUDIO_MESSAGE_DATA_READY => Ok(frames),
                _ => Err(Error::MessageTypeError),
            },
            _ => Err(Error::MessageTypeError),
        }
    }
}

impl<'a, T: CrasStreamData<'a> + BufferDrop> Drop for CrasStream<'a, T> {
    /// A blocking drop function, sends the disconnect message to `CrasClient` and waits for
    /// the return message.
    /// Logs an error message to stderr if the method fails.
    fn drop(&mut self) {
        if let Err(e) = self.server_socket.disconnect_stream(self.stream_id) {
            error!("CrasStream::Drop error: {}", e);
        }
    }
}

impl<'a, T: CrasStreamData<'a> + BufferDrop> PlaybackBufferStream for CrasStream<'a, T> {
    fn next_playback_buffer(&mut self) -> Result<PlaybackBuffer, Box<dyn error::Error>> {
        // Wait for request audio message
        self.wait_request_data()?;
        let header = self.controls.header_mut();
        let frame_size = header.get_frame_size();
        let (offset, len) = header.get_write_offset_and_len()?;
        let buf = &mut self.audio_buffer.get_buffer()[offset..offset + len];

        PlaybackBuffer::new(frame_size, buf, &mut self.controls).map_err(Box::from)
    }
}

impl<'a, T: CrasStreamData<'a> + BufferDrop> CaptureBufferStream for CrasStream<'a, T> {
    fn next_capture_buffer(&mut self) -> Result<CaptureBuffer, Box<dyn error::Error>> {
        // Wait for data ready message
        let frames = self.wait_data_ready()?;
        let header = self.controls.header_mut();
        let frame_size = header.get_frame_size();
        let shm_frames = header.get_readable_frames()?;
        let len = min(shm_frames, frames as usize) * frame_size;
        let offset = header.get_read_buffer_offset()?;
        let buf = &mut self.audio_buffer.get_buffer()[offset..offset + len];

        CaptureBuffer::new(frame_size, buf, &mut self.controls).map_err(Box::from)
    }
}
