// Copyright (c) 2019-2021 Linaro LTD
//
// SPDX-License-Identifier: Apache-2.0

//! Support and tests related to the dependency management for multi-image
//! support.

use crate::image::ImageVersion;

pub trait Depender {
    /// Generate a version for this particular image.  The slot indicates
    /// which slot this is being put in.
    fn my_version(&self, offset: usize, slot: usize) -> ImageVersion;

    /// Return dependencies for this image/slot combination.
    fn my_deps(&self, offset: usize, slot: usize) -> Vec<ImageVersion>;

    /// Return the image ID of the other version.
    fn other_id(&self) -> u8;
}

/// A boring image is used when we aren't testing dependencies.  There will
/// be meaningful version numbers.  The size field is the image number we
/// are.
pub struct BoringDep {
    number: usize,
    test: DepTest,
}

impl BoringDep {
    pub fn new(number: usize, test: &DepTest) -> BoringDep {
        BoringDep {
            number,
            test: test.clone(),
        }
    }
}

impl Depender for BoringDep {
    fn my_version(&self, _offset: usize, slot: usize) -> ImageVersion {
        let slot = if self.test.downgrade {
            1 - slot
        } else {
            slot
        };
        ImageVersion::new_synthetic(self.number as u8, slot as u8, 0)
    }

    fn my_deps(&self, _offset: usize, _slot: usize) -> Vec<ImageVersion> {
        vec![]
    }

    fn other_id(&self) -> u8 {
        0
    }
}

/// An individual test of the dependency mechanism describes one of the
/// possibilities for the dependency information for each image, and what
/// the expected outcome is.
#[derive(Clone, Debug)]
pub struct DepTest {
    /// What kinds of dependency should be installed in the image.
    pub depends: [DepType; 2],

    /// What is the expected outcome of the upgrade.
    pub upgrades: [UpgradeInfo; 2],

    /// Should this be considered a downgrade (cause the version number to
    /// decrease).
    pub downgrade: bool,
}

/// Describes the various types of dependency information that can be
/// provided.
#[derive(Clone, Debug)]
pub enum DepType {
    /// Do not include dependency information
    Nothing,
    /// Provide dependency information that matches the other image.
    Correct,
    /// Provide a dependency that matches the old version of the other
    /// image.
    OldCorrect,
    /// Provide dependency information describing something newer than the
    /// other image.
    Newer,
    /// Don't provide an upgrade image at all for this image
    NoUpgrade,
}

/// Describes what our expectation is for an upgrade.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UpgradeInfo {
    /// The current version should be held.
    Held,
    /// The image should be upgraded
    Upgraded,
}

/// A "test" that gives no dependency information.
pub static NO_DEPS: DepTest = DepTest {
    depends: [DepType::Nothing, DepType::Nothing],
    upgrades: [UpgradeInfo::Upgraded, UpgradeInfo::Upgraded],
    downgrade: false,
};

/// A "test" with no dependency information, and the images marked as a
/// downgrade.
pub static REV_DEPS: DepTest = DepTest {
    depends: [DepType::Nothing, DepType::Nothing],
    upgrades: [UpgradeInfo::Held, UpgradeInfo::Held],
    downgrade: true,
};

/// A PairDep describes the dependencies between two pairs.
pub struct PairDep {
    /// The image number of this image.
    number: usize,

    test: DepTest,
}

impl PairDep {
    pub fn new(total_image: usize, my_image: usize, deps: &DepTest) -> PairDep {
        if total_image != 2 {
            panic!("PairDep only works when there are two images");
        }

        PairDep {
            number: my_image,
            test: deps.clone(),
        }
    }
}

impl Depender for PairDep {
    fn my_version(&self, _offset: usize, slot: usize) -> ImageVersion {
        let slot = if self.test.downgrade {
            1 - slot
        } else {
            slot
        };
        ImageVersion::new_synthetic(self.number as u8, slot as u8, 0)
    }

    fn my_deps(&self, _offset: usize, slot: usize) -> Vec<ImageVersion> {
        // For now, don't put any dependencies in slot zero.  They could be
        // added here if we someday implement checking these.
        if slot == 0 {
            vec![]
        } else {
            match self.test.depends[self.number] {
                DepType::Nothing => vec![],
                DepType::NoUpgrade => panic!("Shouldn't get to this point"),
                DepType::Correct => vec![
                    ImageVersion::new_synthetic(self.other_id(), slot as u8, 0)
                ],
                DepType::OldCorrect => vec![
                    ImageVersion::new_synthetic(self.other_id(), 0, 0)
                ],
                DepType::Newer => vec![
                    ImageVersion::new_synthetic(self.other_id(), slot as u8, 1)
                ],
            }
        }
    }

    fn other_id(&self) -> u8 {
        (1 - self.number) as u8
    }
}

impl ImageVersion {
    /// Generate an image version based on some key information.  The image
    /// number influences the major version number (by an arbitrary factor),
    /// and the slot affects the major number on the build_number.  The minor
    /// number can also be given to force numbers to be different.
    fn new_synthetic(image_id: u8, slot: u8, minor: u8) -> ImageVersion {
        ImageVersion {
            major: image_id * 20 + slot,
            minor,
            revision: 1,
            build_num: slot as u32,
        }
    }
}
