mod arch;
mod dependency;
mod specs;

use {
    crate::tar::{Node, Owner},
    crate::{Plist, Version},
    ron::ser::{to_string_pretty, PrettyConfig},
    serde::{Deserialize, Serialize},
    std::{
        error::Error,
        fs,
        fs::File,
        io::{BufWriter, Write},
        path::{Path, PathBuf},
    },
};

pub use {arch::Arch, dependency::Dependency, specs::Specs};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct User {
    pub name: String,
    pub uid: Option<u32>,
}

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Group {
    pub name: String,
    pub gid: Option<u32>,
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
/// the metadata associated with a package
pub struct Package {
    /// The name of the package minus all version information
    pub name: String,
    /// The `Version` of the package
    pub version: Version,
    /// The architecture the package is built for
    pub arch: Arch,
    /// The release number for this package
    pub release: u8,
    /// a single line description of the package
    pub description: String,
    /// a more verbose description of the package
    pub long_description: String,
    /// a listing of all files, directories and symlinks which are a part of
    /// this package
    pub plist: Plist,
    /// the total size of this package
    pub size: usize,
    /// all of this package's runtime dependencies
    pub dependencies: Vec<Dependency>,
    /// an optional list of users to be created upon installation
    pub users: Option<Vec<User>>,
    /// an optional list of groups to be created upon installation
    pub groups: Option<Vec<Group>>,
    /// applicable only if this package contains a shell
    pub shells: Option<Vec<PathBuf>>,
    /// an optional post installation shell script to be run
    pub post_install: Option<String>,
}

impl From<Specs> for Package {
    fn from(value: Specs) -> Self {
        Package {
            name: value.name,
            version: value.version,
            release: value.release,
            description: value.description,
            long_description: value.long_description,
            dependencies: value.dependencies,
            users: value.users,
            groups: value.groups,
            shells: value.shells,
            post_install: value.post_install,
            ..Default::default()
        }
    }
}

impl Package {
    fn as_ron(&self) -> Result<String, ron::Error> {
        let cfg = PrettyConfig::new().struct_names(true);
        to_string_pretty(self, cfg)
    }

    pub fn save_ron_and_create_tar_node(&self, outdir: &Path) -> Result<Node, Box<dyn Error>> {
        if !outdir.exists() {
            fs::create_dir_all(outdir)?;
        }
        let mut outfile = outdir.to_path_buf();
        outfile.push("package.ron");
        let fd = File::create(&outfile)?;
        let s = self.as_ron()?;
        let mut writer = BufWriter::new(&fd);
        writer.write_all(s.as_bytes())?;
        writer.flush()?;
        let node = Node::read_data_to_tar(
            s.as_bytes(),
            "package.ron",
            &outfile.metadata()?,
            Some(Owner::default()),
        )?;
        Ok(node)
    }

    /// Returns the formatted full package name including version and release strings
    pub fn fullname(&self) -> String {
        format!(
            "{}-{}_{}_{}",
            self.name, self.version, self.release, self.arch
        )
    }

    /// Returns the name of the package archive
    pub fn archive_name(&self) -> String {
        format!(
            "{}-{}_{}_{}.tar.zstd",
            self.name, self.version, self.release, self.arch
        )
    }

    /// Tests whether this package is an update for another
    pub fn is_upgrade(&self, other: &Self) -> bool {
        self.arch == other.arch
            && self.name == other.name
            && (self.version > other.version
                || (self.version == other.version && self.release > other.release))
    }

    pub fn resolve_deps(&self, queue: &mut Vec<Package>) -> Result<(), Box<dyn Error>> {
        unimplemented!();
    }
}
