<commit_before>use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::hashmap::{Values, MutEntries};
use std::fmt::{mod, Show, Formatter};
use std::hash;
use std::iter;
use std::mem;
use std::sync::Arc;
use serialize::{Decodable, Decoder, Encodable, Encoder};

use url::Url;

use core::{Summary, Package, PackageId, Registry, Dependency};
use sources::{PathSource, GitSource, RegistrySource};
use sources::git;
use util::{human, Config, CargoResult, CargoError, ToUrl};

/// A Source finds and downloads remote packages based on names and
/// versions.
pub trait Source: Registry {
    /// The update method performs any network operations required to
    /// get the entire list of all names, versions and dependencies of
    /// packages managed by the Source.
    fn update(&mut self) -> CargoResult<()>;

    /// The download method fetches the full package for each name and
    /// version specified.
    fn download(&mut self, packages: &[PackageId]) -> CargoResult<()>;

    /// The get method returns the Path of each specified package on the
    /// local file system. It assumes that `download` was already called,
    /// and that the packages are already locally available on the file
    /// system.
    fn get(&self, packages: &[PackageId]) -> CargoResult<Vec<Package>>;

    /// Generates a unique string which represents the fingerprint of the
    /// current state of the source.
    ///
    /// This fingerprint is used to determine the "fresheness" of the source
    /// later on. It must be guaranteed that the fingerprint of a source is
    /// constant if and only if the output product will remain constant.
    ///
    /// The `pkg` argument is the package which this fingerprint should only be
    /// interested in for when this source may contain multiple packages.
    fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
}

#[deriving(Encodable, Decodable, Show, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SourceKind {
    /// GitKind(<git reference>) represents a git repository
    GitKind(String),
    /// represents a local path
    PathKind,
    /// represents the central registry
    RegistryKind
}

type Error = Box<CargoError + Send>;

/// Unique identifier for a source of packages.
#[deriving(Clone, Eq)]
pub struct SourceId {
    inner: Arc<SourceIdInner>,
}

#[deriving(Eq, Clone)]
struct SourceIdInner {
    url: Url,
    kind: SourceKind,
    // e.g. the exact git revision of the specified branch for a Git Source
    precise: Option<String>
}

impl SourceId {
    pub fn new(kind: SourceKind, url: Url) -> SourceId {
        SourceId {
            inner: Arc::new(SourceIdInner {
                kind: kind,
                url: url,
                precise: None,
            }),
        }
    }

    /// Parses a source URL and returns the corresponding ID.
    ///
    /// ## Example
    ///
    /// ```
    /// use cargo::core::SourceId;
    /// SourceId::from_url("git+https://github.com/alexcrichton/\
    ///                     libssh2-static-sys#80e71a3021618eb05\
    ///                     656c58fb7c5ef5f12bc747f".to_string());
    /// ```
    pub fn from_url(string: String) -> SourceId {
        let mut parts = string.as_slice().splitn(1, '+');
        let kind = parts.next().unwrap();
        let url = parts.next().unwrap();

        match kind {
            "git" => {
                let mut url = url.to_url().unwrap();
                let mut reference = "master".to_string();
                let pairs = url.query_pairs().unwrap_or(Vec::new());
                for &(ref k, ref v) in pairs.iter() {
                    if k.as_slice() == "ref" {
                        reference = v.clone();
                    }
                }
                url.query = None;
                let precise = mem::replace(&mut url.fragment, None);
                SourceId::for_git(&url, reference.as_slice(), precise)
            },
            "registry" => {
                let url = url.to_url().unwrap();
                SourceId::for_registry(&url)
            }
            "path" => SourceId::for_path(&Path::new(url.slice_from(5))).unwrap(),
            _ => fail!("Unsupported serialized SourceId")
        }
    }

    pub fn to_url(&self) -> String {
        match *self.inner {
            SourceIdInner { kind: PathKind, .. } => {
                fail!("Path sources are not included in the lockfile, \
                       so this is unimplemented")
            },
            SourceIdInner {
                kind: GitKind(ref reference), ref url, ref precise, ..
            } => {
                let ref_str = if reference.as_slice() != "master" {
                    format!("?ref={}", reference)
                } else {
                    "".to_string()
                };

                let precise_str = if precise.is_some() {
                    format!("#{}", precise.as_ref().unwrap())
                } else {
                    "".to_string()
                };

                format!("git+{}{}{}", url, ref_str, precise_str)
            },
            SourceIdInner { kind: RegistryKind, .. } => {
                // TODO: Central registry vs. alternates
                "registry+https://crates.io/".to_string()
            }
        }
    }

    // Pass absolute path
    pub fn for_path(path: &Path) -> CargoResult<SourceId> {
        let url = try!(path.to_url().map_err(human));
        Ok(SourceId::new(PathKind, url))
    }

    pub fn for_git(url: &Url, reference: &str, precise: Option<String>) -> SourceId {
        let mut id = SourceId::new(GitKind(reference.to_string()), url.clone());
        if precise.is_some() {
            id = id.with_precise(precise.unwrap());
        }

        id
    }

    pub fn for_registry(url: &Url) -> SourceId {
        SourceId::new(RegistryKind, url.clone())
    }

    /// Returns the `SourceId` corresponding to the main repository.
    ///
    /// This is the main cargo registry by default, but it can be overridden in
    /// a `.cargo/config`.
    pub fn for_central() -> CargoResult<SourceId> {
        Ok(SourceId::for_registry(&try!(RegistrySource::url())))
    }

    pub fn get_url(&self) -> &Url {
        &self.inner.url
    }

    pub fn get_kind(&self) -> &SourceKind {
        &self.inner.kind
    }

    pub fn is_path(&self) -> bool {
        self.inner.kind == PathKind
    }

    pub fn is_git(&self) -> bool {
        match self.inner.kind {
            GitKind(_) => true,
            _ => false
        }
    }

    /// Creates an implementation of `Source` corresponding to this ID.
    pub fn load<'a>(&self, config: &'a mut Config) -> Box<Source+'a> {
        log!(5, "loading SourceId; {}", self);
        match self.inner.kind {
            GitKind(..) => box GitSource::new(self, config) as Box<Source+'a>,
            PathKind => {
                let path = match self.inner.url.to_file_path() {
                    Ok(p) => p,
                    Err(()) => fail!("path sources cannot be remote"),
                };
                box PathSource::new(&path, self) as Box<Source>
            },
            RegistryKind => box RegistrySource::new(self, config) as Box<Source+'a>,
        }
    }

    pub fn get_precise(&self) -> Option<&str> {
        self.inner.precise.as_ref().map(|s| s.as_slice())
    }

    pub fn with_precise(&self, v: String) -> SourceId {
        SourceId {
            inner: Arc::new(SourceIdInner {
                precise: Some(v),
                .. (*self.inner).clone()
            }),
        }
    }
}

impl PartialEq for SourceId {
    fn eq(&self, other: &SourceId) -> bool {
        self.inner.eq(&*other.inner)
    }
}

impl PartialOrd for SourceId {
    fn partial_cmp(&self, other: &SourceId) -> Option<Ordering> {
        self.to_string().partial_cmp(&other.to_string())
    }
}

impl Ord for SourceId {
    fn cmp(&self, other: &SourceId) -> Ordering {
        self.to_string().cmp(&other.to_string())
    }
}

impl<E, S: Encoder<E>> Encodable<S, E> for SourceId {
    fn encode(&self, s: &mut S) -> Result<(), E> {
        if self.is_path() {
            s.emit_option_none()
        } else {
           self.to_url().encode(s)
        }
    }
}

impl<E, D: Decoder<E>> Decodable<D, E> for SourceId {
    fn decode(d: &mut D) -> Result<SourceId, E> {
        let string: String = Decodable::decode(d).ok().expect("Invalid encoded SourceId");
        Ok(SourceId::from_url(string))
    }
}

impl Show for SourceId {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self.inner {
            SourceIdInner { kind: PathKind, ref url, .. } => url.fmt(f),
            SourceIdInner { kind: GitKind(ref reference), ref url,
                            ref precise, .. } => {
                try!(write!(f, "{}", url));
                if reference.as_slice() != "master" {
                    try!(write!(f, "?ref={}", reference));
                }

                match *precise {
                    Some(ref s) => {
                        try!(write!(f, "#{}", s.as_slice().slice_to(8)));
                    }
                    None => {}
                }
                Ok(())
            },
            SourceIdInner { kind: RegistryKind, ref url, .. } => {
                let default = RegistrySource::url().ok();
                if default.as_ref() == Some(url) {
                    write!(f, "the package registry")
                } else {
                    write!(f, "registry {}", url)
                }
            }
        }
    }
}

// This custom implementation handles situations such as when two git sources
// point at *almost* the same URL, but not quite, even when they actually point
// to the same repository.
impl PartialEq for SourceIdInner {
    fn eq(&self, other: &SourceIdInner) -> bool {
        if self.kind != other.kind { return false }
        if self.url == other.url { return true }

        match (&self.kind, &other.kind, &self.url, &other.url) {
            (&GitKind(ref ref1), &GitKind(ref ref2), u1, u2) => {
                ref1 == ref2 &&
                git::canonicalize_url(u1) == git::canonicalize_url(u2)
            }
            _ => false,
        }
    }
}

impl<S: hash::Writer> hash::Hash<S> for SourceId {
    fn hash(&self, into: &mut S) {
        self.inner.kind.hash(into);
        match *self.inner {
            SourceIdInner { kind: GitKind(..), ref url, .. } => {
                git::canonicalize_url(url).hash(into)
            }
            _ => self.inner.url.hash(into),
        }
    }
}

pub struct SourceMap<'a> {
    map: HashMap<SourceId, Box<Source+'a>>
}

pub type Sources<'a> = Values<'a, SourceId, Box<Source+'a>>;
pub type SourcesMut<'a> = iter::Map<'static, (&'a SourceId, &'a mut Box<Source+'a>),
                                    &'a mut Source+'a,
                                    MutEntries<'a, SourceId, Box<Source+'a>>>;

impl<'a> SourceMap<'a> {
    pub fn new() -> SourceMap<'a> {
        SourceMap {
            map: HashMap::new()
        }
    }

    pub fn contains(&self, id: &SourceId) -> bool {
        self.map.contains_key(id)
    }

    pub fn get(&self, id: &SourceId) -> Option<&Source+'a> {
        let source = self.map.find(id);

        source.map(|s| {
            let s: &Source+'a = &**s;
            s
        })
    }

    pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut Source+'a> {
        self.map.find_mut(id).map(|s| {
            let s: &mut Source+'a = &mut **s;
            s
        })
    }

    pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&Source+'a> {
        self.get(pkg_id.get_source_id())
    }

    pub fn insert(&mut self, id: &SourceId, source: Box<Source+'a>) {
        self.map.insert(id.clone(), source);
    }

    pub fn len(&self) -> uint {
        self.map.len()
    }

    pub fn sources(&'a self) -> Sources<'a> {
        self.map.values()
    }

    pub fn sources_mut(&'a mut self) -> SourcesMut<'a> {
        self.map.iter_mut().map(|(_, v)| { let s: &mut Source+'a = &mut **v; s })
    }
}

/// List of `Source` implementors. `SourceSet` itself implements `Source`.
pub struct SourceSet<'a> {
    sources: Vec<Box<Source+'a>>
}

impl<'a> SourceSet<'a> {
    pub fn new(sources: Vec<Box<Source+'a>>) -> SourceSet<'a> {
        SourceSet { sources: sources }
    }
}

impl<'a> Registry for SourceSet<'a> {
    fn query(&mut self, name: &Dependency) -> CargoResult<Vec<Summary>> {
        let mut ret = Vec::new();

        for source in self.sources.iter_mut() {
            ret.extend(try!(source.query(name)).into_iter());
        }

        Ok(ret)
    }
}

impl<'a> Source for SourceSet<'a> {
    fn update(&mut self) -> CargoResult<()> {
        for source in self.sources.iter_mut() {
            try!(source.update());
        }

        Ok(())
    }

    fn download(&mut self, packages: &[PackageId]) -> CargoResult<()> {
        for source in self.sources.iter_mut() {
            try!(source.download(packages));
        }

        Ok(())
    }

    fn get(&self, packages: &[PackageId]) -> CargoResult<Vec<Package>> {
        let mut ret = Vec::new();

        for source in self.sources.iter() {
            ret.push_all(try!(source.get(packages)).as_slice());
        }

        Ok(ret)
    }

    fn fingerprint(&self, id: &Package) -> CargoResult<String> {
        let mut ret = String::new();
        for source in self.sources.iter() {
            ret.push_str(try!(source.fingerprint(id)).as_slice());
        }
        return Ok(ret);
    }
}

#[cfg(test)]
mod tests {
    use super::{SourceId, GitKind};
    use util::ToUrl;

    #[test]
    fn github_sources_equal() {
        let loc = "https://github.com/foo/bar".to_url().unwrap();
        let s1 = SourceId::new(GitKind("master".to_string()), loc);

        let loc = "git://github.com/foo/bar".to_url().unwrap();
        let mut s2 = SourceId::new(GitKind("master".to_string()), loc);

        assert_eq!(s1, s2);

        s2.kind = GitKind("foo".to_string());
        assert!(s1 != s2);
    }
}
<commit_msg>source changes<commit_after>use std::cmp::Ordering;
use std::collections::HashMap;
use std::collections::hashmap::{Values, MutEntries};
use std::fmt::{mod, Show, Formatter};
use std::hash;
use std::iter;
use std::mem;
use std::sync::Arc;
use serialize::{Decodable, Decoder, Encodable, Encoder};

use url::Url;

use core::{Summary, Package, PackageId, Registry, Dependency};
use sources::{PathSource, GitSource, RegistrySource};
use sources::git;
use util::{human, Config, CargoResult, CargoError, ToUrl};

/// A Source finds and downloads remote packages based on names and
/// versions.
pub trait Source: Registry {
    /// The update method performs any network operations required to
    /// get the entire list of all names, versions and dependencies of
    /// packages managed by the Source.
    fn update(&mut self) -> CargoResult<()>;

    /// The download method fetches the full package for each name and
    /// version specified.
    fn download(&mut self, packages: &[PackageId]) -> CargoResult<()>;

    /// The get method returns the Path of each specified package on the
    /// local file system. It assumes that `download` was already called,
    /// and that the packages are already locally available on the file
    /// system.
    fn get(&self, packages: &[PackageId]) -> CargoResult<Vec<Package>>;

    /// Generates a unique string which represents the fingerprint of the
    /// current state of the source.
    ///
    /// This fingerprint is used to determine the "fresheness" of the source
    /// later on. It must be guaranteed that the fingerprint of a source is
    /// constant if and only if the output product will remain constant.
    ///
    /// The `pkg` argument is the package which this fingerprint should only be
    /// interested in for when this source may contain multiple packages.
    fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
}

#[deriving(Encodable, Decodable, Show, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SourceKind {
    /// GitKind(<git reference>) represents a git repository
    GitKind(String),
    /// represents a local path
    PathKind,
    /// represents the central registry
    RegistryKind
}

type Error = Box<CargoError + Send>;

/// Unique identifier for a source of packages.
#[deriving(Clone, Eq)]
pub struct SourceId {
    inner: Arc<SourceIdInner>,
}

#[deriving(Eq, Clone)]
struct SourceIdInner {
    url: Url,
    kind: SourceKind,
    // e.g. the exact git revision of the specified branch for a Git Source
    precise: Option<String>
}

impl SourceId {
    pub fn new(kind: SourceKind, url: Url) -> SourceId {
        SourceId {
            inner: Arc::new(SourceIdInner {
                kind: kind,
                url: url,
                precise: None,
            }),
        }
    }

    /// Parses a source URL and returns the corresponding ID.
    ///
    /// ## Example
    ///
    /// ```
    /// use cargo::core::SourceId;
    /// SourceId::from_url("git+https://github.com/alexcrichton/\
    ///                     libssh2-static-sys#80e71a3021618eb05\
    ///                     656c58fb7c5ef5f12bc747f".to_string());
    /// ```
    pub fn from_url(string: String) -> SourceId {
        let mut parts = string.as_slice().splitn(1, '+');
        let kind = parts.next().unwrap();
        let url = parts.next().unwrap();

        match kind {
            "git" => {
                let mut url = url.to_url().unwrap();
                let mut reference = "master".to_string();
                let pairs = url.query_pairs().unwrap_or(Vec::new());
                for &(ref k, ref v) in pairs.iter() {
                    if k.as_slice() == "ref" {
                        reference = v.clone();
                    }
                }
                url.query = None;
                let precise = mem::replace(&mut url.fragment, None);
                SourceId::for_git(&url, reference.as_slice(), precise)
            },
            "registry" => {
                let url = url.to_url().unwrap();
                SourceId::for_registry(&url)
            }
            "path" => SourceId::for_path(&Path::new(url.slice_from(5))).unwrap(),
            _ => fail!("Unsupported serialized SourceId")
        }
    }

    pub fn to_url(&self) -> String {
        match *self.inner {
            SourceIdInner { kind: PathKind, .. } => {
                fail!("Path sources are not included in the lockfile, \
                       so this is unimplemented")
            },
            SourceIdInner {
                kind: GitKind(ref reference), ref url, ref precise, ..
            } => {
                let ref_str = if reference.as_slice() != "master" {
                    format!("?ref={}", reference)
                } else {
                    "".to_string()
                };

                let precise_str = if precise.is_some() {
                    format!("#{}", precise.as_ref().unwrap())
                } else {
                    "".to_string()
                };

                format!("git+{}{}{}", url, ref_str, precise_str)
            },
            SourceIdInner { kind: RegistryKind, .. } => {
                // TODO: Central registry vs. alternates
                "registry+https://crates.io/".to_string()
            }
        }
    }

    // Pass absolute path
    pub fn for_path(path: &Path) -> CargoResult<SourceId> {
        let url = try!(path.to_url().map_err(human));
        Ok(SourceId::new(PathKind, url))
    }

    pub fn for_git(url: &Url, reference: &str, precise: Option<String>) -> SourceId {
        let mut id = SourceId::new(GitKind(reference.to_string()), url.clone());
        if precise.is_some() {
            id = id.with_precise(precise.unwrap());
        }

        id
    }

    pub fn for_registry(url: &Url) -> SourceId {
        SourceId::new(RegistryKind, url.clone())
    }

    /// Returns the `SourceId` corresponding to the main repository.
    ///
    /// This is the main cargo registry by default, but it can be overridden in
    /// a `.cargo/config`.
    pub fn for_central() -> CargoResult<SourceId> {
        Ok(SourceId::for_registry(&try!(RegistrySource::url())))
    }

    pub fn get_url(&self) -> &Url {
        &self.inner.url
    }

    pub fn get_kind(&self) -> &SourceKind {
        &self.inner.kind
    }

    pub fn is_path(&self) -> bool {
        self.inner.kind == PathKind
    }

    pub fn is_git(&self) -> bool {
        match self.inner.kind {
            GitKind(_) => true,
            _ => false
        }
    }

    /// Creates an implementation of `Source` corresponding to this ID.
    pub fn load<'a>(&self, config: &'a mut Config) -> Box<Source+'a> {
        log!(5, "loading SourceId; {}", self);
        match self.inner.kind {
            GitKind(..) => box GitSource::new(self, config) as Box<Source+'a>,
            PathKind => {
                let path = match self.inner.url.to_file_path() {
                    Ok(p) => p,
                    Err(()) => fail!("path sources cannot be remote"),
                };
                box PathSource::new(&path, self) as Box<Source>
            },
            RegistryKind => box RegistrySource::new(self, config) as Box<Source+'a>,
        }
    }

    pub fn get_precise(&self) -> Option<&str> {
        self.inner.precise.as_ref().map(|s| s.as_slice())
    }

    pub fn with_precise(&self, v: String) -> SourceId {
        SourceId {
            inner: Arc::new(SourceIdInner {
                precise: Some(v),
                .. (*self.inner).clone()
            }),
        }
    }
}

impl PartialEq for SourceId {
    fn eq(&self, other: &SourceId) -> bool {
        self.inner.eq(&*other.inner)
    }
}

impl PartialOrd for SourceId {
    fn partial_cmp(&self, other: &SourceId) -> Option<Ordering> {
        self.to_string().partial_cmp(&other.to_string())
    }
}

impl Ord for SourceId {
    fn cmp(&self, other: &SourceId) -> Ordering {
        self.to_string().cmp(&other.to_string())
    }
}

impl<E, S: Encoder<E>> Encodable<S, E> for SourceId {
    fn encode(&self, s: &mut S) -> Result<(), E> {
        if self.is_path() {
            s.emit_option_none()
        } else {
           self.to_url().encode(s)
        }
    }
}

impl<E, D: Decoder<E>> Decodable<D, E> for SourceId {
    fn decode(d: &mut D) -> Result<SourceId, E> {
        let string: String = Decodable::decode(d).ok().expect("Invalid encoded SourceId");
        Ok(SourceId::from_url(string))
    }
}

impl Show for SourceId {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self.inner {
            SourceIdInner { kind: PathKind, ref url, .. } => url.fmt(f),
            SourceIdInner { kind: GitKind(ref reference), ref url,
                            ref precise, .. } => {
                try!(write!(f, "{}", url));
                if reference.as_slice() != "master" {
                    try!(write!(f, "?ref={}", reference));
                }

                match *precise {
                    Some(ref s) => {
                        try!(write!(f, "#{}", s.as_slice().slice_to(8)));
                    }
                    None => {}
                }
                Ok(())
            },
            SourceIdInner { kind: RegistryKind, ref url, .. } => {
                let default = RegistrySource::url().ok();
                if default.as_ref() == Some(url) {
                    write!(f, "the package registry")
                } else {
                    write!(f, "registry {}", url)
                }
            }
        }
    }
}

// This custom implementation handles situations such as when two git sources
// point at *almost* the same URL, but not quite, even when they actually point
// to the same repository.
impl PartialEq for SourceIdInner {
    fn eq(&self, other: &SourceIdInner) -> bool {
        if self.kind != other.kind { return false }
        if self.url == other.url { return true }

        match (&self.kind, &other.kind, &self.url, &other.url) {
            (&GitKind(ref ref1), &GitKind(ref ref2), u1, u2) => {
                ref1 == ref2 &&
                git::canonicalize_url(u1) == git::canonicalize_url(u2)
            }
            _ => false,
        }
    }
}

impl<S: hash::Writer> hash::Hash<S> for SourceId {
    fn hash(&self, into: &mut S) {
        self.inner.kind.hash(into);
        match *self.inner {
            SourceIdInner { kind: GitKind(..), ref url, .. } => {
                git::canonicalize_url(url).hash(into)
            }
            _ => self.inner.url.hash(into),
        }
    }
}

pub struct SourceMap<'a> {
    map: HashMap<SourceId, Box<Source+'a>>
}

pub type Sources<'a> = Values<'a, SourceId, Box<Source+'a>>;
pub type SourcesMut<'a> = iter::Map<'static, (&'a SourceId, &'a mut Box<Source+'a>),
                                    &'a mut Source+'a,
                                    MutEntries<'a, SourceId, Box<Source+'a>>>;

impl<'a> SourceMap<'a> {
    pub fn new() -> SourceMap<'a> {
        SourceMap {
            map: HashMap::new()
        }
    }

    pub fn contains(&self, id: &SourceId) -> bool {
        self.map.contains_key(id)
    }

    pub fn get(&self, id: &SourceId) -> Option<&Source+'a> {
        let source = self.map.find(id);

        source.map(|s| {
            let s: &Source+'a = &**s;
            s
        })
    }

    pub fn get_mut(&mut self, id: &SourceId) -> Option<&mut Source+'a> {
        self.map.find_mut(id).map(|s| {
            let s: &mut Source+'a = &mut **s;
            s
        })
    }

    pub fn get_by_package_id(&self, pkg_id: &PackageId) -> Option<&Source+'a> {
        self.get(pkg_id.get_source_id())
    }

    pub fn insert(&mut self, id: &SourceId, source: Box<Source+'a>) {
        self.map.insert(id.clone(), source);
    }

    pub fn len(&self) -> uint {
        self.map.len()
    }

    pub fn sources(&'a self) -> Sources<'a> {
        self.map.values()
    }

    pub fn sources_mut(&'a mut self) -> SourcesMut<'a> {
        self.map.iter_mut().map(|(_, v)| { let s: &mut Source+'a = &mut **v; s })
    }
}

/// List of `Source` implementors. `SourceSet` itself implements `Source`.
pub struct SourceSet<'a> {
    sources: Vec<Box<Source+'a>>
}

impl<'a> SourceSet<'a> {
    pub fn new(sources: Vec<Box<Source+'a>>) -> SourceSet<'a> {
        SourceSet { sources: sources }
    }
}

impl<'a> Registry for SourceSet<'a> {
    fn query(&mut self, name: &Dependency) -> CargoResult<Vec<Summary>> {
        let mut ret = Vec::new();

        for source in self.sources.iter_mut() {
            ret.extend(try!(source.query(name)).into_iter());
        }

        Ok(ret)
    }
}

impl<'a> Source for SourceSet<'a> {
    fn update(&mut self) -> CargoResult<()> {
        for source in self.sources.iter_mut() {
            try!(source.update());
        }

        Ok(())
    }

    fn download(&mut self, packages: &[PackageId]) -> CargoResult<()> {
        for source in self.sources.iter_mut() {
            try!(source.download(packages));
        }

        Ok(())
    }

    fn get(&self, packages: &[PackageId]) -> CargoResult<Vec<Package>> {
        let mut ret = Vec::new();

        for source in self.sources.iter() {
            ret.push_all(try!(source.get(packages)).as_slice());
        }

        Ok(ret)
    }

    fn fingerprint(&self, id: &Package) -> CargoResult<String> {
        let mut ret = String::new();
        for source in self.sources.iter() {
            ret.push_str(try!(source.fingerprint(id)).as_slice());
        }
        return Ok(ret);
    }
}

#[cfg(test)]
mod tests {
    use super::{SourceId, GitKind};
    use util::ToUrl;

    #[test]
    fn github_sources_equal() {
        let loc = "https://github.com/foo/bar".to_url().unwrap();
        let s1 = SourceId::new(GitKind("master".to_string()), loc);

        let loc = "git://github.com/foo/bar".to_url().unwrap();
        let s2 = SourceId::new(GitKind("master".to_string()), loc.clone());

        assert_eq!(s1, s2);

        let s3 = SourceId::new(GitKind("foo".to_string()), loc);
        assert!(s1 != s3);
    }
}
<|endoftext|>


<commit_before>use std::error::Error;
use std::fmt;
use std::io;
use std::num;
use std::process::{Output, ExitStatus};
use std::str;
use std::string;

use core::TargetKind;

use curl;
use git2;
use semver;
use serde_json;
use toml;
use registry;

error_chain! {
    types {
        CargoError, CargoErrorKind, CargoResultExt, CargoResult;
    }

    links {
        CrateRegistry(registry::Error, registry::ErrorKind);
    }

    foreign_links {
        ParseSemver(semver::ReqParseError);
        Semver(semver::SemVerError);
        Io(io::Error);
        SerdeJson(serde_json::Error);
        TomlSer(toml::ser::Error);
        TomlDe(toml::de::Error);
        ParseInt(num::ParseIntError);
        ParseBool(str::ParseBoolError);
        Parse(string::ParseError);
        Git(git2::Error);
        Curl(curl::Error);
    }

    errors {
        Internal(err: Box<CargoErrorKind>) {
            description(err.description())
            display("{}", *err)
        }
        ProcessErrorKind(proc_err: ProcessError) {
            description(&proc_err.desc)
            display("{}", &proc_err.desc)
        }
        CargoTestErrorKind(test_err: CargoTestError) {
            description(&test_err.desc)
            display("{}", &test_err.desc)
        }
        HttpNot200(code: u32, url: String) {
            description("failed to get a 200 response")
            display("failed to get 200 response from `{}`, got {}", url, code)
        }
    }
}

impl CargoError {
    pub fn into_internal(self) -> Self {
        CargoError(CargoErrorKind::Internal(Box::new(self.0)), self.1)
    }

    fn is_human(&self) -> bool {
        match &self.0 {
            &CargoErrorKind::Msg(_) => true,
            &CargoErrorKind::TomlSer(_) => true,
            &CargoErrorKind::TomlDe(_) => true,
            &CargoErrorKind::Curl(_) => true,
            &CargoErrorKind::HttpNot200(..) => true,
            &CargoErrorKind::ProcessErrorKind(_) => true,
            &CargoErrorKind::CrateRegistry(_) |
            &CargoErrorKind::ParseSemver(_) |
            &CargoErrorKind::Semver(_) |
            &CargoErrorKind::Io(_) |
            &CargoErrorKind::SerdeJson(_) |
            &CargoErrorKind::ParseInt(_) |
            &CargoErrorKind::ParseBool(_) |
            &CargoErrorKind::Parse(_) |
            &CargoErrorKind::Git(_) |
            &CargoErrorKind::Internal(_) |
            &CargoErrorKind::CargoTestErrorKind(_) => false
        }
    }
}

// =============================================================================
// Process errors
#[derive(Debug)]
pub struct ProcessError {
    pub desc: String,
    pub exit: Option<ExitStatus>,
    pub output: Option<Output>,
}

// =============================================================================
// Cargo test errors.

/// Error when testcases fail
#[derive(Debug)]
pub struct CargoTestError {
    pub test: Test,
    pub desc: String,
    pub exit: Option<ExitStatus>,
    pub causes: Vec<ProcessError>,
}

#[derive(Debug)]
pub enum Test {
    Multiple,
    Doc,
    UnitTest(TargetKind, String)
}

impl CargoTestError {
    pub fn new(test: Test, errors: Vec<ProcessError>) -> Self {
        if errors.is_empty() {
            panic!("Cannot create CargoTestError from empty Vec")
        }
        let desc = errors.iter().map(|error| error.desc.clone())
                                .collect::<Vec<String>>()
                                .join("\n");
        CargoTestError {
            test: test,
            desc: desc,
            exit: errors[0].exit,
            causes: errors,
        }
    }

    pub fn hint(&self) -> String {
        match &self.test {
            &Test::UnitTest(ref kind, ref name) => {
                match *kind {
                    TargetKind::Bench => format!("test failed, to rerun pass '--bench {}'", name),
                    TargetKind::Bin => format!("test failed, to rerun pass '--bin {}'", name),
                    TargetKind::Lib(_) => "test failed, to rerun pass '--lib'".into(),
                    TargetKind::Test => format!("test failed, to rerun pass '--test {}'", name),
                    TargetKind::ExampleBin | TargetKind::ExampleLib(_) =>
                        format!("test failed, to rerun pass '--example {}", name),
                    _ => "test failed.".into()
                }
            },
            &Test::Doc => "test failed, to rerun pass '--doc'".into(),
            _ => "test failed.".into()
        }
    }
}

// =============================================================================
// CLI errors

pub type CliResult = Result<(), CliError>;

#[derive(Debug)]
pub struct CliError {
    pub error: Option<CargoError>,
    pub unknown: bool,
    pub exit_code: i32
}

impl Error for CliError {
    fn description(&self) -> &str {
        self.error.as_ref().map(|e| e.description())
            .unwrap_or("unknown cli error")
    }

    fn cause(&self) -> Option<&Error> {
        self.error.as_ref().and_then(|e| e.cause())
    }
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref error) = self.error {
            error.fmt(f)
        } else {
            self.description().fmt(f)
        }
    }
}

impl CliError {
    pub fn new(error: CargoError, code: i32) -> CliError {
        let human = &error.is_human();
        CliError { error: Some(error), exit_code: code, unknown: !human }
    }

    pub fn code(code: i32) -> CliError {
        CliError { error: None, exit_code: code, unknown: false }
    }
}

impl From<CargoError> for CliError {
    fn from(err: CargoError) -> CliError {
        CliError::new(err, 101)
    }
}

// =============================================================================
// Construction helpers

pub fn process_error(msg: &str,
                     status: Option<&ExitStatus>,
                     output: Option<&Output>) -> ProcessError
{
    let exit = match status {
        Some(s) => status_to_string(s),
        None => "never executed".to_string(),
    };
    let mut desc = format!("{} ({})", &msg, exit);

    if let Some(out) = output {
        match str::from_utf8(&out.stdout) {
            Ok(s) if !s.trim().is_empty() => {
                desc.push_str("\n--- stdout\n");
                desc.push_str(s);
            }
            Ok(..) | Err(..) => {}
        }
        match str::from_utf8(&out.stderr) {
            Ok(s) if !s.trim().is_empty() => {
                desc.push_str("\n--- stderr\n");
                desc.push_str(s);
            }
            Ok(..) | Err(..) => {}
        }
    }

    return ProcessError {
        desc: desc,
        exit: status.cloned(),
        output: output.cloned(),
    };

    #[cfg(unix)]
    fn status_to_string(status: &ExitStatus) -> String {
        use std::os::unix::process::*;
        use libc;

        if let Some(signal) = status.signal() {
            let name = match signal as libc::c_int {
                libc::SIGABRT => ", SIGABRT: process abort signal",
                libc::SIGALRM => ", SIGALRM: alarm clock",
                libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
                libc::SIGHUP => ", SIGHUP: hangup",
                libc::SIGILL => ", SIGILL: illegal instruction",
                libc::SIGINT => ", SIGINT: terminal interrupt signal",
                libc::SIGKILL => ", SIGKILL: kill",
                libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
                libc::SIGQUIT => ", SIGQUIT: terminal quite signal",
                libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
                libc::SIGTERM => ", SIGTERM: termination signal",
                libc::SIGBUS => ", SIGBUS: access to undefined memory",
                #[cfg(not(target_os = "haiku"))]
                libc::SIGSYS => ", SIGSYS: bad system call",
                libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
                _ => "",
            };
            format!("signal: {}{}", signal, name)
        } else {
            status.to_string()
        }
    }

    #[cfg(windows)]
    fn status_to_string(status: &ExitStatus) -> String {
        status.to_string()
    }
}

pub fn internal<S: fmt::Display>(error: S) -> CargoError {
    _internal(&error)
}

fn _internal(error: &fmt::Display) -> CargoError {
    CargoError::from_kind(error.to_string().into()).into_internal()
}
<commit_msg>Flag crate registry errors as "human"<commit_after>use std::error::Error;
use std::fmt;
use std::io;
use std::num;
use std::process::{Output, ExitStatus};
use std::str;
use std::string;

use core::TargetKind;

use curl;
use git2;
use semver;
use serde_json;
use toml;
use registry;

error_chain! {
    types {
        CargoError, CargoErrorKind, CargoResultExt, CargoResult;
    }

    links {
        CrateRegistry(registry::Error, registry::ErrorKind);
    }

    foreign_links {
        ParseSemver(semver::ReqParseError);
        Semver(semver::SemVerError);
        Io(io::Error);
        SerdeJson(serde_json::Error);
        TomlSer(toml::ser::Error);
        TomlDe(toml::de::Error);
        ParseInt(num::ParseIntError);
        ParseBool(str::ParseBoolError);
        Parse(string::ParseError);
        Git(git2::Error);
        Curl(curl::Error);
    }

    errors {
        Internal(err: Box<CargoErrorKind>) {
            description(err.description())
            display("{}", *err)
        }
        ProcessErrorKind(proc_err: ProcessError) {
            description(&proc_err.desc)
            display("{}", &proc_err.desc)
        }
        CargoTestErrorKind(test_err: CargoTestError) {
            description(&test_err.desc)
            display("{}", &test_err.desc)
        }
        HttpNot200(code: u32, url: String) {
            description("failed to get a 200 response")
            display("failed to get 200 response from `{}`, got {}", url, code)
        }
    }
}

impl CargoError {
    pub fn into_internal(self) -> Self {
        CargoError(CargoErrorKind::Internal(Box::new(self.0)), self.1)
    }

    fn is_human(&self) -> bool {
        match &self.0 {
            &CargoErrorKind::Msg(_) => true,
            &CargoErrorKind::TomlSer(_) => true,
            &CargoErrorKind::TomlDe(_) => true,
            &CargoErrorKind::Curl(_) => true,
            &CargoErrorKind::HttpNot200(..) => true,
            &CargoErrorKind::ProcessErrorKind(_) => true,
            &CargoErrorKind::CrateRegistry(_) => true,
            &CargoErrorKind::ParseSemver(_) |
            &CargoErrorKind::Semver(_) |
            &CargoErrorKind::Io(_) |
            &CargoErrorKind::SerdeJson(_) |
            &CargoErrorKind::ParseInt(_) |
            &CargoErrorKind::ParseBool(_) |
            &CargoErrorKind::Parse(_) |
            &CargoErrorKind::Git(_) |
            &CargoErrorKind::Internal(_) |
            &CargoErrorKind::CargoTestErrorKind(_) => false
        }
    }
}

// =============================================================================
// Process errors
#[derive(Debug)]
pub struct ProcessError {
    pub desc: String,
    pub exit: Option<ExitStatus>,
    pub output: Option<Output>,
}

// =============================================================================
// Cargo test errors.

/// Error when testcases fail
#[derive(Debug)]
pub struct CargoTestError {
    pub test: Test,
    pub desc: String,
    pub exit: Option<ExitStatus>,
    pub causes: Vec<ProcessError>,
}

#[derive(Debug)]
pub enum Test {
    Multiple,
    Doc,
    UnitTest(TargetKind, String)
}

impl CargoTestError {
    pub fn new(test: Test, errors: Vec<ProcessError>) -> Self {
        if errors.is_empty() {
            panic!("Cannot create CargoTestError from empty Vec")
        }
        let desc = errors.iter().map(|error| error.desc.clone())
                                .collect::<Vec<String>>()
                                .join("\n");
        CargoTestError {
            test: test,
            desc: desc,
            exit: errors[0].exit,
            causes: errors,
        }
    }

    pub fn hint(&self) -> String {
        match &self.test {
            &Test::UnitTest(ref kind, ref name) => {
                match *kind {
                    TargetKind::Bench => format!("test failed, to rerun pass '--bench {}'", name),
                    TargetKind::Bin => format!("test failed, to rerun pass '--bin {}'", name),
                    TargetKind::Lib(_) => "test failed, to rerun pass '--lib'".into(),
                    TargetKind::Test => format!("test failed, to rerun pass '--test {}'", name),
                    TargetKind::ExampleBin | TargetKind::ExampleLib(_) =>
                        format!("test failed, to rerun pass '--example {}", name),
                    _ => "test failed.".into()
                }
            },
            &Test::Doc => "test failed, to rerun pass '--doc'".into(),
            _ => "test failed.".into()
        }
    }
}

// =============================================================================
// CLI errors

pub type CliResult = Result<(), CliError>;

#[derive(Debug)]
pub struct CliError {
    pub error: Option<CargoError>,
    pub unknown: bool,
    pub exit_code: i32
}

impl Error for CliError {
    fn description(&self) -> &str {
        self.error.as_ref().map(|e| e.description())
            .unwrap_or("unknown cli error")
    }

    fn cause(&self) -> Option<&Error> {
        self.error.as_ref().and_then(|e| e.cause())
    }
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if let Some(ref error) = self.error {
            error.fmt(f)
        } else {
            self.description().fmt(f)
        }
    }
}

impl CliError {
    pub fn new(error: CargoError, code: i32) -> CliError {
        let human = &error.is_human();
        CliError { error: Some(error), exit_code: code, unknown: !human }
    }

    pub fn code(code: i32) -> CliError {
        CliError { error: None, exit_code: code, unknown: false }
    }
}

impl From<CargoError> for CliError {
    fn from(err: CargoError) -> CliError {
        CliError::new(err, 101)
    }
}

// =============================================================================
// Construction helpers

pub fn process_error(msg: &str,
                     status: Option<&ExitStatus>,
                     output: Option<&Output>) -> ProcessError
{
    let exit = match status {
        Some(s) => status_to_string(s),
        None => "never executed".to_string(),
    };
    let mut desc = format!("{} ({})", &msg, exit);

    if let Some(out) = output {
        match str::from_utf8(&out.stdout) {
            Ok(s) if !s.trim().is_empty() => {
                desc.push_str("\n--- stdout\n");
                desc.push_str(s);
            }
            Ok(..) | Err(..) => {}
        }
        match str::from_utf8(&out.stderr) {
            Ok(s) if !s.trim().is_empty() => {
                desc.push_str("\n--- stderr\n");
                desc.push_str(s);
            }
            Ok(..) | Err(..) => {}
        }
    }

    return ProcessError {
        desc: desc,
        exit: status.cloned(),
        output: output.cloned(),
    };

    #[cfg(unix)]
    fn status_to_string(status: &ExitStatus) -> String {
        use std::os::unix::process::*;
        use libc;

        if let Some(signal) = status.signal() {
            let name = match signal as libc::c_int {
                libc::SIGABRT => ", SIGABRT: process abort signal",
                libc::SIGALRM => ", SIGALRM: alarm clock",
                libc::SIGFPE => ", SIGFPE: erroneous arithmetic operation",
                libc::SIGHUP => ", SIGHUP: hangup",
                libc::SIGILL => ", SIGILL: illegal instruction",
                libc::SIGINT => ", SIGINT: terminal interrupt signal",
                libc::SIGKILL => ", SIGKILL: kill",
                libc::SIGPIPE => ", SIGPIPE: write on a pipe with no one to read",
                libc::SIGQUIT => ", SIGQUIT: terminal quite signal",
                libc::SIGSEGV => ", SIGSEGV: invalid memory reference",
                libc::SIGTERM => ", SIGTERM: termination signal",
                libc::SIGBUS => ", SIGBUS: access to undefined memory",
                #[cfg(not(target_os = "haiku"))]
                libc::SIGSYS => ", SIGSYS: bad system call",
                libc::SIGTRAP => ", SIGTRAP: trace/breakpoint trap",
                _ => "",
            };
            format!("signal: {}{}", signal, name)
        } else {
            status.to_string()
        }
    }

    #[cfg(windows)]
    fn status_to_string(status: &ExitStatus) -> String {
        status.to_string()
    }
}

pub fn internal<S: fmt::Display>(error: S) -> CargoError {
    _internal(&error)
}

fn _internal(error: &fmt::Display) -> CargoError {
    CargoError::from_kind(error.to_string().into()).into_internal()
}
<|endoftext|>


<commit_before>// CITA
// Copyright 2016-2017 Cryptape Technologies LLC.

// This program is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any
// later version.

// This program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! Node manager.

use super::{encode_contract_name, to_address_vec, to_low_u64_vec};
use super::ContractCallExt;
use libexecutor::executor::Executor;
use rand::{Rng, SeedableRng, StdRng};
use rustc_hex::ToHex;
use std::cmp::Ordering;
use std::iter;
use std::str::FromStr;
use util::{Address, H160};

const LIST_NODE: &'static [u8] = &*b"listNode()";
const LIST_STAKE: &'static [u8] = *&b"listStake()";
/// Each epoch is divided into 1000 slots, each slot represent one opportunity of block proposer
const EPOCH: u64 = 1000;

lazy_static! {
    static ref LIST_NODE_ENCODED: Vec<u8> = encode_contract_name(LIST_NODE);
    static ref LIST_STAKE_ENCODED: Vec<u8> = encode_contract_name(LIST_STAKE);
    static ref CONTRACT_ADDRESS: H160 = H160::from_str("00000000000000000000000000000000013241a2").unwrap();
}

pub fn party_seats<T>(parties: Vec<T>, seats: &[u64]) -> Vec<T>
where
    T: Clone,
{
    let mut party_seats: Vec<T> = Vec::new();
    let seats_len = seats.len();
    for (index, party) in parties.into_iter().enumerate() {
        if index < seats_len {
            party_seats.extend(iter::repeat(party).take(seats[index] as usize));
        }
    }
    party_seats
}

/// https://en.wikipedia.org/wiki/Largest_remainder_method
/// Hare quota
pub fn largest_remainder_electoral(votes: &Vec<u64>, seat_number: u64) -> Vec<u64> {
    let total = votes.iter().fold(0, |acc, &x| acc + x);
    // TODO: PLS fix me!!!
    let hare_quota = total as f64 / seat_number as f64;

    let votes_quota: Vec<f64> = votes
        .iter()
        .map(|vote| (*vote as f64) / hare_quota)
        .collect();

    // calculate automatic seats first
    let mut seats: Vec<u64> = votes_quota.iter().map(|v| v.floor() as u64).collect();

    let mut remainders: Vec<(f64, u64)> = votes_quota
        .iter()
        .enumerate()
        .map(|(i, v)| (v.fract(), i as u64))
        .collect();

    let remaining_seat: u64 = seat_number - seats.iter().fold(0, |acc, &x| acc + x);

    remainders.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
    remainders.reverse();

    let highest_remainder_seats: Vec<u64> = remainders
        .iter()
        .take(remaining_seat as usize)
        .map(|v| v.1)
        .collect();

    for index in highest_remainder_seats {
        seats[index as usize] += 1;
    }

    seats
}

pub struct NodeManager;

impl NodeManager {
    pub fn nodes(executor: &Executor) -> Vec<Address> {
        let output = executor.call_method_latest(&*CONTRACT_ADDRESS, &*LIST_NODE_ENCODED.as_slice());
        trace!(
            "node manager output: {:?}",
            ToHex::to_hex(output.as_slice())
        );

        let nodes: Vec<Address> = to_address_vec(&output);
        trace!("node manager nodes: {:?}", nodes);
        nodes
    }

    pub fn shuffle_node<T>(node_vec: &mut Vec<T>, rng_seed: u64) {
        let seed: &[_] = &[rng_seed as usize];
        let mut rng: StdRng = SeedableRng::from_seed(seed);

        for i in 0..node_vec.len() {
            let j: usize = rng.gen::<usize>() % (i + 1);
            node_vec.swap(i, j);
        }
    }

    pub fn stakes(executor: &Executor) -> Vec<u64> {
        let output = executor.call_method_latest(&*CONTRACT_ADDRESS, &*LIST_STAKE_ENCODED.as_slice());
        trace!("stakes output: {:?}", ToHex::to_hex(output.as_slice()));

        let stakes: Vec<u64> = to_low_u64_vec(&output);
        trace!("node manager stakes: {:?}", stakes);
        stakes
    }

    pub fn stake_nodes(exeuctor: &Executor) -> Vec<Address> {
        let nodes = NodeManager::nodes(&exeuctor);
        let stakes = NodeManager::stakes(&exeuctor);
        let total = stakes.iter().fold(0, |acc, &x| acc + x);
        let stake_nodes: Vec<Address>;

        if total == 0 {
            stake_nodes = nodes;
        } else {
            let total_seats = largest_remainder_electoral(&stakes, EPOCH);
            stake_nodes = party_seats(nodes, &total_seats);
        }
        stake_nodes
    }

#[cfg(test)]
mod tests {
    extern crate logger;
    extern crate mktemp;

    use super::{largest_remainder_electoral, party_seats, NodeManager};
    use std::str::FromStr;
    use tests::helpers::init_executor;
    use util::H160;

    #[test]
    fn test_node_manager_contract() {
        let executor = init_executor();
        let nodes = NodeManager::nodes(&executor);

        assert_eq!(
            nodes,
            vec![
                H160::from_str("50ad2b9d6946d9c75ae978534043e3021ee1bfb1").unwrap(),
                H160::from_str("eeb3a71c4046f63a941013f826fccc503be26b77").unwrap(),
                H160::from_str("a2bbb65d4f8c3ada29f7471abe416e18061127f3").unwrap(),
                H160::from_str("72eb1e258c9cdccebb7b62930a35cfb6ef4cd24b").unwrap(),
            ]
        )
    }

    #[test]
    fn test_largest_remainder_electoral() {
        let votes = vec![47_000, 16_000, 15_800, 12_000, 6_100, 3_100];
        let seats = 10;
        let total_seats = largest_remainder_electoral(&votes, seats);
        assert_eq!(vec![5, 2, 1, 1, 1, 0], total_seats);
    }

    #[test]
    fn test_largest_remainder_electoral_again() {
        let votes = vec![1500, 1500, 900, 500, 500, 200];
        let seats = 25;
        let total_seats = largest_remainder_electoral(&votes, seats);
        assert_eq!(vec![7, 7, 4, 3, 3, 1], total_seats);

        let new_seats = 26;
        let new_total_seats = largest_remainder_electoral(&votes, new_seats);
        assert_eq!(vec![8, 8, 5, 2, 2, 1], new_total_seats);
    }

    #[test]
    fn test_party_seats() {
        let parties = vec!["a", "b", "c"];
        let seats = vec![3, 5, 2, 2, 1];
        assert_eq!(
            party_seats(parties, &seats),
            vec!["a", "a", "a", "b", "b", "b", "b", "b", "c", "c"]
        );

        let parties = vec!["a", "b"];
        let seats = vec![2, 1];
        assert_eq!(party_seats(parties, &seats), vec!["a", "a", "b"]);

        let parties = vec!["a", "b", "c"];
        let seats = vec![2, 2];
        assert_eq!(party_seats(parties, &seats), vec!["a", "a", "b", "b"]);
    }
}
<commit_msg>add something missing<commit_after>// CITA
// Copyright 2016-2017 Cryptape Technologies LLC.

// This program is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any
// later version.

// This program is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! Node manager.

use super::{encode_contract_name, to_address_vec, to_low_u64_vec};
use super::ContractCallExt;
use libexecutor::executor::Executor;
use rand::{Rng, SeedableRng, StdRng};
use rustc_hex::ToHex;
use std::cmp::Ordering;
use std::iter;
use std::str::FromStr;
use util::{Address, H160};

const LIST_NODE: &'static [u8] = &*b"listNode()";
const LIST_STAKE: &'static [u8] = *&b"listStake()";
/// Each epoch is divided into 1000 slots, each slot represent one opportunity of block proposer
const EPOCH: u64 = 1000;

lazy_static! {
    static ref LIST_NODE_ENCODED: Vec<u8> = encode_contract_name(LIST_NODE);
    static ref LIST_STAKE_ENCODED: Vec<u8> = encode_contract_name(LIST_STAKE);
    static ref CONTRACT_ADDRESS: H160 = H160::from_str("00000000000000000000000000000000013241a2").unwrap();
}

pub fn party_seats<T>(parties: Vec<T>, seats: &[u64]) -> Vec<T>
where
    T: Clone,
{
    let mut party_seats: Vec<T> = Vec::new();
    let seats_len = seats.len();
    for (index, party) in parties.into_iter().enumerate() {
        if index < seats_len {
            party_seats.extend(iter::repeat(party).take(seats[index] as usize));
        }
    }
    party_seats
}

/// https://en.wikipedia.org/wiki/Largest_remainder_method
/// Hare quota
pub fn largest_remainder_electoral(votes: &Vec<u64>, seat_number: u64) -> Vec<u64> {
    let total = votes.iter().fold(0, |acc, &x| acc + x);
    // TODO: PLS fix me!!!
    let hare_quota = total as f64 / seat_number as f64;

    let votes_quota: Vec<f64> = votes
        .iter()
        .map(|vote| (*vote as f64) / hare_quota)
        .collect();

    // calculate automatic seats first
    let mut seats: Vec<u64> = votes_quota.iter().map(|v| v.floor() as u64).collect();

    let mut remainders: Vec<(f64, u64)> = votes_quota
        .iter()
        .enumerate()
        .map(|(i, v)| (v.fract(), i as u64))
        .collect();

    let remaining_seat: u64 = seat_number - seats.iter().fold(0, |acc, &x| acc + x);

    remainders.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
    remainders.reverse();

    let highest_remainder_seats: Vec<u64> = remainders
        .iter()
        .take(remaining_seat as usize)
        .map(|v| v.1)
        .collect();

    for index in highest_remainder_seats {
        seats[index as usize] += 1;
    }

    seats
}

pub struct NodeManager;

impl NodeManager {
    pub fn nodes(executor: &Executor) -> Vec<Address> {
        let output = executor.call_method_latest(&*CONTRACT_ADDRESS, &*LIST_NODE_ENCODED.as_slice());
        trace!(
            "node manager output: {:?}",
            ToHex::to_hex(output.as_slice())
        );

        let nodes: Vec<Address> = to_address_vec(&output);
        trace!("node manager nodes: {:?}", nodes);
        nodes
    }

    pub fn shuffle_node<T>(node_vec: &mut Vec<T>, rng_seed: u64) {
        let seed: &[_] = &[rng_seed as usize];
        let mut rng: StdRng = SeedableRng::from_seed(seed);

        for i in 0..node_vec.len() {
            let j: usize = rng.gen::<usize>() % (i + 1);
            node_vec.swap(i, j);
        }
    }

    pub fn stakes(executor: &Executor) -> Vec<u64> {
        let output = executor.call_method_latest(&*CONTRACT_ADDRESS, &*LIST_STAKE_ENCODED.as_slice());
        trace!("stakes output: {:?}", ToHex::to_hex(output.as_slice()));

        let stakes: Vec<u64> = to_low_u64_vec(&output);
        trace!("node manager stakes: {:?}", stakes);
        stakes
    }

    pub fn stake_nodes(exeuctor: &Executor) -> Vec<Address> {
        let nodes = NodeManager::nodes(&exeuctor);
        let stakes = NodeManager::stakes(&exeuctor);
        let total = stakes.iter().fold(0, |acc, &x| acc + x);
        let stake_nodes: Vec<Address>;

        if total == 0 {
            stake_nodes = nodes;
        } else {
            let total_seats = largest_remainder_electoral(&stakes, EPOCH);
            stake_nodes = party_seats(nodes, &total_seats);
        }
        stake_nodes
    }
}

#[cfg(test)]
mod tests {
    extern crate logger;
    extern crate mktemp;

    use super::{largest_remainder_electoral, party_seats, NodeManager};
    use std::str::FromStr;
    use tests::helpers::init_executor;
    use util::H160;

    #[test]
    fn test_node_manager_contract() {
        let executor = init_executor();
        let nodes = NodeManager::nodes(&executor);

        assert_eq!(
            nodes,
            vec![
                H160::from_str("50ad2b9d6946d9c75ae978534043e3021ee1bfb1").unwrap(),
                H160::from_str("eeb3a71c4046f63a941013f826fccc503be26b77").unwrap(),
                H160::from_str("a2bbb65d4f8c3ada29f7471abe416e18061127f3").unwrap(),
                H160::from_str("72eb1e258c9cdccebb7b62930a35cfb6ef4cd24b").unwrap(),
            ]
        )
    }

    #[test]
    fn test_largest_remainder_electoral() {
        let votes = vec![47_000, 16_000, 15_800, 12_000, 6_100, 3_100];
        let seats = 10;
        let total_seats = largest_remainder_electoral(&votes, seats);
        assert_eq!(vec![5, 2, 1, 1, 1, 0], total_seats);
    }

    #[test]
    fn test_largest_remainder_electoral_again() {
        let votes = vec![1500, 1500, 900, 500, 500, 200];
        let seats = 25;
        let total_seats = largest_remainder_electoral(&votes, seats);
        assert_eq!(vec![7, 7, 4, 3, 3, 1], total_seats);

        let new_seats = 26;
        let new_total_seats = largest_remainder_electoral(&votes, new_seats);
        assert_eq!(vec![8, 8, 5, 2, 2, 1], new_total_seats);
    }

    #[test]
    fn test_party_seats() {
        let parties = vec!["a", "b", "c"];
        let seats = vec![3, 5, 2, 2, 1];
        assert_eq!(
            party_seats(parties, &seats),
            vec!["a", "a", "a", "b", "b", "b", "b", "b", "c", "c"]
        );

        let parties = vec!["a", "b"];
        let seats = vec![2, 1];
        assert_eq!(party_seats(parties, &seats), vec!["a", "a", "b"]);

        let parties = vec!["a", "b", "c"];
        let seats = vec![2, 2];
        assert_eq!(party_seats(parties, &seats), vec!["a", "a", "b", "b"]);
    }
}
<|endoftext|>


<commit_before>use std::path;

#[cfg(feature = "sass")]
use sass_rs;

use error::*;
use super::files;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SassOutputStyle {
    Nested,
    Expanded,
    Compact,
    Compressed,
}

impl Default for SassOutputStyle {
    fn default() -> Self {
        SassOutputStyle::Nested
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct SassBuilder {
    pub import_dir: Option<String>,
    pub style: SassOutputStyle,
}

impl SassBuilder {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn build(self) -> SassCompiler {
        let Self { import_dir, style } = self;
        SassCompiler { import_dir, style }
    }
}

#[derive(Debug, PartialEq)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SassCompiler {
    pub import_dir: Option<String>,
    pub style: SassOutputStyle,
}

impl Default for SassCompiler {
    fn default() -> Self {
        SassBuilder::default().build()
    }
}

impl SassCompiler {
    pub fn compile_file<S: AsRef<path::Path>, D: AsRef<path::Path>, F: AsRef<path::Path>>
        (&self,
         source: S,
         dest: D,
         file_path: F)
         -> Result<()> {
        self.compile_sass_internal(source.as_ref(), dest.as_ref(), file_path.as_ref())
    }

    #[cfg(feature = "sass")]
    fn compile_sass_internal(&self,
                             source: &path::Path,
                             dest: &path::Path,
                             file_path: &path::Path)
                             -> Result<()> {
        let mut sass_opts = sass_rs::Options::default();
        sass_opts.include_paths = self.import_dir.iter().cloned().collect();
        sass_opts.output_style = match self.style {
            SassOutputStyle::Nested => sass_rs::OutputStyle::Nested,
            SassOutputStyle::Expanded => sass_rs::OutputStyle::Expanded,
            SassOutputStyle::Compact => sass_rs::OutputStyle::Compact,
            SassOutputStyle::Compressed => sass_rs::OutputStyle::Compressed,
        };
        let content = sass_rs::compile_file(file_path, sass_opts)?;

        let rel_src = file_path
            .strip_prefix(source)
            .expect("file was found under the root");
        let mut dest_file = dest.join(rel_src);
        dest_file.set_extension("css");

        files::write_document_file(content, dest_file)
    }

    #[cfg(not(feature = "sass"))]
    fn compile_sass_internal(&self,
                             source: &path::Path,
                             dest: &path::Path,
                             file_path: &path::Path)
                             -> Result<()> {
        let rel_src = file_path
            .strip_prefix(source)
            .expect("file was found under the root");
        let dest_file = dest.join(rel_src);
        files::copy_file(file_path, &dest_file)
    }
}
<commit_msg>refactor(sass): Remove unused code<commit_after>use std::path;

#[cfg(feature = "sass")]
use sass_rs;

use error::*;
use super::files;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SassOutputStyle {
    Nested,
    Expanded,
    Compact,
    Compressed,
}

impl Default for SassOutputStyle {
    fn default() -> Self {
        SassOutputStyle::Nested
    }
}

#[derive(Debug, Clone, PartialEq, Default)]
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct SassBuilder {
    pub import_dir: Option<String>,
    pub style: SassOutputStyle,
}

impl SassBuilder {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn build(self) -> SassCompiler {
        let Self { import_dir, style } = self;
        SassCompiler { import_dir, style }
    }
}

#[derive(Debug, PartialEq)]
pub struct SassCompiler {
    import_dir: Option<String>,
    style: SassOutputStyle,
}

impl Default for SassCompiler {
    fn default() -> Self {
        SassBuilder::default().build()
    }
}

impl SassCompiler {
    pub fn compile_file<S: AsRef<path::Path>, D: AsRef<path::Path>, F: AsRef<path::Path>>
        (&self,
         source: S,
         dest: D,
         file_path: F)
         -> Result<()> {
        self.compile_sass_internal(source.as_ref(), dest.as_ref(), file_path.as_ref())
    }

    #[cfg(feature = "sass")]
    fn compile_sass_internal(&self,
                             source: &path::Path,
                             dest: &path::Path,
                             file_path: &path::Path)
                             -> Result<()> {
        let mut sass_opts = sass_rs::Options::default();
        sass_opts.include_paths = self.import_dir.iter().cloned().collect();
        sass_opts.output_style = match self.style {
            SassOutputStyle::Nested => sass_rs::OutputStyle::Nested,
            SassOutputStyle::Expanded => sass_rs::OutputStyle::Expanded,
            SassOutputStyle::Compact => sass_rs::OutputStyle::Compact,
            SassOutputStyle::Compressed => sass_rs::OutputStyle::Compressed,
        };
        let content = sass_rs::compile_file(file_path, sass_opts)?;

        let rel_src = file_path
            .strip_prefix(source)
            .expect("file was found under the root");
        let mut dest_file = dest.join(rel_src);
        dest_file.set_extension("css");

        files::write_document_file(content, dest_file)
    }

    #[cfg(not(feature = "sass"))]
    fn compile_sass_internal(&self,
                             source: &path::Path,
                             dest: &path::Path,
                             file_path: &path::Path)
                             -> Result<()> {
        let rel_src = file_path
            .strip_prefix(source)
            .expect("file was found under the root");
        let dest_file = dest.join(rel_src);
        files::copy_file(file_path, &dest_file)
    }
}
<|endoftext|>


<commit_before>pub mod tag_command;
pub mod autotag;
pub mod update_tags;
pub mod update_tag;

pub use self::tag_command::TagCommand;
pub use self::autotag::AutoTagCommand;
pub use self::update_tags::UpdateTagsCommand;
pub use self::update_tag::UpdateTagCommand;

use bot::BotEnv;
use database::models::{Tag, NewTag, Verification};

use lalafell::error::*;
use lalafell::commands::prelude::*;

use serenity;
use serenity::prelude::Mentionable;
use serenity::model::guild::Role;
use serenity::model::id::{RoleId, UserId};

use diesel::prelude::*;

use url::Url;

use make_hyper_great_again::Client;
use hyper_rustls::HttpsConnector;

use std::sync::Mutex;
use std::collections::HashSet;
use std::thread;

lazy_static! {
  // Limbo roles are roles that may or may not be added to the Discord bot state.
  static ref LIMBO_ROLES: Mutex<Vec<Role>> = Mutex::default();
}

pub struct Tagger;

impl Tagger {
  fn add_to_xivdb<N: Into<String>, S: Into<String>>(name: N, server: S) {
    let name: String = name.into();
    let server: String = server.into();
    thread::spawn(move || {
      let mut url = Url::parse("https://xivsync.com/character/search").unwrap();
      url.query_pairs_mut()
        .append_pair("name", &name)
        .append_pair("server", &server);
      let client = Client::create_connector(|c| HttpsConnector::new(4, &c.handle())).unwrap();
      client.get(url).send().ok();
    });
  }

  pub fn search_tag(env: &BotEnv, who: UserId, on: GuildId, server: &str, character_name: &str, ignore_verified: bool) -> Result<Option<String>> {
    let params = &[
      ("one", "characters"),
      ("strict", "on"),
      ("server|et", server)
    ];

    let res = env.xivdb.search(character_name, params).chain_err(|| "could not query XIVDB")?;

    let search_chars = res.characters.unwrap().results;
    if search_chars.is_empty() {
      Tagger::add_to_xivdb(character_name, server);
      return Ok(Some(format!("Could not find any character by the name {} on {}.\nIf you typed everything correctly, wait up to five minutes and try again.", character_name, server)));
    }

    let char_id = match search_chars[0]["id"].as_u64() {
      Some(u) => u,
      None => bail!("character ID was not a u64")
    };

    let name = match search_chars[0]["name"].as_str() {
      Some(s) => s,
      None => bail!("character name was not a string")
    };

    if name.to_lowercase() != character_name.to_lowercase() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    Tagger::tag(env, who, on, char_id, ignore_verified)
  }

  fn find_or_create_role(guild: GuildId, name: &str, add_roles: &mut Vec<Role>, created_roles: &mut Vec<Role>) -> Result<()> {
    let lower_name = name.to_lowercase();
    let guild = guild.get().chain_err(|| "could not get guild")?;
    match guild.roles.values().find(|x| x.name.to_lowercase() == lower_name) {
      Some(r) => add_roles.push(r.clone()),
      None => {
        let role = guild.create_role(|r| r.name(&lower_name)).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    Ok(())
  }

  pub fn tag(env: &BotEnv, who: UserId, on: GuildId, char_id: u64, ignore_verified: bool) -> Result<Option<String>> {
    let tag: Option<Tag> = ::bot::CONNECTION.with(|c| {
      use database::schema::tags::dsl;
      dsl::tags
        .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string())))
        .first(c)
        .optional()
        .chain_err(|| "could not load tags")
    })?;
    let is_verified = match tag {
      Some(t) => {
        let verification: Verification = ::bot::CONNECTION.with(|c| {
          Verification::belonging_to(&t).first(c).optional().chain_err(|| "could not load verifications")
        })?.unwrap_or_default();
        if verification.verified && !ignore_verified && char_id != *t.character_id {
          return Ok(Some(format!("{} is verified as {} on {}, so they cannot switch to another account.", who.mention(), t.character, t.server)));
        }
        verification.verified
      },
      None => false
    };

    // This is still a disaster, just slightly less so
    let member = match on.member(who) {
      Ok(m) => m,
      Err(serenity::Error::Json(_)) => {
        ::bot::CONNECTION.with(|c| {
          use database::schema::tags::dsl;
          ::diesel::delete(dsl::tags
            .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string()))))
            .execute(c)
            .chain_err(|| format!("could not remove tag {} on {}, but it was expected to be in database", who.0, on.0))
        })?;
        bail!("could not get user {} as a member of {}: removing their tag", who.0, on.0);
      },
      Err(e) => return Err(e).chain_err(|| "could not get member for tagging")
    };

    let character = env.xivdb.character(char_id).chain_err(|| "could not look up character")?;

    let tag: Option<Tag> = ::bot::CONNECTION.with(|c| {
      use database::schema::tags::dsl;
      dsl::tags
        .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string())))
        .first(c)
        .optional()
        .chain_err(|| "could not load tags")
    })?;
    match tag {
      Some(mut t) => {
        let (id, name, server) = (character.lodestone_id, character.name.clone(), character.server.clone());
        t.character_id = id.into();
        t.character = name;
        t.server = server;
        ::bot::CONNECTION.with(|c| t.save_changes::<Tag>(c).chain_err(|| "could not update tag"))?;
      },
      None => {
        let new_tag = NewTag::new(
          who.0,
          on.0,
          character.lodestone_id,
          &character.name,
          &character.server
        );
        ::bot::CONNECTION.with(|c| {
          use database::schema::tags;
          ::diesel::insert_into(tags::table).values(&new_tag).execute(c).chain_err(|| "could not insert tag")
        })?;
      }
    }

    // Get a copy of the roles on the server.
    let mut roles: Vec<Role> = match on.find() {
      Some(g) => g.read().roles.values().cloned().collect(),
      None => on.get().chain_err(|| "could not get guild")?.roles.values().cloned().collect()
    };

    // Check for existing limbo roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for role in &roles {
        // If the server has updated to contain the limbo role, remove it.
        if let Some(i) = limbo.iter().position(|x| x.id == role.id) {
          limbo.remove(i);
        }
      }
    }
    // Get a copy of the limbo roles.
    let limbo = LIMBO_ROLES.lock().unwrap().clone();
    // Extend the server roles with the limbo roles.
    roles.extend(limbo);

    // Find or create the necessary roles
    let mut created_roles = Vec::new();
    let mut add_roles = Vec::new();
    Tagger::find_or_create_role(on, &character.data.race, &mut add_roles, &mut created_roles)?;
    Tagger::find_or_create_role(on, &character.data.gender, &mut add_roles, &mut created_roles)?;
    Tagger::find_or_create_role(on, &character.server, &mut add_roles, &mut created_roles)?;

    if is_verified {
      Tagger::find_or_create_role(on, "verified", &mut add_roles, &mut created_roles)?;
    }

    // If we created any roles, the server may or may not update with them fast enough, so store a copy in the limbo
    // roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for created in &created_roles {
        limbo.push(created.clone());
      }
    }

    debug!("Created the following roles:\n:{:#?}", created_roles);

    debug!("Existing roles:\n{:#?}", add_roles);

    // Extend the roles to add with the roles we created.
    add_roles.extend(created_roles);
    // Get all the roles that are part of groups
    let all_group_roles: Vec<String> = env.config.roles.groups.iter().flat_map(|x| x).map(|x| x.to_lowercase()).collect();
    // Filter all roles on the server to only the roles the member has
    let keep: Vec<&Role> = roles.iter().filter(|x| member.roles.contains(&x.id)).collect();
    // Filter all the roles the member has, keeping the ones not in a group. These roles will not be touched when
    // updating the tag.
    let keep: Vec<&Role> = keep.into_iter().filter(|x| !all_group_roles.contains(&x.name.to_lowercase())).collect();
    debug!("Roles to keep:\n{:#?}", keep);
    // Combine the two sets of roles and map them to IDs
    let mut role_set: Vec<RoleId> = add_roles.iter().map(|r| r.id).chain(keep.into_iter().map(|r| r.id)).collect();
    // Sort the IDs so we can dedup them
    role_set.sort();
    // Remove the duplicate roles, if any
    role_set.dedup();

    debug!("Final role set:\n{:#?}", role_set);

    // Only update the roles if they are different
    let different = {
      let member_roles: HashSet<u64> = member.roles.iter().map(|x| x.0).collect();
      let actual_role_set: HashSet<u64> = role_set.iter().map(|x| x.0).collect();
      member_roles != actual_role_set
    };
    if different {
      on.edit_member(who, |m| m.roles(&role_set)).chain_err(|| "could not add roles")?;
    }

    // cannot edit nickname of those with a higher role
    let nick = match member.nick {
      Some(n) => n,
      None => Default::default()
    };
    if nick != character.name {
      on.edit_member(who, |m| m.nickname(&character.name)).ok();
    }
    Ok(None)
  }
}

#[derive(Debug, Deserialize)]
struct DiscordNotFoundError {
  code: u64,
  message: Option<String>
}
<commit_msg>fix: remove tags that are no longer in the guild<commit_after>pub mod tag_command;
pub mod autotag;
pub mod update_tags;
pub mod update_tag;

pub use self::tag_command::TagCommand;
pub use self::autotag::AutoTagCommand;
pub use self::update_tags::UpdateTagsCommand;
pub use self::update_tag::UpdateTagCommand;

use bot::BotEnv;
use database::models::{Tag, NewTag, Verification};

use lalafell::error::*;
use lalafell::commands::prelude::*;

use serenity::prelude::Mentionable;
use serenity::model::guild::Role;
use serenity::model::id::{RoleId, UserId};
use serenity::Error as SError;
use serenity::http::{HttpError, StatusCode};

use diesel::prelude::*;

use url::Url;

use make_hyper_great_again::Client;
use hyper_rustls::HttpsConnector;

use std::sync::Mutex;
use std::collections::HashSet;
use std::thread;

lazy_static! {
  // Limbo roles are roles that may or may not be added to the Discord bot state.
  static ref LIMBO_ROLES: Mutex<Vec<Role>> = Mutex::default();
}

pub struct Tagger;

impl Tagger {
  fn add_to_xivdb<N: Into<String>, S: Into<String>>(name: N, server: S) {
    let name: String = name.into();
    let server: String = server.into();
    thread::spawn(move || {
      let mut url = Url::parse("https://xivsync.com/character/search").unwrap();
      url.query_pairs_mut()
        .append_pair("name", &name)
        .append_pair("server", &server);
      let client = Client::create_connector(|c| HttpsConnector::new(4, &c.handle())).unwrap();
      client.get(url).send().ok();
    });
  }

  pub fn search_tag(env: &BotEnv, who: UserId, on: GuildId, server: &str, character_name: &str, ignore_verified: bool) -> Result<Option<String>> {
    let params = &[
      ("one", "characters"),
      ("strict", "on"),
      ("server|et", server)
    ];

    let res = env.xivdb.search(character_name, params).chain_err(|| "could not query XIVDB")?;

    let search_chars = res.characters.unwrap().results;
    if search_chars.is_empty() {
      Tagger::add_to_xivdb(character_name, server);
      return Ok(Some(format!("Could not find any character by the name {} on {}.\nIf you typed everything correctly, wait up to five minutes and try again.", character_name, server)));
    }

    let char_id = match search_chars[0]["id"].as_u64() {
      Some(u) => u,
      None => bail!("character ID was not a u64")
    };

    let name = match search_chars[0]["name"].as_str() {
      Some(s) => s,
      None => bail!("character name was not a string")
    };

    if name.to_lowercase() != character_name.to_lowercase() {
      return Ok(Some(format!("Could not find any character by the name {} on {}.", character_name, server)));
    }

    Tagger::tag(env, who, on, char_id, ignore_verified)
  }

  fn find_or_create_role(guild: GuildId, name: &str, add_roles: &mut Vec<Role>, created_roles: &mut Vec<Role>) -> Result<()> {
    let lower_name = name.to_lowercase();
    let guild = guild.get().chain_err(|| "could not get guild")?;
    match guild.roles.values().find(|x| x.name.to_lowercase() == lower_name) {
      Some(r) => add_roles.push(r.clone()),
      None => {
        let role = guild.create_role(|r| r.name(&lower_name)).chain_err(|| "could not create role")?;
        created_roles.push(role);
      }
    }
    Ok(())
  }

  pub fn tag(env: &BotEnv, who: UserId, on: GuildId, char_id: u64, ignore_verified: bool) -> Result<Option<String>> {
    let tag: Option<Tag> = ::bot::CONNECTION.with(|c| {
      use database::schema::tags::dsl;
      dsl::tags
        .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string())))
        .first(c)
        .optional()
        .chain_err(|| "could not load tags")
    })?;
    let is_verified = match tag {
      Some(t) => {
        let verification: Verification = ::bot::CONNECTION.with(|c| {
          Verification::belonging_to(&t).first(c).optional().chain_err(|| "could not load verifications")
        })?.unwrap_or_default();
        if verification.verified && !ignore_verified && char_id != *t.character_id {
          return Ok(Some(format!("{} is verified as {} on {}, so they cannot switch to another account.", who.mention(), t.character, t.server)));
        }
        verification.verified
      },
      None => false
    };

    // This is still a disaster, just slightly less so
    let member = match on.member(who) {
      Ok(m) => m,
      Err(SError::Http(HttpError::UnsuccessfulRequest(ref r))) if r.status == StatusCode::NotFound => {
        ::bot::CONNECTION.with(|c| {
          use database::schema::tags::dsl;
          ::diesel::delete(dsl::tags
            .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string()))))
            .execute(c)
            .chain_err(|| format!("could not remove tag {} on {}, but it was expected to be in database", who.0, on.0))
        })?;
        bail!("could not get user {} as a member of {}: removing their tag", who.0, on.0);
      },
      Err(e) => return Err(e).chain_err(|| "could not get member for tagging")
    };

    let character = env.xivdb.character(char_id).chain_err(|| "could not look up character")?;

    let tag: Option<Tag> = ::bot::CONNECTION.with(|c| {
      use database::schema::tags::dsl;
      dsl::tags
        .filter(dsl::user_id.eq(who.0.to_string()).and(dsl::server_id.eq(on.0.to_string())))
        .first(c)
        .optional()
        .chain_err(|| "could not load tags")
    })?;
    match tag {
      Some(mut t) => {
        let (id, name, server) = (character.lodestone_id, character.name.clone(), character.server.clone());
        t.character_id = id.into();
        t.character = name;
        t.server = server;
        ::bot::CONNECTION.with(|c| t.save_changes::<Tag>(c).chain_err(|| "could not update tag"))?;
      },
      None => {
        let new_tag = NewTag::new(
          who.0,
          on.0,
          character.lodestone_id,
          &character.name,
          &character.server
        );
        ::bot::CONNECTION.with(|c| {
          use database::schema::tags;
          ::diesel::insert_into(tags::table).values(&new_tag).execute(c).chain_err(|| "could not insert tag")
        })?;
      }
    }

    // Get a copy of the roles on the server.
    let mut roles: Vec<Role> = match on.find() {
      Some(g) => g.read().roles.values().cloned().collect(),
      None => on.get().chain_err(|| "could not get guild")?.roles.values().cloned().collect()
    };

    // Check for existing limbo roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for role in &roles {
        // If the server has updated to contain the limbo role, remove it.
        if let Some(i) = limbo.iter().position(|x| x.id == role.id) {
          limbo.remove(i);
        }
      }
    }
    // Get a copy of the limbo roles.
    let limbo = LIMBO_ROLES.lock().unwrap().clone();
    // Extend the server roles with the limbo roles.
    roles.extend(limbo);

    // Find or create the necessary roles
    let mut created_roles = Vec::new();
    let mut add_roles = Vec::new();
    Tagger::find_or_create_role(on, &character.data.race, &mut add_roles, &mut created_roles)?;
    Tagger::find_or_create_role(on, &character.data.gender, &mut add_roles, &mut created_roles)?;
    Tagger::find_or_create_role(on, &character.server, &mut add_roles, &mut created_roles)?;

    if is_verified {
      Tagger::find_or_create_role(on, "verified", &mut add_roles, &mut created_roles)?;
    }

    // If we created any roles, the server may or may not update with them fast enough, so store a copy in the limbo
    // roles.
    {
      let limbo = &mut *LIMBO_ROLES.lock().unwrap();
      for created in &created_roles {
        limbo.push(created.clone());
      }
    }

    debug!("Created the following roles:\n:{:#?}", created_roles);

    debug!("Existing roles:\n{:#?}", add_roles);

    // Extend the roles to add with the roles we created.
    add_roles.extend(created_roles);
    // Get all the roles that are part of groups
    let all_group_roles: Vec<String> = env.config.roles.groups.iter().flat_map(|x| x).map(|x| x.to_lowercase()).collect();
    // Filter all roles on the server to only the roles the member has
    let keep: Vec<&Role> = roles.iter().filter(|x| member.roles.contains(&x.id)).collect();
    // Filter all the roles the member has, keeping the ones not in a group. These roles will not be touched when
    // updating the tag.
    let keep: Vec<&Role> = keep.into_iter().filter(|x| !all_group_roles.contains(&x.name.to_lowercase())).collect();
    debug!("Roles to keep:\n{:#?}", keep);
    // Combine the two sets of roles and map them to IDs
    let mut role_set: Vec<RoleId> = add_roles.iter().map(|r| r.id).chain(keep.into_iter().map(|r| r.id)).collect();
    // Sort the IDs so we can dedup them
    role_set.sort();
    // Remove the duplicate roles, if any
    role_set.dedup();

    debug!("Final role set:\n{:#?}", role_set);

    // Only update the roles if they are different
    let different = {
      let member_roles: HashSet<u64> = member.roles.iter().map(|x| x.0).collect();
      let actual_role_set: HashSet<u64> = role_set.iter().map(|x| x.0).collect();
      member_roles != actual_role_set
    };
    if different {
      on.edit_member(who, |m| m.roles(&role_set)).chain_err(|| "could not add roles")?;
    }

    // cannot edit nickname of those with a higher role
    let nick = match member.nick {
      Some(n) => n,
      None => Default::default()
    };
    if nick != character.name {
      on.edit_member(who, |m| m.nickname(&character.name)).ok();
    }
    Ok(None)
  }
}

#[derive(Debug, Deserialize)]
struct DiscordNotFoundError {
  code: u64,
  message: Option<String>
}
<|endoftext|>