//! Backend-neutral Mime database implementation.

use cfg_if::cfg_if;
use mime_guess::Mime;

pub trait MimeDb {
	fn init() -> Self;
	fn get_type(&self, data: &[u8]) -> Option<Mime>;
}

cfg_if! {
	if #[cfg(any(all(unix, feature = "infer-backend"), all(not(unix), not(feature = "xdg-mime-backend"))))] {
		use std::str::FromStr;

		pub struct InferDb {
			db: infer::Infer,
		}

		fn open_document_check(buf: &[u8], kind: &str) -> bool {
			let mime = format!("application/vnd.oasis.opendocument.{}", kind);
			let mime = mime.as_bytes();

			buf.len() > 38 + mime.len() && buf.starts_with(b"PK\x03\x04") && buf[38..mime.len() + 38] == mime[..]
		}

		impl MimeDb for InferDb {
			fn init() -> Self {
				let mut info = infer::Infer::new();

				info.add("application/vnd.oasis.opendocument.text", "odt", |buf| {
					open_document_check(buf, "text")
				});

				info.add("application/vnd.oasis.opendocument.spreadsheet", "ods", |buf| {
					open_document_check(buf, "spreadsheet")
				});

				info.add("application/vnd.oasis.opendocument.presentation", "odp", |buf| {
					open_document_check(buf, "presentation")
				});

				info.add("image/svg+xml", "svg", |buf| {
					// before doing the moderately expensive SVG check, we should make sure that the input is actually SGML-ish
					// by "SGML-ish", i mean starts with anywhere from zero to ∞-1 whitespace characters, and then a less than sign,
					// and then there's some other stuff we don't care about right now

					// so, here comes our fancy pants """""SGML-ish validator"""""
					for c in buf {
						match c {
							// whitespace (according to https://www.w3.org/TR/xml/#NT-S)
							b'\t' | b'\r' | b'\n' | b'\x20' => continue,
							b'<' => break,
							_ => return false,
						}
					}

					// finally, to check whether or not the file is an SVG:
					// - split the buffer up into chunks separated by the less than sign
					// - check to see if this chunk starts with any of these identifiers:
					let identifiers: Vec<&[u8]> = vec![b"svg", b"SVG", b"!DOCTYPE svg", b"!DOCTYPE SVG"];
					// - if it does, the nested `any` will short circuit and immediately return true, causing the parent `any` to do
					//   the same
					// - and finally, if none of the chunks match, we'll return false

					// TODO: this is kind of messy, i'd like to clean it up somehow :(
					buf
						.split(|c| *c == b'<')
						.any(|buf| identifiers.iter().any(|id| buf.starts_with(id)))
				});

				// unmut
				let info = info;

				Self { db: info }
			}

			fn get_type(&self, data: &[u8]) -> Option<Mime> {
				self.db.get(data).map(|f| Mime::from_str(f.mime_type()).unwrap())
			}
		}
	} else {
		pub struct XdgDb {
			db: xdg_mime::SharedMimeInfo,
		}

		impl MimeDb for XdgDb {
			fn init() -> Self {
				Self { db: xdg_mime::SharedMimeInfo::new() }
			}

			fn get_type(&self, data: &[u8]) -> Option<Mime> {
				self.db.get_mime_type_for_data(data).map(|m| m.0)
			}
		}
	}
}
