#![allow(missing_debug_implementations)]
use std::{
    fs::File,
    io::{BufReader, Read},
    path::{Path, PathBuf},
    result::Result,
    u8,
};
#[cfg(feature = "raspicam")]
use std::{ffi::OsStr, i8, u16};
#[cfg(any(feature = "gps", feature = "fona"))]
use std::fmt;
use colored::Colorize;
use failure::{Error, ResultExt};
use lazy_static::lazy_static;
use serde::Deserialize;
use toml;
#[cfg(any(feature = "gps", feature = "fona"))]
use serde::de::{self, Deserializer, Visitor};
#[cfg(any(feature = "gps", feature = "fona"))]
use sysfs_gpio::Pin;
use crate::{error, generate_error_string, CONFIG_FILE};
lazy_static! {
    
    pub static ref CONFIG: Config = match Config::from_file(CONFIG_FILE) {
        Err(e) => {
            panic!("{}", generate_error_string(&e, "error loading configuration").red());
        },
        Ok(c) => c,
    };
}
#[derive(Debug, Deserialize)]
pub struct Config {
    
    debug: Option<bool>,
    
    data_dir: PathBuf,
    
    flight: Flight,
    
    #[cfg(feature = "fona")]
    battery: Battery,
    
    #[cfg(feature = "raspicam")]
    video: Video,
    
    #[cfg(feature = "raspicam")]
    picture: Picture,
    
    #[cfg(feature = "gps")]
    gps: Gps,
    
    #[cfg(feature = "fona")]
    fona: Fona,
    
    #[cfg(feature = "telemetry")]
    telemetry: Telemetry,
}
impl Config {
    
    fn from_file<P: AsRef<Path>>(path: P) -> Result<Config, Error> {
        let file = File::open(path.as_ref()).context(error::Config::Open {
            path: path.as_ref().to_owned(),
        })?;
        let mut reader = BufReader::new(file);
        let mut contents = String::new();
        let _ = reader
            .read_to_string(&mut contents)
            .context(error::Config::Read {
                path: path.as_ref().to_owned(),
            })?;
        let config: Config = toml::from_str(&contents).context(error::Config::InvalidToml {
            path: path.as_ref().to_owned(),
        })?;
        if let (false, errors) = config.verify() {
            Err(error::Config::Invalid { errors }.into())
        } else {
            Ok(config)
        }
    }
    
    #[allow(clippy::replace_consts)]
    fn verify(&self) -> (bool, String) {
        
        #[cfg(feature = "raspicam")]
        let mut errors = String::new();
        #[cfg(feature = "raspicam")]
        let mut ok = true;
        #[cfg(feature = "raspicam")]
        {
            
            if self.picture.width > 3280 {
                ok = false;
                errors.push_str(&format!(
                    "picture width must be below or equal to 3280px, found {}px\n",
                    self.picture.width
                ));
            }
            if self.picture.height > 2464 {
                ok = false;
                errors.push_str(&format!(
                    "picture height must be below or equal to 2464px, found {}px\n",
                    self.picture.height
                ));
            }
            if let Some(r @ 360...u16::MAX) = self.picture.rotation {
                ok = false;
                errors.push_str(&format!(
                    "camera rotation must be between 0 and 359 degrees, found {} degrees\n",
                    r
                ));
            }
            if self.picture.quality > 100 {
                ok = false;
                errors.push_str(&format!(
                    "picture quality must be a number between 0 and 100, found {}px\n",
                    self.picture.quality
                ));
            }
            if let Some(b @ 101...u8::MAX) = self.picture.brightness {
                ok = false;
                errors.push_str(&format!(
                    "picture brightness must be between 0 and 100, found {}\n",
                    b
                ));
            }
            match self.picture.contrast {
                Some(c @ i8::MIN...-101) | Some(c @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "picture contrast must be between -100 and 100, found {}\n",
                        c
                    ));
                }
                _ => {}
            }
            match self.picture.sharpness {
                Some(s @ i8::MIN...-101) | Some(s @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "picture sharpness must be between -100 and 100, found {}\n",
                        s
                    ));
                }
                _ => {}
            }
            match self.picture.saturation {
                Some(s @ i8::MIN...-101) | Some(s @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "picture saturation must be between -100 and 100, found {}\n",
                        s
                    ));
                }
                _ => {}
            }
            match self.picture.iso {
                Some(i @ 0...99) | Some(i @ 801...u16::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "picture ISO must be between 100 and 800, found {}\n",
                        i
                    ));
                }
                _ => {}
            }
            match self.picture.ev {
                Some(e @ i8::MIN...-11) | Some(e @ 11...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "picture EV compensation must be between -10 and 10, found {}\n",
                        e
                    ));
                }
                _ => {}
            }
            
            if self.video.width > 2592 {
                ok = false;
                errors.push_str(&format!(
                    "video width must be below or equal to 2592px, found {}px\n",
                    self.video.width
                ));
            }
            if self.video.height > 1944 {
                ok = false;
                errors.push_str(&format!(
                    "video height must be below or equal to 1944px, found {}px\n",
                    self.video.height
                ));
            }
            if let Some(r @ 360...u16::MAX) = self.video.rotation {
                ok = false;
                errors.push_str(&format!(
                    "camera rotation must be between 0 and 359 degrees, found {} degrees\n",
                    r
                ));
            }
            if self.video.fps > 90 {
                ok = false;
                errors.push_str(&format!(
                    "video framerate must be below or equal to 90fps, found {}fps\n",
                    self.video.fps
                ));
            }
            if let Some(b @ 101...u8::MAX) = self.video.brightness {
                ok = false;
                errors.push_str(&format!(
                    "video brightness must be between 0 and 100, found {}\n",
                    b
                ));
            }
            match self.video.contrast {
                Some(c @ i8::MIN...-101) | Some(c @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video contrast must be between -100 and 100, found {}\n",
                        c
                    ));
                }
                _ => {}
            }
            match self.video.sharpness {
                Some(s @ i8::MIN...-101) | Some(s @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video sharpness must be between -100 and 100, found \
                         {}\n",
                        s
                    ));
                }
                _ => {}
            }
            match self.video.saturation {
                Some(s @ i8::MIN...-101) | Some(s @ 101...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video saturation must be between -100 and 100, found {}\n",
                        s
                    ));
                }
                _ => {}
            }
            match self.video.iso {
                Some(i @ 0...99) | Some(i @ 801...u16::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video ISO must be between 100 and 800, found {}\n",
                        i
                    ));
                }
                _ => {}
            }
            match self.video.ev {
                Some(e @ i8::MIN...-11) | Some(e @ 11...i8::MAX) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video EV compensation must be between -10 and 10, found {}\n",
                        e
                    ));
                }
                _ => {}
            }
            
            match (self.video.width, self.video.height, self.video.fps) {
                (2592, 1944, 1...15)
                | (1920, 1080, 1...30)
                | (1296, 972, 1...42)
                | (1296, 730, 1...49)
                | (640, 480, 1...90) => {}
                (w, h, f) => {
                    ok = false;
                    errors.push_str(&format!(
                        "video mode must be one of 2592\u{d7}1944 1-15fps, 1920\u{d7}1080 \
                         1-30fps, 1296\u{d7}972 1-42fps, 1296\u{d7}730 1-49fps, 640\u{d7}480 \
                         1-60fps, found {}x{} {}fps\n",
                        w, h, f
                    ));
                }
            }
        }
        
        
        #[cfg(feature = "raspicam")]
        {
            (ok, errors)
        }
        #[cfg(not(feature = "raspicam"))]
        {
            (true, String::new())
        }
    }
    
    pub fn debug(&self) -> bool {
        self.debug == Some(true)
    }
    
    pub fn flight(&self) -> Flight {
        self.flight
    }
    
    #[cfg(feature = "fona")]
    pub fn battery(&self) -> Battery {
        self.battery
    }
    
    #[cfg(feature = "raspicam")]
    pub fn video(&self) -> &Video {
        &self.video
    }
    
    #[cfg(feature = "raspicam")]
    pub fn picture(&self) -> &Picture {
        &self.picture
    }
    
    #[cfg(feature = "gps")]
    pub fn gps(&self) -> &Gps {
        &self.gps
    }
    
    #[cfg(feature = "fona")]
    pub fn fona(&self) -> &Fona {
        &self.fona
    }
    
    #[cfg(feature = "telemetry")]
    pub fn telemetry(&self) -> &Telemetry {
        &self.telemetry
    }
    
    pub fn data_dir(&self) -> &Path {
        self.data_dir.as_path()
    }
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct Flight {
    
    length: u32,
    
    expected_max_height: u32,
}
impl Flight {
    
    pub fn length(self) -> u32 {
        self.length
    }
    
    pub fn expected_max_height(self) -> u32 {
        self.expected_max_height
    }
}
#[cfg(feature = "fona")]
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct Battery {
    
    main_min: f32,
    
    main_max: f32,
    
    fona_min: f32,
    
    fona_max: f32,
    
    main_min_percent: f32,
    
    fona_min_percent: f32,
}
#[cfg(feature = "fona")]
impl Battery {
    
    pub fn main_min(self) -> f32 {
        self.main_min
    }
    
    pub fn main_max(self) -> f32 {
        self.main_max
    }
    
    pub fn fona_min(self) -> f32 {
        self.fona_min
    }
    
    pub fn fona_max(self) -> f32 {
        self.fona_min
    }
    
    pub fn main_min_percent(self) -> f32 {
        self.main_min_percent
    }
    
    pub fn fona_min_percent(self) -> f32 {
        self.fona_min_percent
    }
}
#[cfg(feature = "raspicam")]
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct Video {
    
    height: u16,
    
    width: u16,
    
    rotation: Option<u16>,
    
    fps: u8,
    
    bitrate: u32,
    
    exposure: Option<Exposure>,
    
    brightness: Option<u8>,
    
    contrast: Option<i8>,
    
    sharpness: Option<i8>,
    
    saturation: Option<i8>,
    
    iso: Option<u16>,
    
    stabilization: Option<bool>,
    
    ev: Option<i8>,
    
    white_balance: Option<WhiteBalance>,
}
#[cfg(feature = "raspicam")]
impl Video {
    
    pub fn height(self) -> u16 {
        self.height
    }
    
    pub fn width(self) -> u16 {
        self.width
    }
    
    pub fn rotation(self) -> Option<u16> {
        self.rotation
    }
    
    pub fn fps(self) -> u8 {
        self.fps
    }
    
    pub fn bitrate(self) -> u32 {
        self.bitrate
    }
    
    pub fn exposure(self) -> Option<Exposure> {
        self.exposure
    }
    
    pub fn brightness(self) -> Option<u8> {
        self.brightness
    }
    
    pub fn contrast(self) -> Option<i8> {
        self.contrast
    }
    
    pub fn sharpness(self) -> Option<i8> {
        self.sharpness
    }
    
    pub fn saturation(self) -> Option<i8> {
        self.saturation
    }
    
    pub fn iso(self) -> Option<u16> {
        self.iso
    }
    
    pub fn stabilization(self) -> bool {
        self.stabilization == Some(true)
    }
    
    pub fn ev(self) -> Option<i8> {
        self.ev
    }
    
    pub fn white_balance(self) -> Option<WhiteBalance> {
        self.white_balance
    }
}
#[cfg(feature = "raspicam")]
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct Picture {
    
    height: u16,
    
    width: u16,
    
    rotation: Option<u16>,
    
    quality: u8,
    
    #[cfg(feature = "gps")]
    exif: Option<bool>,
    
    raw: Option<bool>,
    
    exposure: Option<Exposure>,
    
    brightness: Option<u8>,
    
    contrast: Option<i8>,
    
    sharpness: Option<i8>,
    
    saturation: Option<i8>,
    
    iso: Option<u16>,
    
    ev: Option<i8>,
    
    white_balance: Option<WhiteBalance>,
    
    interval: u32,
    
    repeat: Option<u32>,
    
    first_timeout: u32,
}
#[cfg(feature = "raspicam")]
impl Picture {
    
    pub fn height(self) -> u16 {
        self.height
    }
    
    pub fn width(self) -> u16 {
        self.width
    }
    
    pub fn rotation(self) -> Option<u16> {
        self.rotation
    }
    
    pub fn quality(self) -> u8 {
        self.quality
    }
    
    #[cfg(feature = "gps")]
    pub fn exif(self) -> bool {
        self.exif == Some(true)
    }
    
    pub fn raw(self) -> bool {
        self.raw == Some(true)
    }
    
    pub fn exposure(self) -> Option<Exposure> {
        self.exposure
    }
    
    pub fn brightness(self) -> Option<u8> {
        self.brightness
    }
    
    pub fn contrast(self) -> Option<i8> {
        self.contrast
    }
    
    pub fn sharpness(self) -> Option<i8> {
        self.sharpness
    }
    
    pub fn saturation(self) -> Option<i8> {
        self.saturation
    }
    
    pub fn iso(self) -> Option<u16> {
        self.iso
    }
    
    pub fn ev(self) -> Option<i8> {
        self.ev
    }
    
    pub fn white_balance(self) -> Option<WhiteBalance> {
        self.white_balance
    }
    
    pub fn interval(self) -> u32 {
        self.interval
    }
    
    
    
    pub fn repeat(self) -> Option<u32> {
        self.repeat
    }
    
    pub fn first_timeout(self) -> u32 {
        self.first_timeout
    }
}
#[cfg(feature = "raspicam")]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
pub enum Exposure {
    
    Off,
    
    Auto,
    
    Night,
    
    NightPreview,
    
    BackLight,
    
    SpotLight,
    
    Sports,
    
    Snow,
    
    Beach,
    
    VeryLong,
    
    FixedFps,
    
    AntiShake,
    
    Fireworks,
}
#[cfg(feature = "raspicam")]
impl AsRef<OsStr> for Exposure {
    fn as_ref(&self) -> &OsStr {
        OsStr::new(match *self {
            Exposure::Off => "off",
            Exposure::Auto => "auto",
            Exposure::Night => "night",
            Exposure::NightPreview => "nightpreview",
            Exposure::BackLight => "backlight",
            Exposure::SpotLight => "spotlight",
            Exposure::Sports => "sports",
            Exposure::Snow => "snow",
            Exposure::Beach => "beach",
            Exposure::VeryLong => "verylong",
            Exposure::FixedFps => "fixedfps",
            Exposure::AntiShake => "antishake",
            Exposure::Fireworks => "fireworks",
        })
    }
}
#[cfg(feature = "raspicam")]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
pub enum WhiteBalance {
    
    Off,
    
    Auto,
    
    Sun,
    
    CloudShade,
    
    Tungsten,
    
    Fluorescent,
    
    Incandescent,
    
    Flash,
    
    Horizon,
}
#[cfg(feature = "raspicam")]
impl AsRef<OsStr> for WhiteBalance {
    fn as_ref(&self) -> &OsStr {
        OsStr::new(match *self {
            WhiteBalance::Off => "off",
            WhiteBalance::Auto => "auto",
            WhiteBalance::Sun => "sun",
            WhiteBalance::CloudShade => "cloudshade",
            WhiteBalance::Tungsten => "tungsten",
            WhiteBalance::Fluorescent => "fluorescent",
            WhiteBalance::Incandescent => "incandescent",
            WhiteBalance::Flash => "flash",
            WhiteBalance::Horizon => "horizon",
        })
    }
}
#[cfg(feature = "gps")]
#[derive(Debug, Deserialize)]
pub struct Gps {
    
    uart: PathBuf,
    
    baud_rate: u32,
    
    #[serde(deserialize_with = "deserialize_pin")]
    power_gpio: Pin,
}
#[cfg(feature = "gps")]
impl Gps {
    
    pub fn uart(&self) -> &Path {
        &self.uart
    }
    
    pub fn baud_rate(&self) -> u32 {
        self.baud_rate
    }
    
    pub fn power_gpio(&self) -> Pin {
        self.power_gpio
    }
}
#[cfg(feature = "fona")]
#[derive(Debug, Deserialize)]
pub struct Fona {
    
    uart: PathBuf,
    
    baud_rate: u32,
    
    #[serde(deserialize_with = "deserialize_pin")]
    power_gpio: Pin,
    
    #[serde(deserialize_with = "deserialize_pin")]
    status_gpio: Pin,
    
    sms_phone: PhoneNumber,
    
    location_service: String,
}
#[cfg(feature = "fona")]
impl Fona {
    
    pub fn uart(&self) -> &Path {
        &self.uart
    }
    
    pub fn baud_rate(&self) -> u32 {
        self.baud_rate
    }
    
    pub fn power_gpio(&self) -> Pin {
        self.power_gpio
    }
    
    pub fn status_gpio(&self) -> Pin {
        self.status_gpio
    }
    
    pub fn sms_phone(&self) -> &PhoneNumber {
        &self.sms_phone
    }
    
    pub fn location_service(&self) -> &str {
        &self.location_service
    }
}
#[cfg(feature = "fona")]
#[derive(Debug)]
pub struct PhoneNumber(String);
#[cfg(feature = "fona")]
impl PhoneNumber {
    
    pub fn as_str(&self) -> &str {
        &self.0
    }
}
#[cfg(feature = "fona")]
impl<'de> Deserialize<'de> for PhoneNumber {
    fn deserialize<D>(deserializer: D) -> Result<PhoneNumber, D::Error>
    where
        D: Deserializer<'de>,
    {
        
        
        struct PhoneNumberVisitor;
        impl<'dev> Visitor<'dev> for PhoneNumberVisitor {
            type Value = PhoneNumber;
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a valid phone number")
            }
            fn visit_str<E>(self, value: &str) -> Result<PhoneNumber, E>
            where
                E: de::Error,
            {
                Ok(PhoneNumber(value.to_owned()))
            }
        }
        deserializer.deserialize_str(PhoneNumberVisitor)
    }
}
#[cfg(feature = "telemetry")]
#[derive(Debug, Deserialize)]
pub struct Telemetry {
    
    uart: PathBuf,
    
    baud_rate: u32,
}
#[cfg(feature = "telemetry")]
impl Telemetry {
    
    pub fn uart(&self) -> &Path {
        &self.uart
    }
    
    pub fn baud_rate(&self) -> u32 {
        self.baud_rate
    }
}
#[cfg(any(feature = "gps", feature = "fona"))]
fn deserialize_pin<'de, D>(deserializer: D) -> Result<Pin, D::Error>
where
    D: Deserializer<'de>,
{
    
    struct PinVisitor;
    impl<'dev> Visitor<'dev> for PinVisitor {
        type Value = Pin;
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("an integer between 2 and 28")
        }
        #[allow(clippy::absurd_extreme_comparisons)]
        fn visit_i64<E>(self, value: i64) -> Result<Pin, E>
        where
            E: de::Error,
        {
            if value >= 2 && value <= 28 {
                #[allow(clippy::cast_sign_loss)]
                {
                    Ok(Pin::new(value as u64))
                }
            } else {
                Err(E::custom(format!("pin out of range: {}", value)))
            }
        }
    }
    deserializer.deserialize_u8(PinVisitor)
}
#[cfg(test)]
mod tests {
    #[cfg(all(feature = "gps", feature = "raspicam"))]
    use super::Gps;
    #[cfg(all(feature = "raspicam", feature = "telemetry"))]
    use super::Telemetry;
    #[cfg(all(feature = "raspicam", feature = "fona"))]
    use super::{Battery, Fona, PhoneNumber};
    use super::{Config, CONFIG};
    #[cfg(feature = "raspicam")]
    use super::{Exposure, Flight, Picture, Video, WhiteBalance};
    #[cfg(all(feature = "raspicam", any(feature = "gps", feature = "fona")))]
    use sysfs_gpio::Pin;
    #[cfg(feature = "gps")]
    use std::path::Path;
    #[cfg(feature = "raspicam")]
    use std::path::PathBuf;
    
    #[test]
    fn load_config() {
        let config = Config::from_file("config.toml").unwrap();
        assert_eq!(config.debug(), true);
        #[cfg(feature = "raspicam")]
        {
            assert_eq!(config.picture().height(), 2464);
            assert_eq!(config.picture().width(), 3280);
            #[cfg(feature = "gps")]
            {
                assert_eq!(config.picture().exif(), true);
            }
            assert_eq!(config.video().height(), 1080);
            assert_eq!(config.video().width(), 1920);
            assert_eq!(config.video().fps(), 30);
        }
        #[cfg(feature = "gps")]
        {
            assert_eq!(config.gps().uart(), Path::new("/dev/ttyAMA0"));
            assert_eq!(config.gps().baud_rate(), 9_600);
            assert_eq!(config.gps().power_gpio().get_pin(), 3)
        }
    }
    
    #[test]
    #[cfg(feature = "raspicam")]
    fn config_error() {
        let flight = Flight {
            length: 300,
            expected_max_height: 35000,
        };
        #[cfg(feature = "gps")]
        let picture = Picture {
            height: 10_345,
            width: 5_246,
            rotation: Some(180),
            quality: 95,
            raw: Some(true),
            exif: Some(true),
            exposure: Some(Exposure::AntiShake),
            brightness: Some(50),
            contrast: Some(50),
            sharpness: None,
            saturation: None,
            iso: None,
            ev: None,
            white_balance: Some(WhiteBalance::Horizon),
            first_timeout: 120,
            interval: 300,
            repeat: Some(30),
        };
        #[cfg(not(feature = "gps"))]
        let picture = Picture {
            height: 10_345,
            width: 5_246,
            rotation: Some(180),
            quality: 95,
            raw: Some(true),
            exposure: Some(Exposure::AntiShake),
            brightness: Some(50),
            contrast: Some(50),
            sharpness: None,
            saturation: None,
            iso: None,
            ev: None,
            white_balance: Some(WhiteBalance::Horizon),
            first_timeout: 120,
            interval: 300,
            repeat: Some(30),
        };
        let video = Video {
            height: 12_546,
            width: 5_648,
            rotation: Some(180),
            fps: 92,
            bitrate: 20_000_000,
            exposure: Some(Exposure::AntiShake),
            brightness: Some(50),
            contrast: Some(50),
            sharpness: None,
            saturation: None,
            iso: None,
            stabilization: Some(true),
            ev: None,
            white_balance: Some(WhiteBalance::Horizon),
        };
        #[cfg(feature = "fona")]
        let fona = Fona {
            uart: PathBuf::from("/dev/ttyUSB0"),
            baud_rate: 9_600,
            power_gpio: Pin::new(7),
            status_gpio: Pin::new(21),
            sms_phone: PhoneNumber(String::new()),
            location_service: "gprs-service.com".to_owned(),
        };
        #[cfg(feature = "fona")]
        let battery = Battery {
            main_min: 1.952_777_7,
            main_max: 2.216_666_7,
            fona_min: 3.7,
            fona_max: 4.2,
            main_min_percent: 0.8,
            fona_min_percent: 0.75,
        };
        #[cfg(feature = "telemetry")]
        let telemetry = Telemetry {
            uart: PathBuf::from("/dev/ttyUSB0"),
            baud_rate: 230_400,
        };
        #[cfg(feature = "gps")]
        let gps = Gps {
            uart: PathBuf::from("/dev/ttyAMA0"),
            baud_rate: 9_600,
            power_gpio: Pin::new(3),
        };
        #[cfg(all(feature = "gps", feature = "fona", feature = "telemetry"))]
        let config = Config {
            debug: None,
            flight,
            battery,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            gps,
            fona,
            telemetry,
        };
        #[cfg(all(feature = "gps", feature = "fona", not(feature = "telemetry")))]
        let config = Config {
            debug: None,
            flight,
            battery,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            gps,
            fona,
        };
        #[cfg(all(feature = "gps", not(feature = "fona"), feature = "telemetry"))]
        let config = Config {
            debug: None,
            flight,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            gps,
            telemetry,
        };
        #[cfg(all(feature = "gps", not(feature = "fona"), not(feature = "telemetry")))]
        let config = Config {
            debug: None,
            flight,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            gps,
        };
        #[cfg(all(not(feature = "gps"), feature = "fona", feature = "telemetry"))]
        let config = Config {
            debug: None,
            flight,
            battery,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            fona,
            telemetry,
        };
        #[cfg(all(not(feature = "gps"), feature = "fona", not(feature = "telemetry")))]
        let config = Config {
            debug: None,
            flight,
            battery,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            fona,
        };
        #[cfg(all(not(feature = "gps"), not(feature = "fona"), feature = "telemetry"))]
        let config = Config {
            debug: None,
            flight,
            data_dir: PathBuf::from("data"),
            picture,
            video,
            telemetry,
        };
        #[cfg(all(
            not(feature = "gps"),
            not(feature = "fona"),
            not(feature = "telemetry")
        ))]
        let config = Config {
            debug: None,
            flight,
            data_dir: PathBuf::from("data"),
            picture,
            video,
        };
        let (verify, errors) = config.verify();
        assert_eq!(verify, false);
        assert_eq!(
            errors,
            "picture width must be below or equal to 3280px, found 5246px\npicture height \
             must be below or equal to 2464px, found 10345px\nvideo width must be below or \
             equal to 2592px, found 5648px\nvideo height must be below or equal to 1944px, \
             found 12546px\nvideo framerate must be below or equal to 90fps, found 92fps\n\
             video mode must be one of 2592\u{d7}1944 1-15fps, 1920\u{d7}1080 1-30fps, \
             1296\u{d7}972 1-42fps, 1296\u{d7}730 1-49fps, 640\u{d7}480 1-60fps, found 5648x12546 \
             92fps\n"
        );
    }
    
    #[test]
    fn config_static() {
        assert!(CONFIG.debug());
    }
}