Documentation
use std::{cell::RefCell, path::PathBuf};

mod dwrite;
mod source;
mod utils;
mod windows;

use dwrite::*;
use source::*;
use utils::*;
use windows::*;

use crate::{Fallback, Stretch, Style, Weight, XFontType};

pub struct XFont(Option<XFontInner>);
struct XFontInner {
	factory: ComPtr<IDWriteFactory2>,
	fallback: ComPtr<IDWriteFontFallback>,
	collection: ComPtr<IDWriteFontCollection>,
}
impl Default for XFont {
	fn default() -> Self {
		unsafe {
			let factory = DWriteFactory();
			if factory.is_null() {
				return Self(None);
			}
			let factory = &mut *factory;

			let mut factroy2 = ptr::null_mut();
			if S_OK != factory.QueryInterface(&IDWriteFactory2::uuidof(), &mut factroy2) {
				return Self(None);
			}
			let Some(factory) = ComPtr::try_from_raw(factroy2 as *mut IDWriteFactory2) else {
				return Self(None);
			};

			let mut collection: *mut IDWriteFontCollection = ptr::null_mut();
			if S_OK != (*DWriteFactory()).GetSystemFontCollection(&mut collection, FALSE) {
				return Self(None);
			}
			let Some(collection) = ComPtr::try_from_raw(collection) else {
				return Self(None);
			};

			let mut fallback: *mut IDWriteFontFallback = ptr::null_mut();
			if S_OK != factory.GetSystemFontFallback(&mut fallback) {
				return Self(None);
			}
			let Some(fallback) = ComPtr::try_from_raw(fallback) else {
				return Self(None);
			};

			// let count = collection.GetFontFamilyCount();
			// let mut families = Vec::with_capacity(count as _);
			// for family_idx in 0..count {
			// 	let mut family: *mut IDWriteFontFamily = ptr::null_mut();
			// 	if S_OK != collection.GetFontFamily(family_idx, &mut family) {
			// 		continue;
			// 	}
			// 	let Some(family) = ComPtr::try_from_raw(family) else {
			// 		continue;
			// 	};
			// 	let mut names: *mut IDWriteLocalizedStrings = ptr::null_mut();
			// 	if S_OK != family.GetFamilyNames(&mut names) {
			// 		continue;
			// 	};
			// 	let Some(mut names) = ComPtr::try_from_raw(names) else {
			// 		continue;
			// 	};
			// 	for idx in 0..names.GetCount() {
			// 		if let Some(name) = get_locale_string(&mut names, idx) {
			// 			println!("{}", name);
			// 			families.push(name);
			// 		}
			// 	}
			// }
			Self(Some(XFontInner {
				factory,
				fallback,
				collection,
			}))
		}
	}
}
impl XFontType for XFont {
	fn fallback(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Fallback {
		self.fallback_impl(text, &name, weight, stretch, style).unwrap_or_default()
	}
	fn system() -> String {
		let mut metrics = NONCLIENTMETRICSW {
			cbSize: std::mem::size_of::<NONCLIENTMETRICSW>() as _,
			..Default::default()
		};
		unsafe {
			if TRUE
				== SystemParametersInfoW(
					SPI_GETNONCLIENTMETRICS, //
					size_of_val(&metrics) as _,
					&mut metrics as *mut _ as _,
					0,
				) {
				let wide_name = &metrics.lfMessageFont.lfFaceName;
				let utf16_iter = wide_name.iter().take_while(|&&c| c != 0).map(|&c| c as u16);
				return String::from_utf16_lossy(&utf16_iter.collect::<Vec<u16>>());
			}
		}
		"Segoe UI".to_string()
	}
}
impl XFont {
	fn fallback_impl(&self, text: &str, name: &str, weight: Weight, stretch: Stretch, style: Style) -> Option<Fallback> {
		let Some(inner) = &self.0 else {
			return None;
		};
		thread_local! {
			static MAP_BUF:RefCell<(Vec<u16>,Vec<u16>)> = RefCell::new((vec![],vec![]));
		}
		MAP_BUF.with_borrow_mut(move |(text_buf, name_buf)| {
			unsafe {
				name_buf.clear();
				name_buf.extend(name.encode_utf16());
				name_buf.push(0);
				if text.is_empty() {
					// if empty find by name first
					let mut index = 0;
					let mut exists: BOOL = 0;
					let hr = inner.collection.FindFamilyName(name_buf.as_mut_ptr(), &mut index, &mut exists);
					if hr == S_OK && exists == TRUE {
						let mut family: *mut IDWriteFontFamily = ptr::null_mut();
						if S_OK == inner.collection.GetFontFamily(index, &mut family) {
							let family = ComPtr::from_raw(family);
							let mut out_font: *mut IDWriteFont = ptr::null_mut();
							if S_OK
								== family.GetFirstMatchingFont(conv_weight(weight), conv_stretch(stretch), conv_style(style), &mut out_font)
							{
								let mut font = ComPtr::from_raw(out_font);
								return Some(build_fallback(&mut font));
							}
						}
					}
				}
				//
				text_buf.clear();
				text_buf.extend(text.encode_utf16());
				let text_len = text_buf.len();
				text_buf.push(0);
				let (text, locale) = text_buf.split_at_mut(text_len);
				//
				let mut source = TextSource::new(text, locale, false);
				let mut source = &mut source;
				let source = &mut source;
				let source = source as *mut &mut TextSource<'_> as *mut c_void as *mut IDWriteTextAnalysisSource;
				//
				let mut out_len = 0;
				let mut out_font: *mut IDWriteFont = ptr::null_mut();
				let mut out_scale = 1.0;
				if S_OK
					== inner.fallback.MapCharacters(
						source, //
						0,      //
						text_len as _,
						inner.collection.as_raw(),
						if name_buf.is_empty() {
							ptr::null_mut()
						} else {
							name_buf.as_mut_ptr()
						},
						conv_weight(weight), //
						conv_style(style),
						conv_stretch(stretch),
						&mut out_len,
						&mut out_font,
						&mut out_scale,
					) {
					if let Some(mut font) = ComPtr::try_from_raw(out_font) {
						return Some(build_fallback(&mut font));
					}
				}
			}
			None
		})
	}
}

unsafe fn build_fallback(font: &mut ComPtr<IDWriteFont>) -> Fallback {
	let Some((index, path)) = font_file(font) else {
		return Default::default();
	};
	Fallback {
		index: Some(index),
		unique: None,
		file: path,
	}
}
fn conv_weight(weight: Weight) -> UINT32 {
	match weight {
		Weight::THIN => DWRITE_FONT_WEIGHT_THIN,
		Weight::EXTRA_LIGHT => DWRITE_FONT_WEIGHT_EXTRA_LIGHT,
		Weight::LIGHT => DWRITE_FONT_WEIGHT_LIGHT,
		Weight::NORMAL => DWRITE_FONT_WEIGHT_NORMAL,
		Weight::MEDIUM => DWRITE_FONT_WEIGHT_MEDIUM,
		Weight::SEMIBOLD => DWRITE_FONT_WEIGHT_DEMI_BOLD,
		Weight::BOLD => DWRITE_FONT_WEIGHT_BOLD,
		Weight::EXTRA_BOLD => DWRITE_FONT_WEIGHT_EXTRA_BOLD,
		Weight::BLACK => DWRITE_FONT_WEIGHT_BLACK,
		_ => DWRITE_FONT_WEIGHT_REGULAR,
	}
}
fn conv_stretch(stretch: Stretch) -> UINT32 {
	match stretch {
		Stretch::ULTRA_CONDENSED => DWRITE_FONT_STRETCH_ULTRA_CONDENSED, // Stretch(500);
		Stretch::EXTRA_CONDENSED => DWRITE_FONT_STRETCH_EXTRA_CONDENSED, // Stretch(625);
		Stretch::CONDENSED => DWRITE_FONT_STRETCH_CONDENSED,             // Stretch(750);
		Stretch::SEMI_CONDENSED => DWRITE_FONT_STRETCH_SEMI_CONDENSED,   // Stretch(875);
		Stretch::NORMAL => DWRITE_FONT_STRETCH_NORMAL,                   // Stretch(1000);
		Stretch::SEMI_EXPANDED => DWRITE_FONT_STRETCH_SEMI_EXPANDED,     // Stretch(1125);
		Stretch::EXPANDED => DWRITE_FONT_STRETCH_EXPANDED,               // Stretch(1250);
		Stretch::EXTRA_EXPANDED => DWRITE_FONT_STRETCH_EXTRA_EXPANDED,   // Stretch(1375);
		Stretch::ULTRA_EXPANDED => DWRITE_FONT_STRETCH_ULTRA_EXPANDED,   // Stretch(1500);
		_ => DWRITE_FONT_STRETCH_NORMAL,
	}
}
fn conv_style(style: Style) -> UINT32 {
	match style {
		Style::Normal => DWRITE_FONT_STYLE_NORMAL,
		Style::Italic => DWRITE_FONT_STYLE_ITALIC,
		Style::Oblique => DWRITE_FONT_STYLE_OBLIQUE,
	}
}