use crate::{Error, Read, Reader, V21, V32, V64, Write, Writer, cold_path};

impl V21 {
    #[inline]
    pub const fn to_array(self) -> [u8; 3] {
        let n = self.0;
        [n as u8 | 0x80, (n >> 7) as u8 | 0x80, (n >> 14) as u8]
    }
}

impl<'a> Read<'a> for V21 {
    fn read(buf: &mut Reader) -> Result<Self, Error> {
        let mut a = u8::read(buf)?;
        let mut p = (a & 0x7F) as u32;
        'd: {
            if a & 0x80 == 0 {
                break 'd;
            }
            a = u8::read(buf)?;
            p |= ((a & 0x7F) as u32) << 7;
            if a & 0x80 == 0 {
                break 'd;
            }
            a = u8::read(buf)?;
            p |= ((a & 0x7F) as u32) << 14;
            if a & 0x80 == 0 {
                break 'd;
            }
            a = u8::read(buf)?;
            if a == 0x00 {
                cold_path();
                break 'd;
            }
            let e = u8::read(buf)?;
            if a == 0x80 && e == 0x00 {
                break 'd;
            }
            cold_path();
            return Err(Error);
        };
        Ok(Self(p))
    }
}

impl Write for V21 {
    #[inline]
    unsafe fn write(&self, w: &mut Writer) {
        unsafe {
            let n = self.0;
            if n & 0xFFFFFF80 == 0 {
                w.write_byte(n as u8);
            } else if n & 0xFFFFC000 == 0 {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8]);
            } else {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8 | 0x80, (n >> 14) as u8]);
            }
        }
    }

    #[inline]
    fn len_s(&self) -> usize {
        let n = self.0;
        if n & 0xFFFFFF80 == 0 {
            1
        } else if n & 0xFFFFC000 == 0 {
            2
        } else {
            3
        }
    }
}

impl V32 {
    #[inline]
    pub const fn to_array(self) -> [u8; 5] {
        let n = self.0;
        [
            n as u8 | 0x80,
            (n >> 7) as u8 | 0x80,
            (n >> 14) as u8 | 0x80,
            (n >> 21) as u8 | 0x80,
            (n >> 28) as u8,
        ]
    }
}

impl Write for V32 {
    #[inline]
    unsafe fn write(&self, w: &mut Writer) {
        unsafe {
            let n = self.0;
            if n & 0xFFFFFF80 == 0 {
                w.write_byte(n as u8);
            } else if n & 0xFFFFC000 == 0 {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8]);
            } else if n & 0xFFE00000 == 0 {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8 | 0x80, (n >> 14) as u8]);
            } else if n & 0xF0000000 == 0 {
                w.write(&[
                    n as u8 | 0x80,
                    (n >> 7) as u8 | 0x80,
                    (n >> 14) as u8 | 0x80,
                    (n >> 21) as u8,
                ]);
            } else {
                w.write(&[
                    n as u8 | 0x80,
                    (n >> 7) as u8 | 0x80,
                    (n >> 14) as u8 | 0x80,
                    (n >> 21) as u8 | 0x80,
                    (n >> 28) as u8,
                ]);
            }
        }
    }

    #[inline]
    fn len_s(&self) -> usize {
        let n = self.0;
        if n & 0xFFFFFF80 == 0 {
            1
        } else if n & 0xFFFFC000 == 0 {
            2
        } else if n & 0xFFE00000 == 0 {
            3
        } else if n & 0xF0000000 == 0 {
            4
        } else {
            5
        }
    }
}

impl<'a> Read<'a> for V32 {
    fn read(buf: &mut Reader) -> Result<Self, Error> {
        let mut a = u8::read(buf)?;
        let mut shl = 7;
        let mut p = (a & 0x7F) as u32;
        'd: {
            if a & 0x80 == 0 {
                break 'd;
            }
            for _ in 0..3 {
                a = u8::read(buf)?;
                p |= ((a & 0x7F) as u32) << shl;
                shl += 7;
                if a & 0x80 == 0 {
                    break 'd;
                }
            }
            a = u8::read(buf)?;
            p |= ((a & 0x0F) as u32) << shl;
            if a & 0xF0 == 0 {
                break 'd;
            }
            cold_path();
            return Err(Error);
        };
        Ok(Self(p))
    }
}

impl V64 {
    #[inline]
    pub const fn to_array(self) -> [u8; 10] {
        let n = self.0;
        [
            n as u8 | 0x80,
            (n >> 7) as u8 | 0x80,
            (n >> 14) as u8 | 0x80,
            (n >> 21) as u8 | 0x80,
            (n >> 28) as u8 | 0x80,
            (n >> 35) as u8 | 0x80,
            (n >> 42) as u8 | 0x80,
            (n >> 49) as u8 | 0x80,
            (n >> 56) as u8 | 0x80,
            (n >> 63) as u8,
        ]
    }
}

impl Write for V64 {
    unsafe fn write(&self, w: &mut Writer) {
        unsafe {
            let n = self.0;
            if n & 0xFFFFFFFFFFFFFF80 == 0 {
                w.write_byte(n as u8);
            } else if n & 0x8000000000000000 != 0 {
                w.write(&[
                    n as u8 | 0x80,
                    (n >> 7) as u8 | 0x80,
                    (n >> 14) as u8 | 0x80,
                    (n >> 21) as u8 | 0x80,
                    (n >> 28) as u8 | 0x80,
                    (n >> 35) as u8 | 0x80,
                    (n >> 42) as u8 | 0x80,
                    (n >> 49) as u8 | 0x80,
                    (n >> 56) as u8 | 0x80,
                    (n >> 63) as u8,
                ]);
            } else if n & 0xFFFFFFFFFFFFC000 == 0 {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8]);
            } else if n & 0xFFFFFFFFFFE00000 == 0 {
                w.write(&[n as u8 | 0x80, (n >> 7) as u8 | 0x80, (n >> 14) as u8]);
            } else {
                let b = if n & 0xFFFFFFFFF0000000 == 0 {
                    (n >> 21) as u8
                } else {
                    (n >> 21) as u8 | 0x80
                };
                w.write(&[
                    n as u8 | 0x80,
                    (n >> 7) as u8 | 0x80,
                    (n >> 14) as u8 | 0x80,
                    b,
                ]);
                if n & 0xFFFFFFFFF0000000 == 0 {
                } else if n & 0xFFFFFFF800000000 == 0 {
                    w.write_byte((n >> 28) as u8);
                } else if n & 0xFFFFFC0000000000 == 0 {
                    w.write(&[(n >> 28) as u8 | 0x80, (n >> 35) as u8]);
                } else if n & 0xFFFE000000000000 == 0 {
                    w.write(&[
                        (n >> 28) as u8 | 0x80,
                        (n >> 35) as u8 | 0x80,
                        (n >> 42) as u8,
                    ]);
                } else if n & 0xFF00000000000000 == 0 {
                    w.write(&[
                        (n >> 28) as u8 | 0x80,
                        (n >> 35) as u8 | 0x80,
                        (n >> 42) as u8 | 0x80,
                        (n >> 49) as u8,
                    ]);
                } else {
                    w.write(&[
                        (n >> 28) as u8 | 0x80,
                        (n >> 35) as u8 | 0x80,
                        (n >> 42) as u8 | 0x80,
                        (n >> 49) as u8 | 0x80,
                        (n >> 56) as u8,
                    ]);
                }
            }
        }
    }

    fn len_s(&self) -> usize {
        let n = self.0;
        if n & 0xFFFFFFFFFFFFFF80 == 0 {
            1
        } else if n & 0x8000000000000000 != 0 {
            10
        } else if n & 0xFFFFFFFFFFFFC000 == 0 {
            2
        } else if n & 0xFFFFFFFFFFE00000 == 0 {
            3
        } else if n & 0xFFFFFFFFF0000000 == 0 {
            4
        } else if n & 0xFFFFFFF800000000 == 0 {
            5
        } else if n & 0xFFFFFC0000000000 == 0 {
            6
        } else if n & 0xFFFE000000000000 == 0 {
            7
        } else if n & 0xFF00000000000000 == 0 {
            8
        } else {
            9
        }
    }
}

impl<'a> Read<'a> for V64 {
    fn read(buf: &mut Reader) -> Result<Self, Error> {
        let mut a = u8::read(buf)?;
        let mut shl = 7;
        let mut p = (a & 0x7F) as u64;
        'd: {
            if a & 0x80 == 0 {
                return Ok(Self(a as u64));
            }
            a = u8::read(buf)?;
            p |= ((a & 0x7F) as u64) << shl;
            shl += 7;
            if a & 0x80 == 0 {
                break 'd;
            }
            if let Ok(y) = buf.peek_array::<8>() {
                let y = u64::from_le_bytes(*y);
                if y & 0xFE80_8080_8080_8080 == 0x0080_8080_8080_8080 {
                    unsafe { buf.advance(8) }
                    p |= ((y & 0x0000_0000_0000_007F) << 14)
                        | ((y & 0x0000_0000_0000_7F00) << 13)
                        | ((y & 0x0000_0000_007F_0000) << 12)
                        | ((y & 0x0000_0000_7F00_0000) << 11)
                        | ((y & 0x0000_007F_0000_0000) << 10)
                        | ((y & 0x0000_7F00_0000_0000) << 9)
                        | ((y & 0x007F_0000_0000_0000) << 8)
                        | ((y & 0x0100_0000_0000_0000) << 7);
                    break 'd;
                }
            }
            for _ in 0..7 {
                a = u8::read(buf)?;
                p |= ((a & 0x7F) as u64) << shl;
                shl += 7;
                if a & 0x80 == 0 {
                    break 'd;
                }
            }
            return Err(Error);
        }
        Ok(Self(p))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::V21MAX;

    #[test]
    fn test_write() {
        let mut r = 0xE3D172B05F73CBC3u64;
        let mut buf = [0u8; 10];
        let mut arr = [0; 200];
        for i in &mut arr {
            r = r.wrapping_add(0xa0761d6478bd642f);
            let x = (r ^ 0xe7037ed1a0b428db) as u128;
            let t = (r as u128).wrapping_mul(x);
            let x = (t.wrapping_shr(64) ^ t) as u64;
            *i = x;
        }
        arr[0] = u64::MAX;
        arr[1] = u32::MAX as u64;
        arr[2] = V21MAX as u64;

        for x in arr {
            unsafe {
                let y = V64(x);
                let sz = y.len_s();
                crate::write_unchecked(buf.as_mut_ptr(), &y);
                let mut sl = Reader::new(core::slice::from_raw_parts(buf.as_ptr(), sz));
                assert_eq!(V64::read(&mut sl).unwrap(), y);
                assert!(sl.is_empty());

                let y = V32(x as u32);
                crate::write_unchecked(buf.as_mut_ptr(), &y);
                let sz = y.len_s();
                let mut sl = Reader::new(core::slice::from_raw_parts(buf.as_ptr(), sz));
                assert_eq!(V32::read(&mut sl).unwrap(), y);
                assert!(sl.is_empty());

                let y = V21(x as u32 & 0x1FFFFF);
                crate::write_unchecked(buf.as_mut_ptr(), &y);
                let sz = y.len_s();
                let mut sl = Reader::new(core::slice::from_raw_parts(buf.as_ptr(), sz));
                assert_eq!(V21::read(&mut sl).unwrap(), y);
                assert!(sl.is_empty());
            }
        }
    }

    #[test]
    fn test_v21() {
        let val = V21(V21MAX as u32);
        let [a, b, c] = val.to_array();
        let a5 = [a, b, c | 0x80, 0x80, 0];
        let a4 = [a, b, c | 0x80, 0];
        assert_eq!(V21::read(&mut Reader::new(&a5)).unwrap(), val);
        assert_eq!(V21::read(&mut Reader::new(&a4)).unwrap(), val);
        assert_eq!(V21::read(&mut Reader::new(&[a, b, c])).unwrap(), val);

        let val = V21(0x1fff);
        let [a, b, c] = val.to_array();
        let a5 = [a, b | 0x80, c | 0x80, 0x80, 0];
        let a4 = [a, b | 0x80, c | 0x80, 0];
        assert_eq!(V21::read(&mut Reader::new(&a5)).unwrap(), val);
        assert_eq!(V21::read(&mut Reader::new(&a4)).unwrap(), val);
        assert_eq!(V21::read(&mut Reader::new(&[a, b, c])).unwrap(), val);
    }
}
