use crate::server::error::MurError;

pub struct MurCodec;

impl MurCodec {
	pub fn url_encode(s: &str) -> String {
		urlencoding::encode(s).into_owned()
	}

	pub fn url_decode(s: &str) -> Result<String, MurError> {
		urlencoding::decode(s)
			.map(|s| s.into_owned())
			.map_err(|e| MurError::BadRequest(format!("Invalid URL encoding: {}", e)))
	}

	pub fn base64_encode(data: &[u8]) -> String {
		const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

		let mut result = String::new();
		let mut i = 0;

		while i < data.len() {
			let b0 = data[i] as usize;
			let b1 = if i + 1 < data.len() {
				data[i + 1] as usize
			} else {
				0
			};
			let b2 = if i + 2 < data.len() {
				data[i + 2] as usize
			} else {
				0
			};

			result.push(CHARS[b0 >> 2] as char);
			result.push(CHARS[((b0 & 0x03) << 4) | (b1 >> 4)] as char);

			if i + 1 < data.len() {
				result.push(CHARS[((b1 & 0x0f) << 2) | (b2 >> 6)] as char);
			} else {
				result.push('=');
			}

			if i + 2 < data.len() {
				result.push(CHARS[b2 & 0x3f] as char);
			} else {
				result.push('=');
			}

			i += 3;
		}

		result
	}

	pub fn base64_decode(s: &str) -> Result<Vec<u8>, MurError> {
		const DECODE: [i8; 128] = [
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62,
			-1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0,
			1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
			25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
			41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
		];

		let decode_error: MurError = MurError::BadRequest(String::from("Invalid base64"));
		let s = s.trim_end_matches('=');
		let mut result = Vec::new();
		let mut buf = 0u32;
		let mut bits = 0;

		for c in s.bytes() {
			if c >= 128 {
				return Err(decode_error);
			}
			let val = DECODE[c as usize];
			if val < 0 {
				return Err(decode_error);
			}

			buf = (buf << 6) | (val as u32);
			bits += 6;

			if bits >= 8 {
				bits -= 8;
				result.push((buf >> bits) as u8);
				buf &= (1 << bits) - 1;
			}
		}

		Ok(result)
	}
}
