use crate::permissions::Perm;
use crate::Config;
use axum::{
    debug_handler,
    extract::{Path, State},
    response::{IntoResponse, Response},
    Json,
};
use axum_extra::extract::SignedCookieJar;
use diesel_async::AsyncPgConnection;
use libpijul::Hash;
use libpijul::{changestore::ChangeStore, Base32, EdgeFlags, TxnT};
use serde_derive::*;
use std::collections::HashMap;
use tracing::*;

pub struct Record<'a, L> {
    pub rec: &'a libpijul::change::Hunk<Option<Hash>, L>,
    pub hashes: &'a mut HashMap<Hash, usize>,
    pub hash: &'a libpijul::Hash,
    pub changes: &'a crate::repository::changestore::FileSystem,
    pub hunk: usize,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TreePath {
    hash: String,
}

#[debug_handler]
pub async fn change(
    State(config): State<Config>,
    jar: SignedCookieJar,
    repo: crate::repository::RepoPath,
    token: axum_csrf::CsrfToken,
    Path(tree): Path<TreePath>,
) -> Result<Response, crate::Error> {
    let (uid, login) = crate::get_user_login_(&jar, &config).await?;
    let mut db = config.db.get().await.unwrap();
    let (id, _) =
        crate::repository::repository_id(&mut db, &repo.owner, &repo.repo, uid, Perm::READ).await?;

    let repo_locks = config.repo_locks.clone();
    let repo_ = repo_locks.get(&id).await.unwrap();
    let pristine = repo_.pristine.read().await;
    let txn = pristine.txn_begin().unwrap();
    let r = render(
        &mut db,
        &txn,
        &token,
        &repo_.changes,
        repo.owner,
        repo.repo,
        libpijul::Hash::from_base32(tree.hash.as_bytes()).unwrap(),
        login,
    )
    .await?;
    debug!("r = {:#?}", r);
    Ok((token, Json(r)).into_response())
}

async fn render<T: TxnT>(
    db: &mut AsyncPgConnection,
    txn: &T,
    token: &axum_csrf::CsrfToken,
    changes: &crate::repository::changestore::FileSystem,
    owner: String,
    repo: String,
    hash: libpijul::Hash,
    login: Option<String>,
) -> Result<Patch, crate::Error> {
    use libpijul::changestore::ChangeStore;

    let mut header = changes.get_header(&hash).unwrap();
    let hunks = changes.get_changes(&hash).unwrap();
    let dependencies = changes.get_dependencies(&hash).unwrap();
    let extra_known = changes.get_extra_known(&hash).unwrap();

    let mut deps = Vec::new();
    let mut hashes = HashMap::new();
    hashes.insert(hash, 0);
    hashes.insert(libpijul::Hash::None, 1);
    for h in dependencies.iter() {
        hashes.insert(*h, deps.len() + 2);
        deps.push(h.to_base32())
    }
    use std::collections::hash_map::Entry;
    let mut known = Vec::new();
    for hash in extra_known.iter() {
        if let Entry::Vacant(e) = hashes.entry(*hash) {
            e.insert(deps.len() + 2);
            known.push(hash.to_base32())
        }
    }
    let mut body = Vec::with_capacity(hunks.len());
    for (hunk, c) in hunks.iter().enumerate() {
        body.push(
            (Record {
                rec: c,
                hashes: &mut hashes,
                hash: &hash,
                changes: &changes,
                hunk,
            })
            .render(txn),
        );
    }
    let mut hashes_: Vec<_> = hashes.into_iter().collect();
    hashes_.sort_by(|a, b| a.1.cmp(&b.1));
    let hashes: Vec<_> = hashes_.into_iter().map(|x| x.0.to_base32()).collect();

    Ok(Patch {
        authors: super::get_authors(db, &mut header.authors).await.unwrap(),
        header,
        hash: hash.to_base32(),
        owner,
        repo,
        token: token.authenticity_token()?,
        deps: Deps {
            hashes,
            deps,
            known,
        },
        hunks: body,
        login,
    })
}

#[derive(Debug, Serialize, Clone)]
pub struct Metadata {
    basename: String,
    metadata: libpijul::pristine::InodeMetadata,
}

impl<'a> From<libpijul::changestore::FileMetadata<'a>> for Metadata {
    fn from(f: libpijul::changestore::FileMetadata<'a>) -> Metadata {
        Metadata {
            basename: f.basename.to_string(),
            metadata: f.metadata,
        }
    }
}

#[derive(Debug, Serialize, Clone)]
pub enum Contents {
    Add(String),
    Del(String),
}

type EdgeMap = libpijul::change::EdgeMap<usize>;
type NewVertex = libpijul::change::NewVertex<usize>;

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
struct Pos {
    hunk: usize,
    sub: usize,
    l1: usize,
}

pub type Position = libpijul::pristine::Position<usize>;

#[derive(Debug, Serialize, Clone)]
pub struct Inode(Position);

pub type Atom = libpijul::change::Atom<usize>;

#[derive(Debug, Serialize, Clone)]
pub enum Hunk {
    FileAdd {
        meta: Metadata,
        context: Vec<Position>,
        contents: Vec<Contents>,
    },
    FileMove {
        meta: Metadata,
        old: Vec<Metadata>,
        up: Vec<Position>,
        down: Vec<Position>,
        show_perms: bool,
    },
    FileDel {
        deleted_names: Vec<Metadata>,
        del: Atom,
        content_edges: Option<EdgeMap>,
        content: Vec<Contents>,
    },
    FileUndel {
        undel_names: Vec<Metadata>,
        undel: Atom,
        content_edges: Option<EdgeMap>,
        content: Vec<Contents>,
    },
    SolveNameConflict {
        old: Vec<Metadata>,
        name: Atom,
    },
    UnsolveNameConflict {
        old: Vec<Metadata>,
        name: Atom,
    },
    Edit {
        inode: Inode,
        path: String,
        line: usize,
        atom: Atom,
        contents: Vec<Contents>,
    },
    Replacement {
        inode: Inode,
        path: String,
        line: usize,
        ins: NewVertex,
        del: EdgeMap,
        ins_contents: Vec<Contents>,
        del_contents: Vec<Contents>,
    },
    SolveOrderConflict {
        inode: Inode,
        path: String,
        line: usize,
        atom: Atom,
        contents: Vec<Contents>,
    },
    UnsolveOrderConflict {
        inode: Inode,
        path: String,
        line: usize,
        atom: Atom,
        contents: Vec<Contents>,
    },
    ResurrectZombie {
        inode: Inode,
        path: String,
        line: usize,
        atom: Atom,
        contents: Vec<Contents>,
    },
    AddRoot {
        inode: Inode,
        atom: Atom,
        name: Atom,
    },
    DelRoot {
        inode: Inode,
        atom: Atom,
        name: Atom,
    },
}

impl<'a> Record<'a, libpijul::change::Local> {
    pub fn render<T: TxnT>(&'a mut self, txn: &T) -> Hunk {
        match self.rec {
            libpijul::change::Hunk::FileAdd {
                add_name: libpijul::change::Atom::NewVertex(ref n),
                ref contents,
                ..
            } => self.render_file_add(txn, n, contents),
            libpijul::change::Hunk::FileAdd { .. } => unreachable!(),
            libpijul::change::Hunk::FileMove {
                add: libpijul::change::Atom::NewVertex(ref add),
                del,
                ..
            } => self.render_file_move(txn, add, &del),
            libpijul::change::Hunk::FileMove { .. } => unreachable!(),
            libpijul::change::Hunk::FileDel { del, contents, .. } => {
                self.render_file_del(txn, del, contents)
            }
            libpijul::change::Hunk::FileUndel {
                undel, contents, ..
            } => self.render_file_undel(txn, undel, contents),
            libpijul::change::Hunk::SolveNameConflict { name, .. } => {
                self.render_solve_name_conflict(txn, name)
            }
            libpijul::change::Hunk::UnsolveNameConflict { name, .. } => {
                self.render_unsolve_name_conflict(txn, name)
            }
            libpijul::change::Hunk::Edit { change, local, .. } => {
                self.render_edit(txn, change, local)
            }
            libpijul::change::Hunk::Replacement {
                change: libpijul::change::Atom::EdgeMap(ref change),
                replacement: libpijul::change::Atom::NewVertex(ref replacement),
                local,
                ..
            } => self.render_replacement(txn, change, replacement, local),
            libpijul::change::Hunk::Replacement { .. } => unreachable!(),
            libpijul::change::Hunk::SolveOrderConflict { change, local } => {
                self.render_solve_order_conflict(txn, change, local)
            }
            libpijul::change::Hunk::UnsolveOrderConflict { change, local } => {
                self.render_unsolve_order_conflict(txn, change, local)
            }
            libpijul::change::Hunk::ResurrectZombies { change, local, .. } => {
                self.render_resurrect_zombies(txn, change, local)
            }
            libpijul::change::Hunk::AddRoot { inode, name } => {
                self.render_add_root(txn, inode, name)
            }
            libpijul::change::Hunk::DelRoot { inode, name } => {
                self.render_del_root(txn, inode, name)
            }
        }
    }
}

impl<'a, L> Record<'a, L> {
    fn inode<T: TxnT>(&mut self, txn: &T, id: &libpijul::change::Atom<Option<Hash>>) -> Inode {
        Inode(position(txn, self.hashes, &id.inode()))
    }

    fn inode_edgemap<T: TxnT>(
        &mut self,
        txn: &T,
        id: &libpijul::change::EdgeMap<Option<Hash>>,
    ) -> Inode {
        Inode(position(txn, self.hashes, &id.inode))
    }

    fn hash(&mut self, hash: &Option<Hash>) -> usize {
        if let Some(h) = hash {
            let len = self.hashes.len();
            *self.hashes.entry(h.clone()).or_insert(len + 1)
        } else {
            0
        }
    }

    fn map_pos(
        &mut self,
        pos: &libpijul::pristine::Position<Option<Hash>>,
    ) -> libpijul::pristine::Position<usize> {
        libpijul::pristine::Position {
            change: self.hash(&pos.change),
            pos: pos.pos,
        }
    }

    fn map_vertex(&mut self, pos: &libpijul::Vertex<Option<Hash>>) -> libpijul::Vertex<usize> {
        libpijul::Vertex {
            change: self.hash(&pos.change),
            start: pos.start,
            end: pos.end,
        }
    }

    fn atom(
        &mut self,
        atom: &libpijul::change::Atom<Option<Hash>>,
    ) -> libpijul::change::Atom<usize> {
        use libpijul::change::Atom;
        match atom {
            Atom::NewVertex(v) => Atom::NewVertex(self.new_vertex(v)),
            Atom::EdgeMap(e) => Atom::EdgeMap(self.edge_map(e)),
        }
    }

    fn edge_map(
        &mut self,
        e: &libpijul::change::EdgeMap<Option<Hash>>,
    ) -> libpijul::change::EdgeMap<usize> {
        libpijul::change::EdgeMap {
            edges: e
                .edges
                .iter()
                .map(|e| libpijul::change::NewEdge {
                    flag: e.flag,
                    previous: e.previous,
                    from: self.map_pos(&e.from),
                    to: self.map_vertex(&e.to),
                    introduced_by: self.hash(&e.introduced_by),
                })
                .collect(),
            inode: self.map_pos(&e.inode),
        }
    }

    fn new_vertex(
        &mut self,
        v: &libpijul::change::NewVertex<Option<Hash>>,
    ) -> libpijul::change::NewVertex<usize> {
        libpijul::change::NewVertex {
            up_context: v.up_context.iter().map(|x| self.map_pos(x)).collect(),
            down_context: v.down_context.iter().map(|x| self.map_pos(x)).collect(),
            start: v.start,
            end: v.end,
            flag: v.flag,
            inode: self.map_pos(&v.inode),
        }
    }
}

#[derive(Debug, Serialize, Clone)]
pub struct Patch {
    header: libpijul::change::ChangeHeader,
    hash: String,
    authors: Vec<super::Author>,
    deps: Deps,
    hunks: Vec<Hunk>,
    owner: String,
    repo: String,
    token: String,
    login: Option<String>,
}

#[derive(Debug, Serialize, Clone)]
struct Deps {
    hashes: Vec<String>,
    deps: Vec<String>,
    known: Vec<String>,
}

impl<'a> Record<'a, libpijul::change::Local> {
    fn render_contents<T: TxnT>(
        &self,
        txn: &T,
        v: &libpijul::change::Atom<Option<Hash>>,
        lang: Option<&str>,
        pos: Pos,
    ) -> Vec<Contents> {
        match v {
            libpijul::change::Atom::NewVertex(ref n) => {
                self.render_newvertex_contents(txn, n, lang, pos)
            }
            libpijul::change::Atom::EdgeMap(ref e) => {
                self.render_edgemap_contents(txn, e, lang, pos)
            }
        }
    }

    fn render_newvertex_contents<H, T: TxnT>(
        &self,
        _txn: &T,
        v: &libpijul::change::NewVertex<H>,
        _lang: Option<&str>,
        _pos: Pos,
    ) -> Vec<Contents> {
        let v_start: u64 = v.start.0.into();
        let v_end: u64 = v.end.0.into();
        let mut b = vec![0; v_end as usize - v_start as usize];
        self.changes
            .get_contents_ext(
                libpijul::pristine::Vertex {
                    change: Some(*self.hash),
                    start: v.start,
                    end: v.end,
                },
                &mut b,
            )
            .unwrap();
        let mut v = Vec::new();
        if let Ok(l) = std::str::from_utf8(&b) {
            for l in l.lines() {
                v.push(Contents::Add(l.to_string()))
            }
        }
        v
    }

    fn render_edgemap_contents<T: TxnT>(
        &self,
        _txn: &T,
        contents: &libpijul::change::EdgeMap<Option<Hash>>,
        _lang: Option<&str>,
        _pos: Pos,
    ) -> Vec<Contents> {
        let mut buf = Vec::new();
        let mut current_edge = None;
        let mut v = Vec::new();
        for e in contents.edges.iter() {
            if Some(e.to) == current_edge {
                continue;
            }
            current_edge = Some(e.to);
            buf.resize(e.to.end - e.to.start, 0);
            if self.changes.get_contents_ext(e.to, &mut buf).is_err() {
                continue;
            }
            let buf = if let Ok(buf) = std::str::from_utf8(&buf) {
                buf
            } else {
                continue;
            };
            v.push(if e.flag.contains(EdgeFlags::DELETED) {
                Contents::Del(buf.to_string())
            } else {
                Contents::Add(buf.to_string())
            });
        }
        v
    }

    fn render_file_add<T: TxnT>(
        &mut self,
        txn: &T,
        n: &libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
        contents: &Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
    ) -> Hunk {
        let n_start: u64 = n.start.0.into();
        let n_end: u64 = n.end.0.into();

        let mut buf = vec![0; (n_end - n_start) as usize];
        self.changes
            .get_contents_ext(
                libpijul::pristine::Vertex {
                    change: Some(*self.hash),
                    start: n.start,
                    end: n.end,
                },
                &mut buf,
            )
            .unwrap();
        let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();

        let context = render_context(txn, self.hashes, &n.up_context[..]);

        let contents = if let Some(ref contents) = contents {
            let lang = if let Some(p) = meta.basename.rfind('.') {
                Some(meta.basename.split_at(p + 1).1)
            } else {
                None
            };
            let pos = Pos {
                hunk: self.hunk,
                sub: 1,
                l1: 0,
            };
            self.render_contents(txn, contents, lang, pos)
        } else {
            Vec::new()
        };

        Hunk::FileAdd {
            meta,
            context,
            contents,
        }
    }

    fn render_file_move<T: TxnT>(
        &'a mut self,
        txn: &T,
        add: &'a libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
        del: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
    ) -> Hunk {
        let add_start: u64 = add.start.0.into();
        let add_end: u64 = add.end.0.into();
        let mut buf = vec![0; (add_end - add_start) as usize];
        self.changes
            .get_contents_ext(
                libpijul::pristine::Vertex {
                    change: Some(*self.hash),
                    start: add.start,
                    end: add.end,
                },
                &mut buf,
            )
            .unwrap();
        let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();
        let (old, show_perms) = if let libpijul::change::Atom::EdgeMap(ref del) = del {
            self.render_deleted_names(txn, del, Some(meta.metadata))
        } else {
            (Vec::new(), true)
        };

        Hunk::FileMove {
            meta,
            old,
            show_perms,
            up: render_context(txn, self.hashes, &add.up_context[..]),
            down: render_context(txn, self.hashes, &add.down_context[..]),
        }
    }

    fn render_file_del<T: TxnT>(
        &'a mut self,
        txn: &T,
        del: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        contents: &'a Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
    ) -> Hunk {
        let (deleted_names, _) = if let libpijul::change::Atom::EdgeMap(ref del) = del {
            self.render_deleted_names(txn, del, None)
        } else {
            (Vec::new(), true)
        };
        Hunk::FileDel {
            deleted_names,
            del: self.atom(del),
            content_edges: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
                Some(self.edge_map(contents))
            } else {
                None
            },
            content: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
                self.render_edgemap_contents(
                    txn,
                    contents,
                    None,
                    Pos {
                        hunk: self.hunk,
                        sub: 1,
                        l1: 0,
                    },
                )
            } else {
                Vec::new()
            },
        }
    }

    fn render_file_undel<T: TxnT>(
        &'a mut self,
        txn: &T,
        undel: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        contents: &'a Option<libpijul::change::Atom<Option<libpijul::pristine::Hash>>>,
    ) -> Hunk {
        let (undel_names, _) = if let libpijul::change::Atom::EdgeMap(ref undel) = undel {
            self.render_deleted_names(txn, undel, None)
        } else {
            (Vec::new(), true)
        };

        Hunk::FileUndel {
            undel_names,
            undel: self.atom(undel),
            content_edges: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
                Some(self.edge_map(contents))
            } else {
                None
            },
            content: if let Some(libpijul::change::Atom::EdgeMap(ref contents)) = contents {
                self.render_edgemap_contents(
                    txn,
                    contents,
                    None,
                    Pos {
                        hunk: self.hunk,
                        sub: 0,
                        l1: 0,
                    },
                )
            } else {
                Vec::new()
            },
        }
    }

    fn render_solve_name_conflict<T: TxnT>(
        &'a mut self,
        txn: &T,
        name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
    ) -> Hunk {
        let (old, _) = if let libpijul::change::Atom::EdgeMap(ref name) = name {
            self.render_deleted_names(txn, name, None)
        } else {
            (Vec::new(), true)
        };
        Hunk::SolveNameConflict {
            old,
            name: self.atom(name),
        }
    }

    fn render_unsolve_name_conflict<T: TxnT>(
        &'a mut self,
        txn: &T,
        name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
    ) -> Hunk {
        let (old, _) = if let libpijul::change::Atom::EdgeMap(ref name) = name {
            self.render_deleted_names(txn, name, None)
        } else {
            (Vec::new(), true)
        };
        Hunk::UnsolveNameConflict {
            old,
            name: self.atom(name),
        }
    }

    fn render_edit<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        local: &'a libpijul::change::Local,
    ) -> Hunk {
        Hunk::Edit {
            inode: self.inode(txn, change),
            path: local.path.to_string(),
            line: local.line,
            atom: self.atom(&change),
            contents: self.render_contents(
                txn,
                change,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 1,
                    l1: 0,
                },
            ),
        }
    }

    fn render_replacement<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::EdgeMap<Option<libpijul::pristine::Hash>>,
        replacement: &'a libpijul::change::NewVertex<Option<libpijul::pristine::Hash>>,
        local: &'a libpijul::change::Local,
    ) -> Hunk {
        Hunk::Replacement {
            inode: self.inode_edgemap(txn, change),
            path: local.path.to_string(),
            line: local.line,
            del: self.edge_map(change),
            del_contents: self.render_edgemap_contents(
                txn,
                change,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 0,
                    l1: 0,
                },
            ),
            ins: self.new_vertex(replacement),
            ins_contents: self.render_newvertex_contents(
                txn,
                replacement,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 1,
                    l1: 0,
                },
            ),
        }
    }

    fn render_solve_order_conflict<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        local: &'a libpijul::change::Local,
    ) -> Hunk {
        Hunk::SolveOrderConflict {
            inode: self.inode(txn, change),
            path: local.path.to_string(),
            line: local.line,
            atom: self.atom(change),
            contents: self.render_contents(
                txn,
                change,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 0,
                    l1: 0,
                },
            ),
        }
    }

    fn render_unsolve_order_conflict<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        local: &'a libpijul::change::Local,
    ) -> Hunk {
        Hunk::UnsolveOrderConflict {
            inode: self.inode(txn, change),
            path: local.path.to_string(),
            line: local.line,
            atom: self.atom(change),
            contents: self.render_contents(
                txn,
                change,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 0,
                    l1: 0,
                },
            ),
        }
    }

    fn render_resurrect_zombies<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        local: &'a libpijul::change::Local,
    ) -> Hunk {
        Hunk::ResurrectZombie {
            inode: self.inode(txn, change),
            path: local.path.to_string(),
            line: local.line,
            atom: self.atom(change),
            contents: self.render_contents(
                txn,
                change,
                None,
                Pos {
                    hunk: self.hunk,
                    sub: 0,
                    l1: 0,
                },
            ),
        }
    }

    fn render_add_root<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
    ) -> Hunk {
        Hunk::AddRoot {
            inode: self.inode(txn, change),
            atom: self.atom(change),
            name: self.atom(name),
        }
    }

    fn render_del_root<T: TxnT>(
        &'a mut self,
        txn: &T,
        change: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
        name: &'a libpijul::change::Atom<Option<libpijul::pristine::Hash>>,
    ) -> Hunk {
        Hunk::DelRoot {
            inode: self.inode(txn, change),
            atom: self.atom(change),
            name: self.atom(name),
        }
    }

    fn render_deleted_names<T: TxnT>(
        &self,
        _txn: &T,
        del: &libpijul::change::EdgeMap<Option<Hash>>,
        perm: Option<libpijul::pristine::InodeMetadata>,
    ) -> (Vec<Metadata>, bool) {
        let mut buf = Vec::new();
        let mut show_perms = false;
        let mut result = Vec::new();
        for d in del.edges.iter() {
            buf.resize(d.to.end - d.to.start, 0);
            if self.changes.get_contents_ext(d.to, &mut buf).is_err() {
                continue;
            }
            if !buf.is_empty() {
                let meta: Metadata = libpijul::changestore::FileMetadata::read(&buf).into();
                if let Some(perm) = perm {
                    if meta.metadata != perm {
                        show_perms = true;
                    }
                }
                result.push(meta);
            }
        }
        (result, show_perms)
    }
}

fn position<T: TxnT>(
    _txn: &T,
    hashes: &mut HashMap<Hash, usize>,
    c: &libpijul::pristine::Position<Option<Hash>>,
) -> Position {
    let n = if let Some(ref h) = c.change {
        let len = hashes.len();
        *hashes.entry(h.clone()).or_insert(len + 1)
    } else {
        0
    };
    Position {
        change: n,
        pos: c.pos,
    }
}

fn render_context<T: TxnT>(
    txn: &T,
    hashes: &mut HashMap<Hash, usize>,
    context: &[libpijul::pristine::Position<Option<Hash>>],
) -> Vec<Position> {
    context.iter().map(|c| position(txn, hashes, c)).collect()
}