// This was originally based on code from: https://github.com/nwin/lzw
// Copyright (c) 2015 nwin
// which is under both Apache 2.0 and MIT

//! This module provides a bit writer
use std::io::{self, Write};

#[cfg(target_pointer_width = "64")]
#[macro_use]
mod arch_dep {
    /// The data type of the accumulator.
    /// a 64-bit value allows us to store more before
    /// each push to the vector, but is sub-optimal
    /// on 32-bit platforms.
    pub type AccType = u64;
    pub const FLUSH_AT: u8 = 48;
    /// Push pending bits to vector.
    /// Using a macro here since an inline function.
    /// didn't optimise properly.
    /// TODO June 2019: See if it's still needed.
    macro_rules! push {
        ($s:ident) => {
            $s.w.extend_from_slice(
                &[
                    $s.acc as u8,
                    ($s.acc >> 8) as u8,
                    ($s.acc >> 16) as u8,
                    ($s.acc >> 24) as u8,
                    ($s.acc >> 32) as u8,
                    ($s.acc >> 40) as u8,
                ][..],
            )
        };
    }
}
#[cfg(not(target_pointer_width = "64"))]
#[macro_use]
mod arch_dep {
    pub type AccType = u32;
    pub const FLUSH_AT: u8 = 16;
    macro_rules! push {
        ($s:ident) => {
            // Unlike the 64-bit case, using copy_from_slice seemed to worsen performance here.
            // TODO: Needs benching on a 32-bit system to see what works best.
            $s.w.push($s.acc as u8);
            $s.w.push(($s.acc >> 8) as u8);
        };
    }
}

use self::arch_dep::*;

/// Writes bits to a byte stream, LSB first.
pub struct LsbWriter {
    // Public for now so it can be replaced after initialization.
    pub w: Vec<u8>,
    bits: u8,
    acc: AccType,
}

impl LsbWriter {
    /// Creates a new bit reader
    pub const fn new(writer: Vec<u8>) -> LsbWriter {
        LsbWriter {
            w: writer,
            bits: 0,
            acc: 0,
        }
    }

    pub const fn pending_bits(&self) -> u8 {
        self.bits
    }

    /// Buffer n number of bits, and write them to the vec if there are enough pending bits.
    pub fn write_bits(&mut self, v: u16, n: u8) {
        // NOTE: This outputs garbage data if n is 0, but v is not 0
        self.acc |= (AccType::from(v)) << self.bits;
        self.bits += n;
        // Waiting until we have FLUSH_AT bits and pushing them all in one batch.
        while self.bits >= FLUSH_AT {
            push!(self);
            self.acc >>= FLUSH_AT;
            self.bits -= FLUSH_AT;
        }
    }

    fn write_bits_finish(&mut self, v: u16, n: u8) {
        // NOTE: This outputs garbage data if n is 0, but v is not 0
        self.acc |= (AccType::from(v)) << self.bits;
        self.bits += n % 8;
        while self.bits >= 8 {
            self.w.push(self.acc as u8);
            self.acc >>= 8;
            self.bits -= 8;
        }
    }

    pub fn flush_raw(&mut self) {
        let missing = FLUSH_AT - self.bits;
        // Have to test for self.bits > 0 here,
        // otherwise flush would output an extra byte when flush was called at a byte boundary
        if missing > 0 && self.bits > 0 {
            self.write_bits_finish(0, missing);
        }
    }
}

impl Write for LsbWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        if self.acc == 0 {
            self.w.extend_from_slice(buf)
        } else {
            for &byte in buf.iter() {
                self.write_bits(u16::from(byte), 8)
            }
        }
        Ok(buf.len())
    }

    fn flush(&mut self) -> io::Result<()> {
        self.flush_raw();
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::LsbWriter;

    #[test]
    fn write_bits() {
        let input = [
            (3, 3),
            (10, 8),
            (88, 7),
            (0, 2),
            (0, 5),
            (0, 0),
            (238, 8),
            (126, 8),
            (161, 8),
            (10, 8),
            (238, 8),
            (174, 8),
            (126, 8),
            (174, 8),
            (65, 8),
            (142, 8),
            (62, 8),
            (10, 8),
            (1, 8),
            (161, 8),
            (78, 8),
            (62, 8),
            (158, 8),
            (206, 8),
            (10, 8),
            (64, 7),
            (0, 0),
            (24, 5),
            (0, 0),
            (174, 8),
            (126, 8),
            (193, 8),
            (174, 8),
        ];
        let expected = [
            83, 192, 2, 220, 253, 66, 21, 220, 93, 253, 92, 131, 28, 125, 20, 2, 66, 157, 124, 60,
            157, 21, 128, 216, 213, 47, 216, 21,
        ];
        let mut writer = LsbWriter::new(Vec::new());
        for v in input.iter() {
            writer.write_bits(v.0, v.1);
        }
        writer.flush_raw();
        assert_eq!(writer.w, expected);
    }
}

#[cfg(all(test, feature = "benchmarks"))]
mod bench {
    use super::LsbWriter;
    use test_std::Bencher;
    #[bench]
    fn bit_writer(b: &mut Bencher) {
        let input = [
            (3, 3),
            (10, 8),
            (88, 7),
            (0, 2),
            (0, 5),
            (0, 0),
            (238, 8),
            (126, 8),
            (161, 8),
            (10, 8),
            (238, 8),
            (174, 8),
            (126, 8),
            (174, 8),
            (65, 8),
            (142, 8),
            (62, 8),
            (10, 8),
            (1, 8),
            (161, 8),
            (78, 8),
            (62, 8),
            (158, 8),
            (206, 8),
            (10, 8),
            (64, 7),
            (0, 0),
            (24, 5),
            (0, 0),
            (174, 8),
            (126, 8),
            (193, 8),
            (174, 8),
        ];
        let mut writer = LsbWriter::new(Vec::with_capacity(100));
        b.iter(|| {
            for v in input.iter() {
                let _ = writer.write_bits(v.0, v.1);
            }
        });
    }
}
