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

use crate::apk_arrays::{ApkBlobT, ApkDependencyArray, ApkStringArray};
use crate::apk_package::{ApkOwnedPackages, ApkPackage};
use crate::apk_repository::ApkRepository;
use crate::error::{Error, Errors};
use crate::{
    i18n::{i18n, i18n_f},
    PackageState,
};
use apk_tools_sys::*;
use log::{debug, info, trace, warn};
use std::convert::TryFrom;
use std::ffi::{c_void, CStr, CString};
use std::os::raw::{c_char, c_ulong};
use std::path::Path;
use std::ptr::null_mut;

pub struct ApkDatabase<'a> {
    additional_repo: Option<Box<apk_repository_list>>,
    db: Box<apk_database>,
    root: Option<&'a str>,
}

impl<'a> ApkDatabase<'a> {
    /**
     * Creates a new ApkDatabase Object and opens the DB. Keep in mind that you
     * can only have one ApkDatabase with `read_only=false` at the time. Since the DB
     * will be locked for the time you hold it, make sure to drop it ASAP.
     *
     * # Errors
     * * Returns an Error::DatabaseOpen if opening the DB fails (e.g. due to missing permissions,
     *   multiple `!read_only` instances etc.)
     */
    pub fn new(
        read_only: bool,
        additional_repo_uri: Option<&'a str>,
        root: Option<&'a str>,
    ) -> Result<ApkDatabase<'a>, Error> {
        debug!("Creating new ApkDatabase");
        let mut db_options = apk_db_options::default();
        let mut db = Box::new(apk_database::default());
        let additional_repo = unsafe {
            apk_db_init(db.as_mut());
            db_options.repository_list.next = &mut db_options.repository_list;
            db_options.repository_list.prev = &mut db_options.repository_list;
            if let Some(uri) = additional_repo_uri {
                trace!("Adding additional repository {}", uri);
                let mut additional_repo = Box::new(apk_repository_list::default());
                additional_repo.url = CString::new(uri).unwrap().into_raw();
                (*db_options.repository_list.next).prev = &mut additional_repo.as_mut().list;
                additional_repo.as_mut().list.next = db_options.repository_list.next;
                additional_repo.as_mut().list.prev = &mut db_options.repository_list;
                db_options.repository_list.next = &mut additional_repo.as_mut().list;
                Some(additional_repo)
            } else {
                None
            }
        };
        if read_only {
            trace!("Opening ApkDatabase as read-only");
            db_options.open_flags = (APK_OPENF_READ | APK_OPENF_NO_AUTOUPDATE) as c_ulong;
        } else {
            trace!("Opening ApkDatabase as read-write");
            db_options.open_flags = (APK_OPENF_READ
                | APK_OPENF_WRITE
                | APK_OPENF_NO_AUTOUPDATE
                | APK_OPENF_CACHE_WRITE
                | APK_OPENF_CREATE) as c_ulong;
        }
        if let Some(root) = root {
            trace!("Setting root {}", root);
            db_options.root = CString::new(root).unwrap().into_raw();
        }
        db_options.lock_wait = 1;

        let res = unsafe { apk_db_open(db.as_mut(), &mut db_options) };

        if !db_options.root.is_null() {
            unsafe { CString::from_raw(db_options.root as *mut c_char) };
        }

        if res == 0 {
            trace!("Successfully opened DB");
            Ok(ApkDatabase {
                additional_repo,
                db,
                root,
            })
        } else {
            Err(Error::DatabaseOpen(i18n_f(
                "Failed to open the apk database due to error {}",
                &[&ApkDatabase::res_to_string(res)],
            )))
        }
    }

    /**
     * This sets the APK_EXTRAF_NO_CHOWN flag, meaning that apk won't attempt to chown
     * files to the `root` user. This is required for situations where this isn't run as
     * root (e.g. in tests).
     */
    pub fn no_extract_chown(&mut self) {
        warn!("Setting APK_EXTRACTF_NO_CHOWN - this should only happen in tests");
        self.db.extract_flags = APK_EXTRACTF_NO_CHOWN;
    }

    /**
     * Add (install) the package identified by `pkgname`.
     *
     * # Errors
     * * Returns an `Error::Add` if the package can't be added, e.g. because adding the
     *   package would introducing broken dependencies, or no such package exists.
     */
    pub fn add_package(&mut self, pkgname: &str) -> Result<(), Error> {
        info!("Adding package {}", pkgname);
        let mut world_copy = unsafe {
            let mut world_copy: *mut apk_dependency_array =
                apk_array_resize(std::ptr::null_mut(), 0, 0).cast();
            apk_dependency_array_copy(&mut world_copy, self.db.world);
            ApkDependencyArray(world_copy)
        };
        let mut dep = self.package_name_to_apk_dependency(pkgname).map_err(|e| {
            Error::Add(i18n_f(
                "Failed to add package {} due to error {}",
                &[pkgname, &e.to_string()],
            ))
        })?;
        unsafe {
            apk_deps_add(&mut world_copy.0, &mut dep);
        }
        if unsafe { apk_solver_commit(self.db.as_mut(), 0, world_copy.0) } != 0 {
            return Err(Error::Add(i18n_f(
                "Failed to add package {}! Please run 'apk add {}' for more information.",
                &[pkgname, pkgname],
            )));
        }
        debug!("Done adding package {}", pkgname);
        Ok(())
    }

    /**
     * Delete (remove) the package identified by `pkgname`.
     *
     * # Errors
     * * Returns an `Error::DeleteRequiredPackage` if the package can't be delete because
     *   it's still required by another package.
     * * Returns an `Error::Delete` if the package can't be deleted due to some other reason.
     */
    pub fn delete_package(&mut self, pkgname: &str) -> Result<(), Error> {
        info!("Deleting package {}", pkgname);
        let mut world_copy = unsafe {
            let mut world_copy: *mut apk_dependency_array =
                apk_array_resize(std::ptr::null_mut(), 0, 0).cast();
            apk_dependency_array_copy(&mut world_copy, (*self.db).world);
            ApkDependencyArray(world_copy)
        };
        let dep = self.package_name_to_apk_dependency(pkgname).map_err(|e| {
            Error::Delete(i18n_f(
                "Failed to delete package {} due to error {}",
                &[pkgname, &e.to_string()],
            ))
        })?;
        unsafe {
            apk_deps_del(&mut world_copy.0, dep.name);
        }
        let mut changeset = apk_changeset::default();
        if unsafe { apk_solver_solve(self.db.as_mut(), 0, world_copy.0, &mut changeset) } != 0 {
            return Err(Error::Delete(i18n_f(
                "Failed to delete package {}! Please run 'apk del {}' for more information.",
                &[pkgname, pkgname],
            )));
        }
        for i in 0..unsafe { changeset.changes.as_ref() }.unwrap().num {
            if let Some(mut pkg) = unsafe {
                (*changeset.changes)
                    .item
                    .as_mut_slice((*changeset.changes).num)
            }
            .get_mut(i)
            .and_then(|c| unsafe { c.new_pkg.as_mut() })
            {
                trace!(
                    "Marking package {} as potential reverse dependency during deletion",
                    unsafe { CStr::from_ptr((*pkg.name).name).to_str().unwrap() }
                );
                pkg.marked = 1;
            }
        }
        let dependants = unsafe {
            let mut pkgname_arr: *mut apk_string_array =
                apk_array_resize(std::ptr::null_mut(), 0, 0).cast();
            *apk_string_array_add(&mut pkgname_arr) = CString::new(pkgname).unwrap().into_raw();

            let pkgname_arr = ApkStringArray(pkgname_arr);
            let mut dependants: Vec<String> = Vec::new();

            apk_name_foreach_matching(
                self.db.as_mut(),
                pkgname_arr.0,
                apk_foreach_genid() | APK_FOREACH_MARKED | APK_DEP_SATISFIES,
                Some(ApkDatabase::foreach_matching),
                &mut dependants as *mut _ as *mut c_void,
            );
            dependants.retain(|e| e != pkgname);
            dependants
        };

        if !dependants.is_empty() {
            return Err(Error::DeleteRequiredPackage(i18n_f(
                "Package {} still required by the following packages: {}",
                &[pkgname, &dependants.join(" ")],
            )));
        }
        if unsafe {
            apk_solver_commit_changeset(self.db.as_mut(), &mut changeset, world_copy.0) != 0
        } {
            return Err(Error::Delete(i18n_f(
                "Failed to delete package {}! Please run 'apk del {}' for more information.",
                &[pkgname, pkgname],
            )));
        }
        debug!("Done deleting package {}", pkgname);
        Ok(())
    }

    /**
     * Returns an ApkPackage with detailed info for the package `pkgname`.
     */
    pub fn get_package_details(&mut self, pkgname: &str) -> Result<ApkPackage, Error> {
        info!("Trying to get details of package {}", pkgname);
        let dep = self.package_name_to_apk_dependency(pkgname).map_err(|e| {
            Error::Details(i18n_f(
                "Couldn't retrieve details for package {} due to error {}",
                &[pkgname, &e.to_string()],
            ))
        })?;
        if let Some(providers) = unsafe { dep.name.as_ref().and_then(|n| n.providers.as_ref()) } {
            if let Some(pkg) = providers
                .as_slice()
                .ok()
                .and_then(|s| s.first())
                .and_then(|p| unsafe { p.pkg.as_ref() })
            {
                let pkg_state = if pkg.ipkg.is_null() {
                    PackageState::Available
                } else {
                    PackageState::Installed
                };
                return Ok(ApkPackage::new(pkg, None, pkg_state));
            }
        }
        Err(Error::Details(i18n_f(
            "Couldn't retrieve details for unknown package {}",
            &[pkgname],
        )))
    }

    /**
     * Returns a list of available packages.
     */
    pub fn list_available_packages(mut self) -> ApkOwnedPackages<'a> {
        info!("Listing available packages");
        let mut packages: Vec<ApkPackage> = Vec::new();
        unsafe {
            apk_hash_foreach(
                &mut (*self.db).available.packages,
                Some(ApkDatabase::append_package),
                &mut packages as *mut _ as *mut c_void,
            );
        }
        debug!(
            "Done listing available packages. Found {} packages",
            packages.len()
        );
        ApkOwnedPackages::new(self, packages)
    }

    /**
     * Returns a list of all upgradable packages.
     *
     * # Errors
     * * Returns an `Error::ListUpgradable` if the upgrade changeset can't be calculated
     *   (e.g. because the system has a broken depgraph).
     * * Returns an `Error::IntegerOverflow` if the undyling C library, libapk, sends
     *   changesets whose size is >usize.
     */
    pub fn list_upgradable_packages(mut self) -> Result<ApkOwnedPackages<'a>, Error> {
        info!("Listing upgradable packages");
        let upgrade_changeset = self.get_upgrade_changeset().map_err(|e| {
            Error::ListUpgradable(i18n_f(
                "Failed to list upgradable packages due to error {}",
                &[&e.to_string()],
            ))
        })?;
        let mut packages = Vec::new();

        for i in 0..unsafe { (*upgrade_changeset.changes).num } {
            let change = unsafe { &(*upgrade_changeset.changes).as_slice()?[i] };
            if change.new_pkg.is_null() && !change.old_pkg.is_null() {
                let pkg = unsafe {
                    ApkPackage::new(
                        change.old_pkg.as_ref().unwrap(),
                        None,
                        PackageState::PendingRemoval,
                    )
                };
                trace!(
                    "Adding package {} as pending removal, to be replaced.",
                    pkg.name(),
                );
                packages.push(pkg);
            } else if !change.new_pkg.is_null() && change.old_pkg.is_null() {
                let pkg = unsafe {
                    ApkPackage::new(
                        change.new_pkg.as_ref().unwrap(),
                        None,
                        PackageState::PendingInstall,
                    )
                };
                trace!(
                    "Adding package {} as pending install, replaces another package.",
                    pkg.name(),
                );
                packages.push(pkg);
            } else if change.reinstall() != 0 {
                let pkg = unsafe {
                    ApkPackage::new(
                        change.new_pkg.as_ref().unwrap(),
                        Some(change.old_pkg.as_ref().unwrap()),
                        PackageState::Reinstall,
                    )
                };
                trace!("Reinstalling package {} (e.g. because it was rebuilt but pkgrel wasn't changed).",
                    pkg.name());
                packages.push(pkg);
            } else if !change.new_pkg.is_null() && !change.new_pkg.is_null() {
                if unsafe {
                    (apk_pkg_version_compare(change.new_pkg, change.old_pkg)
                        & (i32::try_from(APK_VERSION_LESS)?))
                        != 0
                } && change.new_pkg != change.old_pkg
                {
                    let pkg = unsafe {
                        ApkPackage::new(
                            change.new_pkg.as_ref().unwrap(),
                            Some(change.old_pkg.as_ref().unwrap()),
                            PackageState::Downgradable,
                        )
                    };
                    trace!(
                        "Adding package {} as downgradable (e.g. because the repo downgraded).",
                        pkg.name(),
                    );
                    packages.push(pkg);
                } else if unsafe {
                    (apk_pkg_version_compare(change.new_pkg, change.old_pkg)
                        & (i32::try_from(APK_VERSION_GREATER | APK_VERSION_EQUAL)?))
                        != 0
                } && change.new_pkg != change.old_pkg
                {
                    let pkg = unsafe {
                        ApkPackage::new(
                            change.new_pkg.as_ref().unwrap(),
                            Some(change.old_pkg.as_ref().unwrap()),
                            PackageState::Upgradable,
                        )
                    };
                    trace!(
                        "Adding package {} as upgradable, version {:?} -> {}",
                        pkg.name(),
                        pkg.old_version(),
                        pkg.new_version(),
                    );
                    packages.push(pkg);
                } else {
                    trace!(
                        "Not adding package {} as upgradable, version compare: {}, new != old: {}",
                        unsafe {
                            CStr::from_ptr((*(*change.new_pkg).name).name)
                                .to_str()
                                .unwrap()
                        },
                        unsafe {
                            apk_pkg_version_compare(change.new_pkg, change.old_pkg)
                                & (i32::try_from(APK_VERSION_GREATER | APK_VERSION_EQUAL)?)
                        },
                        change.new_pkg != change.old_pkg
                    );
                }
            }
        }

        debug!(
            "Done listing upgradable packages. Found {} packages",
            packages.len()
        );
        Ok(ApkOwnedPackages::new(self, packages))
    }

    /**
     * Search for the `ApkPackage` that owns `path`. Returns `None` if no package owning this package can be found.
     */
    pub fn search_file_owner(&mut self, path: &str) -> Option<ApkPackage> {
        info!("Searching for owner package of file {}", path);
        let file_path = if let Some(ref root) = self.root {
            trace!("Adding root {} to search path", root);
            let path = Path::new(path);
            if path.has_root() {
                Path::new(root).join(Path::new(path).strip_prefix("/").unwrap())
            } else {
                Path::new(root).join(&path)
            }
        } else {
            Path::new(path).to_path_buf()
        };
        if let Ok(mut real_path) = std::fs::canonicalize(file_path) {
            debug!(
                "Resolved real path (w/o symlinks) to be: {:?}",
                real_path.to_str()
            );
            if let Some(root) = self.root.as_deref() {
                trace!("Stripping root {} from search path", root);
                real_path = real_path.strip_prefix(root).unwrap().to_path_buf();
            }
            if let Some(path_str) = real_path.as_os_str().to_str() {
                let blob = ApkBlobT::new(path_str);
                if let Some(pkg) =
                    unsafe { apk_db_get_file_owner(self.db.as_mut(), blob.blob.clone()).as_ref() }
                {
                    let pkg = ApkPackage::new(pkg, None, PackageState::Installed);
                    debug!(
                        "Done searching for owner package of file {}. Found matching package {}",
                        path, pkg,
                    );
                    return Some(pkg);
                }
            }
        }
        debug!(
            "Done searching for owner package of file {}. Found no matching package",
            path
        );
        None
    }

    /**
     * Gets all available repositories in `$root/etc/apk/repositories`.
     *
     * # Errors
     * * Returns an `Error::GetRepositories` if opening the file or reading from it fails.
     */
    pub fn get_repositories(root: Option<&str>) -> Result<Vec<ApkRepository>, Error> {
        use std::fs::File;
        use std::io::{BufRead, BufReader};

        info!("Getting repositories");
        let repo_file_path = if let Some(root) = root {
            format!("{}/etc/apk/repositories", root)
        } else {
            "/etc/apk/repositories".to_string()
        };
        let file = File::open(&repo_file_path).map_err(|e| {
            Error::GetRepositories(i18n_f(
                "Failed to get repositories from file {} due to error {}",
                &[&repo_file_path, &e.to_string()],
            ))
        })?;
        let reader = BufReader::new(file);
        let mut ret = Vec::new();

        for line in reader.lines() {
            if let Ok(line) = line.map(|l| l.trim().to_string()) {
                if !line.is_empty() {
                    trace!("Creating repository from line {}", line);
                    ret.push(ApkRepository::new(
                        !line.starts_with('#'),
                        Some(repo_file_path.clone()),
                        line.replace('#', "").trim().to_string(),
                    ));
                }
            }
        }

        debug!(
            "Done listing repositories. Found following repositories: {:?}",
            ret
        );
        Ok(ret)
    }

    /**
     * Sets the repositories `repos` in the repo file `$root/etc/apk/repositories`.
     *
     * # Errors
     * * Returns an `Error::SetRepositories` if the file can't be opened or we can't write to it.
     */
    pub fn set_repositories(repos: &[ApkRepository], root: Option<&str>) -> Result<(), Error> {
        use std::fs::File;
        use std::io::Write;

        info!("Setting repositories to {:?}", repos);

        let mut line = String::new();
        let repo_file_path = if let Some(root) = root {
            format!("{}/etc/apk/repositories", root)
        } else {
            "/etc/apk/repositories".to_string()
        };
        let mut file = File::create(&repo_file_path).map_err(|e| {
            Error::SetRepositories(i18n_f(
                "Failed to set repositories from file {} due to error {}",
                &[&repo_file_path, &e.to_string()],
            ))
        })?;
        for repo in repos {
            trace!("Writing repository {} to file {}", repo, repo_file_path);
            if !repo.enabled {
                line.push('#');
            }
            line.push_str(&format!("{}\n", repo.url));
        }
        file.write_all(line.as_bytes()).map_err(|e| {
            Error::SetRepositories(i18n_f(
                "Failed to set repositories from file {} due to error {}",
                &[&repo_file_path, &e.to_string()],
            ))
        })?;

        debug!("Done setting repositories to {:?}", repos);
        Ok(())
    }

    /**
     * Updates all available repositories. Returns `false` if updating one of the repositories
     * went wrong.
     */
    pub fn update_repositories(
        &mut self,
        allow_untrusted_repositories: bool,
    ) -> Result<(), Errors> {
        let mut err_vec = Vec::new();
        info!("Updating repositories");

        for i in APK_REPOSITORY_FIRST_CONFIGURED..self.db.num_repos {
            if i == APK_REPOSITORY_CACHED {
                continue;
            }
            if let Err(e) = self.update_repository(i, allow_untrusted_repositories) {
                err_vec.push(e);
            }
        }

        if err_vec.is_empty() {
            debug!("Successfully updated the repositories");
            Ok(())
        } else {
            Err(Errors(err_vec))
        }
    }

    /**
     * Upgrade the package `pkgname`.
     *
     * # Errors
     * * Returns `Error::Upgrade` if calculating the dependency tree failed, e.g. due to a broken depgraph.
     * * Returns an `Error::IntegerOverflow` if the undyling C library, libapk, sends
     *   changesets whose size is >usize.
     */
    pub fn upgrade_package(&mut self, pkgname: &str) -> Result<(), Error> {
        info!("Upgrading package {}", pkgname);

        let main_dep = self.package_name_to_apk_dependency(pkgname).map_err(|e| {
            Error::Update(i18n_f(
                "Failed to upgrade package {} due to error {}",
                &[pkgname, &e.to_string()],
            ))
        })?;

        unsafe {
            apk_solver_set_name_flags(
                main_dep.name,
                u16::try_from(APK_SOLVERF_UPGRADE | APK_SOLVERF_AVAILABLE)?,
                0,
            )
        };

        if let Some(origin) = unsafe {
            apk_pkg_get_installed(main_dep.name)
                .as_ref()
                .and_then(|p| p.origin.as_ref())
                .and_then(|o| String::try_from(o).ok())
        } {
            trace!("Found origin {} for package {}", origin, pkgname);
            if origin != pkgname {
                let origin_blob = ApkBlobT::new(&origin);
                if let Some(origin_pkgname) = unsafe {
                    apk_db_query_name(self.db.as_mut(), origin_blob.blob.clone()).as_mut()
                } {
                    debug!(
                        "Upgrading main package {} too since subpackage {} is being upgraded.",
                        origin, pkgname
                    );
                    if !unsafe { apk_pkg_get_installed(origin_pkgname) }.is_null() {
                        let origin_dep = self.package_name_to_apk_dependency(&origin)
                            .map_err(|e| {
                                Error::Update(i18n_f(
                                "Failed to upgrade subpackage {} because main package couldn't be upgraded due to error {}",
                                &[pkgname, &e.to_string()],
                            ))
                            })?;
                        unsafe {
                            apk_solver_set_name_flags(
                                origin_dep.name,
                                u16::try_from(APK_SOLVERF_UPGRADE | APK_SOLVERF_AVAILABLE)?,
                                0,
                            )
                        };
                    }
                }
            }
        }
        if unsafe { apk_solver_commit(self.db.as_mut(), 0, self.db.world) } != 0 {
            return Err(Error::Upgrade(i18n_f(
                "Failed to upgrade package {}! Please run 'apk add -u {}' for more information.",
                &[pkgname, pkgname],
            )));
        }
        debug!("Done upgrading package {}", pkgname);
        Ok(())
    }

    extern "C" fn append_package(
        item: apk_hash_item,
        ctx: *mut ::std::os::raw::c_void,
    ) -> ::std::os::raw::c_int {
        let packages = ctx.cast::<Vec<ApkPackage>>();
        if let Some(package) = unsafe { item.cast::<apk_package>().as_ref() } {
            let pkg = ApkPackage::new(package, None, PackageState::Available);
            trace!("Appending package {} to package array", pkg.name());
            unsafe { packages.as_mut() }.unwrap().push(pkg);
        }
        0
    }

    unsafe extern "C" fn foreach_matching(
        _: *mut apk_database,
        _: *const std::os::raw::c_char,
        name: *mut apk_name,
        ctx: *mut c_void,
    ) {
        let pkgnames = ctx.cast::<Vec<String>>().as_mut().unwrap();

        unsafe fn add(pkg: &apk_package, pkgnames: &mut Vec<String>) {
            if let Some(name) = pkg.name.as_ref() {
                let pkgname = CStr::from_ptr(name.name).to_str().unwrap();
                if !pkgnames.iter().any(|e| e == pkgname) {
                    pkgnames.push(pkgname.to_string());
                }
            }
            for rdepend in (*(*pkg.name).rdepends)
                .iter()
                .unwrap()
                .filter_map(|r| r.as_ref())
            {
                for pkg in (*rdepend.providers)
                    .iter()
                    .unwrap()
                    .filter_map(|p| p.pkg.as_ref())
                {
                    if !pkg.ipkg.is_null() || pkg.marked == 1 {
                        add(pkg, pkgnames);
                    }
                }
            }
        }

        if let Some(providers) = name.as_ref().and_then(|n| n.providers.as_ref()) {
            for provider in providers.as_slice().unwrap() {
                if let Some(pkg) = provider.pkg.as_ref() {
                    if pkg.marked == 1 {
                        add(pkg, pkgnames)
                    }
                }
            }
        }
    }

    fn package_name_to_apk_dependency(&mut self, pkgname: &str) -> Result<apk_dependency, Error> {
        trace!("Converting package {} to apk_dependency", pkgname);

        let mut blob = ApkBlobT::new(pkgname);
        let mut dependency = apk_dependency::default();
        unsafe {
            apk_blob_pull_dep(&mut blob.blob, self.db.as_mut(), &mut dependency);
        }
        if blob.blob.ptr.is_null() || blob.blob.len > 0 {
            return Err(Error::Dependency(i18n_f("{} is not a correctly formated world dependency, the format should be: name(@tag)([<>~=]version)", &[pkgname])));
        }
        if dependency.name.is_null() {
            return Err(Error::NoSuchPackage(i18n_f(
                "Couldn't find package {}",
                &[pkgname],
            )));
        }
        Ok(dependency)
    }

    fn update_repository(
        &mut self,
        repo_num: u32,
        allow_untrusted_repositories: bool,
    ) -> Result<(), Error> {
        trace!(
            "Updating repository {}",
            unsafe { CStr::from_ptr(self.db.repos[usize::try_from(repo_num).unwrap()].url) }
                .to_str()
                .unwrap(),
        );
        let apk_verify = if allow_untrusted_repositories {
            warn!("Allowing untrusted repos!");
            APK_SIGN_NONE
        } else {
            APK_SIGN_VERIFY
        };
        let download_res = unsafe {
            apk_cache_download(
                self.db.as_mut(),
                &mut self.db.repos[usize::try_from(repo_num).unwrap()],
                null_mut(),
                i32::try_from(apk_verify)?,
                0,
                None,
                null_mut(),
            )
        };
        if download_res == 0 {
            trace!("Successfully updated repository.");
            self.db.repo_update_counter += 1;
        } else if download_res != -libc::EALREADY {
            self.db.repo_update_errors += 1;
            return Err(Error::Update(i18n_f(
                "Fetching repository {} failed due to error {}",
                &[
                    unsafe {
                        CStr::from_ptr(self.db.repos[usize::try_from(repo_num).unwrap()].url)
                            .to_str()
                            .unwrap()
                    },
                    &ApkDatabase::res_to_string(download_res),
                ],
            )));
        }
        Ok(())
    }

    fn res_to_string(res: i32) -> String {
        unsafe {
            CStr::from_ptr(apk_error_str(res))
                .to_str()
                .unwrap()
                .to_string()
        }
    }

    fn get_upgrade_changeset(&mut self) -> Result<apk_changeset, Error> {
        trace!("Getting upgrade changeset");
        if unsafe { apk_db_check_world(self.db.as_mut(), self.db.world) } != 0 {
            return Err(Error::Upgrade(i18n(
                "Missing repository tags; can't continue the upgrade!",
            )));
        }

        let world_copy = unsafe {
            let mut world_copy: *mut apk_dependency_array =
                apk_array_resize(std::ptr::null_mut(), 0, 0).cast();
            apk_dependency_array_copy(&mut world_copy, self.db.world);
            ApkDependencyArray(world_copy)
        };

        for dep in unsafe { (*world_copy.0).iter_mut()? } {
            if dep.result_mask() == APK_DEPMASK_CHECKSUM {
                dep.set_result_mask(APK_DEPMASK_ANY);
                dep.version = unsafe { &mut apk_atom_null };
            }
        }

        let mut changeset = apk_changeset::default();
        if unsafe {
            apk_solver_solve(
                self.db.as_mut(),
                u16::try_from(APK_SOLVERF_UPGRADE | APK_SOLVERF_AVAILABLE)?,
                world_copy.0,
                &mut changeset,
            )
        } != 0
        {
            return Err(Error::Upgrade(i18n("Failed to calculate upgrade changeset! Please run 'apk upgrade -a -s' for more information.")));
        }
        Ok(changeset)
    }
}

impl<'a> Drop for ApkDatabase<'a> {
    fn drop(&mut self) {
        if self.db.open_complete() == 1 {
            unsafe { apk_db_close(self.db.as_mut()) };
            if let Some(additional_repo) = &mut self.additional_repo {
                unsafe {
                    CString::from_raw(additional_repo.url as *mut c_char);
                }
            }
        }
    }
}
