use std::io::Write;

use crate::protocol::{ProtocolWriter, ProtocolWriterResult};

pub fn encode_tile<W: Write>(
    data: [u8; 256], 
    quality: u8, 
    writer: &mut ProtocolWriter<W>
) -> ProtocolWriterResult<()> {
    let mut coefs: [i32; 256] = [0; 256];

    for i in 0..256 {
        coefs[i] = data[i] as i32;
    }

    let mut transform = |zero: usize, stride: usize| {
        let mut ixs = [0; 16];
        ixs[0] = zero;
        for i in 1..16 {
            ixs[i] = ixs[i - 1] + stride;
        }

        for bit in [8, 4, 2, 1] {
            for i in 0..16 {
                if i & bit == 0 {
                    let j = i | bit;

                    let ival = coefs[ixs[i]];
                    let jval = coefs[ixs[j]];

                    coefs[ixs[i]] = ival.wrapping_add(jval);
                    coefs[ixs[j]] = ival.wrapping_sub(jval);
                }
            }
        }
    };

    for col in 0..16 {
        transform(col, 16);
    }

    for row in 0..16 {
        transform(row * 16, 1);
    }

    // this operation can be efficiently implemented as a heapsort in C
    // but the code is ugly
    // so just use Rust's default sort
    let mut indices = [0; 256];
    for i in 0..256 {
        indices[i] = i;
    }
    indices.sort_by_key(|ix| -(coefs[*ix]).abs());

    // write indices and values
    // (note that the first index will almost always be zero)
    for i in 0..quality {
        let ix = indices[i as usize];
        writer.write_u8(ix as u8)?;
        writer.write_u16(coefs[ix] as u16)?;
    };
    Ok(())
}

pub fn encode_value(value: i32) -> u8 {
    let value = value / 16;
    let sign = value < 0;
    let mut rescaled = value.abs() + 33;
    let mut exponent = 0;

    while rescaled > 64 {
        exponent += 1;
        rescaled >>= 1;
    }

    let mantissa = (rescaled >> 1) & 0xf;

    return (
        if sign { 128 } else { 0 } |
        exponent << 4 |
        mantissa
    ) as u8;
}

#[cfg(test)]
mod test {
    use quickcheck_macros::quickcheck;

    use crate::decode_tile::decode_value;

    use super::encode_value;

    #[quickcheck]
    fn test_encode_value_2(val: u16) -> bool {
        let val = (val & (0x8000 | 0x3fff)) as i16;
        let canon = decode_value(encode_value(val as i32));

        canon == decode_value(encode_value(canon as i32))
    }

    #[test]
    fn test_encode_value_1() {
        // ...01abcdx cases
        assert_eq!(0b00001111, encode_value(0 + (0b111110 - 33)));
        assert_eq!(0b00001110, encode_value(0 + (0b111100 - 33)));
        assert_eq!(0b10001111, encode_value(0 - (0b111110 - 33)));
        assert_eq!(0b10001110, encode_value(0 - (0b111100 - 33)));

        // ...01abcdxx cases
        assert_eq!(0b00011111, encode_value(0 + (0b1111101 - 33)));
        assert_eq!(0b00011110, encode_value(0 + (0b1111001 - 33)));
        assert_eq!(0b10011111, encode_value(0 - (0b1111101 - 33)));
        assert_eq!(0b10011110, encode_value(0 - (0b1111001 - 33)));

        // ...01abcdxxx cases
        assert_eq!(0b00101111, encode_value(0 + (0b11111000 - 33)));
        assert_eq!(0b00101110, encode_value(0 + (0b11110001 - 33)));
        assert_eq!(0b10101111, encode_value(0 - (0b11111010 - 33)));
        assert_eq!(0b10101110, encode_value(0 - (0b11110011 - 33)));

        // ...01abcdxxxx cases
        assert_eq!(0b00111111, encode_value(0 + (0b111110000 - 33)));
        assert_eq!(0b00111110, encode_value(0 + (0b111100010 - 33)));
        assert_eq!(0b10111111, encode_value(0 - (0b111110100 - 33)));
        assert_eq!(0b10111110, encode_value(0 - (0b111100110 - 33)));

        // ...01abcdxxxxx cases
        assert_eq!(0b01001111, encode_value(0 + (0b1111100000 - 33)));
        assert_eq!(0b01001110, encode_value(0 + (0b1111000100 - 33)));
        assert_eq!(0b11001111, encode_value(0 - (0b1111101000 - 33)));
        assert_eq!(0b11001110, encode_value(0 - (0b1111001100 - 33)));

        // ...01abcdxxxxxx cases
        assert_eq!(0b01011111, encode_value(0 + (0b11111000000 - 33)));
        assert_eq!(0b01011110, encode_value(0 + (0b11110001000 - 33)));
        assert_eq!(0b11011111, encode_value(0 - (0b11111010000 - 33)));
        assert_eq!(0b11011110, encode_value(0 - (0b11110011000 - 33)));

        // ...01abcdxxxxxxx cases
        assert_eq!(0b01101111, encode_value(0 + (0b111110000000 - 33)));
        assert_eq!(0b01101110, encode_value(0 + (0b111100010000 - 33)));
        assert_eq!(0b11101111, encode_value(0 - (0b111110100000 - 33)));
        assert_eq!(0b11101110, encode_value(0 - (0b111100110000 - 33)));

        // ...01abcdxxxxxxx cases
        assert_eq!(0b01111111, encode_value(0 + (0b1111100000000 - 33)));
        assert_eq!(0b01111110, encode_value(0 + (0b1111000100001 - 33)));
        assert_eq!(0b11111111, encode_value(0 - (0b1111101000010 - 33)));
        assert_eq!(0b11111110, encode_value(0 - (0b1111001100011 - 33)));
    }
}