// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! A simple fuzz tester for the library.

#![deny(warnings)]

use std::borrow::ToOwned;

use rand::distr::uniform::Uniform as Range;
use rand::distr::Distribution;
use rand::Rng;
use tendril::StrTendril;

fn fuzz() {
    let mut rng = rand::rng();
    let capacity = Range::new(0u32, 1 << 14).unwrap().sample(&mut rng);
    let mut buf_string = String::with_capacity(capacity as usize);
    let mut buf_tendril = StrTendril::with_capacity(capacity);
    let mut string_slices = vec![];
    let mut tendril_slices = vec![];

    for _ in 1..100_000 {
        if buf_string.len() > (1 << 30) {
            buf_string.truncate(0);
            buf_tendril.clear();
        }

        let dist_action = Range::new(0, 100).unwrap();
        match dist_action.sample(&mut rng) {
            0..=15 => {
                let (start, end) = random_slice(&mut rng, TEXT);
                let snip = &TEXT[start..end];
                buf_string.push_str(snip);
                buf_tendril.push_slice(snip);
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            16..=31 => {
                let (start, end) = random_slice(&mut rng, &buf_string);
                let snip = &buf_string[start..end].to_owned();
                buf_string.push_str(snip);
                buf_tendril.push_slice(snip);
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            32..=47 => {
                let lenstr = format!("[length = {}]", buf_tendril.len());
                buf_string.push_str(&lenstr);
                buf_tendril.push_slice(&lenstr);
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            48..=63 => {
                let n = random_boundary(&mut rng, &buf_string);
                buf_tendril.pop_front(n as u32);
                buf_string = buf_string[n..].to_owned();
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            64..=79 => {
                let new_len = random_boundary(&mut rng, &buf_string);
                let n = buf_string.len() - new_len;
                buf_string.truncate(new_len);
                buf_tendril.pop_back(n as u32);
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            80..=90 => {
                let (start, end) = random_slice(&mut rng, &buf_string);
                buf_string = buf_string[start..end].to_owned();
                buf_tendril = buf_tendril.subtendril(start as u32, (end - start) as u32);
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            91..=96 => {
                let c = rng.random();
                buf_string.push(c);
                assert!(buf_tendril.try_push_char(c).is_ok());
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            97 => {
                buf_string.truncate(0);
                buf_tendril.clear();
                assert_eq!(&*buf_string, &*buf_tendril);
            },

            _ => {
                let (start, end) = random_slice(&mut rng, &buf_string);
                string_slices.push(buf_string[start..end].to_owned());
                tendril_slices.push(buf_tendril.subtendril(start as u32, (end - start) as u32));
                assert_eq!(string_slices.len(), tendril_slices.len());
                assert!(string_slices
                    .iter()
                    .zip(tendril_slices.iter())
                    .all(|(s, t)| **s == **t));
            },
        }
    }
}

fn random_boundary<R: Rng>(rng: &mut R, text: &str) -> usize {
    loop {
        let i = Range::new(0, text.len() + 1).unwrap().sample(rng);
        if text.is_char_boundary(i) {
            return i;
        }
    }
}

fn random_slice<R: Rng>(rng: &mut R, text: &str) -> (usize, usize) {
    loop {
        let start = Range::new(0, text.len() + 1).unwrap().sample(rng);
        let end = Range::new(start, text.len() + 1).unwrap().sample(rng);
        if !text.is_char_boundary(start) {
            continue;
        }
        if end < text.len() && !text.is_char_boundary(end) {
            continue;
        }
        return (start, end);
    }
}

static TEXT: &str = "It was from the artists and poets that the pertinent answers came, and I \
     know that panic would have broken loose had they been able to compare notes. \
     As it was, lacking their original letters, I half suspected the compiler of \
     having asked leading questions, or of having edited the correspondence in \
     corroboration of what he had latently resolved to see.\
\
     ˙ǝǝs oʇ pǝʌʃosǝɹ ʎʃʇuǝʇɐʃ pɐɥ ǝɥ ʇɐɥʍ ɟo uoıʇɐɹoqoɹɹoɔ uı ǝɔuǝpuodsǝɹɹoɔ ǝɥʇ \
     pǝʇıpǝ ƃuıʌɐɥ ɟo ɹo 'suoıʇsǝnb ƃuıpɐǝʃ pǝʞsɐ ƃuıʌɐɥ ɟo ɹǝʃıdɯoɔ ǝɥʇ pǝʇɔǝdsns \
     ɟʃɐɥ I 'sɹǝʇʇǝʃ ʃɐuıƃıɹo ɹıǝɥʇ ƃuıʞɔɐʃ 'sɐʍ ʇı s∀ ˙sǝʇou ǝɹɐdɯoɔ oʇ ǝʃqɐ uǝǝq \
     ʎǝɥʇ pɐɥ ǝsooʃ uǝʞoɹq ǝʌɐɥ pʃnoʍ ɔıuɐd ʇɐɥʇ ʍouʞ I puɐ 'ǝɯɐɔ sɹǝʍsuɐ ʇuǝuıʇɹǝd \
     ǝɥʇ ʇɐɥʇ sʇǝod puɐ sʇsıʇɹɐ ǝɥʇ ɯoɹɟ sɐʍ ʇI";

fn main() {
    fuzz();
}
