use std;
use std::io::{Write, Error, ErrorKind};

pub trait Writer {
    fn write_u8(&mut self, v: u8) -> std::io::Result<()>;
    fn write_u16(&mut self, v: u16) -> std::io::Result<()>;
    fn write_u32(&mut self, v: u32) -> std::io::Result<()>;
    fn write_u64(&mut self, v: u64) -> std::io::Result<()>;
    fn write_bytes(&mut self, v: &[u8]) -> std::io::Result<()>;
    fn write_str(&mut self, v: &str) -> std::io::Result<()>;
    fn write_string(&mut self, v: &String) -> std::io::Result<()>;
}

pub struct CountWriter {
    count: usize
}

impl CountWriter {
    pub fn new() -> CountWriter {
        CountWriter {
            count: 0
        }
    }

    pub fn count(&self) -> usize {
        return self.count
    }
}

impl Writer for CountWriter {
    fn write_u8(&mut self,  _: u8) -> std::io::Result<()> {
        self.count += 1;
        Ok(())
    }

    fn write_u16(&mut self, _: u16) -> std::io::Result<()> {
        self.count += 2;
        Ok(())
    }

    fn write_u32(&mut self, _: u32) -> std::io::Result<()> {
        self.count += 4;
        Ok(())
    }

    fn write_u64(&mut self, _: u64) -> std::io::Result<()> {
        self.count += 8;
        Ok(())
    }

    fn write_bytes(&mut self, v: &[u8]) -> std::io::Result<()> {
        self.count += v.len();
        Ok(())
    }

    fn write_str(&mut self, v: &str) -> std::io::Result<()> {
        self.count += 2 + v.len();
        Ok(())
    }

    fn write_string(&mut self, v: &String) -> std::io::Result<()> {
        match std::str::from_utf8(v.as_bytes()) {
            Ok(s) => self.write_str(s),
            Err(err) => {
                Err(Error::new(ErrorKind::Other, format!("encoding error: {}", err)))
            }
        }
    }
}

pub struct BasicWriter<'a> {
    write: &'a mut Write
}

impl<'a> BasicWriter<'a> {
    pub fn new(write: &mut Write) -> BasicWriter {
        BasicWriter {
            write: write
        }
    }
}

impl<'a> Writer for BasicWriter<'a> {
    fn write_u8(&mut self, v: u8) -> std::io::Result<()> {
        self.write.write_all(&[v])
    }

    fn write_u16(&mut self, v: u16) -> std::io::Result<()> {
        self.write.write_all(&[(v >> 8) as u8, (v & 0xff) as u8])
    }

    fn write_u32(&mut self, v: u32) -> std::io::Result<()> {
        self.write.write_all(&[
            (v >> 24) as u8,
            ((v >> 16) & 0xff) as u8,
            ((v >> 8) & 0xff) as u8,
            (v & 0xff) as u8
        ])
    }

    fn write_u64(&mut self, v: u64) -> std::io::Result<()> {
        self.write.write_all(&[
            (v >> 56) as u8,
            ((v >> 48) & 0xff) as u8,
            ((v >> 40) & 0xff) as u8,
            ((v >> 32) & 0xff) as u8,
            ((v >> 24) & 0xff) as u8,
            ((v >> 16) & 0xff) as u8,
            ((v >> 8) & 0xff) as u8,
            (v & 0xff) as u8
        ])
    }

    fn write_bytes(&mut self, buf: &[u8]) -> std::io::Result<()> {
        self.write.write_all(buf)
    }

    fn write_str(&mut self, buf: &str) -> std::io::Result<()> {
        try!(self.write_u16(buf.len() as u16));
        self.write_bytes(buf.as_bytes())
    }

    fn write_string(&mut self, v: &String) -> std::io::Result<()> {
        match std::str::from_utf8(v.as_bytes()) {
            Ok(s) => self.write_str(s),
            Err(err) => {
                Err(Error::new(ErrorKind::Other, format!("encoding error: {}", err)))
            }
        }
    }
}

