use byteorder::ReadBytesExt;
use snafu::{ResultExt, Snafu};
use std::io;
use std::io::{Cursor, Read};
use std::string::FromUtf8Error;
use varint::VarintRead;

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Unable to read unsigned VarInt32: {}", source))]
    ReadUnsignedVarInt { source: io::Error },
    #[snafu(display("Unable to convert data to UTF8-String: {}", source))]
    ReadUTF8String { source: FromUtf8Error },
    #[snafu(display("Unable to read fixed length {} data: {}", len, source))]
    ReadFixedLengthData { len: usize, source: io::Error },
}

type Result<T, E = Error> = std::result::Result<T, E>;

pub trait VarIntReader: VarintRead {
    fn read_varint_string(&mut self) -> Result<String> {
        let varint = self.read_unsigned_varint_32().context(ReadUnsignedVarInt)?;
        let mut len: usize = (varint >> 1) as usize;
        if (varint & 1) != 0 {
            len = !len;
        }

        let mut data: Vec<u8> = vec![0; len];
        self.read_exact(&mut data)
            .context(ReadFixedLengthData { len })?;
        String::from_utf8(data).context(ReadUTF8String)
    }

    fn read_bool(&mut self) -> Result<bool> {
        let d = self
            .read_u8()
            .context(ReadFixedLengthData { len: 1_usize })?;
        Ok(!matches!(d, 0))
    }
}

pub trait PackWidthReader: Read {
    fn read_pack_width_long(&mut self, pos: i32, width: i32) -> Result<i64> {
        let mut data: Vec<u8> = vec![0; width as usize];
        self.read_exact(&mut data).context(ReadFixedLengthData {
            len: width as usize,
        })?;

        let mut temp: i32;
        let mut value: i64 = 0;
        for i in 0..width {
            temp = (pos + width - 1 - i) / 8;
            let mut offset = pos + width - 1 - i;
            offset %= 8;
            let byte = if ((0xff & data[temp as usize]) & (1 << (7 - offset))) != 0 {
                1
            } else {
                0
            };
            let offset = i % 64;
            value = if byte == 1 {
                value | (1 << (offset))
            } else {
                value & (1 << (offset))
            }
        }
        Ok(value)
    }
}

impl VarIntReader for Cursor<Vec<u8>> {}

impl PackWidthReader for Cursor<Vec<u8>> {}
