use crate::constants::TILE_SZ;

// This is the Walsh-Hadamard transform, specialized to 8px
// Ported from: 
// - https://colab.research.google.com/drive/1WjtKwUcqxWafAumFO9ET74RsckZSsw6n#scrollTo=e3r9-RBpcgwH
pub fn encode(data: &mut [i16], zero: usize, stride: usize) {
    transform(data, zero, stride, 0, 0, 1)
}

pub fn decode(data: &mut [i16], zero: usize, stride: usize) {
    transform(data, zero, stride, 0, 0, 8)
}

fn transform(data: &mut [i16], zero: usize, stride: usize, offset_in: i16, offset_out: i16, divisor: i16) {
    let ix = |t| zero + t * stride;

    let mut row = [0; TILE_SZ];
    for i in 0..TILE_SZ {
        row[i] = data[ix(i)] + offset_in;
    }

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

                let tmp = row[i];
                row[i] = row[i].wrapping_add(row[j]);
                row[j] = tmp.wrapping_sub(row[j]);
            }
        }
    }

    // reorder according to gray code
    // basically, this moves important stuff towards the left
    const GRAY: [usize; TILE_SZ] = [0, 4, 6, 2, 3, 7, 5, 1];

    for i in 0..TILE_SZ {
        data[ix(i)] = row[GRAY[i]] / divisor + offset_out;
    }
}

#[cfg(test)]
mod tests {
    use quickcheck::{quickcheck, Arbitrary};

    use crate::{constants::TILE_SZ, transform::{decode, encode}};

    #[derive(Clone, Debug)]
    struct Encodable { args: [i16; TILE_SZ] }

    impl Arbitrary for Encodable {
        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
            let mut args = [0; TILE_SZ];
            for i in 0..TILE_SZ {
                args[i] = (
                    i8::arbitrary(g).signum() as i32 * 
                    (i32::arbitrary(g) % 255 * 16)
                ) as i16;
            }
            return Encodable { args };
        }
    }

    quickcheck! {
        fn reversible(xs: Encodable) -> bool {
            let orig = xs.args;
            let mut enc = orig.clone();
            encode(&mut enc, 0, 1);
            let mut dec = enc.clone();
            decode(&mut dec, 0, 1);
            orig == dec
        }
    }
}
