/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::str;
use std::sync::Arc;

use anyhow::Result;
use cpython::*;
use cpython_ext::convert::ImplInto;
use cpython_ext::pyset_add;
use cpython_ext::pyset_new;
use cpython_ext::PyNone;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use manifest::DiffType;
use manifest::File;
use manifest::FileMetadata;
use manifest::FileType;
use manifest::FsNodeMetadata;
use manifest::Manifest;
use manifest_tree::TreeManifest;
use manifest_tree::TreeStore;
use parking_lot::RwLock;
use pathmatcher::AlwaysMatcher;
use pathmatcher::ExactMatcher;
use pathmatcher::Matcher;
use pathmatcher::TreeMatcher;
use pypathmatcher::extract_matcher;
use pypathmatcher::extract_option_matcher;
use types::Node;
use types::RepoPathBuf;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "manifest"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<treemanifest>(py)?;
    m.add(
        py,
        "subdirdiff",
        py_fn!(
            py,
            subdir_diff(
                store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
                path: PyPathBuf,
                binnode: &PyBytes,
                other_binnodes: &PyList,
                depth: i32
            )
        ),
    )?;
    m.add(
        py,
        "prefetch",
        py_fn!(
            py,
            prefetch(
                store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
                node: Vec<PyBytes>,
                paths: Option<Vec<PyPathBuf>> = None,
            )
        ),
    )?;
    Ok(m)
}

py_class!(pub class treemanifest |py| {
    data underlying: Arc<RwLock<TreeManifest>>;
    data pending_delete: RefCell<HashSet<RepoPathBuf>>;

    def __new__(
        _cls,
        store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
        node: Option<&PyBytes> = None
    ) -> PyResult<treemanifest> {
        let manifest_store = store.into();
        let underlying = match node {
            None => TreeManifest::ephemeral(manifest_store),
            Some(value) => TreeManifest::durable(manifest_store, pybytes_to_node(py, value)?),
        };
        treemanifest::create_instance(py, Arc::new(RwLock::new(underlying)), RefCell::new(HashSet::new()))
    }

    // Returns a new instance of treemanifest that contains the same data as the base.
    def copy(&self) -> PyResult<treemanifest> {
        treemanifest::create_instance(
            py,
            Arc::new(RwLock::new(self.underlying(py).read().clone())),
            self.pending_delete(py).clone()
        )
    }

    // Returns (node, flag) for a given `path` in the manifest.
    // When the `path` does not exist, it return a KeyError.
    def find(&self, path: PyPathBuf) -> PyResult<(PyBytes, String)> {
        // Some code... probably sparse profile related is asking find to grab
        // random invalid paths.
        let repo_path = match path.to_repo_path().map_pyerr(py) {
            Ok(value) => value,
            Err(_) => {
                let msg = format!(
                    "cannot find file '{}' in manifest",
                    path,
                );
                return Err(PyErr::new::<exc::KeyError, _>(py, msg))
            }
        };
        let tree = self.underlying(py).read();
        match tree.get_file(&repo_path).map_pyerr(py)? {
            None => {
                let msg = format!("cannot find file '{}' in manifest", repo_path);
                Err(PyErr::new::<exc::KeyError, _>(py, msg))
            }
            Some(file_metadata) => file_metadata_to_py_tuple(py, &file_metadata),
        }
    }

    def get(&self, path: PyPathBuf, default: Option<PyBytes> = None) -> PyResult<Option<PyBytes>> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        let result = match tree.get_file(&repo_path).map_pyerr(py)? {
            None => None,
            Some(file_metadata) => Some(node_to_pybytes(py, file_metadata.hgid)),
        };
        Ok(result.or(default))
    }

    def flags(&self, path: PyPathBuf, default: Option<PyString> = None) -> PyResult<PyString> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        let result = match tree.get_file(&repo_path).map_pyerr(py)? {
            None => None,
            Some(file_metadata) => Some(file_type_to_pystring(py, file_metadata.file_type)),
        };
        Ok(result.or(default).unwrap_or_else(|| PyString::new(py, "")))
    }

    def hasdir(&self, path: PyPathBuf) -> PyResult<bool> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        let result = match tree.get(&repo_path).map_pyerr(py)? {
            Some(FsNodeMetadata::Directory(_)) => true,
            _ => false
        };
        Ok(result)
    }

    // Returns a list<path> for all files that match the predicate passed to the function.
    def walk(&self, pymatcher: PyObject) -> PyResult<Vec<PyPathBuf>> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let matcher = extract_matcher(py, pymatcher)?;
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(matcher).collect()
        });
        for entry in files.into_iter() {
            let file = entry.map_pyerr(py)?;
            result.push(file.path.into());
        }
        Ok(result)
    }

    /// Returns [(path, id)] for directories.
    def walkdirs(&self, pymatcher: PyObject) -> PyResult<Vec<(PyPathBuf, Option<PyBytes>)>> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let matcher = extract_matcher(py, pymatcher)?;
        let dirs = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.dirs(matcher).collect()
        });
        for entry in dirs.into_iter() {
            let dir = entry.map_pyerr(py)?;
            result.push((
                dir.path.into(),
                dir.hgid.map(|id| PyBytes::new(py, id.as_ref())),
            ));
        }
        Ok(result)
    }

    def listdir(&self, path: PyPathBuf) -> PyResult<Vec<PyPathBuf>> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        let result = match tree.list(&repo_path).map_pyerr(py)? {
            manifest::List::NotFound | manifest::List::File => vec![],
            manifest::List::Directory(components) =>
                components.into_iter().map(|(component, _)|
                    component.into()
                ).collect()
        };
        Ok(result)
    }

    def text(&self, matcher: Option<PyObject> = None) -> PyResult<PyBytes> {
        let mut lines = Vec::new();
        let tree = self.underlying(py);
        let matcher = extract_option_matcher(py, matcher)?;
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(matcher).collect()
        });
        for entry in files.into_iter() {
            let file = entry.map_pyerr(py)?;
            lines.push(format!(
                "{}\0{}{}\n",
                file.path,
                file.meta.hgid,
                file_type_to_str(file.meta.file_type)
            ));
        }
        lines.sort();
        // TODO: Optimize this so that the string does not get copied.
        Ok(PyBytes::new(py, lines.concat().as_bytes()))
    }

    def set(&self, path: PyPathBuf, binnode: &PyBytes, flag: &PyString) -> PyResult<PyNone> {
        // TODO: can the node and flag that are passed in be None?
        let tree = self.underlying(py);
        let repo_path_buf = path.to_repo_path_buf().map_pyerr(py)?;
        let node = pybytes_to_node(py, binnode)?;
        let file_type = pystring_to_file_type(py, flag)?;
        let file_metadata = FileMetadata::new(node, file_type);
        let to_delete = py.allow_threads(move || -> Result<HashSet<RepoPathBuf>> {
            let mut tree = tree.write();
            insert(&mut tree, repo_path_buf, file_metadata)
        }).map_pyerr(py)?;
        let mut pending_delete = self.pending_delete(py).borrow_mut();
        for path in to_delete.into_iter() {
            pending_delete.remove(&path);
        }
        Ok(PyNone)
    }

    def setflag(&self, path: PyPathBuf, flag: &PyString) -> PyResult<PyObject> {
        let tree = self.underlying(py);
        let repo_path_buf = path.to_repo_path_buf().map_pyerr(py)?;
        let file_type = pystring_to_file_type(py, flag)?;
        let file_metadata = match tree.read().get_file(&repo_path_buf).map_pyerr(py)? {
            None => {
                let msg = "cannot setflag on file that is not in manifest";
                return Err(PyErr::new::<exc::KeyError, _>(py, msg));
            }
            Some(mut file_metadata) => {
                file_metadata.file_type = file_type;
                file_metadata
            }
        };
        let to_delete = py.allow_threads(move || -> Result<HashSet<RepoPathBuf>> {
            let mut tree = tree.write();
            insert(&mut tree, repo_path_buf, file_metadata)
        }).map_pyerr(py)?;
        let mut pending_delete = self.pending_delete(py).borrow_mut();
        for path in to_delete.into_iter() {
            pending_delete.remove(&path);
        }
        Ok(Python::None(py))
    }

    def diff(&self, other: &treemanifest, matcher: Option<PyObject> = None) -> PyResult<PyDict> {
        fn convert_side_diff(
            py: Python,
            entry: Option<FileMetadata>
        ) -> (Option<PyBytes>, PyString) {
            match entry {
                None => (None, PyString::new(py, "")),
                Some(file_metadata) => (
                    Some(node_to_pybytes(py, file_metadata.hgid)),
                    file_type_to_pystring(py, file_metadata.file_type)
                )
            }
        }

        let result = PyDict::new(py);
        let matcher: Arc<dyn Matcher + Sync + Send> = extract_option_matcher(py, matcher)?;
        let this_tree = self.underlying(py);
        let other_tree = other.underlying(py);

        let results: Vec<_> = py.allow_threads(move || -> Result<_> {
            manifest_tree::Diff::new(&this_tree.read(), &other_tree.read(), &matcher)?.collect()
        }).map_pyerr(py)?;
        for entry in results {
            let path = PyPathBuf::from(entry.path);
            let diff_left = convert_side_diff(py, entry.diff_type.left());
            let diff_right = convert_side_diff(py, entry.diff_type.right());
            result.set_item(py, path, (diff_left, diff_right))?;
        }
        Ok(result)
    }

    /// Find modified directories. Return [(path: str, exist_left: bool, exist_right: bool)].
    /// Modified directories are added, removed, or metadata changed (direct file or subdir added,
    /// removed, similar to when OS updates mtime of a directory). File content change does not
    /// modify its parent directory.
    def modifieddirs(&self, other: &treemanifest, matcher: Option<PyObject> = None) -> PyResult<Vec<(PyPathBuf, bool, bool)>> {
        let matcher: Arc<dyn Matcher + Sync + Send> = extract_option_matcher(py, matcher)?;
        let this_tree = self.underlying(py);
        let other_tree = other.underlying(py);
        let results = py.allow_threads(move || -> Result<_> {
            let this = this_tree.read();
            let other = other_tree.read();
            let modified_dirs = this.modified_dirs(&other, &matcher);
            modified_dirs.and_then(|v| v.collect::<Result<Vec<_>>>())
        }).map_pyerr(py)?;
        let results = results.into_iter().map(|i| (i.path.into(), i.left, i.right)).collect();
        Ok(results)
    }

    def filesnotin(
        &self,
        other: &treemanifest,
        matcher: Option<PyObject> = None
    ) -> PyResult<PyObject> {
        let mut result = pyset_new(py)?;
        let this_tree = self.underlying(py);
        let other_tree = other.underlying(py);
        let matcher: Arc<dyn Matcher + Sync + Send> = extract_option_matcher(py, matcher)?;

        let results: Vec<_> = py.allow_threads(move || -> Result<_> {
            manifest_tree::Diff::new(&this_tree.read(), &other_tree.read(), &matcher)?.collect()
        }).map_pyerr(py)?;
        for entry in results {
            match entry.diff_type {
                DiffType::LeftOnly(_) => {
                    pyset_add(py, &mut result, PyPathBuf::from(entry.path))?;
                }
                DiffType::RightOnly(_) | DiffType::Changed(_, _) => (),
            }
        }
        Ok(result)
    }

    def matches(&self, pymatcher: PyObject) -> PyResult<PyObject> {
        let flatmanifest = self.text(py, Some(pymatcher))?;
        let manifestmod = py.import("edenscm.manifest")?;
        let manifestdict = manifestmod.get(py, "manifestdict")?;
        manifestdict.call(py, (flatmanifest,), None)
    }

    def __setitem__(&self, path: PyPathBuf, binnode: &PyBytes) -> PyResult<()> {
        let tree = self.underlying(py);
        let repo_path_buf = path.to_repo_path_buf().map_pyerr(py)?;
        let node = pybytes_to_node(py, binnode)?;
        let file_metadata = match tree.read().get_file(&repo_path_buf).map_pyerr(py)? {
            None => FileMetadata::new(node, FileType::Regular),
            Some(mut file_metadata) => {
                file_metadata.hgid = node;
                file_metadata
            }
        };
        let to_delete = py.allow_threads(move || -> Result<HashSet<RepoPathBuf>> {
            let mut tree = tree.write();
            insert(&mut tree, repo_path_buf, file_metadata)
        }).map_pyerr(py)?;
        let mut pending_delete = self.pending_delete(py).borrow_mut();
        for path in to_delete.into_iter() {
            pending_delete.remove(&path);
        }
        Ok(())
    }

    def __delitem__(&self, path: PyPathBuf) -> PyResult<()> {
        let mut tree = self.underlying(py).write();
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        tree.remove(&repo_path).map_pyerr(py)?;
        let mut pending_delete = self.pending_delete(py).borrow_mut();
        pending_delete.remove(repo_path);
        Ok(())
    }

    def __getitem__(&self, path: PyPathBuf) -> PyResult<PyBytes> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        match tree.get_file(&repo_path).map_pyerr(py)? {
            Some(file_metadata) => Ok(node_to_pybytes(py, file_metadata.hgid)),
            None => Err(PyErr::new::<exc::KeyError, _>(py, format!("file {} not found", path))),
        }
    }

    def keys(&self) -> PyResult<Vec<PyPathBuf>> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(AlwaysMatcher::new()).collect()
        });
        for entry in files {
            let file = entry.map_pyerr(py)?;
            result.push(file.path.into());
        }
        Ok(result)
    }

    // iterator stuff

    def __contains__(&self, path: PyPathBuf) -> PyResult<bool> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        let tree = self.underlying(py).read();
        match tree.get_file(&repo_path).map_pyerr(py)? {
            Some(_) => Ok(true),
            None => Ok(false),
        }
    }

    def __iter__(&self) -> PyResult<PyObject> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(AlwaysMatcher::new()).collect()
        });
        for entry in files {
            let file = entry.map_pyerr(py)?;
            result.push(PyPathBuf::from(file.path));
        }
        vec_to_iter(py, result)
    }

    def iteritems(&self) -> PyResult<PyObject> {
        self.items(py)
    }

    def items(&self) -> PyResult<PyObject> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(AlwaysMatcher::new()).collect()
        });
        for entry in files {
            let file = entry.map_pyerr(py)?;
            let tuple = (
                PyPathBuf::from(file.path),
                node_to_pybytes(py, file.meta.hgid),
            );
            result.push(tuple);
        }
        vec_to_iter(py, result)
    }

    def iterkeys(&self) -> PyResult<PyObject> {
        let mut result = Vec::new();
        let tree = self.underlying(py);
        let files = py.allow_threads(move || -> Vec<_> {
            let tree = tree.read();
            tree.files(AlwaysMatcher::new()).collect()
        });
        for entry in files {
            let file = entry.map_pyerr(py)?;
            result.push(PyPathBuf::from(file.path));
        }
        vec_to_iter(py, result)
    }

    def finalize(
        &self,
        p1tree: Option<&treemanifest> = None,
        p2tree: Option<&treemanifest> = None
    ) -> PyResult<Vec<PyTuple>> {
        let pending_delete = self.pending_delete(py).borrow();
        if !pending_delete.is_empty() {
            return Err(PyErr::new::<exc::RuntimeError, _>(
                py,
                format!(
                    "Error finalizing manifest. Invalid state: \
                    expecting deletion commands for the following paths: {:?}",
                    pending_delete
                )
            ));
        }
        let mut result = Vec::new();
        let mut tree = self.underlying(py).write();
        let mut parents = vec!();
        if let Some(m1) = p1tree {
            parents.push(m1.underlying(py).read());
        }
        if let Some(m2) = p2tree {
            parents.push(m2.underlying(py).read());
        }
        let entries = tree.finalize(
            parents.iter().map(|x| x.deref()).collect()
        ).map_pyerr(py)?;
        for entry in entries {
            let (repo_path, node, raw, p1node, p2node) = entry;
            let tuple = PyTuple::new(
                py,
                &[
                    PyPathBuf::from(repo_path).to_py_object(py).into_object(),
                    node_to_pybytes(py, node).into_object(),
                    PyBytes::new(py, &raw).into_object(),
                    PyBytes::new(py, &[]).into_object(),
                    node_to_pybytes(py, p1node).into_object(),
                    node_to_pybytes(py, p2node).into_object(),
                ],
            );
            result.push(tuple);
        }
        Ok(result)
    }

    /// flush() -> node.
    /// Write pending trees to store. Return root node.
    /// Only works for git store. Use finalize() for hg store instead.
    def flush(&self) -> PyResult<PyBytes> {
        let mut tree = self.underlying(py).write();
        let hgid = tree.flush().map_pyerr(py)?;
        Ok(PyBytes::new(py, hgid.as_ref()))
    }

});

impl treemanifest {
    pub fn get_underlying(&self, py: Python) -> Arc<RwLock<TreeManifest>> {
        self.underlying(py).clone()
    }
}

pub fn subdir_diff(
    py: Python,
    store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
    path: PyPathBuf,
    binnode: &PyBytes,
    other_binnodes: &PyList,
    depth: i32,
) -> PyResult<PyObject> {
    let manifest_store = store.into();
    let mut others = vec![];
    for pybytes in other_binnodes.iter(py) {
        others.push(pybytes_to_node(py, &pybytes.extract(py)?)?);
    }
    let diff = manifest_tree::compat_subtree_diff(
        manifest_store,
        path.to_repo_path().map_pyerr(py)?,
        pybytes_to_node(py, binnode)?,
        others,
        depth,
    )
    .map_pyerr(py)?;
    let mut result = vec![];
    for (path, node, others, bytes) in diff {
        use types::HgId;
        let p1 = others.get(0).unwrap_or(HgId::null_id()).clone();
        let p2 = others.get(1).unwrap_or(HgId::null_id()).clone();
        let tuple = PyTuple::new(
            py,
            &[
                PyPathBuf::from(path).to_py_object(py).into_object(),
                node_to_pybytes(py, node).into_object(),
                PyBytes::new(py, &bytes).into_object(),
                node_to_pybytes(py, p1).into_object(),
                node_to_pybytes(py, p2).into_object(),
            ],
        );
        result.push(tuple);
    }
    vec_to_iter(py, result)
}

pub fn prefetch(
    py: Python,
    store: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
    nodes: Vec<PyBytes>,
    paths: Option<Vec<PyPathBuf>>,
) -> PyResult<PyNone> {
    let nodes: Vec<Node> = nodes
        .iter()
        .map(|n| pybytes_to_node(py, n))
        .collect::<PyResult<_>>()?;
    let matcher: Arc<dyn Matcher + Send + Sync> = match paths {
        Some(paths) => Arc::new(ExactMatcher::new(
            paths
                .iter()
                .map(|p| p.to_repo_path())
                .collect::<Result<Vec<_>>>()
                .map_pyerr(py)?
                .into_iter(),
            false,
        )),
        None => Arc::new(AlwaysMatcher::new()),
    };

    py.allow_threads(|| manifest_tree::prefetch(store.into(), &nodes, matcher))
        .map_pyerr(py)?;
    Ok(PyNone)
}

fn insert(
    tree: &mut TreeManifest,
    path: RepoPathBuf,
    file_metadata: FileMetadata,
) -> Result<HashSet<RepoPathBuf>> {
    let mut to_delete = HashSet::new();
    let insert_error = match tree.insert(path, file_metadata) {
        Ok(()) => return Ok(to_delete),
        Err(error) => match error.downcast::<manifest_tree::InsertError>() {
            Ok(insert_error) => insert_error,
            Err(err) => return Err(err),
        },
    };
    let path = insert_error.path;
    match insert_error.source {
        manifest_tree::InsertErrorCause::ParentFileExists(file_path) => {
            tree.remove(&file_path)?;
            to_delete.insert(file_path);
        }
        manifest_tree::InsertErrorCause::DirectoryExistsForPath => {
            let files: Vec<File> = tree
                .files(TreeMatcher::from_rules(
                    [format!("{}/**", path)].iter(),
                    true, // case_sensitive=true
                )?)
                .collect::<Result<_>>()?;
            for file in files {
                tree.remove(&file.path)?;
                to_delete.insert(file.path);
            }
        }
    }
    tree.insert(path, file_metadata)?;
    Ok(to_delete)
}

fn vec_to_iter<T: ToPyObject>(py: Python, items: Vec<T>) -> PyResult<PyObject> {
    let list: PyList = items.into_py_object(py);
    list.into_object().call_method(py, "__iter__", NoArgs, None)
}

fn file_metadata_to_py_tuple(
    py: Python,
    file_metadata: &FileMetadata,
) -> PyResult<(PyBytes, String)> {
    let node = PyBytes::new(py, file_metadata.hgid.as_ref());
    let flag = file_type_to_str(file_metadata.file_type).to_string();
    Ok((node, flag))
}

fn pybytes_to_node(py: Python, pybytes: &PyBytes) -> PyResult<Node> {
    Node::from_slice(pybytes.data(py)).map_pyerr(py)
}

fn node_to_pybytes(py: Python, node: Node) -> PyBytes {
    PyBytes::new(py, node.as_ref())
}

fn pystring_to_file_type(py: Python, pystring: &PyString) -> PyResult<FileType> {
    match pystring.to_string_lossy(py).borrow() {
        "x" => Ok(FileType::Executable),
        "l" => Ok(FileType::Symlink),
        "m" => Ok(FileType::GitSubmodule),
        "" => Ok(FileType::Regular),
        _ => Err(PyErr::new::<exc::RuntimeError, _>(py, "invalid file flags")),
    }
}

fn file_type_to_pystring(py: Python, file_type: FileType) -> PyString {
    PyString::new(py, file_type_to_str(file_type))
}

fn file_type_to_str(file_type: FileType) -> &'static str {
    match file_type {
        FileType::Regular => "",
        FileType::Executable => "x",
        FileType::Symlink => "l",
        FileType::GitSubmodule => "m",
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::path::Path;
use std::sync::Arc;
use std::time::SystemTime;

use ::metalog::constants::*;
use ::metalog::CommitOptions;
use ::metalog::Id20;
use ::metalog::MetaLog;
use ::metalog::Repair;
use cpython::*;
use cpython_ext::Bytes;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use parking_lot::RwLock;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "metalog"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<metalog>(py)?;
    m.add(py, "tracked", py_fn!(py, tracked()))?;
    Ok(m)
}

py_class!(pub class metalog |py| {
    data log: Arc<RwLock<MetaLog>>;
    data fspath: String;

    def __new__(_cls, path: String, root: Option<Bytes> = None) -> PyResult<Self> {
        let root = root.and_then(|s| Id20::from_slice(s.as_ref()).ok());
        let log = MetaLog::open(&path, root).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(RwLock::new(log)), path)
    }

    /// Initializes a new metalog at the given path. It obtains the root id from
    /// certain environment variables if they are set.
    @staticmethod
    def openfromenv(path: String) -> PyResult<Self> {
        let log = MetaLog::open_from_env(Path::new(path.as_str())).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(RwLock::new(log)), path)
    }

    /// List all roots.
    def roots(&self) -> PyResult<Vec<Bytes>> {
        let path = self.fspath(py);
        let root_ids = MetaLog::list_roots(&path).map_pyerr(py)?;
        Ok(root_ids.into_iter().map(|id| Bytes::from(id.as_ref().to_vec())).collect())
    }

    /// Check out a "root".
    def checkout(&self, root: Bytes) -> PyResult<Self> {
        let root = Id20::from_slice(root.as_ref()).map_pyerr(py)?;
        let log = self.log(py).read().checkout(root).map_pyerr(py)?;
        let path = self.fspath(py);
        Self::create_instance(py, Arc::new(RwLock::new(log)), path.clone())
    }

    /// Compact the metalog at the given path by only keeping the last entry.
    /// Reduce filesystem usage.
    @staticmethod
    def compact(path: String) -> PyResult<PyNone> {
        MetaLog::compact(path).map_pyerr(py)?;
        Ok(PyNone)
    }

    @staticmethod
    def listroots(path: String) -> PyResult<Vec<Bytes>> {
        let root_ids = MetaLog::list_roots(&path).map_pyerr(py)?;
        Ok(root_ids.into_iter().map(|id| Bytes::from(id.as_ref().to_vec())).collect())
    }

    /// Lookup an item by key. Return None if the key does not exist.
    def get(&self, key: &str) -> PyResult<Option<PyBytes>> {
        let log = self.log(py).read();
        let data = log.get(key).map_pyerr(py)?;
        Ok(data.map(|data| PyBytes::new(py, &data)))
    }

    /// Set an item. Return the Id of value.
    def set(&self, key: &str, value: Bytes) -> PyResult<Bytes> {
        let mut log = self.log(py).write();
        let id = log.set(key, value.as_ref()).map_pyerr(py)?;
        Ok(Bytes::from(id.as_ref().to_vec()))
    }

    /// Remove an item. Does not raise if the key does not exist.
    def remove(&self, key: &str) -> PyResult<PyNone> {
        let mut log = self.log(py).write();
        log.remove(key).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Get all keys.
    def keys(&self) -> PyResult<Vec<Bytes>> {
        let keys = self.log(py).read()
            .keys().iter().map(|s| Bytes::from(s.as_bytes().to_vec())).collect();
        Ok(keys)
    }

    /// Write pending data to disk. Raise if race condition is detected.
    def commit(&self, message: &str, time: Option<u64> = None, pending: bool = false) -> PyResult<Bytes> {
        let mut opts = CommitOptions::default();
        opts.detached = pending;
        opts.timestamp = time.unwrap_or_else(|| {
            SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)
                .map(|d| d.as_secs()).unwrap_or(0)
        });
        opts.message = message;
        let id = self.log(py).write().commit(opts).map_pyerr(py)?;
        Ok(Bytes::from(id.as_ref().to_vec()))
    }

    /// Export to a git respository
    def exportgit(&self, path: String) -> PyResult<PyNone> {
        let log = self.log(py).read();
        log.export_git(Path::new(&path)).map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Test if there are uncommitted changes.
    def isdirty(&self) -> PyResult<bool> {
        Ok(self.log(py).read().is_dirty())
    }

    /// Why the change was made.
    def message(&self) -> PyResult<Str> {
        Ok(Str::from(self.log(py).read().message().to_string()))
    }

    /// When the change was made.
    def timestamp(&self) -> PyResult<u64> {
        Ok(self.log(py).read().timestamp())
    }

    /// The root id.
    def root(&self) -> PyResult<PyBytes> {
        Ok(PyBytes::new(py, self.log(py).read().root_id().as_ref()))
    }

    /// Path on the filesystem.
    def path(&self) -> PyResult<PyPathBuf> {
        Ok(PyPath::from_str(self.fspath(py)).to_owned())
    }

    def __getitem__(&self, key: String) -> PyResult<Option<PyBytes>> {
        self.get(py, &key)
    }

    def __setitem__(&self, key: String, value: Bytes) -> PyResult<()> {
        self.set(py, &key, value)?;
        Ok(())
    }

    def __delitem__(&self, key: String) -> PyResult<()> {
        self.remove(py, &key)?;
        Ok(())
    }

    @staticmethod
    def repair(path: &str) -> PyResult<Str> {
        py.allow_threads(|| MetaLog::repair(path)).map_pyerr(py).map(Into::into)
    }
});

impl self::metalog {
    pub fn metalog_rwlock(&self, py: Python) -> Arc<RwLock<MetaLog>> {
        self.log(py).clone()
    }
}

fn tracked(_py: Python) -> PyResult<Vec<String>> {
    Ok(METALOG_TRACKED
        .into_iter()
        .map(|key| key.to_string())
        .collect())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;
use std::io::Cursor;

use ::mutationstore::DagFlags;
use ::mutationstore::MutationStore;
use ::mutationstore::Repair;
use anyhow::Error;
use async_runtime::block_on;
use byteorder::ReadBytesExt;
use byteorder::WriteBytesExt;
use cpython::*;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use pydag::dagalgo::dagalgo;
use pydag::Names;
use thiserror::Error;
use types::mutation::MutationEntry;
use types::node::Node;
use vlqencoding::VLQDecode;
use vlqencoding::VLQEncode;

/// Supported format of bundle version.
/// Format 1 is:
///  * Single byte version: 0x01
///  * VLQ-encoded count of entries: ``count``
///  * A sequence of ``count`` entries encoded using ``MutationEntry::serialize``
const BUNDLE_FORMAT_VERSION: u8 = 1u8;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "mutationstore"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<mutationentry>(py)?;
    m.add_class::<mutationstore>(py)?;
    m.add(
        py,
        "bundle",
        py_fn!(py, bundle(entries: Vec<mutationentry>)),
    )?;
    m.add(py, "unbundle", py_fn!(py, unbundle(data: PyBytes)))?;
    Ok(m)
}

fn bundle(py: Python, entries: Vec<mutationentry>) -> PyResult<PyBytes> {
    // Pre-allocate capacity for all the entries, plus one for the header and extra breathing room.
    let mut buf = Vec::with_capacity((entries.len() + 1) * types::mutation::DEFAULT_ENTRY_SIZE);
    buf.write_u8(BUNDLE_FORMAT_VERSION).map_pyerr(py)?;
    buf.write_vlq(entries.len()).map_pyerr(py)?;
    for entry in entries {
        let entry = entry.entry(py);
        entry.serialize(&mut buf).map_pyerr(py)?;
    }
    Ok(PyBytes::new(py, &buf))
}

fn unbundle(py: Python, data: PyBytes) -> PyResult<Vec<mutationentry>> {
    let mut cursor = Cursor::new(data.data(py));
    let version = cursor.read_u8().map_pyerr(py)?;
    if version != BUNDLE_FORMAT_VERSION {
        return Err(PyErr::new::<exc::IOError, _>(
            py,
            format!("Unsupported mutation format: {}", version),
        ));
    }
    let count = cursor.read_vlq().map_pyerr(py)?;
    let mut entries = Vec::with_capacity(count);
    for _ in 0..count {
        let entry = MutationEntry::deserialize(&mut cursor).map_pyerr(py)?;
        entries.push(mutationentry::create_instance(py, entry)?);
    }
    Ok(entries)
}

#[derive(Error, Debug)]
enum InvalidNode {
    #[error("Invalid successor node: {0}")]
    Successor(Error),
    #[error("Invalid predecessor node: {0}")]
    Predecessor(Error),
    #[error("Invalid split node: {0}")]
    Split(Error),
}

py_class!(class mutationentry |py| {
    data entry: MutationEntry;

    def __new__(
        _cls,
        succ: &PyBytes,
        preds: Option<Vec<PyBytes>>,
        split: Option<Vec<PyBytes>>,
        op: &PyString,
        user: &PyString,
        time: i64,
        tz: i32,
        extra: Option<Vec<(PyBytes, PyBytes)>>
    ) -> PyResult<mutationentry> {
        let succ = Node::from_slice(succ.data(py))
            .map_err(Error::from)
            .map_err(InvalidNode::Successor)
            .map_pyerr(py)?;
        let preds = {
            let mut nodes = Vec::new();
            if let Some(preds) = preds {
                for p in preds {
                    nodes.push(Node::from_slice(p.data(py))
                        .map_err(Error::from)
                        .map_err(InvalidNode::Predecessor)
                        .map_pyerr(py)?);
                }
            }
            nodes
        };
        let split = {
            let mut nodes = Vec::new();
            if let Some(split) = split {
                for s in split {
                    nodes.push(Node::from_slice(s.data(py))
                        .map_err(Error::from)
                        .map_err(InvalidNode::Split)
                        .map_pyerr(py)?);
                }
            }
            nodes
        };
        let op = op.to_string(py)?.into();
        let user = user.to_string(py)?.into();
        let extra = {
            let mut items = Vec::new();
            if let Some(extra) = extra {
                for (k, v) in extra {
                    items.push((Box::from(k.data(py)), Box::from(v.data(py))));
                }
            }
            items
        };
        mutationentry::create_instance(py, MutationEntry {
            succ, preds, split, op, user, time, tz, extra
        })
    }

    def succ(&self) -> PyResult<PyBytes> {
        Ok(PyBytes::new(py, self.entry(py).succ.as_ref()))
    }

    def preds(&self) -> PyResult<Vec<PyBytes>> {
        Ok(self.entry(py).preds.iter().map(|p| PyBytes::new(py, p.as_ref())).collect())
    }

    def split(&self) -> PyResult<Vec<PyBytes>> {
        Ok(self.entry(py).split.iter().map(|s| PyBytes::new(py, s.as_ref())).collect())
    }

    def op(&self) -> PyResult<Str> {
        Ok(self.entry(py).op.clone().into())
    }

    def user(&self) -> PyResult<Str> {
        Ok(self.entry(py).user.clone().into())
    }

    def time(&self) -> PyResult<i64> {
        Ok(self.entry(py).time)
    }

    def tz(&self) -> PyResult<i32> {
        Ok(self.entry(py).tz)
    }

    def extra(&self) -> PyResult<Vec<(PyBytes, PyBytes)>> {
        Ok(self.entry(py).extra.iter().map(|(k, v)| {
            (PyBytes::new(py, k.as_ref()), PyBytes::new(py, v.as_ref()))
        }).collect())
    }
});

py_class!(class mutationstore |py| {
    data mut_store: RefCell<MutationStore>;

    def __new__(_cls, path: &PyPath) -> PyResult<mutationstore> {
        let ms = MutationStore::open(path).map_pyerr(py)?;
        mutationstore::create_instance(py, RefCell::new(ms))
    }

    def add(&self, entry: &mutationentry) -> PyResult<PyNone> {
        let mut ms = self.mut_store(py).borrow_mut();
        ms.add(entry.entry(py)).map_pyerr(py)?;
        Ok(PyNone)
    }

    def addraw(&self, entry: &mutationentry) -> PyResult<PyNone> {
        let mut ms = self.mut_store(py).borrow_mut();
        ms.add_raw(entry.entry(py)).map_pyerr(py)?;
        Ok(PyNone)
    }

    def flush(&self) -> PyResult<PyNone> {
        let mut ms = self.mut_store(py).borrow_mut();
        block_on(ms.flush()).map_pyerr(py)?;
        Ok(PyNone)
    }

    def has(&self, succ: &PyBytes) -> PyResult<bool> {
        let succ = Node::from_slice(succ.data(py)).map_pyerr(py)?;
        let ms = self.mut_store(py).borrow();
        let entry = ms.get(succ).map_pyerr(py)?;
        Ok(entry.is_some())
    }

    def get(&self, succ: &PyBytes) -> PyResult<Option<mutationentry>> {
        let succ = Node::from_slice(succ.data(py)).map_pyerr(py)?;
        let ms = self.mut_store(py).borrow();
        let entry = ms.get(succ).map_pyerr(py)?;
        let entry = match entry {
            Some(entry) => Some(mutationentry::create_instance(py, entry)?),
            None => None,
        };
        Ok(entry)
    }

    def getsplithead(&self, node: &PyBytes) -> PyResult<Option<PyBytes>> {
        let node = Node::from_slice(node.data(py)).map_pyerr(py)?;
        let ms = self.mut_store(py).borrow();
        let entry = ms.get_split_head(node).map_pyerr(py)?;
        let succ = match entry {
            Some(entry) => Some(PyBytes::new(py, entry.succ.as_ref())),
            None => None,
        };
        Ok(succ)
    }

    def getsuccessorssets(&self, node: &PyBytes) -> PyResult<Vec<Vec<PyBytes>>> {
        let node = Node::from_slice(node.data(py)).map_pyerr(py)?;
        let ms = self.mut_store(py).borrow();
        let ssets = ms.get_successors_sets(node).map_pyerr(py)?;
        let mut pyssets = Vec::with_capacity(ssets.len());
        for sset in ssets.into_iter() {
            let mut pysset = Vec::with_capacity(sset.len());
            for succ in sset.into_iter() {
                pysset.push(PyBytes::new(py, succ.as_ref()));
            }
            pyssets.push(pysset);
        }
        Ok(pyssets)
    }

    /// Figure out connected components related to specified nodes.
    /// The returned graph supports DAG related calculations like
    /// ancestors, heads, roots, etc.
    ///
    /// If successors is True, follow successors.
    /// If predecessors is True, follow predecessors.
    /// like split, fold are ignored.
    def getdag(&self, nodes: Vec<PyBytes>, successors: bool = true, predecessors: bool = true) -> PyResult<dagalgo> {
        let nodes = nodes
            .into_iter()
            .map(|n| Node::from_slice(n.data(py))).collect::<Result<Vec<_>, _>>().map_pyerr(py)?;
        let mut flags = DagFlags::empty();
        if successors {
            flags |= DagFlags::SUCCESSORS;
        }
        if predecessors {
            flags |= DagFlags::PREDECESSORS;
        }
        let dag = block_on(self.mut_store(py).borrow().get_dag_advanced(nodes, flags)).map_pyerr(py)?;
        dagalgo::from_dag(py, dag)
    }

    /// Calculate the `obsolete` set from `public` and `draft` sets.
    def calculateobsolete(&self, public: Names, draft: Names) -> PyResult<Names> {
        let store = self.mut_store(py).borrow();
        Ok(Names(block_on(store.calculate_obsolete(public.0, draft.0)).map_pyerr(py)?))
    }

    @staticmethod
    def repair(path: &str) -> PyResult<Str> {
        py.allow_threads(|| MutationStore::repair(path)).map_pyerr(py).map(Into::into)
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::sync::Arc;

use cpython::*;
use cpython_ext::convert::Serde;
use cpython_ext::PyNone;
use cpython_ext::ResultPyErrExt;
use nodeipc::get_singleton;
use serde_json::Value;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "nodeipc"].join(".");
    let m = PyModule::new(py, &name)?;
    let ipc = NodeIpc::create_instance(py, Box::new(get_singleton))?;
    m.add(py, "IPC", ipc)?;
    Ok(m)
}

py_class!(class NodeIpc |py| {
    data inner: Box<dyn Fn() -> Option<Arc<nodeipc::NodeIpc>> + Send>;

    /// send(json_like) -> None
    ///
    /// Send a message. Might block.
    /// Do nothing if the other side is not connected.
    def send(&self, message: Serde<Value>) -> PyResult<PyNone> {
        let inner = (self.inner(py))();
        if let Some(ipc) = inner {
            let message = message.0;
            py.allow_threads(move || ipc.send(message)).map_pyerr(py)?
        }
        Ok(PyNone)
    }

    /// recv() -> Optional[json_like]
    ///
    /// Receive a message. Might block. Returns None if there are
    /// no more messages to receive.
    def recv(&self) -> PyResult<Option<Serde<Value>>> {
        let inner = (self.inner(py))();
        if let Some(ipc) = inner {
            let message = py
                .allow_threads(move || ipc.recv::<Value>())
                .map_pyerr(py)?;
            return Ok(message.map(Serde));
        }
        Ok(None)
    }

    // Test if the other side is connected.
    // No "///" docstring due to "///" makes this a regular function,
    // i.e. it has to be called with `ipc.__bool__`, not `bool(ipc)`.
    def __bool__(&self) -> PyResult<bool> {
        let inner = (self.inner(py))();
        Ok(inner.is_some())
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;

use ::nodemap::NodeMap;
use ::nodemap::NodeSet;
use ::nodemap::Repair;
use cpython::*;
use cpython_ext::Bytes;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use types::node::Node;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "nodemap"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<nodemap>(py)?;
    m.add_class::<nodeset>(py)?;
    Ok(m)
}

py_class!(class nodemap |py| {
    data log: RefCell<NodeMap>;

    def __new__(_cls, path: &PyPath) -> PyResult<nodemap> {
        let nodemap = NodeMap::open(path)
            .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?;
        nodemap::create_instance(py, RefCell::new(nodemap))
    }

    def add(&self, first: &PyBytes, second: &PyBytes) -> PyResult<PyNone> {
        let first = Node::from_slice(first.data(py))
            .map_err(|e| PyErr::new::<exc::ValueError, _>(py, format!("{}", e)))?;
        let second = Node::from_slice(second.data(py))
            .map_err(|e| PyErr::new::<exc::ValueError, _>(py, format!("{}", e)))?;

        let cell = self.log(py);
        let mut log = cell.borrow_mut();
        log.add(&first, &second)
            .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?;

        Ok(PyNone)
    }

    def flush(&self) -> PyResult<PyNone> {
        self.log(py).borrow_mut().flush()
            .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?;
        Ok(PyNone)
    }

    def lookupbyfirst(&self, first: &PyBytes) -> PyResult<PyObject> {
        let first = Node::from_slice(first.data(py))
            .map_err(|e| PyErr::new::<exc::ValueError, _>(py, format!("{}", e)))?;
        Ok(self.log(py).borrow().lookup_by_first(&first)
            .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?
            .map_or(py.None(), |node| PyBytes::new(py, node.as_ref()).into_object()))
    }

    def lookupbysecond(&self, second: &PyBytes) -> PyResult<PyObject> {
        let second = Node::from_slice(second.data(py))
            .map_err(|e| PyErr::new::<exc::ValueError, _>(py, format!("{}", e)))?;
        Ok(self.log(py).borrow().lookup_by_second(&second)
            .map_err(|e| PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?
            .map_or(py.None(), |node| PyBytes::new(py, node.as_ref()).into_object()))
    }

    def items(&self) -> PyResult<Vec<(PyBytes, PyBytes)>> {
        let log = self.log(py).borrow();
        let iter = log.iter()
            .map_err(|e|  PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?;
        let keys = iter
            .map(|result| result.map(|keys| {
                let (first, second) = keys;
                (PyBytes::new(py, first.as_ref()),
                 PyBytes::new(py, second.as_ref()))
            }))
            .collect::<Result<Vec<_>, _>>()
            .map_err(|e|  PyErr::new::<exc::RuntimeError, _>(py, format!("{}", e)))?;
        Ok(keys)
    }

    @staticmethod
    def repair(path: &str) -> PyResult<Str> {
        py.allow_threads(|| NodeMap::repair(path)).map_pyerr(py).map(Into::into)
    }
});

py_class!(class nodeset |py| {
    data set: RefCell<NodeSet>;

    def __new__(_cls, path: &PyPath) -> PyResult<Self> {
        let nodeset = NodeSet::open(path).map_pyerr(py)?;
        Self::create_instance(py, RefCell::new(nodeset))
    }

    def add(&self, node: &PyBytes) -> PyResult<PyNone> {
        let node = Node::from_slice(node.data(py)).map_pyerr(py)?;
        let set = self.set(py);
        let mut set = set.borrow_mut();
        set.add(&node).map_pyerr(py)?;
        Ok(PyNone)
    }

    def flush(&self) -> PyResult<PyNone> {
        self.set(py).borrow_mut().flush().map_pyerr(py)?;
        Ok(PyNone)
    }

    def __contains__(&self, node: &PyBytes) -> PyResult<bool> {
        let node = Node::from_slice(node.data(py)).map_pyerr(py)?;
        Ok(self.set(py).borrow().contains(&node).map_pyerr(py)?)
    }

    def items(&self) -> PyResult<Vec<Bytes>> {
        let set = self.set(py).borrow();
        let nodes = set.iter()
            .map(|node| node.map(|node| Bytes::from(node.as_ref().to_vec())))
            .collect::<Result<Vec<Bytes>, _>>()
            .map_pyerr(py)?;
        Ok(nodes)
    }

    @staticmethod
    def repair(path: &str) -> PyResult<Str> {
        NodeSet::repair(path).map_pyerr(py).map(Into::into)
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::cell::RefCell;
use std::sync::Arc;

use ::pathhistory::PathHistory;
use async_runtime::try_block_unless_interrupted as block_on;
use cpython::*;
use cpython_ext::convert::ImplInto;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use dag::Set;
use dag::Vertex;
use storemodel::ReadRootTreeIds;
use storemodel::TreeStore;
use types::RepoPathBuf;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "pathhistory"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<pathhistory>(py)?;
    Ok(m)
}

py_class!(class pathhistory |py| {
    data inner: RefCell<PathHistory>;

    def __new__(
        _cls,
        set: ImplInto<Set>,
        paths: Vec<PyPathBuf>,
        roottreereader: ImplInto<Arc<dyn ReadRootTreeIds + Send + Sync>>,
        treestore: ImplInto<Arc<dyn TreeStore + Send + Sync>>,
    ) -> PyResult<Self> {
        let set = set.into();
        let paths: Vec<RepoPathBuf> = paths.into_iter().map(|p| p.to_repo_path_buf()).collect::<Result<Vec<_>, _>>().map_pyerr(py)?;
        let root_tree_reader = roottreereader.into();
        let tree_store = treestore.into();
        let history = block_on(PathHistory::new(set, paths, root_tree_reader, tree_store)).map_pyerr(py)?;
        Self::create_instance(py, RefCell::new(history))
    }

    def __next__(&self) -> PyResult<Option<PyBytes>> {
        let mut inner = self.inner(py).borrow_mut();
        let next: Option<Vertex> = block_on(inner.next()).map_pyerr(py)?;
        Ok(next.map(|v| PyBytes::new(py, v.as_ref())))
    }

    def __iter__(&self) -> PyResult<Self> {
        Ok(self.clone_ref(py))
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::path::Path;
use std::sync::Arc;

use anyhow::Result;
use cpython::*;
use cpython_ext::error::AnyhowResultExt;
use cpython_ext::error::ResultPyErrExt;
use cpython_ext::ExtractInner;
use cpython_ext::ExtractInnerRef;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::Str;
use pathmatcher::build_patterns;
use pathmatcher::AlwaysMatcher;
use pathmatcher::DifferenceMatcher;
use pathmatcher::DirectoryMatch;
use pathmatcher::GitignoreMatcher;
use pathmatcher::Matcher;
use pathmatcher::NeverMatcher;
use pathmatcher::PatternKind;
use pathmatcher::RegexMatcher;
use pathmatcher::TreeMatcher;
use pathmatcher::UnionMatcher;
use types::RepoPath;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "pathmatcher"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<gitignorematcher>(py)?;
    m.add_class::<treematcher>(py)?;
    m.add_class::<regexmatcher>(py)?;
    m.add_class::<dynmatcher>(py)?;
    m.add(py, "normalizeglob", py_fn!(py, normalize_glob(path: &str)))?;
    m.add(py, "plaintoglob", py_fn!(py, plain_to_glob(path: &str)))?;
    m.add(
        py,
        "expandcurlybrackets",
        py_fn!(py, expand_curly_brackets(path: &str)),
    )?;
    Ok(m)
}

py_class!(class gitignorematcher |py| {
    data matcher: Arc<GitignoreMatcher>;

    def __new__(_cls, root: &PyPath, global_paths: Vec<PyPathBuf>, case_sensitive: bool) -> PyResult<gitignorematcher> {
        let global_paths: Vec<&Path> = global_paths.iter().map(PyPathBuf::as_path).collect();
        let matcher = GitignoreMatcher::new(root, global_paths, case_sensitive);
        Self::create_instance(py, Arc::new(matcher))
    }

    def match_relative(&self, path: &PyPath, is_dir: bool) -> PyResult<bool> {
        Ok(self.matcher(py).match_relative(path, is_dir))
    }

    def explain(&self, path: &PyPath, is_dir: bool) -> PyResult<Str> {
        Ok(self.matcher(py).explain(path, is_dir).into())
    }
});

impl ExtractInnerRef for gitignorematcher {
    type Inner = Arc<GitignoreMatcher>;

    fn extract_inner_ref<'a>(&'a self, py: Python<'a>) -> &'a Self::Inner {
        self.matcher(py)
    }
}

py_class!(pub class regexmatcher |py| {
    data matcher: Arc<RegexMatcher>;

    def __new__(_cls, pattern: &str, case_sensitive: bool) -> PyResult<Self> {
        let matcher = RegexMatcher::new(pattern, case_sensitive).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(matcher))
    }

    def matches(&self, path: &str) -> PyResult<bool> {
        Ok(self.matcher(py).matches(path))
    }

    def match_prefix(&self, dir: &str) -> PyResult<Option<bool>> {
        if dir.is_empty() {
            Ok(None)
        } else {
            Ok(self.matcher(py).match_prefix(dir))
        }
    }
});

impl ExtractInnerRef for regexmatcher {
    type Inner = Arc<RegexMatcher>;

    fn extract_inner_ref<'a>(&'a self, py: Python<'a>) -> &'a Self::Inner {
        self.matcher(py)
    }
}

py_class!(pub class treematcher |py| {
    data matcher: Arc<TreeMatcher>;

    def __new__(_cls, rules: Vec<String>, case_sensitive: bool) -> PyResult<Self> {
        let matcher = TreeMatcher::from_rules(rules.into_iter(), case_sensitive).map_pyerr(py)?;
        Self::create_instance(py, Arc::new(matcher))
    }

    def matches(&self, path: &PyPath) -> PyResult<bool> {
        Ok(self.matcher(py).matches(path))
    }

    def match_recursive(&self, path: &PyPath) -> PyResult<Option<bool>> {
        if path.as_path().as_os_str().is_empty() {
            Ok(None)
        } else {
            Ok(self.matcher(py).match_recursive(path))
        }
    }

    def matching_rule_indexes(&self, path: &PyPath) -> PyResult<Vec<usize>> {
        Ok(self.matcher(py).matching_rule_indexes(path))
    }
});

impl ExtractInnerRef for treematcher {
    type Inner = Arc<TreeMatcher>;

    fn extract_inner_ref<'a>(&'a self, py: Python<'a>) -> &'a Self::Inner {
        self.matcher(py)
    }
}

py_class!(pub class dynmatcher |py| {
    data matcher: Arc<dyn 'static + Matcher + Send + Sync>;

    def __new__(_cls,
        patterns: Vec<String>,
        include: Vec<String>,
        exclude: Vec<String>,
        case_sensitive: bool
    ) -> PyResult<Self> {
        let default_kind = PatternKind::Glob;
        let matcher = pathmatcher::build_matcher(
            &build_patterns(&patterns, default_kind),
            &build_patterns(&include, default_kind),
            &build_patterns(&exclude, default_kind),
            case_sensitive
        ).map_pyerr(py)?;
        Self::create_instance(py, matcher)
    }

    def matches_file(&self, path: &PyPath) -> PyResult<bool> {
        let repo_path = path.to_repo_path().map_pyerr(py)?;
        self.matcher(py).matches_file(repo_path).map_pyerr(py)
    }

    def matches_directory(&self, path: &PyPath) -> PyResult<Option<bool>> {
        if path.as_path().as_os_str().is_empty() {
            Ok(None)
        } else {
            let repo_path = path.to_repo_path().map_pyerr(py)?;
            let directory_match = self.matcher(py).matches_directory(repo_path).map_pyerr(py)?;
            match directory_match {
                DirectoryMatch::Everything => Ok(Some(true)),
                DirectoryMatch::Nothing => Ok(Some(false)),
                DirectoryMatch::ShouldTraverse => Ok(None)
            }
        }
    }

});

impl ExtractInnerRef for dynmatcher {
    type Inner = Arc<dyn 'static + Matcher + Send + Sync>;

    fn extract_inner_ref<'a>(&'a self, py: Python<'a>) -> &'a Self::Inner {
        self.matcher(py)
    }
}

fn normalize_glob(_py: Python, path: &str) -> PyResult<Str> {
    Ok(pathmatcher::normalize_glob(path).into())
}

fn plain_to_glob(_py: Python, path: &str) -> PyResult<Str> {
    Ok(pathmatcher::plain_to_glob(path).into())
}

fn expand_curly_brackets(_py: Python, pattern: &str) -> PyResult<Vec<Str>> {
    Ok(pathmatcher::expand_curly_brackets(pattern)
        .into_iter()
        .map(|s| s.into())
        .collect())
}

pub struct PythonMatcher<'a> {
    py: Python<'a>,
    py_matcher: PyObject,
}

impl<'a> PythonMatcher<'a> {
    pub fn new(py: Python<'a>, py_matcher: PyObject) -> Self {
        PythonMatcher { py, py_matcher }
    }
}

impl<'a> Matcher for PythonMatcher<'a> {
    fn matches_directory(&self, path: &RepoPath) -> Result<DirectoryMatch> {
        matches_directory_impl(self.py, &self.py_matcher, &path).into_anyhow_result()
    }

    fn matches_file(&self, path: &RepoPath) -> Result<bool> {
        matches_file_impl(self.py, &self.py_matcher, &path).into_anyhow_result()
    }
}

pub struct ThreadPythonMatcher {
    py_matcher: PyObject,
}

impl ThreadPythonMatcher {
    pub fn new(py_matcher: PyObject) -> Self {
        ThreadPythonMatcher { py_matcher }
    }
}

impl Matcher for ThreadPythonMatcher {
    fn matches_directory(&self, path: &RepoPath) -> Result<DirectoryMatch> {
        let gil = Python::acquire_gil();
        matches_directory_impl(gil.python(), &self.py_matcher, &path).into_anyhow_result()
    }

    fn matches_file(&self, path: &RepoPath) -> Result<bool> {
        let gil = Python::acquire_gil();
        matches_file_impl(gil.python(), &self.py_matcher, &path).into_anyhow_result()
    }
}

fn matches_directory_impl(
    py: Python,
    py_matcher: &PyObject,
    path: &RepoPath,
) -> PyResult<DirectoryMatch> {
    let py_path = PyPathBuf::from(path);
    // PANICS! The interface in Rust doesn't expose exceptions. Unwrapping seems fine since
    // it crashes the rust stuff and returns a rust exception to Python.
    let py_value = py_matcher.call_method(py, "visitdir", (py_path,), None)?;

    let is_all = PyString::extract(py, &py_value)
        .and_then(|py_str| py_str.to_string(py).map(|s| s == "all"))
        .unwrap_or(false);
    let matches = if is_all {
        DirectoryMatch::Everything
    } else {
        if py_value.is_true(py).unwrap() {
            DirectoryMatch::ShouldTraverse
        } else {
            DirectoryMatch::Nothing
        }
    };
    Ok(matches)
}

fn matches_file_impl(py: Python, py_matcher: &PyObject, path: &RepoPath) -> PyResult<bool> {
    let py_path = PyPathBuf::from(path);
    // PANICS! The interface in Rust doesn't expose exceptions. Unwrapping seems fine since
    // it crashes the rust stuff and returns a rust exception to Python.
    let matches = py_matcher.call(py, (py_path,), None)?.is_true(py)?;
    Ok(matches)
}

/// Extracts a Rust matcher from a Python Object
/// When possible it converts it into a pure-Rust matcher.
pub fn extract_matcher(py: Python, matcher: PyObject) -> PyResult<Arc<dyn Matcher + Sync + Send>> {
    if let Ok(matcher) = treematcher::downcast_from(py, matcher.clone_ref(py)) {
        return Ok(matcher.extract_inner(py));
    }
    if let Ok(matcher) = gitignorematcher::downcast_from(py, matcher.clone_ref(py)) {
        return Ok(matcher.extract_inner(py));
    }
    if let Ok(matcher) = regexmatcher::downcast_from(py, matcher.clone_ref(py)) {
        return Ok(matcher.extract_inner(py));
    }
    if let Ok(matcher) = dynmatcher::downcast_from(py, matcher.clone_ref(py)) {
        return Ok(matcher.extract_inner(py));
    }
    let py_type = matcher.get_type(py);
    let type_name = py_type.name(py);
    if type_name.as_ref() == "treematcher" {
        return extract_matcher(py, matcher.getattr(py, "_matcher")?);
    }
    if type_name.as_ref() == "gitignorematcher" {
        return extract_matcher(py, matcher.getattr(py, "_matcher")?);
    }
    if type_name.as_ref() == "regexmatcher" {
        return extract_matcher(py, matcher.getattr(py, "_matcher")?);
    }
    if type_name.as_ref() == "dynmatcher" {
        return extract_matcher(py, matcher.getattr(py, "_matcher")?);
    }
    if type_name.as_ref() == "unionmatcher" {
        let py_matchers = matcher.getattr(py, "_matchers")?;
        let py_matchers = PyList::extract(py, &py_matchers)?;
        let mut matchers: Vec<Arc<dyn Matcher + Sync + Send>> = vec![];
        for matcher in py_matchers.iter(py) {
            matchers.push(extract_matcher(py, matcher)?);
        }

        return Ok(Arc::new(UnionMatcher::new(matchers)));
    }
    if type_name.as_ref() == "differencematcher" {
        let include = extract_matcher(py, matcher.getattr(py, "_m1")?)?;
        let exclude = extract_matcher(py, matcher.getattr(py, "_m2")?)?;
        return Ok(Arc::new(DifferenceMatcher::new(include, exclude)));
    }

    if type_name.as_ref() == "alwaysmatcher" {
        return Ok(Arc::new(AlwaysMatcher::new()));
    }
    if type_name.as_ref() == "nevermatcher" {
        return Ok(Arc::new(NeverMatcher::new()));
    }

    Ok(Arc::new(ThreadPythonMatcher::new(matcher)))
}

pub fn extract_option_matcher(
    py: Python,
    matcher: Option<PyObject>,
) -> PyResult<Arc<dyn Matcher + Sync + Send>> {
    match matcher {
        None => Ok(Arc::new(AlwaysMatcher::new())),
        Some(m) => extract_matcher(py, m),
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use cpython::*;
use cpython_ext::ResultPyErrExt;
use pprint::pformat_value;
use pprint::Value;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "pprint"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(py, "pformat", py_fn!(py, pformat(obj: PyObject)))?;
    Ok(m)
}

fn pformat(py: Python, obj: PyObject) -> PyResult<String> {
    let value: Value = cpython_ext::de::from_object(py, obj).map_pyerr(py)?;
    Ok(pformat_value(&value))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::cell::RefCell;
use std::fs;
use std::io;
use std::process::Child as RustChild;
use std::process::Command as RustCommand;
use std::process::ExitStatus as RustExitStatus;
use std::process::Stdio as RustStdio;

use cpython::*;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::ResultPyErrExt;
use spawn_ext::CommandExt;

py_class!(class Command |py| {
    data inner: RefCell<RustCommand>;

    /// Constructs a new Command for launching the program at path program, with
    /// the following default configuration:
    /// - No arguments to the program
    /// - Inherit the current process's environment
    /// - Inherit the current process's working directory
    /// - Inherit stdin/stdout/stderr for spawn or status, but create pipes for output
    @staticmethod
    def new(program: String) -> PyResult<Self> {
        let command = RustCommand::new(program);
        Self::create_instance(py, RefCell::new(command))
    }

    /// Adds an argument to pass to the program.
    def arg(&self, arg: &str) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.arg(arg))
    }

    /// Adds multiple arguments to pass to the program.
    def args(&self, args: Vec<String>) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.args(args))
    }

    /// Inserts or updates an environment variable mapping.
    def env(&self, key: &str, val: &str) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.env(key, val))
    }

    /// Adds or updates multiple environment variable mappings.
    def envs(&self, items: Vec<(String, String)>) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.envs(items))
    }

    /// Clears the entire environment map for the child process.
    def envclear(&self) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.env_clear())
    }

    /// Sets the working directory for the child process.
    def currentdir(&self, dir: &PyPath) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.current_dir(dir))
    }

    /// Configuration for the child process's standard input (stdin) handle.
    def stdin(&self, cfg: Stdio) -> PyResult<Self> {
        let f = cfg.to_rust(py).map_pyerr(py)?;
        self.mutate_then_clone(py, |c| c.stdin(f))
    }

    /// Configuration for the child process's standard output (stdout) handle.
    def stdout(&self, cfg: Stdio) -> PyResult<Self> {
        let f = cfg.to_rust(py).map_pyerr(py)?;
        self.mutate_then_clone(py, |c| c.stdout(f))
    }

    /// Configuration for the child process's standard error (stderr) handle.
    def stderr(&self, cfg: Stdio) -> PyResult<Self> {
        let f = cfg.to_rust(py).map_pyerr(py)?;
        self.mutate_then_clone(py, |c| c.stderr(f))
    }

    /// Attempt to avoid inheriting file handles.
    /// Call this before setting up redirections.
    def avoidinherithandles(&self) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.avoid_inherit_handles())
    }

    /// Use new session or process group.
    /// Call this after avoidinherithandles.
    def newsession(&self) -> PyResult<Self> {
        self.mutate_then_clone(py, |c| c.new_session())
    }

    /// Executes the command as a child process, returning a handle to it.
    def spawn(&self) -> PyResult<Child> {
        // This is safer than `os.fork()` in Python because Python cannot
        // interrupt between `fork()` and `exec()` due to Rust holding GIL.
        let mut inner = self.inner(py).borrow_mut();
        let child = inner.spawn().map_pyerr(py)?;
        Child::from_rust(py, child)
    }

    /// Spawn the process then forget about it.
    /// File handles are not inherited. stdio will be redirected to /dev/null.
    def spawndetached(&self) -> PyResult<Child> {
        // This is safer than `os.fork()` in Python because Python cannot
        // interrupt between `fork()` and `exec()` due to Rust holding GIL.
        let mut inner = self.inner(py).borrow_mut();
        let child = inner.spawn_detached().map_pyerr(py)?;
        Child::from_rust(py, child)
    }

});

impl Command {
    /// Make changes to `inner`, then clone self.
    fn mutate_then_clone(
        &self,
        py: Python,
        func: impl FnOnce(&mut RustCommand) -> &mut RustCommand,
    ) -> PyResult<Self> {
        let mut inner = self.inner(py).borrow_mut();
        func(&mut inner);
        Ok(self.clone_ref(py))
    }
}

py_class!(class Stdio |py| {
    data inner: Box<dyn Fn() -> io::Result<RustStdio> + Send + 'static> ;

    /// A new pipe should be arranged to connect the parent and child processes.
    @staticmethod
    def piped() -> PyResult<Self> {
        Self::create_instance(py, Box::new(|| Ok(RustStdio::piped())))
    }

    /// The child inherits from the corresponding parent descriptor.
    @staticmethod
    def inherit() -> PyResult<Self> {
        Self::create_instance(py, Box::new(|| Ok(RustStdio::inherit())))
    }

    /// This stream will be ignored. This is the equivalent of attaching the
    /// stream to /dev/null.
    @staticmethod
    def null() -> PyResult<Self> {
        Self::create_instance(py, Box::new(|| Ok(RustStdio::null())))
    }

    /// Open a file as `Stdio`.
    @staticmethod
    def open(path: &PyPath, read: bool = false, write: bool = false, create: bool = false, append: bool = false) -> PyResult<Self> {
        let path = path.to_path_buf();
        Self::create_instance(py, Box::new(move || {
            let mut opts = fs::OpenOptions::new();
            let file = opts.write(write).read(read).create(create).append(append).open(&path)?;
            Ok(file.into())
        }))
    }
});

impl Stdio {
    fn to_rust(&self, py: Python) -> io::Result<RustStdio> {
        self.inner(py)()
    }
}

py_class!(class Child |py| {
    data inner: RefCell<RustChild>;

    /// Forces the child process to exit. If the child has already exited, an
    /// InvalidInput error is returned.
    def kill(&self) -> PyResult<PyNone> {
        let mut inner = self.inner(py).borrow_mut();
        inner.kill().map_pyerr(py)?;
        Ok(PyNone)
    }

    /// Returns the OS-assigned process identifier associated with this child.
    def id(&self) -> PyResult<u32> {
        let inner = self.inner(py).borrow();
        Ok(inner.id())
    }

    /// Waits for the child to exit completely, returning the status that it
    /// exited with. This function will continue to have the same return value
    /// after it has been called at least once.
    def wait(&self) -> PyResult<ExitStatus> {
        let mut inner = self.inner(py).borrow_mut();
        let status = inner.wait().map_pyerr(py)?;
        ExitStatus::from_rust(py, status)
    }

    /// Attempts to collect the exit status of the child if it has already exited.
    def try_wait(&self) -> PyResult<Option<ExitStatus>> {
        let mut inner = self.inner(py).borrow_mut();
        match inner.try_wait().map_pyerr(py)? {
            Some(s) => Ok(Some(ExitStatus::from_rust(py, s)?)),
            None => Ok(None)
        }
    }
});

impl Child {
    fn from_rust(py: Python, child: RustChild) -> PyResult<Self> {
        Self::create_instance(py, RefCell::new(child))
    }
}

py_class!(class ExitStatus |py| {
    data inner: RustExitStatus;

    /// Was termination successful? Signal termination is not considered a
    /// success, and success is defined as a zero exit status.
    def success(&self) -> PyResult<bool> {
        Ok(self.inner(py).success())
    }

    /// Returns the exit code of the process, if any.
    /// On Unix, this will return None if the process was terminated by a signal.
    def code(&self) -> PyResult<Option<i32>> {
        Ok(self.inner(py).code())
    }
});

impl ExitStatus {
    fn from_rust(py: Python, status: RustExitStatus) -> PyResult<Self> {
        Self::create_instance(py, status)
    }
}

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "process"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<Child>(py)?;
    m.add_class::<Command>(py)?;
    m.add_class::<Stdio>(py)?;
    Ok(m)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! Rust bindings for Mercurial's Python `progress` module.
//!
//! This crate provides wrappers around Mercurial's Python progress bar objects
//! so that they may be used by pure Rust code, as well as compatibility shims
//! so that Python code can also use the Rust progress API. This will enable
//! the eventual transition to a pure Rust progress bar implementation.

#![allow(non_camel_case_types)]

use cpython::*;

mod model;
mod render;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "progress"].join(".");
    let m = PyModule::new(py, &name)?;

    let model_mod = PyModule::new(py, &format!("{}.model", name))?;
    model_mod.add_class::<model::ProgressBar>(py)?;
    model_mod.add_class::<model::CacheStats>(py)?;
    m.add(py, "model", model_mod)?;

    let render_mod = PyModule::new(py, &format!("{}.render", name))?;
    use render::*;
    render_mod.add(py, "simple", py_fn!(py, simple()))?;
    render_mod.add(py, "step", py_fn!(py, step()))?;
    render_mod.add(py, "debug", py_fn!(py, debug()))?;
    m.add(py, "render", render_mod)?;

    Ok(m)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! This module exposes Rust's progress model to Python.

use std::sync::Arc;

use cpython::*;
use cpython_ext::PyNone;
use progress_model::CacheStats as CacheStatsModel;
use progress_model::ProgressBar as ProgressBarModel;
use progress_model::Registry;

py_class!(pub class ProgressBar |py| {
    data model: Arc<ProgressBarModel>;

    def __new__(
        _cls,
        topic: String,
        total: Option<u64> = None,
        unit: Option<String> = None
    ) -> PyResult<Self> {
        let unit = unit.clone().unwrap_or_default();
        let total = total.unwrap_or_default();
        let bar = ProgressBarModel::register_new(topic, total, unit);
        Self::create_instance(py, bar)
    }

    def position_total(&self) -> PyResult<(u64, u64)> {
        Ok(self.model(py).position_total())
    }

    def set_position(&self, value: u64) -> PyResult<PyNone> {
        self.model(py).set_position(value);
        Ok(PyNone)
    }

    def set_total(&self, value: u64) -> PyResult<PyNone> {
        self.model(py).set_total(value);
        Ok(PyNone)
    }

    def increase_position(&self, value: u64) -> PyResult<PyNone> {
        self.model(py).increase_position(value);
        Ok(PyNone)
    }

    def increase_total(&self, value: u64) -> PyResult<PyNone> {
        self.model(py).increase_total(value);
        Ok(PyNone)
    }

    def set_message(&self, message: String) -> PyResult<PyNone> {
        self.model(py).set_message(message);
        Ok(PyNone)
    }
});

py_class!(pub class CacheStats |py| {
    data model: Arc<CacheStatsModel>;

    def __new__(
        _cls,
        topic: String,
    ) -> PyResult<Self> {
        let model = CacheStatsModel::new(topic);
        Registry::main().register_cache_stats(&model);
        Self::create_instance(py, model)
    }

    def increase_hit(&self, value: usize = 1) -> PyResult<PyNone> {
        self.model(py).increase_hit(value);
        Ok(PyNone)
    }

    def increase_miss(&self, value: usize = 1) -> PyResult<PyNone> {
        self.model(py).increase_miss(value);
        Ok(PyNone)
    }
});


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

//! This module exposes Rust's progress rendering to Python.

use cpython::*;
use cpython_ext::PyNone;
use progress_model::Registry;

pub(crate) fn simple(_py: Python) -> PyResult<String> {
    // XXX: Default config is hard-coded. This API is for debugging purpose.
    // Formal progress rendering is done by pure Rust.
    let config = Default::default();
    let reg = Registry::main();
    Ok(progress_render::simple::render_string(reg, &config))
}

pub(crate) fn debug(_py: Python) -> PyResult<String> {
    let reg = Registry::main();
    Ok(format!("{:?}", reg))
}

pub(crate) fn step(_py: Python) -> PyResult<PyNone> {
    Registry::main().step();
    Ok(PyNone)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

use std::collections::BTreeMap;

use cpython::*;
use cpython_ext::convert::Serde;
use cpython_ext::ResultPyErrExt;
use refencode::HgId;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "refencode"].join(".");
    let m = PyModule::new(py, &name)?;

    m.add(
        py,
        "decodebookmarks",
        py_fn!(py, decodebookmarks(data: PyBytes)),
    )?;
    m.add(
        py,
        "decoderemotenames",
        py_fn!(py, decoderemotenames(data: PyBytes)),
    )?;
    m.add(
        py,
        "decodevisibleheads",
        py_fn!(py, decodevisibleheads(data: PyBytes)),
    )?;

    m.add(
        py,
        "encodebookmarks",
        py_fn!(
            py,
            encodebookmarks(namenodes: Serde<BTreeMap<String, HgId>>)
        ),
    )?;
    m.add(
        py,
        "encoderemotenames",
        py_fn!(
            py,
            encoderemotenames(namenodes: Serde<BTreeMap<String, HgId>>)
        ),
    )?;
    m.add(
        py,
        "encodevisibleheads",
        py_fn!(py, encodevisibleheads(namenodes: Serde<Vec<HgId>>)),
    )?;

    Ok(m)
}

fn decodebookmarks(py: Python, data: PyBytes) -> PyResult<Serde<BTreeMap<String, HgId>>> {
    let data = data.data(py);
    let decoded = refencode::decode_bookmarks(data).map_pyerr(py)?;
    Ok(Serde(decoded))
}

fn decoderemotenames(py: Python, data: PyBytes) -> PyResult<Serde<BTreeMap<String, HgId>>> {
    let data = data.data(py);
    let decoded = refencode::decode_remotenames(data).map_pyerr(py)?;
    Ok(Serde(decoded))
}

fn decodevisibleheads(py: Python, data: PyBytes) -> PyResult<Serde<Vec<HgId>>> {
    let data = data.data(py);
    let decoded = refencode::decode_visibleheads(data).map_pyerr(py)?;
    Ok(Serde(decoded))
}

fn encodebookmarks(py: Python, namenodes: Serde<BTreeMap<String, HgId>>) -> PyResult<PyBytes> {
    let encoded = refencode::encode_bookmarks(&namenodes.0);
    Ok(PyBytes::new(py, encoded.as_ref()))
}

fn encoderemotenames(py: Python, namenodes: Serde<BTreeMap<String, HgId>>) -> PyResult<PyBytes> {
    let encoded = refencode::encode_remotenames(&namenodes.0);
    Ok(PyBytes::new(py, encoded.as_ref()))
}

fn encodevisibleheads(py: Python, nodes: Serde<Vec<HgId>>) -> PyResult<PyBytes> {
    let encoded = refencode::encode_visibleheads(&nodes.0);
    Ok(PyBytes::new(py, encoded.as_ref()))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

use std::collections::HashMap;
use std::ops::Range;

use cpython::*;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use regex::bytes::Regex as BinaryRegex;
use regex::Regex;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "regex"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add(py, "compile", py_fn!(py, compile(s: PyObject)))?;
    m.add(py, "escape", py_fn!(py, escape(s: &str)))?;
    Ok(m)
}

fn compile(py: Python, s: PyObject) -> PyResult<PyObject> {
    if let Ok(bytes) = s.extract::<PyBytes>(py) {
        let text = std::str::from_utf8(bytes.data(py)).map_pyerr(py)?;
        Ok(BytesPattern::compile(py, text)?.into_object())
    } else if let Ok(text) = s.extract::<String>(py) {
        Ok(StringPattern::compile(py, &text)?.into_object())
    } else {
        Err(PyErr::new::<exc::TypeError, _>(
            py,
            "compile requires str or bytes",
        ))
    }
}

fn escape(_py: Python, s: &str) -> PyResult<Str> {
    Ok(regex::escape(s).into())
}

py_class!(class StringPattern |py| {
    data re: Regex;
    data match_re: Regex;
    data raw_pattern: String;

    @staticmethod
    def compile(s: &str) -> PyResult<Self> {
        let re = Regex::new(s).map_pyerr(py)?;
        // match_re is used for the Python "match" API, which aligns the start, but not the end.
        // (Rust regex does not have such equivalent).
        let match_re = Regex::new(&format!(r"\A{}", s)).map_pyerr(py)?;
        let raw_pattern = s.to_string();
        Self::create_instance(py, re, match_re, raw_pattern)
    }

    def search(&self, s: PyString) -> PyResult<Option<StringMatchObject>> {
        StringMatchObject::new(py, self.re(py), s, self.clone_ref(py))
    }

    def r#match(&self, s: PyString) -> PyResult<Option<StringMatchObject>> {
        StringMatchObject::new(py, self.match_re(py), s, self.clone_ref(py))
    }

    def __repr__(&self) -> PyResult<String> {
        Ok(format!("<StringPattern {:?}>", self.raw_pattern(py)))
    }
});

py_class!(class StringMatchObject |py| {
    data ranges: Vec<Option<Range<usize>>>;
    data text: PyString;
    data dict: HashMap<String, Range<usize>>;

    def group(&self, i: usize = 0) -> PyResult<Option<Str>> {
        match self.get_range(py, i)? {
            None => Ok(None),
            Some(range) => Ok(Some(self.get_slice(py, range)?)),
        }
    }

    def groups(&self) -> PyResult<Vec<Option<Str>>> {
        self.ranges(py).iter().skip(1).cloned().map(|range| {
            range.map(|r| self.get_slice(py, r)).transpose()
        }).collect::<PyResult<_>>()
    }

    def groupdict(&self) -> PyResult<HashMap<Str, Str>> {
        let mut dict = HashMap::new();
        for (name, range) in self.dict(py).iter() {
            dict.insert(name.to_string().into(), self.get_slice(py, range.clone())?);
        }
        Ok(dict)
    }

    def start(&self, i: usize = 0) -> PyResult<Option<usize>> {
        Ok(self.get_range(py, i)?.map(|r| r.start))
    }

    def end(&self, i: usize = 0) -> PyResult<Option<usize>> {
        Ok(self.get_range(py, i)?.map(|r| r.end))
    }

    def span(&self, i: usize = 0) -> PyResult<Option<(usize, usize)>> {
        let range = self.get_range(py, i)?;
        Ok(range.map(|r| (r.start, r.end)))
    }
});

impl StringMatchObject {
    fn get_range(&self, py: Python, i: usize) -> PyResult<Option<Range<usize>>> {
        match self.ranges(py).get(i) {
            None => Err(PyErr::new::<exc::IndexError, _>(py, "no such group")),
            Some(range) => Ok(range.clone()),
        }
    }

    fn get_slice(&self, py: Python, range: Range<usize>) -> PyResult<Str> {
        let slice = self
            .text(py)
            .to_string(py)
            .expect("should not error since it was verified by 'StringMatchObject::new'")
            .get(range)
            .ok_or_else(|| {
                PyErr::new::<exc::IndexError, _>(
                    py,
                    "unexpected str slice (possibly a bug in the Rust regex crate)",
                )
            })?
            .to_string();
        Ok(slice.into())
    }

    fn new(
        py: Python,
        re: &Regex,
        pystr: PyString,
        pattern: StringPattern,
    ) -> PyResult<Option<Self>> {
        let s = pystr.to_string(py)?;
        re.captures(&s)
            .map(|c| {
                let ranges = (0..c.len()).map(|i| c.get(i).map(|r| r.range())).collect();
                let mut dict = HashMap::new();
                for name in pattern.re(py).capture_names() {
                    if let Some(name) = name {
                        if let Some(m) = c.name(name) {
                            dict.insert(name.to_string(), m.range());
                        }
                    }
                }
                StringMatchObject::create_instance(py, ranges, pystr.clone_ref(py), dict)
            })
            .transpose()
    }
}

py_class!(class BytesPattern |py| {
    data re: BinaryRegex;
    data match_re: BinaryRegex;
    data raw_pattern: String;

    @staticmethod
    def compile(s: &str) -> PyResult<Self> {
        let re = BinaryRegex::new(s).map_pyerr(py)?;
        // For Python "match" API - only search the beginning.
        let match_re = BinaryRegex::new(&format!(r"\A{}", s)).map_pyerr(py)?;
        let raw_pattern = s.to_string();
        Self::create_instance(py, re, match_re, raw_pattern)
    }

    def search(&self, s: PyBytes) -> PyResult<Option<BytesMatchObject>> {
        BytesMatchObject::new(py, self.re(py), s, self.clone_ref(py))
    }

    def r#match(&self, s: PyBytes) -> PyResult<Option<BytesMatchObject>> {
        BytesMatchObject::new(py, self.match_re(py), s, self.clone_ref(py))
    }

    def __repr__(&self) -> PyResult<String> {
        Ok(format!("<BytesPattern {:?}>", self.raw_pattern(py)))
    }
});

py_class!(class BytesMatchObject |py| {
    data ranges: Vec<Option<Range<usize>>>;
    data text: PyBytes;
    data dict: HashMap<String, Range<usize>>;

    def group(&self, i: usize = 0) -> PyResult<Option<PyBytes>> {
        match self.get_range(py, i)? {
            None => Ok(None),
            Some(range) => Ok(Some(self.get_slice(py, range)?)),
        }
    }

    def groups(&self) -> PyResult<Vec<Option<PyBytes>>> {
        self.ranges(py).iter().skip(1).cloned().map(|range| {
            range.map(|r| self.get_slice(py, r)).transpose()
        }).collect::<PyResult<_>>()
    }

    def groupdict(&self) -> PyResult<HashMap<String, PyBytes>> {
        let mut dict = HashMap::new();
        for (name, range) in self.dict(py).iter() {
            dict.insert(name.to_string(), self.get_slice(py, range.clone())?);
        }
        Ok(dict)
    }

    def start(&self, i: usize = 0) -> PyResult<Option<usize>> {
        Ok(self.get_range(py, i)?.map(|r| r.start))
    }

    def end(&self, i: usize = 0) -> PyResult<Option<usize>> {
        Ok(self.get_range(py, i)?.map(|r| r.end))
    }

    def span(&self, i: usize = 0) -> PyResult<Option<(usize, usize)>> {
        let range = self.get_range(py, i)?;
        Ok(range.map(|r| (r.start, r.end)))
    }
});

impl BytesMatchObject {
    fn get_range(&self, py: Python, i: usize) -> PyResult<Option<Range<usize>>> {
        match self.ranges(py).get(i) {
            None => Err(PyErr::new::<exc::IndexError, _>(py, "no such group")),
            Some(range) => Ok(range.clone()),
        }
    }

    fn get_slice(&self, py: Python, range: Range<usize>) -> PyResult<PyBytes> {
        Ok(PyBytes::new(py, &self.text(py).data(py)[range]))
    }

    fn new(
        py: Python,
        re: &BinaryRegex,
        pybytes: PyBytes,
        pattern: BytesPattern,
    ) -> PyResult<Option<Self>> {
        re.captures(pybytes.data(py))
            .map(|c| {
                let ranges = (0..c.len()).map(|i| c.get(i).map(|c| c.range())).collect();
                let mut dict = HashMap::new();
                for name in pattern.re(py).capture_names() {
                    if let Some(name) = name {
                        if let Some(m) = c.name(name) {
                            dict.insert(name.to_string(), m.range());
                        }
                    }
                }
                BytesMatchObject::create_instance(py, ranges, pybytes.clone_ref(py), dict)
            })
            .transpose()
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This software may be used and distributed according to the terms of the
 * GNU General Public License version 2.
 */

#![allow(non_camel_case_types)]

extern crate repo as rsrepo;
extern crate repolock as rsrepolock;
extern crate workingcopy as rsworkingcopy;

use std::cell::Cell;
use std::cell::RefCell;
use std::sync::Arc;

use configmodel::config::ConfigExt;
use cpython::*;
use cpython_ext::error::ResultPyErrExt;
use cpython_ext::ExtractInner;
use cpython_ext::PyNone;
use cpython_ext::PyPathBuf;
use parking_lot::RwLock;
use pyconfigloader::config;
use pydag::commits::commits as PyCommits;
use pyeagerepo::EagerRepoStore as PyEagerRepoStore;
use pyedenapi::PyClient as PyEdenApi;
use pymetalog::metalog as PyMetaLog;
use pyrevisionstore::filescmstore as PyFileScmStore;
use pyrevisionstore::pyremotestore as PyRemoteStore;
use pyrevisionstore::treescmstore as PyTreeScmStore;
use pyworkingcopy::workingcopy as PyWorkingCopy;
use revisionstore::ContentStoreBuilder;
use rsrepo::repo::Repo;
use rsworkingcopy::workingcopy::WorkingCopy;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "repo"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<repo>(py)?;
    m.add_class::<repolock>(py)?;
    Ok(m)
}

py_class!(pub class repo |py| {
    data inner: RwLock<Repo>;
    data inner_wc: RefCell<Option<Arc<RwLock<WorkingCopy>>>>;

    @staticmethod
    def initialize(path: PyPathBuf, config: &config, repo_config: Option<String>) -> PyResult<PyNone> {
        Repo::init(path.as_path(), &config.get_cfg(py), repo_config, &[]).map_pyerr(py)?;
        Ok(PyNone)
    }

    def __new__(_cls, path: PyPathBuf, config: &config) -> PyResult<Self> {
        let config = config.get_cfg(py);
        let abs_path = util::path::absolute(path.as_path()).map_pyerr(py)?;
        let repo = Repo::load_with_config(&abs_path, config).map_pyerr(py)?;
        Self::create_instance(py, RwLock::new(repo), RefCell::new(None))
    }

    def workingcopy(&self) -> PyResult<PyWorkingCopy> {
        let mut wc_option = self.inner_wc(py).borrow_mut();
        if wc_option.is_none() {
            let mut repo = self.inner(py).write();
            let path = repo.path().to_path_buf();
            wc_option.replace(Arc::new(RwLock::new(repo.working_copy(&path).map_pyerr(py)?)));
        }
        PyWorkingCopy::create_instance(py, wc_option.as_ref().unwrap().clone())
    }

    def invalidateworkingcopy(&self) -> PyResult<PyNone> {
        let wc_option = self.inner_wc(py).borrow_mut();
        if wc_option.is_some() {
            let mut repo = self.inner(py).write();
            let path = repo.path().to_path_buf();
            let mut wc = wc_option.as_ref().unwrap().write();
            *wc = repo.working_copy(&path).map_pyerr(py)?;
        }
        Ok(PyNone)
    }

    def metalog(&self) -> PyResult<PyMetaLog> {
        let mut repo_ref = self.inner(py).write();
        let path = String::from(repo_ref.metalog_path().to_string_lossy());
        let log_ref = repo_ref.metalog().map_pyerr(py)?;
        PyMetaLog::create_instance(py, log_ref, path)
    }

    def invalidatemetalog(&self) -> PyResult<PyNone> {
        let mut repo_ref = self.inner(py).write();
        repo_ref.invalidate_metalog();
        Ok(PyNone)
    }

    def edenapi(&self) -> PyResult<PyEdenApi> {
        let mut repo_ref = self.inner(py).write();
        let edenapi_ref = repo_ref.eden_api().map_pyerr(py)?;
        PyEdenApi::create_instance(py, edenapi_ref)
    }

    def nullableedenapi(&self) -> PyResult<Option<PyEdenApi>> {
        let mut repo_ref = self.inner(py).write();
        match repo_ref.optional_eden_api().map_pyerr(py)? {
            Some(api) => Ok(Some(PyEdenApi::create_instance(py, api)?)),
            None => Ok(None),
        }
    }

    def filescmstore(&self, remote: PyRemoteStore) -> PyResult<PyFileScmStore> {
        let mut repo = self.inner(py).write();
        let _ = repo.file_store().map_pyerr(py)?;
        let mut file_scm_store = repo.file_scm_store().unwrap();

        let mut builder = ContentStoreBuilder::new(repo.config())
            .correlator(Some(repo.correlator()))
            .remotestore(remote.extract_inner(py))
            .local_path(repo.store_path());

        if let Some(indexedlog_local) = file_scm_store.indexedlog_local() {
            builder = builder.shared_indexedlog_local(indexedlog_local);
        }

        if let Some(cache) = file_scm_store.indexedlog_cache() {
            builder = builder.shared_indexedlog_shared(cache);
        }

        let contentstore = Arc::new(builder.build().map_pyerr(py)?);

        if repo.config().get_or_default("scmstore", "contentstorefallback").map_pyerr(py)? {
            file_scm_store = Arc::new(file_scm_store.with_content_store(contentstore.clone()));
        }

        PyFileScmStore::create_instance(py, file_scm_store, contentstore)
    }

    def treescmstore(&self, remote: PyRemoteStore) -> PyResult<PyTreeScmStore> {
        let mut repo = self.inner(py).write();
        let _ = repo.tree_store().map_pyerr(py)?;
        let mut tree_scm_store = repo.tree_scm_store().unwrap();

        let mut builder = ContentStoreBuilder::new(repo.config())
            .correlator(Some(repo.correlator()))
            .remotestore(remote.extract_inner(py))
            .local_path(repo.store_path())
            .suffix("manifests");

        if let Some(indexedlog_local) = tree_scm_store.indexedlog_local.clone() {
            builder = builder.shared_indexedlog_local(indexedlog_local);
        }

        if let Some(cache) = tree_scm_store.indexedlog_cache.clone() {
            builder = builder.shared_indexedlog_shared(cache);
        }

        let contentstore = Arc::new(builder.build().map_pyerr(py)?);

        if repo.config().get_or_default("scmstore", "contentstorefallback").map_pyerr(py)? {
            tree_scm_store = Arc::new(tree_scm_store.with_content_store(contentstore.clone()));
        }

        PyTreeScmStore::create_instance(py, tree_scm_store, contentstore)
    }

    def changelog(&self) -> PyResult<PyCommits> {
        let mut repo_ref = self.inner(py).write();
        let changelog_ref = py
            .allow_threads(|| repo_ref.dag_commits())
            .map_pyerr(py)?;
        PyCommits::create_instance(py, changelog_ref)
    }

    def invalidatechangelog(&self) -> PyResult<PyNone> {
        let mut repo_ref = self.inner(py).write();
        repo_ref.invalidate_dag_commits().map_pyerr(py)?;
        Ok(PyNone)
    }

    def invalidatestores(&self) -> PyResult<PyNone> {
        let mut repo_ref = self.inner(py).write();
        repo_ref.invalidate_stores().map_pyerr(py)?;
        Ok(PyNone)
    }

    def invalidaterequires(&self) -> PyResult<PyNone> {
        let mut repo_ref = self.inner(py).write();
        repo_ref.reload_requires().map_pyerr(py)?;
        Ok(PyNone)
    }

    def dotpath(&self) -> PyResult<PyPathBuf> {
        self.inner(py).read().dot_hg_path().try_into().map_pyerr(py)
    }

    def trywlock(&self, wc_dot_hg: PyPathBuf) -> PyResult<repolock> {
        let lock = self.inner(py).read().locker().try_lock_working_copy(wc_dot_hg.to_path_buf()).map_pyerr(py)?;
        if lock.count() > 1 {
            Err(PyErr::new::<exc::ValueError, _>(py, "lock is already locked"))
        } else {
            repolock::create_instance(py, Cell::new(Some(lock)))
        }
    }

    def trylock(&self) -> PyResult<repolock> {
        let lock = self.inner(py).read().locker().try_lock_store().map_pyerr(py)?;
        if lock.count() > 1 {
            Err(PyErr::new::<exc::ValueError, _>(py, "lock is already locked"))
        } else {
            repolock::create_instance(py, Cell::new(Some(lock)))
        }
    }

    def eagerstore(&self) -> PyResult<PyEagerRepoStore> {
        let mut repo = self.inner(py).write();
        let _ = repo.file_store().map_pyerr(py)?;
        PyEagerRepoStore::create_instance(py, repo.eager_store().unwrap())
    }
});

py_class!(pub class repolock |py| {
    data lock: Cell<Option<rsrepolock::RepoLockHandle>>;

    def unlock(&self) -> PyResult<PyNone> {
        if let Some(f) = self.lock(py).replace(None) {
            let count = f.count();
            drop(f);
            if count == 1 {
                Ok(PyNone)
            } else {
                Err(PyErr::new::<exc::ValueError, _>(py, "lock is still locked"))
            }
        } else {
            Err(PyErr::new::<exc::ValueError, _>(py, "lock is already unlocked"))
        }
    }
});