/*
Can nncp be used for this?
Ways of encoding data for compression: entropy encoding (Huffman, Arithmetic, Asymmetric Numerical Systems), dictionary encoding (Lempel-Ziv), delta encoding.
How to create a dictionary: https://groups.google.com/g/comp.compression/c/ZcOTiqck9Tc/m/1KHNFB5ocpoJ.
cmix pre-processor converts uppercase characters to lowercase characters prepended with escapes codes which decreases symbol vocabulary but increases input length.
*/

/* const ALPHABET: [char; 27] = [
	"\n", // 0
	" ", // 1
	",", // 2
	".", // 3
	"9", // 4
	"G" // 5
	"N", // 6
	"T", // 7
	"a", // 8
	"b", // 9
	"d" // 10
	"e" // 11
	"f", // 12
	"h", // 13
	"i", // 14
	"k", // 15
	"l", // 16
	"m", // 17
	"n", // 18
	"o", // 19
	"p", // 20
	"r", // 21
	"s", // 22
	"t", // 23
	"u", // 24
	"w", // 25
	"y" // 26
]; */

/* "<n length> bottles of beer on the wall, <n length> bottles of beer.
Take one down and pass it around, <(n - 1) length> bottles of beer on the wall.

2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Take it down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
" */

const DICT: [&str; 24] = [
	"N", // 0
	"o m", // 1
	"ore", // 2
	" bottle", // 3
	"s", // 4
	" of beer", // 5
	" on", // 6
	" th", // 7
	"e ", // 8
	"wall", // 9
	", ", // 10
	"n", // 11
	".\n", // 12
	"Go to", // 13
	"st", // 14
	" and ", // 15
	"buy som", // 16
	"m", // 17
	"99", // 18
	"Take", // 19
	" it ", // 20
	"down", // 21
	"pass", // 22
	"around", // 23
];

// (count, index)
fn calculate_length(strings: &[(usize, usize)]) -> usize {
	strings.iter().fold(0, |a, (b, c)| a + b * DICT[*c].len())
}

/*
Common string: " bottle of beer on the wall,  bottle of beer.\n,  bottle of beer on the wall.\n".
Composed of:
" bottle": 3 of 3
" of beer": 3 of 5
" on": 2 of 6
" th": 2 of 7
"e ": 2 of 8
"wall": 2 of 9
", ": 2 of 10
".\n": 2 of 12
*/
fn common_length() -> usize {
	calculate_length(&[(3, 3), (3, 5), (2, 6), (2, 7), (2, 8), (2, 9), (2, 10), (2, 12)])
}

fn allocate_string(strings: &[(usize, usize)], more: usize) -> String {
	String::with_capacity(common_length() + calculate_length(strings) + more)
}

fn modify_string(mut string: String, prefices_and_indices: &[(&str, &[usize])]) -> String {
	prefices_and_indices.iter().for_each(|(prefix, indices)| {
		string.push_str(prefix);
		indices.iter().for_each(|index| string.push_str(DICT[*index]));
	});
	string
}

fn allocate_and_modify_verse(strings: &[(usize, usize)], more: usize, prefices_and_indices: &[(&str, &[usize])]) -> String {
	let mut out = String::with_capacity(common_length() + calculate_length(strings) + more);
	prefices_and_indices.iter().for_each(|(prefix, indices)| {
		out.push_str(prefix);
		indices.iter().for_each(|index| out.push_str(DICT[*index]));
	});
	out
}

// TODO: Refactor common code blocks.
pub fn verse(n: u32) -> String {
	match n {
		// Easy mode: 0 => [0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2, 3, 4, 5, 12, 13, 7, 8, 14, 2, 15, 16, 8, 17, 2, 10, 18, 3, 4, 5, 6, 7, 8, 9, 12].iter().map(|a| DICT[*a]).collect::<String>(),
		0 => {
			/*
			Extras are:
			"N": 1 of 0
			"o m": 2 of 1
			"ore": 4 of 2
			"s": 3 of 4
			"n": 1 of 11
			"Go to": 1 of 13
			" th": 1 of 7
			"e ": 2 of 8
			"st": 1 of 14
			" and ": 1 of 15
			"buy som": 1 of 16
			"m": 1 of 17
			"99": 1 of 18
			*/
			// assert_eq!("No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n".len(), common_length + calculate_length(&[(1, 0), (2, 1), (4, 2), (3, 4), (1, 11), (1, 13), (1, 7), (2, 8), (1, 14), (1, 15), (1, 16), (1, 17), (1, 18)]));
			allocate_and_modify_verse(&[(1, 0), (2, 1), (4, 2), (3, 4), (1, 11), (1, 13), (1, 7), (2, 8), (1, 14), (1, 15), (1, 16), (1, 17), (1, 18)], 0, &[(&"", &[0usize, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2, 3, 4, 5, 12, 13, 7, 8, 14, 2, 15, 16, 8, 17, 2, 10, 18, 3, 4, 5, 6, 7, 8, 9, 12])])
		}
		1 => {
			/*
			Extras are:
			n: 2
			"Take": 1 of 19
			" it ": 2 of 20
			"down": 1 of 21
			" and ": 1 of 15
			"pass": 1 of 22
			"around": 1 of 23
			"n": 1 of 11
			"o m": 1 of 1
			"ore": 1 of 2
			"s": 1 of 4
			*/
			let n_string = n.to_string();
			// assert_eq!("1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n".len(), common_length + 2 + calculate_length(&[(1, 19), (2, 20), (1, 21), (1, 15), (1, 22), (1, 23), (1, 11), (1, 1), (1, 2), (1, 4)]));
			allocate_and_modify_verse(&[(1, 19), (2, 20), (1, 21), (1, 15), (1, 22), (1, 23), (1, 11), (1, 1), (1, 2), (1, 4)], 2, &[(&n_string, &[3usize, 5, 6, 7, 8, 9, 10]), (&n_string, &[3usize, 5, 12, 19, 20, 21, 15, 22, 20, 23, 10, 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12])])
		}
		2 => {
			/*
			Extras are:
			n: 2
			"s": 2 of 4
			"Take": 1 of 19
			" on": 1 of 6
			"e ": 1 of 8
			"down": 1 of 21
			" and ": 1 of 15
			"pass": 1 of 22
			" it": 1 of 20
			"around": 1 of 23
			n - 1: 1
			*/
			let (n_string, m_string) = (n.to_string(), (n - 1).to_string());
			// assert_eq!("2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n".len(), common_length + 2 * n_string.len() + calculate_length(&[(2, 4), (1, 19), (1, 6), (1, 8), (1, 21), (1, 15), (1, 22), (1, 20), (1, 23)]) + m_string.len());
			allocate_and_modify_verse(&[(2, 4), (1, 19), (1, 6), (1, 8), (1, 21), (1, 15), (1, 22), (1, 20), (1, 23)], 2 * n_string.len() + m_string.len(), &[(&n_string, &[3usize, 4, 5, 6, 7, 8, 9, 10]), (&n_string, &[3usize, 4, 5, 12, 19, 6, 8, 21, 15, 22, 20, 23, 10]), (&m_string, &[3, 5, 6, 7, 8, 9, 12])])
		}
		_ => {
			/*
			Extras are:
			n: 2
			"s": 3 of 4
			"Take": 1 of 19
			" on": 1 of 6
			"e ": 1 of 8
			"down": 1 of 21
			" and ": 1 of 15
			"pass": 1 of 22
			" it": 1 of 20
			"around": 1 of 23
			n - 1: 1
			*/
			let (n_string, m_string) = (n.to_string(), (n - 1).to_string());
			// assert_eq!("3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n".len(), common_length + 2 * n_string.len() + calculate_length(&[(3, 4), (1, 19), (1, 6), (1, 8), (1, 21), (1, 15), (1, 22), (1, 20), (1, 23)]) + m_string.len());
			allocate_and_modify_verse(&[(3, 4), (1, 19), (1, 6), (1, 8), (1, 21), (1, 15), (1, 22), (1, 20), (1, 23)], 2 * n_string.len() + m_string.len(), &[(&n_string, &[3usize, 4, 5, 6, 7, 8, 9, 10]), (&n_string, &[3usize, 4, 5, 12, 19, 6, 8, 21, 15, 22, 20, 23, 10]), (&m_string, &[3, 4, 5, 6, 7, 8, 9, 12])])
		}
	}
}

pub fn sing(start: u32, end: u32) -> String {
	"".to_string()
	// (start..=end).for_each(verse)
}