//! Raw representation wrappers for libgit2

use crate::branch::{Branch, BranchCommit};
use git2::{self, Oid};
use std::{fmt, sync::Arc};

pub type GitResult<T> = Result<T, GitError>;

/// The hex ID of a commit
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HashId(String);

impl fmt::Display for HashId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl HashId {
    pub fn to_oid(&self) -> Oid {
        self.clone().into()
    }

    pub fn to_string(&self) -> String {
        self.0.clone()
    }
}

impl From<Oid> for HashId {
    fn from(o: Oid) -> Self {
        Self(o.to_string())
    }
}

impl From<HashId> for Oid {
    fn from(hid: HashId) -> Self {
        Oid::from_str(hid.0.as_str()).expect(&format!(
            "Tried turning an invalid HashId variant into an Oid: {:?}",
            hid
        ))
    }
}

impl<'any> From<&'any HashId> for Oid {
    fn from(hid: &'any HashId) -> Self {
        Oid::from_str(hid.0.as_str()).expect(&format!(
            "Tried turning an invalid HashId variant into an Oid: {:?}",
            hid
        ))
    }
}

/// An error abstraction for raw git operations
#[derive(Debug)]
pub enum GitError {
    AllBad,
}

impl From<git2::Error> for GitError {
    fn from(_: git2::Error) -> Self {
        Self::AllBad
    }
}

/// Represents a git repository with lazy data loading
#[derive(Clone)]
pub struct Repository {
    inner: Arc<git2::Repository>,
}

impl Repository {
    /// Open a repository read-only at a specific path
    pub fn open(path: &str) -> GitResult<Self> {
        Ok(Self {
            inner: Arc::new(git2::Repository::open(path)?),
        })
    }

    /// Parse branch data from repository
    ///
    /// If you only care about a single branch, you can also use the
    /// convenience function `get_branch()`.
    ///
    /// ## Panics
    ///
    /// This function can panic when branch metadata is missing.
    pub fn branches(&self) -> GitResult<Vec<Branch>> {
        Ok(self
            .inner
            .branches(None)?
            .into_iter()
            .filter_map(|e| e.ok())
            .map(|(branch, _)| {
                let name = branch.name().unwrap().unwrap().into();
                let head = branch.get().peel_to_commit().unwrap().id().into();
                Branch::new(&self.inner, name, head)
            })
            .collect())
    }

    /// Get a single branch by name
    ///
    /// This function will enumerate all branches, and then select the
    /// desired one.  If you want to make repeated queries onto the
    /// branch set, it's recommended you call `branches()`, and cache
    /// the data yourself.
    pub fn branch(&self, name: String) -> Option<Branch> {
        self.branches().ok().and_then(|ok| {
            ok.into_iter()
                .filter(|b| b.name().is_some())
                .find(|b| &b.name().unwrap() == &name)
        })
    }
}
