/*
    Copyright: 2020 Rasmus Thomsen <oss@cogitri.dev>
    SPDX-License-Identifier: GPL-3.0-or-later
*/

use crate::ApkDatabase;
use apk_tools_sys::apk_package;
use serde::{ser::SerializeStruct, Serialize, Serializer};
use std::convert::TryFrom;
use std::ffi::CStr;
use std::fmt;

/**
* The state of a package
*/
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, zvariant_derive::Type)]
pub enum PackageState {
    Available,
    Installed,
    PendingInstall,
    PendingRemoval,
    Upgradable,
    Downgradable,
    Reinstall,
}

impl Serialize for PackageState {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_u32(*self as u32)
    }
}

/**
* An `ApkPackage` is a safe wrapper around an `apk_package`. Since the `apk_package` only lives
* for as long as the underlying `ApkDatabase` lives, its lifetime is rather restricted. Use a
* `ApkOwnedPackages` to keep the `ApkDabatase` alive while you handle the packages.
*/
pub struct ApkPackage<'a> {
    apk_package: &'a apk_package,
    old_version: Option<String>,
    package_state: PackageState,
}

/**
* A vector of `ApkPackage`s that also holds the `ApkDatabase` that they originate from. This is done
* so the `ApkDatabase` (and as such the `ApkPackage`s whose lifetime is attached to the `ApkDatabase`)
* lives for long enough to do e.g. serialization. Please don't hold this for longer than necessary, as
* the DB is locked for as long as this isn't dropped.
*/
pub struct ApkOwnedPackages<'a> {
    pub packages: Vec<ApkPackage<'a>>,
    #[allow(dead_code)]
    database: ApkDatabase<'a>,
}

impl<'a> ApkOwnedPackages<'a> {
    pub fn new(database: ApkDatabase<'a>, packages: Vec<ApkPackage<'a>>) -> Self {
        Self { database, packages }
    }
}

impl<'a> Serialize for ApkOwnedPackages<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.packages.serialize(serializer)
    }
}

impl<'a> zvariant::Type for ApkOwnedPackages<'a> {
    fn signature() -> zvariant::Signature<'static> {
        zvariant::Signature::try_from("a(ssssssttu)").unwrap()
    }
}

impl<'a> ApkPackage<'a> {
    pub fn new(
        new_package: &'a apk_package,
        old_package: Option<&apk_package>,
        package_state: PackageState,
    ) -> ApkPackage<'a> {
        assert!(!new_package.name.is_null());
        assert!(!new_package.version.is_null());
        ApkPackage {
            apk_package: new_package,
            old_version: old_package.and_then(|p| match unsafe { p.version.as_ref() } {
                Some(v) => String::try_from(v).ok(),
                None => None,
            }),
            package_state,
        }
    }

    pub fn name(&self) -> &str {
        if let Some(name) = unsafe { self.apk_package.name.as_ref() } {
            unsafe { CStr::from_ptr((*name).name).to_str().unwrap() }
        } else {
            ""
        }
    }

    pub fn new_version(&self) -> String {
        String::try_from(unsafe { self.apk_package.version.as_ref().unwrap() }).unwrap()
    }

    pub fn old_version(&self) -> Option<&str> {
        self.old_version.as_deref()
    }

    pub fn package_state(&self) -> PackageState {
        self.package_state
    }

    pub fn license(&self) -> Option<String> {
        if let Some(license) = unsafe { self.apk_package.license.as_ref() } {
            return String::try_from(license).ok();
        }
        None
    }

    pub fn url(&self) -> Option<&str> {
        if self.apk_package.url.is_null() {
            None
        } else {
            unsafe { Some(CStr::from_ptr(self.apk_package.url).to_str().unwrap()) }
        }
    }

    pub fn description(&self) -> Option<&str> {
        if self.apk_package.description.is_null() {
            None
        } else {
            unsafe {
                Some(
                    CStr::from_ptr(self.apk_package.description)
                        .to_str()
                        .unwrap(),
                )
            }
        }
    }

    pub fn size(&self) -> usize {
        self.apk_package.size
    }

    pub fn installed_size(&self) -> usize {
        self.apk_package.installed_size
    }
}

impl<'a> fmt::Display for ApkPackage<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}-{}", self.name(), self.new_version())
    }
}

impl<'a> Serialize for ApkPackage<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("ApkPackage", 9)?;
        state.serialize_field("name", &self.name())?;
        state.serialize_field("new_version", &self.new_version())?;
        state.serialize_field("description", &self.description().unwrap_or("").to_string())?;
        state.serialize_field("license", &self.license().unwrap_or_else(|| "".to_string()))?;
        state.serialize_field("old_version", &self.old_version().unwrap_or(""))?;
        state.serialize_field("url", &self.url().unwrap_or("").to_string())?;
        state.serialize_field(
            "installed_size",
            &u64::try_from(self.installed_size()).unwrap_or(0),
        )?;
        state.serialize_field("size", &u64::try_from(self.size()).unwrap_or(0))?;
        state.serialize_field("package_state", &(self.package_state() as u32))?;
        state.end()
    }
}

impl<'a> zvariant::Type for ApkPackage<'a> {
    fn signature() -> zvariant::Signature<'static> {
        zvariant::Signature::try_from("(ssssssttu)").unwrap()
    }
}
