/*
 * 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 anyhow::format_err;
use anyhow::Result;
use cpython::PyBytes;
use cpython::PyDict;
use cpython::PyIterator;
use cpython::PyList;
use cpython::PyObject;
use cpython::PyResult;
use cpython::PyTuple;
use cpython::Python;
use cpython::PythonObject;
use cpython::ToPyObject;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use revisionstore::datastore::Delta;
use revisionstore::datastore::StoreResult;
use revisionstore::ContentDataStore;
use revisionstore::ContentHash;
use revisionstore::HgIdDataStore;
use revisionstore::HgIdMutableDeltaStore;
use revisionstore::RemoteDataStore;
use revisionstore::StoreKey;
use revisionstore::ToKeys;
use types::Node;

use crate::pythonutil::from_base;
use crate::pythonutil::from_delta_to_tuple;
use crate::pythonutil::from_key;
use crate::pythonutil::from_key_to_tuple;
use crate::pythonutil::from_tuple_to_key;
use crate::pythonutil::key_error;
use crate::pythonutil::to_delta;
use crate::pythonutil::to_key;
use crate::pythonutil::to_metadata;

pub trait HgIdDataStorePyExt {
    fn get_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes>;
    fn get_delta_chain_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyList>;
    fn get_delta_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyObject>;
    fn get_meta_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyDict>;
    fn get_missing_py(&self, py: Python, keys: &mut PyIterator) -> PyResult<PyList>;
    fn refresh_py(&self, py: Python) -> PyResult<PyNone>;
}

pub trait ContentDataStorePyExt {
    fn blob_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes>;
    fn metadata_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyDict>;
}

pub trait IterableHgIdDataStorePyExt {
    fn iter_py(&self, py: Python) -> PyResult<Vec<PyTuple>>;
}

pub trait HgIdMutableDeltaStorePyExt: HgIdDataStorePyExt {
    fn add_py(
        &self,
        py: Python,
        name: &PyPath,
        node: &PyBytes,
        deltabasenode: &PyBytes,
        delta: &PyBytes,
        metadata: Option<PyDict>,
    ) -> PyResult<PyObject>;
    fn flush_py(&self, py: Python) -> PyResult<Option<Vec<PyPathBuf>>>;
}

pub trait RemoteDataStorePyExt: RemoteDataStore {
    fn prefetch_py(&self, py: Python, keys: PyList) -> PyResult<PyObject>;
    fn upload_py(&self, py: Python, keys: PyList) -> PyResult<PyList>;
}

impl<T: HgIdDataStore + ?Sized> HgIdDataStorePyExt for T {
    fn get_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes> {
        let key = StoreKey::hgid(to_key(py, name, node)?);
        let result = py.allow_threads(|| self.get(key)).map_pyerr(py)?;
        match result {
            StoreResult::Found(data) => Ok(PyBytes::new(py, &data[..])),
            StoreResult::NotFound(key) => Err(key_error(py, &key)),
        }
    }

    fn get_delta_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyObject> {
        let key = to_key(py, name, node)?;
        let storekey = StoreKey::hgid(key.clone());

        let res = py.allow_threads(|| self.get(storekey)).map_pyerr(py)?;
        let data = match res {
            StoreResult::Found(data) => data,
            StoreResult::NotFound(key) => return Err(key_error(py, &key)),
        };

        let delta = Delta {
            data: data.into(),
            base: None,
            key: key.clone(),
        };

        let base_name = PyPathBuf::from(key.path.as_repo_path());
        let base_node = PyBytes::new(py, Node::null_id().as_ref());

        let bytes = PyBytes::new(py, &delta.data);
        let meta = self.get_meta_py(py.clone(), &name, &node)?;
        Ok((
            bytes.into_object(),
            base_name.to_py_object(py).into_object(),
            base_node.into_object(),
            meta.into_object(),
        )
            .into_py_object(py)
            .into_object())
    }

    fn get_delta_chain_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyList> {
        let key = to_key(py, name, node)?;
        let storekey = StoreKey::hgid(key.clone());

        let res = py.allow_threads(|| self.get(storekey)).map_pyerr(py)?;

        let data = match res {
            StoreResult::Found(data) => data,
            StoreResult::NotFound(key) => return Err(key_error(py, &key)),
        };

        let delta = Delta {
            data: data.into(),
            base: None,
            key,
        };

        let deltachain = vec![delta];

        let pychain = deltachain
            .iter()
            .map(|d| from_delta_to_tuple(py, &d))
            .collect::<Vec<PyObject>>();
        Ok(PyList::new(py, &pychain[..]))
    }

    fn get_meta_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let key = StoreKey::hgid(to_key(py, name, node)?);
        let res = py.allow_threads(|| self.get_meta(key)).map_pyerr(py)?;

        let metadata = match res {
            StoreResult::Found(metadata) => metadata,
            StoreResult::NotFound(key) => return Err(key_error(py, &key)),
        };

        let metadict = PyDict::new(py);
        if let Some(size) = metadata.size {
            metadict.set_item(py, "s", size)?;
        }
        if let Some(flags) = metadata.flags {
            metadict.set_item(py, "f", flags)?;
        }

        Ok(metadict)
    }

    fn get_missing_py(&self, py: Python, keys: &mut PyIterator) -> PyResult<PyList> {
        // Copy the PyObjects into a vector so we can get a reference iterator.
        // This lets us get a Vector of Keys without copying the strings.
        let keys = keys
            .map(|k| match k {
                Ok(k) => Ok(StoreKey::from(from_tuple_to_key(py, &k)?)),
                Err(e) => Err(e),
            })
            .collect::<PyResult<Vec<StoreKey>>>()?;
        let missing = py.allow_threads(|| self.get_missing(&keys)).map_pyerr(py)?;

        let results = PyList::new(py, &[]);
        for key in missing {
            match key {
                StoreKey::HgId(key) => {
                    let key_tuple = from_key_to_tuple(py, &key);
                    results.append(py, key_tuple.into_object());
                }
                StoreKey::Content(_, key) => {
                    // This is tricky, when ran on the ContentStore, Python will only call this
                    // method when the network is flaky and the connection to the server is
                    // severed. In this case, the Python code will attempt to not continue from
                    // where it left of, and thus calls datastore.getmissing. It is very possible
                    // that some LFS pointers were received but we didn't have a chance to receive
                    // the blobs themself, leading the ContentStore code to logically indicate that
                    // the blob is missing, but not the pointer.
                    //
                    // It might be worth moving the retry logic from Python to be driven in Rust
                    // entirely as this would eliminate this hack entirely.
                    if let Some(key) = key {
                        let key_tuple = from_key_to_tuple(py, &key);
                        results.append(py, key_tuple.into_object());
                    }
                }
            }
        }

        Ok(results)
    }

    fn refresh_py(&self, py: Python) -> PyResult<PyNone> {
        self.refresh().map_pyerr(py)?;
        Ok(PyNone)
    }
}

impl<T: ContentDataStore + ?Sized> ContentDataStorePyExt for T {
    fn blob_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes> {
        let key = StoreKey::hgid(to_key(py, name, node)?);
        let res = py.allow_threads(|| self.blob(key)).map_pyerr(py)?;
        match res {
            StoreResult::Found(blob) => Ok(PyBytes::new(py, blob.as_ref())),
            StoreResult::NotFound(key) => Err(key_error(py, &key)),
        }
    }

    fn metadata_py(&self, py: Python, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let key = StoreKey::hgid(to_key(py, name, node)?);
        let res = py.allow_threads(|| self.metadata(key)).map_pyerr(py)?;

        let meta = match res {
            StoreResult::Found(meta) => meta,
            StoreResult::NotFound(key) => return Err(key_error(py, &key)),
        };

        let metadict = PyDict::new(py);
        metadict.set_item(py, "size", meta.size)?;
        match meta.hash {
            ContentHash::Sha256(hash) => {
                metadict.set_item(py, "sha256", PyBytes::new(py, hash.as_ref()))?
            }
        }
        metadict.set_item(py, "isbinary", meta.is_binary)?;

        Ok(metadict)
    }
}

impl<T: ToKeys + HgIdDataStore + ?Sized> IterableHgIdDataStorePyExt for T {
    fn iter_py(&self, py: Python) -> PyResult<Vec<PyTuple>> {
        let iter = py.allow_threads(|| self.to_keys()).into_iter().map(|res| {
            let key = res?;
            let res = py.allow_threads(|| self.get(StoreKey::hgid(key.clone())))?;
            let data = match res {
                StoreResult::Found(data) => data,
                StoreResult::NotFound(_) => return Err(format_err!("Key {:?} not found", key)),
            };
            let delta = Delta {
                data: data.into(),
                base: None,
                key: key.clone(),
            };
            let (name, node) = from_key(py, &key);
            let (_, base_node) = from_base(py, &delta);
            let tuple = (
                name.to_py_object(py).into_object(),
                node.into_object(),
                base_node.into_object(),
                delta.data.len().into_py_object(py),
            )
                .into_py_object(py);
            Ok(tuple)
        });
        iter.collect::<Result<Vec<PyTuple>>>().map_pyerr(py)
    }
}

impl<T: HgIdMutableDeltaStore + ?Sized> HgIdMutableDeltaStorePyExt for T {
    fn add_py(
        &self,
        py: Python,
        name: &PyPath,
        node: &PyBytes,
        deltabasenode: &PyBytes,
        delta: &PyBytes,
        py_metadata: Option<PyDict>,
    ) -> PyResult<PyObject> {
        let delta = to_delta(py, name, node, deltabasenode, delta)?;

        let mut metadata = Default::default();
        if let Some(meta) = py_metadata {
            metadata = to_metadata(py, &meta)?;
        }

        py.allow_threads(|| self.add(&delta, &metadata))
            .map_pyerr(py)?;
        Ok(Python::None(py))
    }

    fn flush_py(&self, py: Python) -> PyResult<Option<Vec<PyPathBuf>>> {
        let opt = py.allow_threads(|| self.flush()).map_pyerr(py)?;
        let opt = opt
            .map(|path| path.into_iter().map(|p| p.try_into()).collect())
            .transpose()
            .map_pyerr(py)?;
        Ok(opt)
    }
}

impl<T: RemoteDataStore + ?Sized> RemoteDataStorePyExt for T {
    fn prefetch_py(&self, py: Python, keys: PyList) -> PyResult<PyObject> {
        let keys = keys
            .iter(py)
            .map(|tuple| Ok(StoreKey::from(from_tuple_to_key(py, &tuple)?)))
            .collect::<PyResult<Vec<StoreKey>>>()?;
        py.allow_threads(|| self.prefetch(&keys)).map_pyerr(py)?;
        Ok(Python::None(py))
    }

    fn upload_py(&self, py: Python, keys: PyList) -> PyResult<PyList> {
        let keys = keys
            .iter(py)
            .map(|tuple| Ok(StoreKey::from(from_tuple_to_key(py, &tuple)?)))
            .collect::<PyResult<Vec<StoreKey>>>()?;
        let not_uploaded = py.allow_threads(|| self.upload(&keys)).map_pyerr(py)?;

        let results = PyList::new(py, &[]);
        for key in not_uploaded {
            match key {
                StoreKey::HgId(key) => {
                    let key_tuple = from_key_to_tuple(py, &key);
                    results.append(py, key_tuple.into_object());
                }
                StoreKey::Content(_, _) => {
                    return Err(format_err!("Unsupported key: {:?}", key)).map_pyerr(py);
                }
            }
        }

        Ok(results)
    }
}


/*
 * 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 anyhow::format_err;
use anyhow::Result;
use cpython::PyBytes;
use cpython::PyIterator;
use cpython::PyList;
use cpython::PyObject;
use cpython::PyResult;
use cpython::PyTuple;
use cpython::Python;
use cpython::PythonObject;
use cpython::ToPyObject;
use cpython_ext::PyNone;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use revisionstore::HgIdHistoryStore;
use revisionstore::HgIdMutableHistoryStore;
use revisionstore::RemoteHistoryStore;
use revisionstore::StoreKey;
use revisionstore::ToKeys;
use types::Key;
use types::NodeInfo;

use crate::pythonutil::from_key;
use crate::pythonutil::from_key_to_tuple;
use crate::pythonutil::from_tuple_to_key;
use crate::pythonutil::key_error;
use crate::pythonutil::to_key;
use crate::pythonutil::to_node;
use crate::pythonutil::to_path;

pub trait HgIdHistoryStorePyExt {
    fn get_missing_py(&self, py: Python, keys: &mut PyIterator) -> PyResult<PyList>;
    fn get_node_info_py(&self, py: Python, name: &PyPathBuf, node: &PyBytes) -> PyResult<PyTuple>;
    fn refresh_py(&self, py: Python) -> PyResult<PyNone>;
}

pub trait IterableHgIdHistoryStorePyExt {
    fn iter_py(&self, py: Python) -> PyResult<Vec<PyTuple>>;
}

pub trait HgIdMutableHistoryStorePyExt: HgIdHistoryStorePyExt {
    fn add_py(
        &self,
        py: Python,
        name: &PyPathBuf,
        node: &PyBytes,
        p1: &PyBytes,
        p2: &PyBytes,
        linknode: &PyBytes,
        copyfrom: Option<&PyPathBuf>,
    ) -> PyResult<PyObject>;
    fn flush_py(&self, py: Python) -> PyResult<Option<Vec<PyPathBuf>>>;
}

pub trait RemoteHistoryStorePyExt: RemoteHistoryStore {
    fn prefetch_py(&self, py: Python, keys: PyList) -> PyResult<PyObject>;
}

impl<T: HgIdHistoryStore + ?Sized> HgIdHistoryStorePyExt for T {
    fn get_missing_py(&self, py: Python, keys: &mut PyIterator) -> PyResult<PyList> {
        // Copy the PyObjects into a vector so we can get a reference iterator.
        // This lets us get a Vector of Keys without copying the strings.
        let keys = keys
            .map(|k| match k {
                Ok(k) => Ok(StoreKey::from(from_tuple_to_key(py, &k)?)),
                Err(e) => Err(e),
            })
            .collect::<PyResult<Vec<StoreKey>>>()?;
        let missing = py.allow_threads(|| self.get_missing(&keys)).map_pyerr(py)?;

        let results = PyList::new(py, &[]);
        for key in missing {
            match key {
                StoreKey::HgId(key) => {
                    let key_tuple = from_key_to_tuple(py, &key);
                    results.append(py, key_tuple.into_object());
                }
                StoreKey::Content(_, _) => {
                    return Err(format_err!("Unsupported key: {:?}", key)).map_pyerr(py);
                }
            }
        }

        Ok(results)
    }

    fn get_node_info_py(&self, py: Python, name: &PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        let key = to_key(py, name, node)?;
        let info = py
            .allow_threads(|| self.get_node_info(&key))
            .map_pyerr(py)?
            .ok_or_else(|| key_error(py, &StoreKey::hgid(key.clone())))?;

        Ok(from_node_info(py, &key, &info))
    }

    fn refresh_py(&self, py: Python) -> PyResult<PyNone> {
        self.refresh().map_pyerr(py)?;
        Ok(PyNone)
    }
}

fn from_node_info(py: Python, key: &Key, info: &NodeInfo) -> PyTuple {
    (
        PyBytes::new(py, info.parents[0].hgid.as_ref()),
        PyBytes::new(py, info.parents[1].hgid.as_ref()),
        PyBytes::new(py, info.linknode.as_ref().as_ref()),
        if key.path != info.parents[0].path {
            if info.parents[0].path.is_empty() {
                Python::None(py)
            } else {
                PyPathBuf::from(info.parents[0].path.clone())
                    .to_py_object(py)
                    .into_object()
            }
        } else {
            Python::None(py)
        },
    )
        .into_py_object(py)
}

fn to_node_info(
    py: Python,
    name: &PyPathBuf,
    p1: &PyBytes,
    p2: &PyBytes,
    linknode: &PyBytes,
    copyfrom: Option<&PyPathBuf>,
) -> PyResult<NodeInfo> {
    // Not only can copyfrom be None, it can also be an empty string. In both cases that means that
    // `name` should be used.
    let copyfrom = copyfrom.unwrap_or(name);
    let p1path = if copyfrom.as_path().as_os_str().is_empty() {
        name
    } else {
        copyfrom
    };
    let p1node = to_node(py, p1);
    let p2node = to_node(py, p2);

    let parents = if p1node.is_null() {
        Default::default()
    } else if p2node.is_null() {
        let p1 = Key::new(to_path(py, p1path)?, p1node);
        let p2 = Key::default();
        [p1, p2]
    } else {
        let p1 = Key::new(to_path(py, p1path)?, p1node);
        let p2 = Key::new(to_path(py, name)?, p2node);
        [p1, p2]
    };

    let linknode = to_node(py, linknode);
    Ok(NodeInfo { parents, linknode })
}

impl<T: ToKeys + HgIdHistoryStore + ?Sized> IterableHgIdHistoryStorePyExt for T {
    fn iter_py(&self, py: Python) -> PyResult<Vec<PyTuple>> {
        let iter = py.allow_threads(|| self.to_keys()).into_iter().map(|res| {
            let key = res?;
            let node_info = py.allow_threads(|| self.get_node_info(&key))?.unwrap();
            let (name, node) = from_key(py, &key);
            let copyfrom = if key.path != node_info.parents[0].path {
                if node_info.parents[0].path.is_empty() {
                    PyPathBuf::from(String::from(""))
                } else {
                    PyPathBuf::from(node_info.parents[0].path.as_repo_path())
                }
            } else {
                PyPathBuf::from(String::from(""))
            };
            let tuple = (
                name.to_py_object(py).into_object(),
                node.into_object(),
                PyBytes::new(py, node_info.parents[0].hgid.as_ref()),
                PyBytes::new(py, node_info.parents[1].hgid.as_ref()),
                PyBytes::new(py, node_info.linknode.as_ref().as_ref()),
                copyfrom.to_py_object(py).into_object(),
            )
                .into_py_object(py);
            Ok(tuple)
        });
        iter.collect::<Result<Vec<PyTuple>>>().map_pyerr(py)
    }
}

impl<T: HgIdMutableHistoryStore + ?Sized> HgIdMutableHistoryStorePyExt for T {
    fn add_py(
        &self,
        py: Python,
        name: &PyPathBuf,
        node: &PyBytes,
        p1: &PyBytes,
        p2: &PyBytes,
        linknode: &PyBytes,
        copyfrom: Option<&PyPathBuf>,
    ) -> PyResult<PyObject> {
        let key = to_key(py, name, node)?;
        let nodeinfo = to_node_info(py, name, p1, p2, linknode, copyfrom)?;
        py.allow_threads(|| self.add(&key, &nodeinfo))
            .map_pyerr(py)?;
        Ok(Python::None(py))
    }

    fn flush_py(&self, py: Python) -> PyResult<Option<Vec<PyPathBuf>>> {
        let opt = py.allow_threads(|| self.flush()).map_pyerr(py)?;
        let opt = opt
            .map(|path| path.into_iter().map(|p| p.try_into()).collect())
            .transpose()
            .map_pyerr(py)?;
        Ok(opt)
    }
}

impl<T: RemoteHistoryStore + ?Sized> RemoteHistoryStorePyExt for T {
    fn prefetch_py(&self, py: Python, keys: PyList) -> PyResult<PyObject> {
        let keys = keys
            .iter(py)
            .map(|tuple| Ok(StoreKey::from(from_tuple_to_key(py, &tuple)?)))
            .collect::<PyResult<Vec<StoreKey>>>()?;
        py.allow_threads(|| self.prefetch(&keys)).map_pyerr(py)?;
        Ok(Python::None(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.
 */

//! Support `ImplInto` from cpython-ext.

use std::sync::Arc;

use anyhow::format_err;
use anyhow::Result;
use cpython::*;
use cpython_ext::convert::register_into;
use cpython_ext::ExtractInner;
use revisionstore::trait_impls::ArcFileStore;
use revisionstore::trait_impls::ArcRemoteDataStore;
use revisionstore::HgIdDataStore;
use revisionstore::LegacyStore;
use revisionstore::RemoteDataStore;
use revisionstore::StoreKey;
use revisionstore::StoreResult;
use storemodel::minibytes::Bytes;
use storemodel::ReadFileContents;
use storemodel::TreeStore;
use types::Key;
use types::Node;
use types::RepoPath;

use crate::contentstore;
use crate::filescmstore;
use crate::pyfilescmstore;
use crate::treescmstore;
use crate::PythonHgIdDataStore;

pub(crate) fn register(py: Python) {
    register_into(py, |py, c: contentstore| c.to_dyn_treestore(py));
    register_into(py, |py, t: treescmstore| t.to_dyn_treestore(py));
    register_into(py, py_to_dyn_treestore);

    register_into(py, |py, c: contentstore| c.to_read_file_contents(py));
    register_into(py, |py, f: filescmstore| f.to_read_file_contents(py));
    register_into(py, |py, p: pyfilescmstore| p.to_read_file_contents(py));
}

impl contentstore {
    fn to_dyn_treestore(&self, py: Python) -> Arc<dyn TreeStore + Send + Sync> {
        let store = self.extract_inner(py) as Arc<dyn LegacyStore>;
        Arc::new(ManifestStore::new(store))
    }

    fn to_read_file_contents(
        &self,
        py: Python,
    ) -> Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync> {
        let store = self.extract_inner(py) as Arc<dyn LegacyStore>;
        let store = ArcRemoteDataStore(store as Arc<_>);
        Arc::new(store)
    }
}

impl filescmstore {
    fn to_read_file_contents(
        &self,
        py: Python,
    ) -> Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync> {
        let store = self.extract_inner(py);
        let store = ArcFileStore(store);
        Arc::new(store)
    }
}

impl pyfilescmstore {
    fn to_read_file_contents(
        &self,
        py: Python,
    ) -> Arc<dyn ReadFileContents<Error = anyhow::Error> + Send + Sync> {
        self.extract_inner(py)
    }
}

impl treescmstore {
    fn to_dyn_treestore(&self, py: Python) -> Arc<dyn TreeStore + Send + Sync> {
        let store = self.extract_inner(py) as Arc<dyn LegacyStore>;
        Arc::new(ManifestStore::new(store))
    }
}

// Legacy support for store in Python.
// XXX: Check if it's used and drop support for it.
fn py_to_dyn_treestore(_py: Python, obj: PyObject) -> Arc<dyn TreeStore + Send + Sync> {
    let store = Arc::new(PythonHgIdDataStore::new(obj)) as Arc<dyn LegacyStore>;
    Arc::new(ManifestStore::new(store))
}

struct ManifestStore<T> {
    underlying: T,
}

impl<T> ManifestStore<T> {
    pub fn new(underlying: T) -> Self {
        ManifestStore { underlying }
    }
}

impl<T: HgIdDataStore + RemoteDataStore> TreeStore for ManifestStore<T> {
    fn get(&self, path: &RepoPath, node: Node) -> Result<Bytes> {
        if node.is_null() {
            return Ok(Default::default());
        }
        let key = Key::new(path.to_owned(), node);
        match self.underlying.get(StoreKey::hgid(key))? {
            StoreResult::NotFound(key) => Err(format_err!("Key {:?} not found in manifest", key)),
            StoreResult::Found(data) => Ok(data.into()),
        }
    }

    fn insert(&self, _path: &RepoPath, _node: Node, _data: Bytes) -> Result<()> {
        unimplemented!(
            "At this time we don't expect to ever write manifest in rust using python stores."
        );
    }

    fn prefetch(&self, keys: Vec<Key>) -> Result<()> {
        let keys = keys
            .iter()
            .filter_map(|k| {
                if k.hgid.is_null() {
                    None
                } else {
                    Some(StoreKey::from(k))
                }
            })
            .collect::<Vec<_>>();
        self.underlying.prefetch(&keys).map(|_| ())
    }
}


/*
 * 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.
 */

//! revisionstore - Python interop layer for a Mercurial data and history store

#![allow(non_camel_case_types)]

use std::fs::read_dir;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::format_err;
use anyhow::Error;
use async_runtime::block_on;
use async_runtime::stream_to_iter as block_on_stream;
use configmodel::Config;
use configmodel::ConfigExt;
use cpython::*;
use cpython_ext::ExtractInner;
use cpython_ext::ExtractInnerRef;
use cpython_ext::PyErr;
use cpython_ext::PyNone;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use cpython_ext::Str;
use io::IO;
use parking_lot::RwLock;
use pyconfigloader::config;
use revisionstore::repack;
use revisionstore::scmstore::file_to_async_key_stream;
use revisionstore::scmstore::FetchMode;
use revisionstore::scmstore::FileAttributes;
use revisionstore::scmstore::FileStore;
use revisionstore::scmstore::FileStoreBuilder;
use revisionstore::scmstore::TreeStore;
use revisionstore::scmstore::TreeStoreBuilder;
use revisionstore::ContentStore;
use revisionstore::ContentStoreBuilder;
use revisionstore::CorruptionPolicy;
use revisionstore::DataPack;
use revisionstore::DataPackStore;
use revisionstore::DataPackVersion;
use revisionstore::Delta;
use revisionstore::EdenApiFileStore;
use revisionstore::EdenApiTreeStore;
use revisionstore::ExtStoredPolicy;
use revisionstore::HgIdDataStore;
use revisionstore::HgIdHistoryStore;
use revisionstore::HgIdMutableDeltaStore;
use revisionstore::HgIdMutableHistoryStore;
use revisionstore::HgIdRemoteStore;
use revisionstore::HistoryPack;
use revisionstore::HistoryPackStore;
use revisionstore::HistoryPackVersion;
use revisionstore::IndexedLogHgIdDataStore;
use revisionstore::IndexedLogHgIdDataStoreConfig;
use revisionstore::IndexedLogHgIdHistoryStore;
use revisionstore::LegacyStore;
use revisionstore::LocalStore;
use revisionstore::MemcacheStore;
use revisionstore::Metadata;
use revisionstore::MetadataStore;
use revisionstore::MetadataStoreBuilder;
use revisionstore::MutableDataPack;
use revisionstore::MutableHistoryPack;
use revisionstore::RemoteDataStore;
use revisionstore::RemoteHistoryStore;
use revisionstore::RepackKind;
use revisionstore::RepackLocation;
use revisionstore::StoreKey;
use revisionstore::StoreResult;
use revisionstore::StoreType;
use types::Key;
use types::NodeInfo;

use crate::datastorepyext::ContentDataStorePyExt;
use crate::datastorepyext::HgIdDataStorePyExt;
use crate::datastorepyext::HgIdMutableDeltaStorePyExt;
use crate::datastorepyext::IterableHgIdDataStorePyExt;
use crate::datastorepyext::RemoteDataStorePyExt;
use crate::historystorepyext::HgIdHistoryStorePyExt;
use crate::historystorepyext::HgIdMutableHistoryStorePyExt;
use crate::historystorepyext::IterableHgIdHistoryStorePyExt;
use crate::historystorepyext::RemoteHistoryStorePyExt;
use crate::pythonutil::from_key;
use crate::pythonutil::from_key_to_tuple;
use crate::pythonutil::from_tuple_to_key;

mod datastorepyext;
mod historystorepyext;
mod impl_into;
mod pythondatastore;
mod pythonfilescmstore;
mod pythonutil;

type Result<T, E = Error> = std::result::Result<T, E>;

pub use crate::pythondatastore::PythonHgIdDataStore;
use crate::pythonfilescmstore::PythonFileScmStore;
pub use crate::pythonutil::as_legacystore;

pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
    let name = [package, "revisionstore"].join(".");
    let m = PyModule::new(py, &name)?;
    m.add_class::<datapack>(py)?;
    m.add_class::<datapackstore>(py)?;
    m.add_class::<historypack>(py)?;
    m.add_class::<historypackstore>(py)?;
    m.add_class::<indexedlogdatastore>(py)?;
    m.add_class::<indexedloghistorystore>(py)?;
    m.add_class::<mutabledeltastore>(py)?;
    m.add_class::<mutablehistorystore>(py)?;
    m.add_class::<pyremotestore>(py)?;
    m.add_class::<contentstore>(py)?;
    m.add_class::<metadatastore>(py)?;
    m.add_class::<memcachestore>(py)?;
    m.add_class::<filescmstore>(py)?;
    m.add_class::<treescmstore>(py)?;
    m.add_class::<pyfilescmstore>(py)?;
    m.add(
        py,
        "repack",
        py_fn!(
            py,
            repack_py(
                packpath: &PyPath,
                stores: Option<(PyObject, metadatastore)>,
                full: bool,
                shared: bool,
                config: config
            )
        ),
    )?;
    m.add(
        py,
        "repair",
        py_fn!(
            py,
            repair(
                shared_path: &PyPath,
                local_path: Option<&PyPath>,
                suffix: Option<&PyPath>,
                config: config
            )
        ),
    )?;

    impl_into::register(py);
    Ok(m)
}

fn repack_py(
    py: Python,
    packpath: &PyPath,
    stores: Option<(PyObject, metadatastore)>,
    full: bool,
    shared: bool,
    config: config,
) -> PyResult<PyNone> {
    let stores = if let Some((content, metadata)) = stores {
        Some((as_legacystore(py, content)?, metadata.extract_inner(py)))
    } else {
        None
    };

    let kind = if full {
        RepackKind::Full
    } else {
        RepackKind::Incremental
    };

    let location = if shared {
        RepackLocation::Shared
    } else {
        RepackLocation::Local
    };

    repack(
        packpath.to_path_buf(),
        stores,
        kind,
        location,
        &config.get_cfg(py),
    )
    .map_pyerr(py)?;

    Ok(PyNone)
}

fn repair(
    py: Python,
    shared_path: &PyPath,
    local_path: Option<&PyPath>,
    suffix: Option<&PyPath>,
    config: config,
) -> PyResult<Str> {
    let config = config.get_cfg(py);
    py.allow_threads::<Result<String>, _>(|| {
        let mut message = ContentStore::repair(
            shared_path.as_path(),
            local_path.map(|p| p.as_path()),
            suffix.map(|p| p.as_path()),
            &config,
        )?;
        message.push_str(
            MetadataStore::repair(
                shared_path.as_path(),
                local_path.map(|p| p.as_path()),
                suffix.map(|p| p.as_path()),
                &config,
            )?
            .as_str(),
        );
        Ok(message)
    })
    .map_pyerr(py)
    .map(Into::into)
}

py_class!(class datapack |py| {
    data store: Box<DataPack>;

    def __new__(
        _cls,
        path: &PyPath
    ) -> PyResult<datapack> {
        datapack::create_instance(
            py,
            Box::new(DataPack::new(path, ExtStoredPolicy::Ignore).map_pyerr(py)?),
        )
    }

    def path(&self) -> PyResult<PyPathBuf> {
        self.store(py).base_path().try_into().map_pyerr(py)
    }

    def packpath(&self) -> PyResult<PyPathBuf> {
        self.store(py).pack_path().try_into().map_pyerr(py)
    }

    def indexpath(&self) -> PyResult<PyPathBuf> {
        self.store(py).index_path().try_into().map_pyerr(py)
    }

    def get(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.get_py(py, &name, node)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def iterentries(&self) -> PyResult<Vec<PyTuple>> {
        let store = self.store(py);
        store.iter_py(py)
    }
});

/// Scan the filesystem for files with `extensions`, and compute their size.
fn compute_store_size<P: AsRef<Path>>(
    storepath: P,
    extensions: Vec<&str>,
) -> Result<(usize, usize)> {
    let dirents = read_dir(storepath)?;

    assert_eq!(extensions.len(), 2);

    let mut count = 0;
    let mut size = 0;

    for dirent in dirents {
        let dirent = dirent?;
        let path = dirent.path();

        if let Some(file_ext) = path.extension() {
            for extension in &extensions {
                if extension == &file_ext {
                    size += dirent.metadata()?.len();
                    count += 1;
                    break;
                }
            }
        }
    }

    // We did count the indexes too, but we do not want them counted.
    count /= 2;

    Ok((size as usize, count))
}

py_class!(class datapackstore |py| {
    data store: Box<DataPackStore>;
    data path: PathBuf;

    def __new__(_cls, path: &PyPath, deletecorruptpacks: bool = false, maxbytes: Option<u64> = None) -> PyResult<datapackstore> {
        let corruption_policy = if deletecorruptpacks {
            CorruptionPolicy::REMOVE
        } else {
            CorruptionPolicy::IGNORE
        };

        datapackstore::create_instance(py, Box::new(DataPackStore::new(path, corruption_policy, maxbytes, ExtStoredPolicy::Ignore)), path.to_path_buf())
    }

    def get(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyBytes> {
        self.store(py).get_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        self.store(py).get_meta_py(py, &name, node)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        self.store(py).get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        self.store(py).get_delta_chain_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        self.store(py).get_missing_py(py, &mut keys.iter(py)?)
    }

    def markforrefresh(&self) -> PyResult<PyObject> {
        self.store(py).force_rescan();
        Ok(Python::None(py))
    }

    def getmetrics(&self) -> PyResult<PyDict> {
        let (size, count) = match compute_store_size(self.path(py), vec!["datapack", "dataidx"]) {
            Ok((size, count)) => (size, count),
            Err(_) => (0, 0),
        };

        let res = PyDict::new(py);
        res.set_item(py, "numpacks", count)?;
        res.set_item(py, "totalpacksize", size)?;
        Ok(res)
    }
});

py_class!(class historypack |py| {
    data store: Box<HistoryPack>;

    def __new__(
        _cls,
        path: &PyPath
    ) -> PyResult<historypack> {
        historypack::create_instance(
            py,
            Box::new(HistoryPack::new(path.as_path()).map_pyerr(py)?),
        )
    }

    def path(&self) -> PyResult<PyPathBuf> {
        self.store(py).base_path().try_into().map_pyerr(py)
    }

    def packpath(&self) -> PyResult<PyPathBuf> {
        self.store(py).pack_path().try_into().map_pyerr(py)
    }

    def indexpath(&self) -> PyResult<PyPathBuf> {
        self.store(py).index_path().try_into().map_pyerr(py)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def getnodeinfo(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        let store = self.store(py);
        store.get_node_info_py(py, &name, node)
    }

    def iterentries(&self) -> PyResult<Vec<PyTuple>> {
        let store = self.store(py);
        store.iter_py(py)
    }
});

py_class!(class historypackstore |py| {
    data store: Box<HistoryPackStore>;
    data path: PathBuf;

    def __new__(_cls, path: PyPathBuf, deletecorruptpacks: bool = false, maxbytes: Option<u64> = None) -> PyResult<historypackstore> {
        let corruption_policy = if deletecorruptpacks {
            CorruptionPolicy::REMOVE
        } else {
            CorruptionPolicy::IGNORE
        };

        historypackstore::create_instance(py, Box::new(HistoryPackStore::new(path.as_path(), corruption_policy, maxbytes)), path.to_path_buf())
    }

    def getnodeinfo(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        self.store(py).get_node_info_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        self.store(py).get_missing_py(py, &mut keys.iter(py)?)
    }

    def markforrefresh(&self) -> PyResult<PyObject> {
        self.store(py).force_rescan();
        Ok(Python::None(py))
    }

    def getmetrics(&self) -> PyResult<PyDict> {
        let (size, count) = match compute_store_size(self.path(py), vec!["histpack", "histidx"]) {
            Ok((size, count)) => (size, count),
            Err(_) => (0, 0),
        };

        let res = PyDict::new(py);
        res.set_item(py, "numpacks", count)?;
        res.set_item(py, "totalpacksize", size)?;
        Ok(res)
    }
});

py_class!(class indexedlogdatastore |py| {
    data store: Box<IndexedLogHgIdDataStore>;

    def __new__(_cls, path: &PyPath) -> PyResult<indexedlogdatastore> {
        let config = IndexedLogHgIdDataStoreConfig { max_log_count: None, max_bytes_per_log: None, max_bytes: None };
        indexedlogdatastore::create_instance(
            py,
            Box::new(IndexedLogHgIdDataStore::new(path.as_path(), ExtStoredPolicy::Ignore, &config, StoreType::Local).map_pyerr(py)?),
        )
    }

    def getdelta(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, name, node)
    }

    def getdeltachain(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, name, node)
    }

    def getmeta(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def markforrefresh(&self) -> PyResult<PyObject> {
        let store = self.store(py);
        store.flush_py(py)?;
        Ok(Python::None(py))
    }

    def iterentries(&self) -> PyResult<Vec<PyTuple>> {
        let store = self.store(py);
        store.iter_py(py)
    }
});

py_class!(class indexedloghistorystore |py| {
    data store: Box<IndexedLogHgIdHistoryStore>;

    def __new__(_cls, path: &PyPath, config: config) -> PyResult<indexedloghistorystore> {
        let config = config.get_cfg(py);
        indexedloghistorystore::create_instance(
            py,
            Box::new(IndexedLogHgIdHistoryStore::new(path.as_path(), &config, StoreType::Local).map_pyerr(py)?),
        )
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def getnodeinfo(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        let store = self.store(py);
        store.get_node_info_py(py, &name, node)
    }

    def markforrefresh(&self) -> PyResult<PyObject> {
        let store = self.store(py);
        store.flush_py(py)?;
        Ok(Python::None(py))
    }

    def iterentries(&self) -> PyResult<Vec<PyTuple>> {
        let store = self.store(py);
        store.iter_py(py)
    }
});

fn make_mutabledeltastore(
    packfilepath: Option<PyPathBuf>,
    indexedlogpath: Option<PyPathBuf>,
) -> Result<Arc<dyn HgIdMutableDeltaStore + Send>> {
    let store: Arc<dyn HgIdMutableDeltaStore + Send> = if let Some(packfilepath) = packfilepath {
        Arc::new(MutableDataPack::new(
            packfilepath.as_path(),
            DataPackVersion::One,
        ))
    } else if let Some(indexedlogpath) = indexedlogpath {
        let config = IndexedLogHgIdDataStoreConfig {
            max_log_count: None,
            max_bytes_per_log: None,
            max_bytes: None,
        };
        Arc::new(IndexedLogHgIdDataStore::new(
            indexedlogpath.as_path(),
            ExtStoredPolicy::Ignore,
            &config,
            StoreType::Local,
        )?)
    } else {
        return Err(format_err!("Foo"));
    };
    Ok(store)
}

py_class!(pub class mutabledeltastore |py| {
    data store: Arc<dyn HgIdMutableDeltaStore>;

    def __new__(_cls, packfilepath: Option<PyPathBuf> = None, indexedlogpath: Option<PyPathBuf> = None) -> PyResult<mutabledeltastore> {
        let store = make_mutabledeltastore(packfilepath, indexedlogpath).map_pyerr(py)?;
        mutabledeltastore::create_instance(py, store)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, deltabasenode: &PyBytes, delta: &PyBytes, metadata: Option<PyDict> = None) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, deltabasenode, delta, metadata)
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }
});

impl ExtractInnerRef for mutabledeltastore {
    type Inner = Arc<dyn HgIdMutableDeltaStore>;

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

impl HgIdDataStore for mutabledeltastore {
    fn get(&self, key: StoreKey) -> Result<StoreResult<Vec<u8>>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).get(key)
    }

    fn get_meta(&self, key: StoreKey) -> Result<StoreResult<Metadata>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).get_meta(key)
    }

    fn refresh(&self) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).refresh()
    }
}

impl LocalStore for mutabledeltastore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).get_missing(keys)
    }
}

impl HgIdMutableDeltaStore for mutabledeltastore {
    fn add(&self, delta: &Delta, metadata: &Metadata) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).add(delta, metadata)
    }

    fn flush(&self) -> Result<Option<Vec<PathBuf>>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).flush()
    }
}

fn make_mutablehistorystore(
    packfilepath: Option<PyPathBuf>,
) -> Result<Arc<dyn HgIdMutableHistoryStore + Send>> {
    let store: Arc<dyn HgIdMutableHistoryStore + Send> = if let Some(packfilepath) = packfilepath {
        Arc::new(MutableHistoryPack::new(
            packfilepath.as_path(),
            HistoryPackVersion::One,
        ))
    } else {
        return Err(format_err!("No packfile path passed in"));
    };

    Ok(store)
}

py_class!(pub class mutablehistorystore |py| {
    data store: Arc<dyn HgIdMutableHistoryStore>;

    def __new__(_cls, packfilepath: Option<PyPathBuf>) -> PyResult<mutablehistorystore> {
        let store = make_mutablehistorystore(packfilepath).map_pyerr(py)?;
        mutablehistorystore::create_instance(py, store)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, p1: &PyBytes, p2: &PyBytes, linknode: &PyBytes, copyfrom: Option<PyPathBuf>) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, p1, p2, linknode, copyfrom.as_ref())
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def getnodeinfo(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        let store = self.store(py);
        store.get_node_info_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }
});

impl ExtractInnerRef for mutablehistorystore {
    type Inner = Arc<dyn HgIdMutableHistoryStore>;

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

impl HgIdHistoryStore for mutablehistorystore {
    fn get_node_info(&self, key: &Key) -> Result<Option<NodeInfo>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).get_node_info(key)
    }

    fn refresh(&self) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).refresh()
    }
}

impl LocalStore for mutablehistorystore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).get_missing(keys)
    }
}

impl HgIdMutableHistoryStore for mutablehistorystore {
    fn add(&self, key: &Key, info: &NodeInfo) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).add(key, info)
    }

    fn flush(&self) -> Result<Option<Vec<PathBuf>>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.store(py).flush()
    }
}

struct PyHgIdRemoteStoreInner {
    py_store: PyObject,
    datastore: Option<mutabledeltastore>,
    historystore: Option<mutablehistorystore>,
}

pub struct PyHgIdRemoteStore {
    inner: RwLock<PyHgIdRemoteStoreInner>,
}

impl PyHgIdRemoteStore {
    fn prefetch(&self, keys: &[StoreKey]) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        let keys = keys
            .into_iter()
            .filter_map(|key| match key {
                StoreKey::HgId(key) => Some(from_key(py, &key)),
                StoreKey::Content(_, _) => None,
            })
            .collect::<Vec<_>>();

        if !keys.is_empty() {
            let inner = self.inner.read();
            inner
                .py_store
                .call_method(
                    py,
                    "prefetch",
                    (
                        inner.datastore.clone_ref(py),
                        inner.historystore.clone_ref(py),
                        keys,
                    ),
                    None,
                )
                .map_err(|e| PyErr::from(e))?;
        }
        Ok(())
    }
}

struct PyRemoteDataStore(Arc<PyHgIdRemoteStore>);
struct PyRemoteHistoryStore(Arc<PyHgIdRemoteStore>);

impl HgIdRemoteStore for PyHgIdRemoteStore {
    fn datastore(
        self: Arc<Self>,
        store: Arc<dyn HgIdMutableDeltaStore>,
    ) -> Arc<dyn RemoteDataStore> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.inner.write().datastore = Some(mutabledeltastore::create_instance(py, store).unwrap());

        Arc::new(PyRemoteDataStore(self))
    }

    fn historystore(
        self: Arc<Self>,
        store: Arc<dyn HgIdMutableHistoryStore>,
    ) -> Arc<dyn RemoteHistoryStore> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        self.inner.write().historystore =
            Some(mutablehistorystore::create_instance(py, store).unwrap());

        Arc::new(PyRemoteHistoryStore(self.clone()))
    }
}

impl RemoteDataStore for PyRemoteDataStore {
    fn prefetch(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        self.0.prefetch(keys)?;
        self.get_missing(keys)
    }

    fn upload(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        Ok(keys.to_vec())
    }
}

impl HgIdDataStore for PyRemoteDataStore {
    fn get(&self, key: StoreKey) -> Result<StoreResult<Vec<u8>>> {
        self.prefetch(&[key.clone()])?;
        self.0.inner.read().datastore.as_ref().unwrap().get(key)
    }

    fn get_meta(&self, key: StoreKey) -> Result<StoreResult<Metadata>> {
        match self.prefetch(&[key.clone()]) {
            Ok(_) => self
                .0
                .inner
                .read()
                .datastore
                .as_ref()
                .unwrap()
                .get_meta(key),
            Err(_) => Ok(StoreResult::NotFound(key)),
        }
    }

    fn refresh(&self) -> Result<()> {
        Ok(())
    }
}

impl LocalStore for PyRemoteDataStore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        Ok(keys.to_vec())
    }
}

impl RemoteHistoryStore for PyRemoteHistoryStore {
    fn prefetch(&self, keys: &[StoreKey]) -> Result<()> {
        self.0.prefetch(keys)
    }
}

impl HgIdHistoryStore for PyRemoteHistoryStore {
    fn get_node_info(&self, key: &Key) -> Result<Option<NodeInfo>> {
        match self.prefetch(&[StoreKey::hgid(key.clone())]) {
            Ok(()) => self
                .0
                .inner
                .read()
                .historystore
                .as_ref()
                .unwrap()
                .get_node_info(key),
            Err(_) => Ok(None),
        }
    }

    fn refresh(&self) -> Result<()> {
        Ok(())
    }
}

impl LocalStore for PyRemoteHistoryStore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        self.0
            .inner
            .read()
            .historystore
            .as_ref()
            .unwrap()
            .get_missing(keys)
    }
}

py_class!(pub class pyremotestore |py| {
    data remote: Arc<PyHgIdRemoteStore>;

    def __new__(_cls, py_store: PyObject) -> PyResult<pyremotestore> {
        let store = Arc::new(PyHgIdRemoteStore { inner: RwLock::new(PyHgIdRemoteStoreInner { py_store, datastore: None, historystore: None }) });
        pyremotestore::create_instance(py, store)
    }
});

impl ExtractInnerRef for pyremotestore {
    type Inner = Arc<PyHgIdRemoteStore>;

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

// Python wrapper around an EdenAPI-backed remote store for files.
//
// This type exists for the sole purpose of allowing an `EdenApiFileStore`
// to be passed from Rust to Python and back into Rust. It cannot be created
// by Python code and does not expose any functionality to Python.
py_class!(pub class edenapifilestore |py| {
    data remote: Arc<EdenApiFileStore>;
});

impl edenapifilestore {
    pub fn new(py: Python, remote: Arc<EdenApiFileStore>) -> PyResult<Self> {
        edenapifilestore::create_instance(py, remote)
    }
}

impl ExtractInnerRef for edenapifilestore {
    type Inner = Arc<EdenApiFileStore>;

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

// Python wrapper around an EdenAPI-backed remote store for trees.
//
// This type exists for the sole purpose of allowing an `EdenApiTreeStore`
// to be passed from Rust to Python and back into Rust. It cannot be created
// by Python code and does not expose any functionality to Python.
py_class!(pub class edenapitreestore |py| {
    data remote: Arc<EdenApiTreeStore>;
});

impl edenapitreestore {
    pub fn new(py: Python, remote: Arc<EdenApiTreeStore>) -> PyResult<Self> {
        edenapitreestore::create_instance(py, remote)
    }
}

impl ExtractInnerRef for edenapitreestore {
    type Inner = Arc<EdenApiTreeStore>;

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

py_class!(pub class contentstore |py| {
    data store: Arc<ContentStore>;

    def __new__(_cls,
        path: Option<PyPathBuf>,
        config: config,
        remote: pyremotestore,
        memcache: Option<memcachestore>,
        edenapi: Option<edenapifilestore> = None,
        suffix: Option<String> = None,
        correlator: Option<String> = None
    ) -> PyResult<contentstore> {
        let remotestore = remote.extract_inner(py);
        let config = config.get_cfg(py);

        let mut builder = ContentStoreBuilder::new(&config).correlator(correlator);

        builder = if let Some(edenapi) = edenapi {
            builder.remotestore(edenapi.extract_inner(py))
        } else {
            builder.remotestore(remotestore)
        };

        builder = if let Some(path) = path {
            builder.local_path(path.as_path())
        } else {
            builder.no_local_store()
        };

        builder = if let Some(memcache) = memcache {
            builder.memcachestore(memcache.extract_inner(py))
        } else {
            builder
        };

        builder = if let Some(suffix) = suffix {
            builder.suffix(suffix)
        } else {
            builder
        };

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

    def get(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.get_py(py, &name, node)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, deltabasenode: &PyBytes, delta: &PyBytes, metadata: Option<PyDict> = None) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, deltabasenode, delta, metadata)
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def prefetch(&self, keys: PyList) -> PyResult<PyObject> {
        let store = self.store(py);
        store.prefetch_py(py, keys)
    }

    def markforrefresh(&self) -> PyResult<PyNone> {
        let store = self.store(py);
        store.refresh_py(py)
    }

    def upload(&self, keys: PyList) -> PyResult<PyList> {
        let store = self.store(py);
        store.upload_py(py, keys)
    }

    def blob(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.blob_py(py, name, node)
    }

    def metadata(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.metadata_py(py, name, node)
    }

    def getloggedfetches(&self) -> PyResult<Vec<PyPathBuf>> {
        let store = self.store(py);
        Ok(store.get_logged_fetches().into_iter().map(|p| p.into()).collect::<Vec<PyPathBuf>>())
    }

    def getsharedmutable(&self) -> PyResult<mutabledeltastore> {
        let store = self.store(py);
        mutabledeltastore::create_instance(py, store.get_shared_mutable())
    }
});

impl ExtractInnerRef for contentstore {
    type Inner = Arc<ContentStore>;

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

py_class!(class metadatastore |py| {
    data store: Arc<MetadataStore>;

    def __new__(_cls,
        path: Option<PyPathBuf>,
        config: config,
        remote: pyremotestore,
        memcache: Option<memcachestore>,
        edenapi: Option<edenapifilestore> = None,
        suffix: Option<String> = None
    ) -> PyResult<metadatastore> {
        let remotestore = remote.extract_inner(py);
        let config = config.get_cfg(py);

        let mut builder = MetadataStoreBuilder::new(&config);

        builder = if let Some(edenapi) = edenapi {
            builder.remotestore(edenapi.extract_inner(py))
        } else {
            builder.remotestore(remotestore)
        };

        builder = if let Some(path) = path {
            builder.local_path(path.as_path())
        } else {
            builder.no_local_store()
        };

        builder = if let Some(memcache) = memcache {
            builder.memcachestore(memcache.extract_inner(py))
        } else {
            builder
        };

        builder = if let Some(suffix) = suffix {
            builder.suffix(suffix)
        } else {
            builder
        };

        let metadatastore = Arc::new(builder.build().map_pyerr(py)?);
        metadatastore::create_instance(py, metadatastore)
    }

    def getnodeinfo(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyTuple> {
        self.store(py).get_node_info_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        self.store(py).get_missing_py(py, &mut keys.iter(py)?)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, p1: &PyBytes, p2: &PyBytes, linknode: &PyBytes, copyfrom: Option<PyPathBuf>) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, p1, p2, linknode, copyfrom.as_ref())
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def prefetch(&self, keys: PyList) -> PyResult<PyObject> {
        let store = self.store(py);
        store.prefetch_py(py, keys)
    }

    def markforrefresh(&self) -> PyResult<PyNone> {
        let store = self.store(py);
        store.refresh_py(py)
    }

    def getsharedmutable(&self) -> PyResult<mutablehistorystore> {
        let store = self.store(py);
        mutablehistorystore::create_instance(py, store.get_shared_mutable())
    }
});

impl ExtractInnerRef for metadatastore {
    type Inner = Arc<MetadataStore>;

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

py_class!(pub class memcachestore |py| {
    data memcache: Arc<MemcacheStore>;

    def __new__(_cls, config: config) -> PyResult<memcachestore> {
        let config = config.get_cfg(py);
        let memcache = Arc::new(MemcacheStore::new(&config).map_pyerr(py)?);
        memcachestore::create_instance(py, memcache)
    }
});

impl ExtractInnerRef for memcachestore {
    type Inner = Arc<MemcacheStore>;

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

// TODO(meyer): Make this a `BoxedRwStore` (and introduce such a concept). Will need to implement write
// for FallbackStore.
/// Construct a file ReadStore using the provided config, optionally falling back
/// to the provided legacy HgIdDataStore.
fn make_filescmstore<'a>(
    path: Option<&'a Path>,
    config: &'a dyn Config,
    remote: Arc<PyHgIdRemoteStore>,
    memcache: Option<Arc<MemcacheStore>>,
    edenapi_filestore: Option<Arc<EdenApiFileStore>>,
    suffix: Option<String>,
    correlator: Option<String>,
) -> Result<(Arc<FileStore>, Arc<ContentStore>)> {
    let mut builder = ContentStoreBuilder::new(&config).correlator(correlator.clone());
    let mut filestore_builder = FileStoreBuilder::new(&config);

    if config.get_or_default::<bool>("scmstore", "auxindexedlog")? {
        filestore_builder = filestore_builder.store_aux_data();
    }

    if let Some(correlator) = correlator {
        filestore_builder = filestore_builder.correlator(correlator);
    }

    builder = if let Some(path) = path {
        filestore_builder = filestore_builder.local_path(path);
        builder.local_path(path)
    } else {
        builder.no_local_store()
    };

    if let Some(memcache) = memcache {
        filestore_builder = filestore_builder.memcache(memcache.clone());
        builder = builder.memcachestore(memcache)
    };

    if let Some(ref suffix) = suffix {
        builder = builder.suffix(suffix);
        filestore_builder = filestore_builder.suffix(suffix);
    }

    builder = if let Some(edenapi) = edenapi_filestore {
        filestore_builder = filestore_builder.edenapi(edenapi.clone());
        builder.remotestore(edenapi)
    } else {
        builder.remotestore(remote)
    };

    let indexedlog_local = filestore_builder.build_indexedlog_local()?;
    let indexedlog_cache = filestore_builder.build_indexedlog_cache()?;
    let lfs_local = filestore_builder.build_lfs_local()?;
    let lfs_cache = filestore_builder.build_lfs_cache()?;

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

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

    if let Some(lfs_local) = lfs_local {
        filestore_builder = filestore_builder.lfs_local(lfs_local.clone());
        builder = builder.shared_lfs_local(lfs_local);
    }

    if let Some(lfs_cache) = lfs_cache {
        filestore_builder = filestore_builder.lfs_cache(lfs_cache.clone());
        builder = builder.shared_lfs_shared(lfs_cache);
    }

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

    filestore_builder = filestore_builder.contentstore(contentstore.clone());

    let filestore = Arc::new(filestore_builder.build()?);

    Ok((filestore, contentstore))
}

py_class!(pub class filescmstore |py| {
    data store: Arc<FileStore>;
    data contentstore: Arc<ContentStore>;

    def __new__(_cls,
        path: Option<PyPathBuf>,
        config: config,
        remote: pyremotestore,
        memcache: Option<memcachestore>,
        edenapi: Option<edenapifilestore> = None,
        suffix: Option<String> = None,
        correlator: Option<String> = None
    ) -> PyResult<filescmstore> {
        // Extract Rust Values
        let path = path.as_ref().map(|v| v.as_path());
        let config = config.get_cfg(py);
        let remote = remote.extract_inner(py);
        let memcache = memcache.map(|v| v.extract_inner(py));
        let edenapi = edenapi.map(|v| v.extract_inner(py));

        let (filestore, contentstore) = make_filescmstore(path, &config, remote, memcache, edenapi, suffix, correlator).map_pyerr(py)?;

        filescmstore::create_instance(py, filestore, contentstore)
    }

    def get_contentstore(&self) -> PyResult<contentstore> {
        contentstore::create_instance(py, self.contentstore(py).clone())
    }

    def test_fetch(&self, path: PyPathBuf, local: bool) -> PyResult<PyNone> {
        let fetch_mode = if local {  FetchMode::LocalOnly } else { FetchMode::AllowRemote };
        let keys: Vec<_> = block_on_stream(block_on(file_to_async_key_stream(path.to_path_buf())).map_pyerr(py)?).collect();
        let fetch_result = self.store(py).fetch(keys.into_iter(), FileAttributes { content: true, aux_data: true }, fetch_mode);

        let io = IO::main().map_pyerr(py)?;
        let mut stdout = io.output();

        let (found, missing, _errors) = fetch_result.consume();

        for (_, file) in found.into_iter() {
            write!(stdout, "Successfully fetched file: {:#?}\n", file).map_pyerr(py)?;
        }
        for (key, _) in missing.into_iter() {
            write!(stdout, "Failed to fetch file: {:#?}\n", key).map_pyerr(py)?;
        }

        Ok(PyNone)
    }

    def fetch_contentsha256(&self, keys: PyList) -> PyResult<PyList> {
        let keys = keys
            .iter(py)
            .map(|tuple| from_tuple_to_key(py, &tuple))
            .collect::<PyResult<Vec<Key>>>()?;
        let results = PyList::new(py, &[]);
        let fetch_result = self.store(py).fetch(keys.into_iter(), FileAttributes { content: false, aux_data: true}, FetchMode::AllowRemote );

        let (found, missing, _errors) = fetch_result.consume();
        // TODO(meyer): FileStoreFetch should have utility methods to various consumer cases like this (get complete, get missing, transform to Result<EntireBatch>, transform to iterator of Result<IndividualFetch>, etc)
        // For now we just error with the first incomplete key, passing on the last recorded error if any are available.
        if let Some((key, mut errors)) = missing.into_iter().next() {
            if let Some(err) = errors.pop() {
                return Err(err.context(format!("failed to fetch {}, received error", key))).map_pyerr(py);
            } else {
                return Err(format_err!("failed to fetch {}", key)).map_pyerr(py);
            }
        }
        for (key, storefile) in found.into_iter() {
            let key_tuple = from_key_to_tuple(py, &key).into_object();
            let content_sha256 = storefile.aux_data().map_pyerr(py)?.content_sha256;
            let content_sha256 = PyBytes::new(py, &content_sha256.into_inner());
            let result_tuple = PyTuple::new(
                py,
                &[
                    key_tuple,
                    content_sha256.into_object(),
                ],
            );
            results.append(py, result_tuple.into_object());
        }
        Ok(results)
    }

    def get(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.get_py(py, &name, node)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, deltabasenode: &PyBytes, delta: &PyBytes, metadata: Option<PyDict> = None) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, deltabasenode, delta, metadata)
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def prefetch(&self, keys: PyList) -> PyResult<PyObject> {
        let store = self.store(py);
        store.prefetch_py(py, keys)
    }

    def markforrefresh(&self) -> PyResult<PyNone> {
        let store = self.store(py);
        store.refresh_py(py)
    }

    def upload(&self, keys: PyList) -> PyResult<PyList> {
        let store = self.store(py);
        store.upload_py(py, keys)
    }

    def blob(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.blob_py(py, name, node)
    }

    def metadata(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.metadata_py(py, name, node)
    }

    def getloggedfetches(&self) -> PyResult<Vec<PyPathBuf>> {
        let store = self.store(py);
        Ok(store.get_logged_fetches().into_iter().map(|p| p.into()).collect::<Vec<PyPathBuf>>())
    }

    def getmetrics(&self) -> PyResult<Vec<PyTuple>> {
        let store = self.store(py);
        Ok(store.metrics().into_iter().map(|(k, v)| {
            PyTuple::new(
                py,
                &[
                    k.to_py_object(py).into_object(),
                    v.to_py_object(py).into_object(),
                ],
            )
        }).collect::<Vec<PyTuple>>())
    }

    def getsharedmutable(&self) -> PyResult<mutabledeltastore> {
        let store = self.store(py);
        mutabledeltastore::create_instance(py, store.get_shared_mutable())
    }
});

impl ExtractInnerRef for filescmstore {
    type Inner = Arc<FileStore>;

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

// TODO(meyer): Make this a `BoxedRwStore` (and introduce such a concept). Will need to implement write
// for FallbackStore.
/// Construct a tree ReadStore using the provided config, optionally falling back
/// to the provided legacy HgIdDataStore.
fn make_treescmstore<'a>(
    path: Option<&'a Path>,
    config: &'a dyn Config,
    remote: Arc<PyHgIdRemoteStore>,
    memcache: Option<Arc<MemcacheStore>>,
    edenapi_treestore: Option<Arc<EdenApiTreeStore>>,
    filestore: Option<Arc<FileStore>>,
    suffix: Option<String>,
    correlator: Option<String>,
) -> Result<(Arc<TreeStore>, Arc<ContentStore>)> {
    let mut builder = ContentStoreBuilder::new(&config).correlator(correlator);
    let mut treestore_builder = TreeStoreBuilder::new(&config);

    builder = if let Some(path) = path {
        treestore_builder = treestore_builder.local_path(path);
        builder.local_path(path)
    } else {
        builder.no_local_store()
    };

    if let Some(memcache) = memcache {
        builder = builder.memcachestore(memcache.clone());
        treestore_builder = treestore_builder.memcache(memcache);
    };

    if let Some(ref suffix) = suffix {
        builder = builder.suffix(suffix);
        treestore_builder = treestore_builder.suffix(suffix);
    }

    // Extract EdenApiAdapter for scmstore construction later on
    builder = if let Some(edenapi) = edenapi_treestore {
        treestore_builder = treestore_builder.edenapi(edenapi.clone());
        builder.remotestore(edenapi)
    } else {
        builder.remotestore(remote)
    };

    if let Some(filestore) = filestore {
        treestore_builder = treestore_builder.filestore(filestore);
    }

    let indexedlog_local = treestore_builder.build_indexedlog_local()?;
    let indexedlog_cache = treestore_builder.build_indexedlog_cache()?;

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

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

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

    treestore_builder = treestore_builder.contentstore(contentstore.clone());

    let treestore = Arc::new(treestore_builder.build()?);

    Ok((treestore, contentstore))
}

py_class!(pub class pyfilescmstore |py| {
    data inner: Arc<PythonFileScmStore>;

    def __new__(_cls,
        py_store: PyObject,
    ) -> PyResult<pyfilescmstore> {
        pyfilescmstore::create_instance(py, Arc::new(PythonFileScmStore::new(py_store)))
    }
});

impl ExtractInnerRef for pyfilescmstore {
    type Inner = Arc<PythonFileScmStore>;

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

py_class!(pub class treescmstore |py| {
    data store: Arc<TreeStore>;
    data contentstore: Arc<ContentStore>;

    def __new__(_cls,
        path: Option<PyPathBuf>,
        config: config,
        remote: pyremotestore,
        memcache: Option<memcachestore>,
        edenapi: Option<edenapitreestore> = None,
        filestore: Option<filescmstore> = None,
        suffix: Option<String> = None,
        correlator: Option<String> = None
    ) -> PyResult<treescmstore> {
        // Extract Rust Values
        let path = path.as_ref().map(|v| v.as_path());
        let config = config.get_cfg(py);
        let remote = remote.extract_inner(py);
        let memcache = memcache.map(|v| v.extract_inner(py));
        let edenapi = edenapi.map(|v| v.extract_inner(py));
        let filestore = filestore.map(|v| v.extract_inner(py));

        let (treestore, contentstore) = make_treescmstore(path, &config, remote, memcache, edenapi, filestore, suffix, correlator).map_pyerr(py)?;

        treescmstore::create_instance(py, treestore, contentstore)
    }

    def get_contentstore(&self) -> PyResult<contentstore> {
        contentstore::create_instance(py, self.contentstore(py).clone())
    }

    def test_fetch(&self, path: PyPathBuf, local: bool) -> PyResult<PyNone> {
        let fetch_mode = if local {  FetchMode::LocalOnly } else { FetchMode::AllowRemote };
        let keys: Vec<_> = block_on_stream(block_on(file_to_async_key_stream(path.to_path_buf())).map_pyerr(py)?).collect();
        let fetch_result = self.store(py).fetch_batch(keys.into_iter(), fetch_mode);

        let io = IO::main().map_pyerr(py)?;
        let mut stdout = io.output();

        let (found, missing, _errors) = fetch_result.consume();
        for complete in found.into_iter() {
            write!(stdout, "Successfully fetched tree: {:#?}\n", complete).map_pyerr(py)?;
        }
        for incomplete in missing.into_iter() {
            write!(stdout, "Failed to fetch tree: {:#?}\n", incomplete).map_pyerr(py)?;
        }

        Ok(PyNone)
    }

    def get(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.get_py(py, &name, node)
    }

    def getdelta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyObject> {
        let store = self.store(py);
        store.get_delta_py(py, &name, node)
    }

    def getdeltachain(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_delta_chain_py(py, &name, node)
    }

    def getmeta(&self, name: PyPathBuf, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.get_meta_py(py, &name, node)
    }

    def getmissing(&self, keys: &PyObject) -> PyResult<PyList> {
        let store = self.store(py);
        store.get_missing_py(py, &mut keys.iter(py)?)
    }

    def add(&self, name: PyPathBuf, node: &PyBytes, deltabasenode: &PyBytes, delta: &PyBytes, metadata: Option<PyDict> = None) -> PyResult<PyObject> {
        let store = self.store(py);
        store.add_py(py, &name, node, deltabasenode, delta, metadata)
    }

    def flush(&self) -> PyResult<Option<Vec<PyPathBuf>>> {
        let store = self.store(py);
        store.flush_py(py)
    }

    def prefetch(&self, keys: PyList) -> PyResult<PyObject> {
        let store = self.store(py);
        store.prefetch_py(py, keys)
    }

    def markforrefresh(&self) -> PyResult<PyNone> {
        let store = self.store(py);
        store.refresh_py(py)
    }

    def upload(&self, keys: PyList) -> PyResult<PyList> {
        let store = self.store(py);
        store.upload_py(py, keys)
    }

    def blob(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyBytes> {
        let store = self.store(py);
        store.blob_py(py, name, node)
    }

    def metadata(&self, name: &PyPath, node: &PyBytes) -> PyResult<PyDict> {
        let store = self.store(py);
        store.metadata_py(py, name, node)
    }

    def getloggedfetches(&self) -> PyResult<Vec<PyPathBuf>> {
        let _ = py;
        // TODO(meyer): Make sure we're only supposed to be tracking fetches for files, not trees.
        Ok(Vec::new())
    }

    def getsharedmutable(&self) -> PyResult<mutabledeltastore> {
        let store = self.store(py);
        mutabledeltastore::create_instance(py, store.get_shared_mutable())
    }
});

impl ExtractInnerRef for treescmstore {
    type Inner = Arc<TreeStore>;

    fn extract_inner_ref<'a>(&'a self, py: Python<'a>) -> &'a Self::Inner {
        self.store(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.
 */

use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Result;
use cpython::exc;
use cpython::FromPyObject;
use cpython::NoArgs;
use cpython::ObjectProtocol;
use cpython::PyBytes;
use cpython::PyDict;
use cpython::PyList;
use cpython::PyObject;
use cpython::Python;
use cpython::PythonObject;
use cpython::PythonObjectWithTypeObject;
use cpython_ext::PyErr;
use cpython_ext::PyPathBuf;
use revisionstore::Delta;
use revisionstore::HgIdDataStore;
use revisionstore::HgIdMutableDeltaStore;
use revisionstore::LegacyStore;
use revisionstore::LocalStore;
use revisionstore::Metadata;
use revisionstore::RemoteDataStore;
use revisionstore::RepackLocation;
use revisionstore::StoreKey;
use revisionstore::StoreResult;
use types::Key;
use types::RepoPathBuf;

use crate::pythonutil::from_key_to_tuple;
use crate::pythonutil::from_tuple_to_key;
use crate::pythonutil::to_metadata;

pub struct PythonHgIdDataStore {
    py_store: PyObject,
}

impl PythonHgIdDataStore {
    pub fn new(py_store: PyObject) -> Self {
        PythonHgIdDataStore { py_store }
    }
}

impl HgIdDataStore for PythonHgIdDataStore {
    fn get(&self, key: StoreKey) -> Result<StoreResult<Vec<u8>>> {
        let key = match key {
            StoreKey::HgId(key) => key,
            contentkey => return Ok(StoreResult::NotFound(contentkey)),
        };

        let gil = Python::acquire_gil();
        let py = gil.python();
        let py_name = PyPathBuf::from(key.path.as_repo_path());
        let py_node = PyBytes::new(py, key.hgid.as_ref());

        let py_data = match self
            .py_store
            .call_method(py, "get", (py_name, py_node), None)
        {
            Ok(data) => data,
            Err(py_err) => {
                if py_err.get_type(py) == exc::KeyError::type_object(py) {
                    return Ok(StoreResult::NotFound(StoreKey::hgid(key)));
                } else {
                    return Err(PyErr::from(py_err).into());
                }
            }
        };

        let py_bytes = PyBytes::extract(py, &py_data).map_err(|e| PyErr::from(e))?;

        Ok(StoreResult::Found(py_bytes.data(py).to_vec()))
    }

    fn get_meta(&self, key: StoreKey) -> Result<StoreResult<Metadata>> {
        let key = match key {
            StoreKey::HgId(key) => key,
            contentkey => return Ok(StoreResult::NotFound(contentkey)),
        };

        let gil = Python::acquire_gil();
        let py = gil.python();
        let py_name = PyPathBuf::from(key.path.as_repo_path());
        let py_node = PyBytes::new(py, key.hgid.as_ref());
        let py_meta = match self
            .py_store
            .call_method(py, "getmeta", (py_name, py_node), None)
        {
            Ok(data) => data,
            Err(py_err) => {
                if py_err.get_type(py) == exc::KeyError::type_object(py) {
                    return Ok(StoreResult::NotFound(StoreKey::hgid(key)));
                } else {
                    return Err(PyErr::from(py_err).into());
                }
            }
        };
        let py_dict = PyDict::extract(py, &py_meta).map_err(|e| PyErr::from(e))?;
        to_metadata(py, &py_dict)
            .map_err(|e| PyErr::from(e).into())
            .map(StoreResult::Found)
    }

    fn refresh(&self) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        match self
            .py_store
            .call_method(py, "markforrefresh", NoArgs, None)
        {
            Ok(_) => Ok(()),
            Err(py_err) => Err(PyErr::from(py_err).into()),
        }
    }
}

impl RemoteDataStore for PythonHgIdDataStore {
    fn prefetch(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        let py_keys = keys
            .iter()
            .filter_map(|key| match key {
                StoreKey::HgId(key) => {
                    let py_name = PyPathBuf::from(key.path.as_repo_path());
                    let py_node = PyBytes::new(py, key.hgid.as_ref());
                    Some((py_name, py_node))
                }
                StoreKey::Content(_, _) => None,
            })
            .collect::<Vec<_>>();

        self.py_store
            .call_method(py, "prefetch", (py_keys,), None)
            .map_err(|e| PyErr::from(e))?;

        self.get_missing(&keys)
    }

    fn upload(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        Ok(keys.to_vec())
    }
}

impl LocalStore for PythonHgIdDataStore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        let py_missing = PyList::new(py, &[]);
        for key in keys.iter() {
            match key {
                StoreKey::HgId(key) => {
                    let py_key = from_key_to_tuple(py, &key);
                    py_missing.insert(py, py_missing.len(py), py_key.into_object());
                }
                StoreKey::Content(_, _) => continue,
            }
        }

        let py_missing = self
            .py_store
            .call_method(py, "getmissing", (py_missing,), None)
            .map_err(|e| PyErr::from(e))?;
        let py_list = PyList::extract(py, &py_missing).map_err(|e| PyErr::from(e))?;
        let missing = py_list
            .iter(py)
            .map(|k| {
                Ok(StoreKey::from(
                    from_tuple_to_key(py, &k).map_err(|e| PyErr::from(e))?,
                ))
            })
            .collect::<Result<Vec<StoreKey>>>()?;
        Ok(missing)
    }
}

// Dummy implementation just to satisfy ManifestStore which consumes the sub-traits of LegacyStore
// which PythonHgIdDataStore already implements. This is only required for tests.
impl LegacyStore for PythonHgIdDataStore {
    fn get_file_content(&self, _key: &Key) -> Result<Option<minibytes::Bytes>> {
        unimplemented!("")
    }

    fn get_logged_fetches(&self) -> HashSet<RepoPathBuf> {
        unimplemented!("")
    }

    fn get_shared_mutable(&self) -> Arc<dyn HgIdMutableDeltaStore> {
        unimplemented!("")
    }

    fn add_pending(
        &self,
        _key: &Key,
        _data: minibytes::Bytes,
        _meta: Metadata,
        _location: RepackLocation,
    ) -> Result<()> {
        unimplemented!("")
    }

    fn commit_pending(&self, _location: RepackLocation) -> Result<Option<Vec<PathBuf>>> {
        unimplemented!("")
    }
}

// Dummy implementation just to satisfy ManifestStore which consumes the sub-traits of LegacyStore
// which PythonHgIdDataStore already implements. This is only required for tests.
impl HgIdMutableDeltaStore for PythonHgIdDataStore {
    fn add(&self, _delta: &Delta, _metadata: &Metadata) -> Result<()> {
        unimplemented!()
    }
    fn flush(&self) -> Result<Option<Vec<PathBuf>>> {
        unimplemented!()
    }
}


/*
 * 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 async_trait::async_trait;
use cpython::FromPyObject;
use cpython::ObjectProtocol;
use cpython::PyBytes;
use cpython::PyObject;
use cpython::Python;
use cpython_ext::PyErr;
use cpython_ext::PyPathBuf;
use futures::stream::BoxStream;
use futures::stream::StreamExt;
use minibytes::Bytes;
use storemodel::ReadFileContents;
use types::Key;

pub struct PythonFileScmStore {
    read_file_contents: PyObject,
}

impl PythonFileScmStore {
    pub fn new(read_file_contents: PyObject) -> Self {
        let gil = Python::acquire_gil();
        let py = gil.python();
        if !read_file_contents.is_callable(py) {
            panic!("read_file_contents must be callable, e.g. a lambda");
        }

        PythonFileScmStore { read_file_contents }
    }
}

#[async_trait]
impl ReadFileContents for PythonFileScmStore {
    type Error = anyhow::Error;

    async fn read_file_contents(
        &self,
        keys: Vec<Key>,
    ) -> BoxStream<Result<(Bytes, Key), Self::Error>> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        let contents = keys
            .into_iter()
            .map(|k| {
                let py_name = PyPathBuf::from(k.path.as_repo_path());
                let py_node = PyBytes::new(py, k.hgid.as_ref());
                let result = self
                    .read_file_contents
                    .call(py, (py_name, py_node), None)
                    .map_err(PyErr::from)?;
                let py_bytes = PyBytes::extract(py, &result).map_err(PyErr::from)?;
                let bytes = py_bytes.data(py).to_vec();
                Ok((bytes.into(), k))
            })
            .collect::<Vec<_>>();

        futures::stream::iter(contents.into_iter()).boxed()
    }

    async fn read_rename_metadata(
        &self,
        _keys: Vec<Key>,
    ) -> BoxStream<Result<(Key, Option<Key>), Self::Error>> {
        futures::stream::empty().boxed()
    }
}


/*
 * 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 anyhow::Result;
use cpython::exc;
use cpython::FromPyObject;
use cpython::NoArgs;
use cpython::ObjectProtocol;
use cpython::PyBytes;
use cpython::PyDict;
use cpython::PyList;
use cpython::PyObject;
use cpython::Python;
use cpython::PythonObject;
use cpython::PythonObjectWithTypeObject;
use cpython_ext::PyErr;
use cpython_ext::PyPathBuf;
use revisionstore::HgIdHistoryStore;
use revisionstore::LocalStore;
use revisionstore::StoreKey;
use types::Key;
use types::NodeInfo;

use crate::pythonutil::bytes_from_tuple;
use crate::pythonutil::from_key_to_tuple;
use crate::pythonutil::from_tuple_to_key;
use crate::pythonutil::to_node_info;

pub struct PythonHgIdHistoryStore {
    py_store: PyObject,
}

impl PythonHgIdHistoryStore {
    pub fn new(py_store: PyObject) -> Self {
        PythonHgIdHistoryStore { py_store }
    }
}

impl HgIdHistoryStore for PythonHgIdHistoryStore {
    fn get_node_info(&self, key: &Key) -> Result<Option<NodeInfo>> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        let py_name = PyPathBuf::from(key.path.as_repo_path());
        let py_node = PyBytes::new(py, key.node.as_ref());

        let py_data = match self.py_store.call_method(
            py,
            "getnodeinfo",
            (py_name.clone_ref(py), py_node),
            None,
        ) {
            Ok(data) => data,
            Err(py_err) => {
                if py_err.get_type(py) == exc::KeyError::type_object(py) {
                    return Ok(None);
                } else {
                    return Err(PyErr::from(py_err).into());
                }
            }
        };

        let py_tuple = PyTuple::extract(py, &py_data).map_err(|e| PyErr::from(e))?;

        let py_p1 = bytes_from_tuple(py, &py_tuple, 0)?;
        let py_p2 = bytes_from_tuple(py, &py_tuple, 1)?;
        let py_linknode = bytes_from_tuple(py, &py_tuple, 2)?;
        let py_copyfrom = py_tuple.get_item(py, 3);
        let py_copyfrom = Option::extract(py, &py_copyfrom).map_err(|e| PyErr::from(e))?;

        Ok(Some(
            to_node_info(py, &py_name, &py_p1, &py_p2, &py_linknode, py_copyfrom)
                .map_err(|e| PyErr::from(e))?,
        ))
    }

    fn refresh(&self) -> Result<()> {
        let gil = Python::acquire_gil();
        let py = gil.python();
        match self
            .py_store
            .call_method(py, "markforrefresh", NoArgs, None)
        {
            Ok(_) => Ok(()),
            Err(py_err) => Err(PyErr::from(py_err).into()),
        }
    }
}

impl LocalStore for PythonHgIdHistoryStore {
    fn get_missing(&self, keys: &[StoreKey]) -> Result<Vec<StoreKey>> {
        let gil = Python::acquire_gil();
        let py = gil.python();

        let py_missing = PyList::new(py, &[]);
        for key in keys.iter() {
            match key {
                StoreKey::HgId(key) => {
                    let py_key = from_key_to_tuple(py, &key);
                    py_missing.insert(py, py_missing.len(py), py_key.into_object());
                }
                StoreKey::Content(_) => continue,
            }
        }

        let py_missing = self
            .py_store
            .call_method(py, "getmissing", (py_missing,), None)
            .map_err(|e| PyErr::from(e))?;
        let py_list = PyList::extract(py, &py_missing).map_err(|e| PyErr::from(e))?;
        let missing = py_list
            .iter(py)
            .map(|k| {
                Ok(StoreKey::from(
                    from_tuple_to_key(py, &k).map_err(|e| PyErr::from(e))?,
                ))
            })
            .collect::<Result<Vec<StoreKey>>>()?;
        Ok(missing)
    }
}


/*
 * 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::exc;
use cpython::FromPyObject;
use cpython::PyBytes;
use cpython::PyClone;
use cpython::PyDict;
use cpython::PyErr;
use cpython::PyObject;
use cpython::PyResult;
use cpython::PyTuple;
use cpython::Python;
use cpython::PythonObject;
use cpython::PythonObjectWithCheckedDowncast;
use cpython::ToPyObject;
use cpython_ext::ExtractInner;
use cpython_ext::PyPath;
use cpython_ext::PyPathBuf;
use cpython_ext::ResultPyErrExt;
use revisionstore::datastore::Delta;
use revisionstore::datastore::Metadata;
use revisionstore::LegacyStore;
use revisionstore::StoreKey;
use types::Key;
use types::Node;
use types::RepoPathBuf;

use crate::contentstore;
use crate::filescmstore;
use crate::treescmstore;

pub fn to_node(py: Python, node: &PyBytes) -> Node {
    let mut bytes: [u8; 20] = Default::default();
    bytes.copy_from_slice(&node.data(py)[0..20]);
    (&bytes).into()
}

pub fn to_path(py: Python, name: &PyPath) -> PyResult<RepoPathBuf> {
    name.to_repo_path()
        .map_pyerr(py)
        .map(|path| path.to_owned())
}

pub fn to_key(py: Python, name: &PyPath, node: &PyBytes) -> PyResult<Key> {
    let node = to_node(py, node);
    let path = to_path(py, name)?;
    Ok(Key::new(path, node))
}

pub fn from_key(py: Python, key: &Key) -> (PyPathBuf, PyBytes) {
    (
        PyPathBuf::from(key.path.as_repo_path()),
        PyBytes::new(py, key.hgid.as_ref()),
    )
}

pub fn to_delta(
    py: Python,
    name: &PyPath,
    node: &PyBytes,
    deltabasenode: &PyBytes,
    data: &PyBytes,
) -> PyResult<Delta> {
    let key = to_key(py, name, node)?;
    let base_key = to_key(py, name, deltabasenode)?;
    Ok(Delta {
        data: data.data(py).to_vec().into(),
        base: if base_key.hgid.is_null() {
            None
        } else {
            Some(base_key)
        },
        key,
    })
}

pub fn as_legacystore(py: Python, store: PyObject) -> PyResult<Arc<dyn LegacyStore>> {
    Ok(contentstore::downcast_from(py, store.clone_ref(py))
        .map(|s| s.extract_inner(py) as Arc<dyn LegacyStore>)
        .or_else(|_| {
            filescmstore::downcast_from(py, store.clone_ref(py))
                .map(|s| s.extract_inner(py) as Arc<dyn LegacyStore>)
        })
        .or_else(|_| {
            treescmstore::downcast_from(py, store)
                .map(|s| s.extract_inner(py) as Arc<dyn LegacyStore>)
        })?)
}

pub fn from_base(py: Python, delta: &Delta) -> (PyPathBuf, PyBytes) {
    match delta.base.as_ref() {
        Some(base) => from_key(py, &base),
        None => from_key(
            py,
            &Key::new(delta.key.path.clone(), Node::null_id().clone()),
        ),
    }
}

pub fn from_delta_to_tuple(py: Python, delta: &Delta) -> PyObject {
    let (name, node) = from_key(py, &delta.key);
    let (base_name, base_node) = from_base(py, &delta);
    let bytes = PyBytes::new(py, &delta.data);
    // A python delta is a tuple: (name, node, base name, base node, delta bytes)
    (
        name.to_py_object(py).into_object(),
        node.into_object(),
        base_name.to_py_object(py).into_object(),
        base_node.into_object(),
        bytes.into_object(),
    )
        .into_py_object(py)
        .into_object()
}

pub fn from_key_to_tuple<'a>(py: Python, key: &'a Key) -> PyTuple {
    let (py_name, py_node) = from_key(py, key);
    PyTuple::new(
        py,
        &[
            py_name.to_py_object(py).into_object(),
            py_node.into_object(),
        ],
    )
}

pub fn from_tuple_to_key(py: Python, py_tuple: &PyObject) -> PyResult<Key> {
    let py_tuple = <&PyTuple>::extract(py, &py_tuple)?.as_slice(py);
    let name = <PyPathBuf>::extract(py, &py_tuple[0])?;
    let node = <&PyBytes>::extract(py, &py_tuple[1])?;
    to_key(py, &name, &node)
}

pub fn to_metadata(py: Python, meta: &PyDict) -> PyResult<Metadata> {
    Ok(Metadata {
        flags: match meta.get_item(py, "f") {
            Some(x) => Some(u64::extract(py, &x)?),
            None => None,
        },
        size: match meta.get_item(py, "s") {
            Some(x) => Some(u64::extract(py, &x)?),
            None => None,
        },
    })
}

pub fn key_error(py: Python, key: &StoreKey) -> PyErr {
    PyErr::new::<exc::KeyError, _>(py, format!("Key not found {:?}", key))
}


/*
 * 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::path::Path;

use ::revlogindex::RevlogIndex;
use cpython::*;
use cpython_ext::PyNone;
use cpython_ext::ResultPyErrExt;
use pydag::Spans;

// XXX: The revlogindex is a temporary solution before migrating to
// segmented changelog. It is here to experiment breaking changes with
// revlog, incluing:
//
// - Redefine "head()" to only return remotenames and tracked draft heads.
// - Get rid of "filtered revs" and "repo view" layer entirely.
// - Switch phases to be defined by heads (remotenames), instead of roots.

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

py_class!(class revlogindex |py| {
    data index: RefCell<RevlogIndex>;

    def __new__(_cls, changelogipath: String, nodemappath: String) -> PyResult<Self> {
        let changelogipath = Path::new(&changelogipath);
        let nodemappath = Path::new(&nodemappath);
        let index = RevlogIndex::new(&changelogipath, &nodemappath).map_pyerr(py)?;
        Self::create_instance(py, RefCell::new(index))
    }

    /// Obtain the index data.
    def indexdata(&self) -> PyResult<pybytes::Bytes> {
        let bytes = self.index(py).borrow().changelogi_data.clone();
        pybytes::Bytes::from_bytes(py, bytes)
    }

    /// Calculate `heads(ancestors(revs))`.
    def headsancestors(&self, revs: Vec<u32>) -> PyResult<Vec<u32>> {
        let revlog = self.index(py).borrow();
        Ok(revlog.headsancestors(revs).map_pyerr(py)?)
    }

    /// Given public and draft head revision numbers, calculate the "phase sets".
    /// Return (publicset, draftset).
    def phasesets(&self, publicheads: Vec<u32>, draftheads: Vec<u32>) -> PyResult<(Spans, Spans)> {
        let revlog = self.index(py).borrow();
        let (public_set, draft_set) = revlog.phasesets(publicheads, draftheads).map_pyerr(py)?;
        Ok((Spans(public_set), Spans(draft_set)))
    }

    /// Get parent revisions.
    def parentrevs(&self, rev: u32) -> PyResult<Vec<u32>> {
        let revlog = self.index(py).borrow();
        Ok(revlog.parent_revs(rev).map_pyerr(py)?.as_revs().to_vec())
    }

    /// Insert a new revision that hasn't been written to disk.
    /// Used by revlog._addrevision.
    def insert(&self, node: PyBytes, parents: Vec<u32>, data: Option<PyBytes> = None) -> PyResult<PyNone> {
        let node = node.data(py).to_vec().into();
        let mut revlog = self.index(py).borrow_mut();
        let data = data.map(|p| p.data(py).to_vec()).unwrap_or_default();
        revlog.insert(node, parents, data.into());
        Ok(PyNone)
    }

    def __len__(&self) -> PyResult<usize> {
        let revlog = self.index(py).borrow();
        Ok(revlog.len())
    }
});