use std::path::Path;
use std::process::Command;
use std::str;

use super::{error, Error};

/// A version structure for making relative comparisons.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Version {
    major: usize,
    minor: usize,
    patch: usize,
}

impl Version {
    /// Creates a `Version` instance for a specific `major.minor.patch` version.
    pub fn new(major: usize, minor: usize, patch: usize) -> Self {
        Version {
            major: major,
            minor: minor,
            patch: patch,
        }
    }

    pub fn from_rustc(rustc: &Path) -> Result<Self, Error> {
        // Get rustc's verbose version
        let output = try!(Command::new(rustc)
            .args(&["--version", "--verbose"])
            .output()
            .map_err(error::from_io));
        if !output.status.success() {
            return Err(error::from_str("could not execute rustc"));
        }
        let output = try!(str::from_utf8(&output.stdout).map_err(error::from_utf8));

        // Find the release line in the verbose version output.
        let release = match output.lines().find(|line| line.starts_with("release: ")) {
            Some(line) => &line["release: ".len()..],
            None => return Err(error::from_str("could not find rustc release")),
        };

        // Strip off any extra channel info, e.g. "-beta.N", "-nightly"
        let version = match release.find('-') {
            Some(i) => &release[..i],
            None => release,
        };

        // Split the version into semver components.
        let mut iter = version.splitn(3, '.');
        let major = try!(iter.next().ok_or(error::from_str("missing major version")));
        let minor = try!(iter.next().ok_or(error::from_str("missing minor version")));
        let patch = try!(iter.next().ok_or(error::from_str("missing patch version")));

        Ok(Version::new(
            try!(major.parse().map_err(error::from_num)),
            try!(minor.parse().map_err(error::from_num)),
            try!(patch.parse().map_err(error::from_num)),
        ))
    }
}
