use std::io::Read;
use std::path::{ Path, PathBuf };

/// Every shape `ltk::CursorShape` can request, by its CSS / freedesktop
/// cursor name. The default theme must ship an XCursor file (or alias
/// symlink) for each so the compositor never has to fall back. Keep in
/// sync with the `CursorShape` enum in `src/types.rs`.
const CURSOR_NAMES: &[ &str ] =
&[
	"default", "context-menu", "help", "pointer", "progress", "wait",
	"cell", "crosshair", "text", "vertical-text", "alias", "copy", "move",
	"no-drop", "not-allowed", "grab", "grabbing", "e-resize", "n-resize",
	"ne-resize", "nw-resize", "s-resize", "se-resize", "sw-resize",
	"w-resize", "ew-resize", "ns-resize", "nesw-resize", "nwse-resize",
	"col-resize", "row-resize", "all-scroll", "zoom-in", "zoom-out",
];

fn theme_dir() -> PathBuf
{
	PathBuf::from( env!( "CARGO_MANIFEST_DIR" ) ).join( "themes/default" )
}

fn cursors_dir() -> PathBuf
{
	theme_dir().join( "cursors" )
}

/// True if `path` (following symlinks) is a binary XCursor file, i.e. it
/// opens and starts with the four magic bytes `Xcur`.
fn is_xcursor( path: &Path ) -> bool
{
	let mut file = match std::fs::File::open( path )
	{
		Ok( f )  => f,
		Err( _ ) => return false,
	};
	let mut magic = [ 0u8; 4 ];
	file.read_exact( &mut magic ).is_ok() && &magic == b"Xcur"
}

#[ test ]
fn cursor_theme_manifest_present()
{
	let manifest = theme_dir().join( "cursor.theme" );
	let body     = std::fs::read_to_string( &manifest )
		.unwrap_or_else( |e| panic!( "reading {}: {e}", manifest.display() ) );
	assert!
	(
		body.contains( "[Icon Theme]" ),
		"cursor.theme is missing the [Icon Theme] header",
	);
}

#[ test ]
fn every_cursor_shape_has_a_valid_xcursor_file()
{
	let dir = cursors_dir();
	let missing: Vec<&str> = CURSOR_NAMES
		.iter()
		.copied()
		.filter( |name| !is_xcursor( &dir.join( name ) ) )
		.collect();

	assert!
	(
		missing.is_empty(),
		"default theme has no valid XCursor file for: {missing:?}",
	);
}

/// Packaging (`dh_install`) can in principle drop or break the alias
/// symlinks; a dangling link would make a cursor name unresolvable at
/// runtime. Every entry must resolve to an existing file.
#[ test ]
fn no_dangling_entries()
{
	let dir     = cursors_dir();
	let entries = std::fs::read_dir( &dir )
		.unwrap_or_else( |e| panic!( "reading {}: {e}", dir.display() ) );

	let mut dangling = Vec::new();
	for entry in entries
	{
		let path = entry.expect( "dir entry" ).path();
		// `metadata` follows symlinks, so a broken link is an Err here.
		if std::fs::metadata( &path ).is_err()
		{
			dangling.push( path );
		}
	}

	assert!( dangling.is_empty(), "dangling cursor entries: {dangling:?}" );
}

/// Guard against the name list above drifting from the enum it mirrors.
#[ test ]
fn cursor_name_list_matches_shape_count()
{
	assert_eq!
	(
		CURSOR_NAMES.len(),
		34,
		"CURSOR_NAMES is out of sync with ltk::CursorShape (34 variants)",
	);
}
