use std::fmt::{Display, Formatter};
use git2::{Reference, Commit, Tree, ObjectType, Repository, Oid};
use std::ops::Add;


pub struct GitRepo {
    git2 : Repository,
}

impl GitRepo {

    pub fn new(path : &str) -> Result<GitRepo, git2::Error> {
        Repository::open(path).map(|git2| GitRepo{git2})
    }

    pub fn get_root_tree<'a, 'b> (&'a self, commit : &'b GitCommit) -> Result<GitBrowseDir<'a>, git2::Error> {
        let oid = Oid::from_str(&commit.0)?;
        let commit = self.git2.find_commit(oid)?;
        let tree = commit.tree()?;
        let fullname = "/".to_string();
        Ok(GitBrowseDir{ fullname, tree })
    }

    pub async fn browse<'a>(&'a self, dir: &GitBrowseDir<'a>) -> Result<Vec<GitBrowseEntry<'a>>, git2::Error> {
        let mut res = Vec::new();
        for entry in dir.tree.iter() {
            match entry.kind() {
                None => {Err(git2::Error::from_str("each tree entry must be well defined"))?;}
                Some(kind) => match kind {
                    ObjectType::Any => {Err(git2::Error::from_str("tree entry cannot be of kind Any"))?;}
                    ObjectType::Commit => {Err(git2::Error::from_str("tree entry cannot be of kind Commit"))?;}
                    ObjectType::Tree => {
                        let name = entry.name().ok_or(git2::Error::from_str("entry must have valid utf8 name"))?;
                        let fullname = dir.fullname.clone().add(name);
                        let subtree = self.git2.find_tree(entry.id())?;
                        res.push(GitBrowseEntry::EGitDir(GitBrowseDir{fullname, tree : subtree}));
                    }
                    ObjectType::Blob => {
                        let name = entry.name().ok_or(git2::Error::from_str("entry must have valid utf8 name"))?;
                        let fullname = dir.fullname.clone().add(name);
                        res.push(GitBrowseEntry::EGitFile(GitBrowseFile(fullname)));
                    }
                    ObjectType::Tag => {Err(git2::Error::from_str("tree entry cannot be of kind tag"))?;}
                }
            };
        }
        return Ok(res);
    }

}

#[derive(Debug)]
pub struct GitCommit (String);

impl GitCommit {
    pub fn new(s : String) -> GitCommit {
        GitCommit(s)
    }
}

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

#[derive(Debug)]
pub struct GitBrowseFile (String);
#[derive(Debug)]
pub struct GitBrowseDir<'a>{
    fullname: String,
    tree : git2::Tree<'a>,
}

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

impl Display for GitBrowseDir<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.fullname)
    }
}

#[derive(Debug)]
pub enum GitBrowseEntry<'a> {
    EGitFile(GitBrowseFile),
    EGitDir(GitBrowseDir<'a>)
}

impl Display for GitBrowseEntry<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            GitBrowseEntry::EGitFile(file) => {write!(f, "{}", file)}
            GitBrowseEntry::EGitDir(dir) => {write!(f, "{}", dir)}
        }
    }
}