//! Metadata file related structs.
//!
//! These have multiple lines of at least 3 known types; files,
//! directories, and symlinks.  And files may be hardlinks.  It's
//! probably simplest to just treat them separately.
use std::path::PathBuf;
use std::collections::{HashMap, HashSet};


/*
 * Complete metadata collections.
 */

/// A complete set of metadata.  This will generally be "all the stuff
/// from <some file>", already broken down by component, so a given
/// metadata file probably has a handful of these.  As part of building
/// this, we'll have already sorted the individual sections so that e.g.
/// iterating over the dirs will always hit the parent before the child.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct Metadata
{
	/// All the directories in this set
	pub(crate) dirs: HashMap<PathBuf, MetaDir>,

	/// All the symlinks
	pub(crate) symlinks: HashMap<PathBuf, MetaSymLink>,

	/// The files
	pub(crate) files: HashMap<PathBuf, MetaFile>,

	/// And the hardlinks
	pub(crate) hardlinks: HashMap<PathBuf, MetaHardLink>,

	/// Also the dash lines
	pub(crate) dashes: HashSet<PathBuf>,
}





/*
 * Stuff for individual lines of metadata
 */
use crate::util::hash::Sha256Hash;
use super::{uid_t, gid_t, mode_t, flags_t};
use super::{oct_fmt_u32, hex_fmt_u32};
use super::cmp_ugid;


// n.b. for sorting purposes, the Ord/Eq bits are a bit excessive, since
// we often want to sort Vec's of these by the filename.  But since the
// derives are defined to go in order of the fields, the file is always
// first, and those sort of collections are already unique by the
// filename, they'll work out right for the sorting cases.


/// A metadata line about a [f]ile.  Notable this doesn't include the
/// lines that refer to hardlinks.
#[derive(Default, Clone, PartialOrd, Ord)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(educe::Educe)]
#[educe(PartialEq, Eq)]
#[educe(Debug)]
pub(crate) struct MetaFile
{
	/// The file name this line is about
	pub(crate) path: PathBuf,

	/// SHA256 hash.
	pub(crate) sha256: Sha256Hash,

	// Misc FS metadata
	#[educe(PartialEq(method(cmp_ugid)))]
	pub(crate) uid:   uid_t,
	#[educe(PartialEq(method(cmp_ugid)))]
	pub(crate) gid:   gid_t,
	#[educe(Debug(method(oct_fmt_u32)))]
	pub(crate) mode:  mode_t,
	#[educe(Debug(method(hex_fmt_u32)))]
	pub(crate) flags: flags_t,
}


/// A metadata line about a hardlink.  These come from [f] lines, but
/// I'm separating them for easier processing and more compact
/// representation.  e.g., the hash doesn't matter on these lines, only
/// the [f] line that gives the original file, permissions on the other
/// names can't matter (unless they're different, which means they'd have
/// changed the original file in the first place, and that's just
/// screwy), etc.
#[derive(Debug, Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct MetaHardLink
{
	/// The destination filename
	pub(crate) path: PathBuf,

	/// The hardlink destination (hopefully, a file we already had a
	/// MetaFile about).
	pub(crate) target: PathBuf,
}


/// A metadata line about a [d]irectory
#[derive(Default, Clone, PartialOrd, Ord)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(educe::Educe)]
#[educe(PartialEq, Eq)]
#[educe(Debug)]
pub(crate) struct MetaDir
{
	/// The dir name this line is about
	pub(crate) path: PathBuf,

	// Misc FS metadata
	#[educe(PartialEq(method(cmp_ugid)))]
	pub(crate) uid:   uid_t,
	#[educe(PartialEq(method(cmp_ugid)))]
	pub(crate) gid:   gid_t,
	#[educe(Debug(method(oct_fmt_u32)))]
	pub(crate) mode:  mode_t,
	#[educe(Debug(method(hex_fmt_u32)))]
	pub(crate) flags: flags_t,
}


/// A metadata line about a sym[L]ink
#[derive(Default, Clone, PartialOrd, Ord)]
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(educe::Educe)]
#[educe(PartialEq, Eq)]
#[educe(Debug)]
pub(crate) struct MetaSymLink
{
	/// The file name this line is about (that is, the name that will
	/// _be_ a symlink).
	pub(crate) path: PathBuf,

	/// The target of the symlink
	pub(crate) target: PathBuf,

	// Misc FS metadata, though its meaning for symlinks is questionable.
	// In particular, u/gid never really mean anything, so we just ignore
	// them in comparisons.
	#[educe(PartialEq(ignore))]
	pub(crate) uid:   uid_t,
	#[educe(PartialEq(ignore))]
	pub(crate) gid:   gid_t,
	#[educe(Debug(method(oct_fmt_u32)))]
	pub(crate) mode:  mode_t,
	#[educe(Debug(method(hex_fmt_u32)))]
	pub(crate) flags: flags_t,
}


/// A metadata line about a...   file that's missing in a given state?
/// Comments seem to suggest this is used in old/new comparisons where
/// the file is new on the new side?  Perhaps as a somewhat hacky way of
/// dealing with how f-u kinda pretends it knows the state of the system,
/// even though it's just guessing?  We also use this in some scanning
/// code etc to indicate a missing file generally.
#[derive(Debug, Default, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[derive(serde::Serialize, serde::Deserialize)]
pub(crate) struct MetaDash
{
	/// About a file
	pub(crate) path: PathBuf,
}




#[cfg(test)]
mod tests
{
	use super::*;

	// Some tests of Debug formatting, with our oct/hex bits
	#[test]
	fn file_debug()
	{
		let mut f = MetaFile::default();
		f.mode = 420;
		f.flags = 131072; // happens to be schg
		let f_dbg = format!("{f:?}");

		let x_mode = "mode: 0644";
		assert!(f_dbg.contains(x_mode), "mode formatted right");

		let x_flags = "flags: 0x20000";
		assert!(f_dbg.contains(x_flags), "flags formatted right");
	}

	#[test]
	fn dir_debug()
	{
		let mut d = MetaDir::default();
		d.mode = 493;
		d.flags = 131072; // happens to be schg
		let d_dbg = format!("{d:?}");

		let x_mode = "mode: 0755";
		assert!(d_dbg.contains(x_mode), "mode formatted right");

		let x_flags = "flags: 0x20000";
		assert!(d_dbg.contains(x_flags), "flags formatted right");
	}

	#[test]
	fn symlink_debug()
	{
		let mut s = MetaSymLink::default();
		s.mode = 511;
		s.flags = 0;  // Explicitly no flags
		let s_dbg = format!("{s:?}");

		let x_mode = "mode: 0777";
		assert!(s_dbg.contains(x_mode), "mode formatted right");

		let x_flags = "flags: 0x0";
		assert!(s_dbg.contains(x_flags), "flags formatted right");
	}


	// Comparisons

	// symlinks ignore u/gid in comparisons, since they don't really mean
	// anything in symlinks
	#[test]
	fn symlink_cmp_ugid()
	{
		let mut s1 = MetaSymLink::default();
		s1.uid = 12;
		s1.gid = 34;

		let mut s2 = s1.clone();
		s2.uid = 56;
		s2.gid = 78;

		assert_eq!(s1, s2, "symlinks ignore u/gid in comparison");
	}


	// Files and dirs may or may not ignore u/gid diffs, depending on
	// some magic settings.
	#[test]
	fn file_cmp_ugid()
	{
		let mut f1 = MetaFile::default();
		f1.uid = 11;
		f1.gid = 22;

		let mut fu = f1.clone();
		fu.uid = 33;

		let mut fg = f1.clone();
		fg.gid = 44;

		// The default setting is that we actually do the comparisons.
		crate::metadata::set_ugid_cmp(true);
		assert_ne!(f1, fu, "uid differs");
		assert_ne!(f1, fg, "gid differs");
		assert_ne!(fu, fg, "uid/gid differs");

		// But if we flip the setting, the difference should be ignored
		crate::metadata::set_ugid_cmp(false);
		assert_eq!(f1, fu, "uid diff ignored");
		assert_eq!(f1, fg, "gid diff ignored");
		assert_eq!(fu, fg, "uid/gid diff ignored");
	}

	#[test]
	fn dir_cmp_ugid()
	{
		let mut d1 = MetaDir::default();
		d1.uid = 11;
		d1.gid = 22;

		let mut du = d1.clone();
		du.uid = 33;

		let mut dg = d1.clone();
		dg.gid = 44;

		// The default setting is that we actually do the comparisons.
		crate::metadata::set_ugid_cmp(true);
		assert_ne!(d1, du, "uid differs");
		assert_ne!(d1, dg, "gid differs");
		assert_ne!(du, dg, "uid/gid differs");

		// But if we flip the setting, the difference should be ignored
		crate::metadata::set_ugid_cmp(false);
		assert_eq!(d1, du, "uid diff ignored");
		assert_eq!(d1, dg, "gid diff ignored");
		assert_eq!(du, dg, "uid/gid diff ignored");
	}
}
