use std::io::{self, Result};
use std::iter;
use std::slice;
use std::str;

use winapi::ctypes::c_void;
use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::NULL;
use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW, WriteConsoleW};
use winapi::um::wincon::{
    FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetLargestConsoleWindowSize,
    SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, INPUT_RECORD, SMALL_RECT,
};

use super::{result, Coord, Handle, HandleType, InputRecord, WindowPositions};

/// A wrapper around a screen buffer.
#[derive(Debug, Clone)]
pub struct Console {
    handle: Handle,
}

impl Console {
    /// Create new instance of `Console`.
    ///
    /// This created instance will use the default output handle (STD_OUTPUT_HANDLE) as handle for the function call it wraps.
    pub fn output() -> Result<Console> {
        Ok(Console {
            handle: Handle::new(HandleType::OutputHandle)?,
        })
    }

    /// Sets the attributes of characters written to the console screen buffer by the `WriteFile` or `WriteConsole` functions, or echoed by the `ReadFile` or `ReadConsole` functions.
    /// This function affects text written after the function call.
    ///
    /// The attributes is a bitmask of possible [character
    /// attributes](https://docs.microsoft.com/en-us/windows/console/console-screen-buffers#character-attributes).
    ///
    /// This wraps
    /// [`SetConsoleTextAttribute`](https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute).
    pub fn set_text_attribute(&self, value: u16) -> Result<()> {
        result(unsafe { SetConsoleTextAttribute(*self.handle, value) })?;
        Ok(())
    }

    /// Sets the current size and position of a console screen buffer's window.
    ///
    /// This wraps
    /// [`SetConsoleWindowInfo`](https://docs.microsoft.com/en-us/windows/console/setconsolewindowinfo).
    pub fn set_console_info(&self, absolute: bool, rect: WindowPositions) -> Result<()> {
        let absolute = match absolute {
            true => 1,
            false => 0,
        };
        let a = SMALL_RECT::from(rect);

        result(unsafe { SetConsoleWindowInfo(*self.handle, absolute, &a) })?;

        Ok(())
    }

    /// Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates.
    /// Returns the number of characters that have been written.
    ///
    /// This wraps
    /// [`FillConsoleOutputCharacterA`](https://docs.microsoft.com/en-us/windows/console/fillconsoleoutputcharacter).
    pub fn fill_whit_character(
        &self,
        start_location: Coord,
        cells_to_write: u32,
        filling_char: char,
    ) -> Result<u32> {
        let mut chars_written = 0;
        result(unsafe {
            // fill the cells in console with blanks
            FillConsoleOutputCharacterA(
                *self.handle,
                filling_char as i8,
                cells_to_write,
                COORD::from(start_location),
                &mut chars_written,
            )
        })?;

        Ok(chars_written)
    }

    /// Sets the character attributes for a specified number of character cells, beginning at the specified coordinates in a screen buffer.
    /// Returns the number of cells that have been modified.
    ///
    /// This wraps
    /// [`FillConsoleOutputAttribute`](https://docs.microsoft.com/en-us/windows/console/fillconsoleoutputattribute).
    pub fn fill_whit_attribute(
        &self,
        start_location: Coord,
        cells_to_write: u32,
        dw_attribute: u16,
    ) -> Result<u32> {
        let mut cells_written = 0;
        // Get the position of the current console window
        result(unsafe {
            FillConsoleOutputAttribute(
                *self.handle,
                dw_attribute,
                cells_to_write,
                COORD::from(start_location),
                &mut cells_written,
            )
        })?;

        Ok(cells_written)
    }

    /// Retrieves the size of the largest possible console window, based on the current text and the size of the display.
    ///
    /// This wraps [`GetLargestConsoleWindowSize`](https://docs.microsoft.com/en-us/windows/console/getlargestconsolewindowsize)
    pub fn largest_window_size(&self) -> Result<Coord> {
        crate::coord_result(unsafe { GetLargestConsoleWindowSize(*self.handle) })
    }

    /// Writes a character string to a console screen buffer beginning at the current cursor location.
    ///
    /// This wraps
    /// [`WriteConsoleW`](https://docs.microsoft.com/en-us/windows/console/writeconsole).
    pub fn write_char_buffer(&self, buf: &[u8]) -> Result<usize> {
        // get string from u8[] and parse it to an c_str
        let utf8 = match str::from_utf8(buf) {
            Ok(string) => string,
            Err(_) => {
                return Err(io::Error::new(
                    io::ErrorKind::Other,
                    "Could not parse to utf8 string",
                ));
            }
        };

        let utf16: Vec<u16> = utf8.encode_utf16().collect();
        let utf16_ptr: *const c_void = utf16.as_ptr() as *const _ as *const c_void;

        let mut cells_written: u32 = 0;

        result(unsafe {
            WriteConsoleW(
                *self.handle,
                utf16_ptr,
                utf16.len() as u32,
                &mut cells_written,
                NULL,
            )
        })?;

        Ok(utf8.as_bytes().len())
    }

    /// Read one input event.
    ///
    /// This wraps
    /// [`ReadConsoleInputW`](https://docs.microsoft.com/en-us/windows/console/readconsoleinput).
    pub fn read_single_input_event(&self) -> Result<InputRecord> {
        let mut record: INPUT_RECORD = INPUT_RECORD::default();

        {
            // Convert an INPUT_RECORD to an &mut [INPUT_RECORD] of length 1
            let buf = slice::from_mut(&mut record);
            let num_read = self.read_input(buf)?;

            // The windows API promises that ReadConsoleInput returns at least
            // 1 element
            debug_assert!(num_read == 1);
        }

        Ok(record.into())
    }

    /// Read all available input events without blocking.
    ///
    /// This wraps
    /// [`ReadConsoleInputW`](https://docs.microsoft.com/en-us/windows/console/readconsoleinput).
    pub fn read_console_input(&self) -> Result<Vec<InputRecord>> {
        let buf_len = self.number_of_console_input_events()?;

        // Fast-skipping all the code below if there is nothing to read at all
        if buf_len == 0 {
            return Ok(vec![]);
        }

        let mut buf: Vec<INPUT_RECORD> = iter::repeat_with(INPUT_RECORD::default)
            .take(buf_len as usize)
            .collect();

        let num_read = self.read_input(buf.as_mut_slice())?;

        Ok(buf
            .into_iter()
            .take(num_read)
            .map(InputRecord::from)
            .collect())
    }

    /// Get the number of available input events that can be read without blocking.
    ///
    /// This wraps
    /// [`GetNumberOfConsoleInputEvents`](https://docs.microsoft.com/en-us/windows/console/getnumberofconsoleinputevents).
    pub fn number_of_console_input_events(&self) -> Result<u32> {
        let mut buf_len: DWORD = 0;
        result(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) })?;
        Ok(buf_len)
    }

    /// Read input (via ReadConsoleInputW) into buf and return the number
    /// of events read. ReadConsoleInputW guarantees that at least one event
    /// is read, even if it means blocking the thread. buf.len() must fit in
    /// a u32.
    fn read_input(&self, buf: &mut [INPUT_RECORD]) -> Result<usize> {
        let mut num_records = 0;
        debug_assert!(buf.len() < std::u32::MAX as usize);

        result(unsafe {
            ReadConsoleInputW(
                *self.handle,
                buf.as_mut_ptr(),
                buf.len() as u32,
                &mut num_records,
            )
        })?;

        Ok(num_records as usize)
    }
}

impl From<Handle> for Console {
    /// Create a `Console` instance who's functions will be executed on the the given `Handle`
    fn from(handle: Handle) -> Self {
        Console { handle }
    }
}
