use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::BufRead;
use std::path::PathBuf;

use crate::setup_config::{EnumSetupInstances, SetupInstance};

pub enum VsInstance {
    Com(SetupInstance),
    Vswhere(VswhereInstance),
}

impl VsInstance {
    pub fn installation_name(&self) -> Option<Cow<str>> {
        match self {
            VsInstance::Com(s) => s
                .installation_name()
                .ok()
                .and_then(|s| s.into_string().ok())
                .map(Cow::from),
            VsInstance::Vswhere(v) => v.map.get("installationName").map(Cow::from),
        }
    }

    pub fn installation_path(&self) -> Option<PathBuf> {
        match self {
            VsInstance::Com(s) => s.installation_path().ok().map(PathBuf::from),
            VsInstance::Vswhere(v) => v.map.get("installationPath").map(PathBuf::from),
        }
    }

    pub fn installation_version(&self) -> Option<Cow<str>> {
        match self {
            VsInstance::Com(s) => s
                .installation_version()
                .ok()
                .and_then(|s| s.into_string().ok())
                .map(Cow::from),
            VsInstance::Vswhere(v) => v.map.get("installationVersion").map(Cow::from),
        }
    }
}

pub enum VsInstances {
    ComBased(EnumSetupInstances),
    VswhereBased(VswhereInstance),
}

impl IntoIterator for VsInstances {
    type Item = VsInstance;
    #[allow(bare_trait_objects)]
    type IntoIter = Box<Iterator<Item = Self::Item>>;

    fn into_iter(self) -> Self::IntoIter {
        match self {
            VsInstances::ComBased(e) => {
                Box::new(e.into_iter().filter_map(Result::ok).map(VsInstance::Com))
            }
            VsInstances::VswhereBased(v) => Box::new(std::iter::once(VsInstance::Vswhere(v))),
        }
    }
}

#[derive(Debug)]
pub struct VswhereInstance {
    map: HashMap<String, String>,
}

impl TryFrom<&Vec<u8>> for VswhereInstance {
    type Error = &'static str;

    fn try_from(output: &Vec<u8>) -> Result<Self, Self::Error> {
        let map: HashMap<_, _> = output
            .lines()
            .filter_map(Result::ok)
            .filter_map(|s| {
                let mut splitn = s.splitn(2, ": ");
                Some((splitn.next()?.to_owned(), splitn.next()?.to_owned()))
            })
            .collect();

        if !map.contains_key("installationName")
            || !map.contains_key("installationPath")
            || !map.contains_key("installationVersion")
        {
            return Err("required properties not found");
        }

        Ok(Self { map })
    }
}

#[cfg(test)]
mod tests_ {
    use std::borrow::Cow;
    use std::convert::TryFrom;
    use std::path::PathBuf;

    #[test]
    fn it_parses_vswhere_output_correctly() {
        let output = br"instanceId: 58104422
installDate: 21/02/2021 21:50:33
installationName: VisualStudio/16.9.2+31112.23
installationPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools
installationVersion: 16.9.31112.23
productId: Microsoft.VisualStudio.Product.BuildTools
productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat
state: 4294967295
isComplete: 1
isLaunchable: 1
isPrerelease: 0
isRebootRequired: 0
displayName: Visual Studio Build Tools 2019
description: The Visual Studio Build Tools allows you to build native and managed MSBuild-based applications without requiring the Visual Studio IDE. There are options to install the Visual C++ compilers and libraries, MFC, ATL, and C++/CLI support.
channelId: VisualStudio.16.Release
channelUri: https://aka.ms/vs/16/release/channel
enginePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\resources\app\ServiceHub\Services\Microsoft.VisualStudio.Setup.Service
releaseNotes: https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-v16.9#16.9.2
thirdPartyNotices: https://go.microsoft.com/fwlink/?LinkId=660909
updateDate: 2021-03-17T21:16:46.5963702Z
catalog_buildBranch: d16.9
catalog_buildVersion: 16.9.31112.23
catalog_id: VisualStudio/16.9.2+31112.23
catalog_localBuild: build-lab
catalog_manifestName: VisualStudio
catalog_manifestType: installer
catalog_productDisplayVersion: 16.9.2
catalog_productLine: Dev16
catalog_productLineVersion: 2019
catalog_productMilestone: RTW
catalog_productMilestoneIsPreRelease: False
catalog_productName: Visual Studio
catalog_productPatchVersion: 2
catalog_productPreReleaseMilestoneSuffix: 1.0
catalog_productSemanticVersion: 16.9.2+31112.23
catalog_requiredEngineVersion: 2.9.3365.38425
properties_campaignId: 156063665.1613940062
properties_channelManifestId: VisualStudio.16.Release/16.9.2+31112.23
properties_nickname: 
properties_setupEngineFilePath: C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installershell.exe
"
        .to_vec();

        let vswhere_instance = super::VswhereInstance::try_from(&output);
        assert!(vswhere_instance.is_ok());

        let vs_instance = super::VsInstance::Vswhere(vswhere_instance.unwrap());
        assert_eq!(
            vs_instance.installation_name(),
            Some(Cow::from("VisualStudio/16.9.2+31112.23"))
        );
        assert_eq!(
            vs_instance.installation_path(),
            Some(PathBuf::from(
                r"C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools"
            ))
        );
        assert_eq!(
            vs_instance.installation_version(),
            Some(Cow::from("16.9.31112.23"))
        );
    }

    #[test]
    fn it_returns_an_error_for_empty_output() {
        let output = b"".to_vec();

        let vswhere_instance = super::VswhereInstance::try_from(&output);

        assert!(vswhere_instance.is_err());
    }

    #[test]
    fn it_returns_an_error_for_output_consisting_of_empty_lines() {
        let output = br"

"
        .to_vec();

        let vswhere_instance = super::VswhereInstance::try_from(&output);

        assert!(vswhere_instance.is_err());
    }

    #[test]
    fn it_returns_an_error_for_output_without_required_properties() {
        let output = br"instanceId: 58104422
installDate: 21/02/2021 21:50:33
productId: Microsoft.VisualStudio.Product.BuildTools
productPath: C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\LaunchDevCmd.bat
"
        .to_vec();

        let vswhere_instance = super::VswhereInstance::try_from(&output);

        assert!(vswhere_instance.is_err());
    }
}
