extern crate libc;
extern crate winapi;

use std::{mem, ptr};
use std::c_vec::CVec;

// TODO: determine if should be NoSend or not
pub struct Voice {
    audio_client: *mut winapi::IAudioClient,
    render_client: *mut winapi::IAudioRenderClient,
    max_frames_in_buffer: winapi::UINT32,
    num_channels: winapi::WORD,
    bytes_per_frame: winapi::WORD,
    samples_per_second: winapi::DWORD,
    bits_per_sample: winapi::WORD,
    playing: bool,
}

pub struct Buffer<'a, T> {
    render_client: *mut winapi::IAudioRenderClient,
    buffer: CVec<T>,
    frames: winapi::UINT32,
}

impl Voice {
    pub fn new() -> Voice {
        init().unwrap()
    }

    pub fn get_channels(&self) -> ::ChannelsCount {
        self.num_channels as ::ChannelsCount
    }

    pub fn get_samples_rate(&self) -> ::SamplesRate {
        ::SamplesRate(self.samples_per_second as u32)
    }

    pub fn get_samples_format(&self) -> ::SampleFormat {
        match self.bits_per_sample {
            16 => ::SampleFormat::U16,
            _ => unimplemented!(),
        }
    }

    pub fn append_data<'a, T>(&'a mut self, max_elements: uint) -> Buffer<'a, T> {
        unsafe {
            loop {
                // 
                let frames_available = {
                    let mut padding = mem::uninitialized();
                    let f = self.audio_client.as_mut().unwrap().lpVtbl
                                .as_ref().unwrap().GetCurrentPadding;
                    let hresult = f(self.audio_client, &mut padding);
                    check_result(hresult).unwrap();
                    self.max_frames_in_buffer - padding
                };

                if frames_available == 0 {
                    // TODO: 
                    ::std::io::timer::sleep(::std::time::duration::Duration::milliseconds(1));
                    continue;
                }

                let frames_available = ::std::cmp::min(frames_available,
                                                       max_elements as u32 * mem::size_of::<T>() as u32 /
                                                       self.bytes_per_frame as u32);
                assert!(frames_available != 0);

                // loading buffer
                let buffer: CVec<T> = {
                    let mut buffer: *mut winapi::BYTE = mem::uninitialized();
                    let f = self.render_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().GetBuffer;
                    let hresult = f(self.render_client, frames_available,
                                    &mut buffer as *mut *mut libc::c_uchar);
                    check_result(hresult).unwrap();
                    assert!(!buffer.is_null());

                    CVec::new(buffer as *mut T,
                              frames_available as uint * self.bytes_per_frame as uint
                                  / mem::size_of::<T>())
                };

                let buffer = Buffer {
                    render_client: self.render_client,
                    buffer: buffer,
                    frames: frames_available,
                };

                return buffer;
            }
        }
    }

    pub fn play(&mut self) {
        if !self.playing {
            unsafe {
                let f = self.audio_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().Start;
                let hresult = f(self.audio_client);
                check_result(hresult).unwrap();
            }
        }

        self.playing = true;
    }

    pub fn pause(&mut self) {
        if self.playing {
            unsafe {
                let f = self.audio_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().Stop;
                let hresult = f(self.audio_client);
                check_result(hresult).unwrap();
            }
        }

        self.playing = false;
    }
}

impl Drop for Voice {
    fn drop(&mut self) {
        unsafe {
            {
                let f = self.render_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().Release;
                f(self.render_client);
            }

            {
                let f = self.audio_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().Release;
                f(self.audio_client);
            }
        }
    }
}

impl<'a, T> Buffer<'a, T> {
    pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] {
        self.buffer.as_mut_slice()
    }

    pub fn finish(self) {
        // releasing buffer
        unsafe {
            let f = self.render_client.as_mut().unwrap().lpVtbl.as_ref().unwrap().ReleaseBuffer;
            let hresult = f(self.render_client, self.frames as u32, 0);
            check_result(hresult).unwrap();
        };
    }
}

fn init() -> Result<Voice, String> {
    // FIXME: release everything
    unsafe {
        try!(check_result(winapi::CoInitializeEx(::std::ptr::null_mut(), 0)));

        // building the devices enumerator object
        let enumerator = {
            let mut enumerator: *mut winapi::IMMDeviceEnumerator = ::std::mem::uninitialized();
            
            let hresult = winapi::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator,
                                                   ptr::null_mut(), winapi::CLSCTX_ALL,
                                                   &winapi::IID_IMMDeviceEnumerator,
                                                   mem::transmute(&mut enumerator));

            try!(check_result(hresult));
            enumerator.as_mut().unwrap()
        };

        // getting the default end-point
        let device = {
            let mut device: *mut winapi::IMMDevice = mem::uninitialized();
            let f = enumerator.lpVtbl.as_ref().unwrap().GetDefaultAudioEndpoint;
            let hresult = f(enumerator, winapi::EDataFlow::eRender, winapi::ERole::eConsole,
                            mem::transmute(&mut device));
            try!(check_result(hresult));
            device.as_mut().unwrap()
        };

        // activating in order to get a `IAudioClient`
        let audio_client = {
            let mut audio_client: *mut winapi::IAudioClient = mem::uninitialized();
            let f = device.lpVtbl.as_ref().unwrap().Activate;
            let hresult = f(device, &winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
                            ptr::null_mut(), mem::transmute(&mut audio_client));
            try!(check_result(hresult));
            audio_client.as_mut().unwrap()
        };

        // computing the format and initializing the device
        let format = {
            let format_attempt = winapi::WAVEFORMATEX {
                wFormatTag: 1,      // WAVE_FORMAT_PCM ; TODO: replace by constant
                nChannels: 2,
                nSamplesPerSec: 44100,
                nAvgBytesPerSec: 2 * 44100 * 2,
                nBlockAlign: (2 * 16) / 8,
                wBitsPerSample: 16,
                cbSize: 0,
            };

            let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized();
            let f = audio_client.lpVtbl.as_ref().unwrap().IsFormatSupported;
            let hresult = f(audio_client, winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
                            &format_attempt, &mut format_ptr);
            try!(check_result(hresult));

            let format = match format_ptr.as_ref() {
                Some(f) => f,
                None => &format_attempt,
            };

            let format_copy = ptr::read(format);

            let f = audio_client.lpVtbl.as_ref().unwrap().Initialize;
            let hresult = f(audio_client, winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
                            0, 10000000, 0, format, ptr::null());

            if !format_ptr.is_null() {
                winapi::CoTaskMemFree(format_ptr as *mut libc::c_void);
            }

            try!(check_result(hresult));

            format_copy
        };

        // 
        let max_frames_in_buffer = {
            let mut max_frames_in_buffer = mem::uninitialized();
            let f = audio_client.lpVtbl.as_ref().unwrap().GetBufferSize;
            let hresult = f(audio_client, &mut max_frames_in_buffer);
            try!(check_result(hresult));
            max_frames_in_buffer
        };

        // 
        let render_client = {
            let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized();
            let f = audio_client.lpVtbl.as_ref().unwrap().GetService;
            let hresult = f(audio_client, &winapi::IID_IAudioRenderClient,
                            mem::transmute(&mut render_client));
            try!(check_result(hresult));
            render_client.as_mut().unwrap()
        };

        Ok(Voice {
            audio_client: audio_client,
            render_client: render_client,
            max_frames_in_buffer: max_frames_in_buffer,
            num_channels: format.nChannels,
            bytes_per_frame: format.nBlockAlign,
            samples_per_second: format.nSamplesPerSec,
            bits_per_sample: format.wBitsPerSample,
            playing: false,
        })
    }
}

fn check_result(result: winapi::HRESULT) -> Result<(), String> {
    if result < 0 {
        return Err(::std::os::error_string(result as uint));        // TODO: 
    }

    Ok(())
}
