1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Tabix index header format and coordinate system.

pub mod coordinate_system;

pub use self::coordinate_system::CoordinateSystem;

use std::{error, fmt};

const COORDINATE_SYSTEM_SHIFT: usize = 16;
const FORMAT_MASK: i32 = 0xffff;

/// A tabix index format.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
    /// A generic format with a defined coordinate system.
    Generic(CoordinateSystem),
    /// The SAM (Sequence Alignment/Map) format.
    Sam,
    /// The VCF (Variant Call Format) format.
    Vcf,
}

impl Format {
    /// Returns the coordinate system of the format.
    ///
    /// # Examples
    ///
    /// ```
    /// use noodles_csi::binning_index::index::header::{format::CoordinateSystem, Format};
    ///
    /// let format = Format::Generic(CoordinateSystem::Bed);
    /// assert_eq!(format.coordinate_system(), CoordinateSystem::Bed);
    ///
    /// assert_eq!(Format::Sam.coordinate_system(), CoordinateSystem::Gff);
    /// assert_eq!(Format::Vcf.coordinate_system(), CoordinateSystem::Gff);
    /// ```
    pub fn coordinate_system(&self) -> CoordinateSystem {
        match self {
            Self::Generic(coordinate_system) => *coordinate_system,
            Self::Sam | Self::Vcf => CoordinateSystem::Gff,
        }
    }
}

/// An error returned when a raw format fails to convert.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TryFromIntError {
    /// The coordinate system is invalid.
    InvalidCoordinateSystem(coordinate_system::TryFromIntError),
    /// The kind is invalid.
    InvalidKind(u16),
}

impl error::Error for TryFromIntError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            Self::InvalidCoordinateSystem(e) => Some(e),
            Self::InvalidKind(_) => None,
        }
    }
}

impl fmt::Display for TryFromIntError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidCoordinateSystem(_) => f.write_str("invalid coordinate system"),
            Self::InvalidKind(n) => write!(f, "invalid kind: expected 0..=2, got {n}"),
        }
    }
}

impl TryFrom<i32> for Format {
    type Error = TryFromIntError;

    fn try_from(n: i32) -> Result<Self, Self::Error> {
        let raw_kind = (n & FORMAT_MASK) as u16;

        match raw_kind {
            0 => {
                let raw_format = (n >> COORDINATE_SYSTEM_SHIFT) as u16;
                CoordinateSystem::try_from(raw_format)
                    .map(Self::Generic)
                    .map_err(TryFromIntError::InvalidCoordinateSystem)
            }
            1 => Ok(Self::Sam),
            2 => Ok(Self::Vcf),
            _ => Err(TryFromIntError::InvalidKind(raw_kind)),
        }
    }
}

impl From<Format> for i32 {
    fn from(format: Format) -> Self {
        match format {
            Format::Generic(coordinate_system) => {
                i32::from(u16::from(coordinate_system)) << COORDINATE_SYSTEM_SHIFT
            }
            Format::Sam => 1,
            Format::Vcf => 2,
        }
    }
}

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

    #[test]
    fn test_try_from_i32_for_format() {
        assert_eq!(
            Format::try_from(0x010000),
            Ok(Format::Generic(CoordinateSystem::Bed))
        );

        assert_eq!(
            Format::try_from(0),
            Ok(Format::Generic(CoordinateSystem::Gff))
        );

        assert_eq!(Format::try_from(1), Ok(Format::Sam));
        assert_eq!(Format::try_from(2), Ok(Format::Vcf));

        assert_eq!(Format::try_from(3), Err(TryFromIntError::InvalidKind(3)));
    }

    #[test]
    fn test_from_format_for_i32() {
        assert_eq!(i32::from(Format::Generic(CoordinateSystem::Bed)), 0x010000);
        assert_eq!(i32::from(Format::Generic(CoordinateSystem::Gff)), 0);
        assert_eq!(i32::from(Format::Sam), 1);
        assert_eq!(i32::from(Format::Vcf), 2);
    }
}