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

#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;

use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireHgId;
use crate::wire::WireKey;
use crate::wire::WireParents;
use crate::wire::WireRepoPathBuf;
use crate::wire::WireToApiConversionError;
use crate::HistoryRequest;
use crate::HistoryResponseChunk;
use crate::WireHistoryEntry;

// TODO: attributes in this file aren't renamed to 0, 1, ...
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireHistoryRequest {
    keys: Vec<WireKey>,
    length: Option<u32>,
}

impl ToWire for HistoryRequest {
    type Wire = WireHistoryRequest;

    fn to_wire(self) -> Self::Wire {
        WireHistoryRequest {
            keys: self.keys.to_wire(),
            length: self.length.to_wire(),
        }
    }
}

impl ToApi for WireHistoryRequest {
    type Api = HistoryRequest;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(HistoryRequest {
            keys: self.keys.to_api()?,
            length: self.length.to_api()?,
        })
    }
}

#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
// TODO: Rename, move more functionality to wire types?
pub struct WireWireHistoryEntry {
    node: Option<WireHgId>,
    parents: Option<WireParents>,
    linknode: Option<WireHgId>,
    copyfrom: Option<WireRepoPathBuf>,
}

impl ToWire for WireHistoryEntry {
    type Wire = WireWireHistoryEntry;

    fn to_wire(self) -> Self::Wire {
        WireWireHistoryEntry {
            node: Some(self.node.to_wire()),
            parents: Some(self.parents.to_wire()),
            linknode: Some(self.linknode.to_wire()),
            copyfrom: self.copyfrom.to_wire(),
        }
    }
}

impl ToApi for WireWireHistoryEntry {
    type Api = WireHistoryEntry;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(WireHistoryEntry {
            node: self.node.to_api()?.ok_or(
                WireToApiConversionError::CannotPopulateRequiredField("node"),
            )?,
            parents: self.parents.to_api()?.ok_or(
                WireToApiConversionError::CannotPopulateRequiredField("parents"),
            )?,
            linknode: self.linknode.to_api()?.ok_or(
                WireToApiConversionError::CannotPopulateRequiredField("linknode"),
            )?,
            copyfrom: self.copyfrom.to_api()?,
        })
    }
}

#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireHistoryResponseChunk {
    path: Option<WireRepoPathBuf>,
    entries: Vec<WireWireHistoryEntry>,
}

impl ToWire for HistoryResponseChunk {
    type Wire = WireHistoryResponseChunk;

    fn to_wire(self) -> Self::Wire {
        WireHistoryResponseChunk {
            path: Some(self.path.to_wire()),
            entries: self.entries.to_wire(),
        }
    }
}

impl ToApi for WireHistoryResponseChunk {
    type Api = HistoryResponseChunk;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(HistoryResponseChunk {
            path: self.path.to_api()?.ok_or(
                WireToApiConversionError::CannotPopulateRequiredField("path"),
            )?,
            entries: self.entries.to_api()?,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(
        WireHistoryRequest,
        WireWireHistoryEntry,
        WireHistoryResponseChunk,
    );
}


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

pub use crate::land::WireLandStackRequest;
pub use crate::land::WireLandStackResponse;
pub use crate::land::WirePushVar;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(WirePushVar, WireLandStackRequest, WireLandStackResponse,);
}


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

pub use crate::metadata::WireAnyFileContentId;
pub use crate::metadata::WireDirectoryMetadata;
pub use crate::metadata::WireDirectoryMetadataRequest;
pub use crate::metadata::WireFileMetadata;
pub use crate::metadata::WireFileMetadataRequest;
pub use crate::metadata::WireFileType;
use crate::Blake3;
use crate::ContentId;
use crate::FsnodeId;
use crate::Sha1;
use crate::Sha256;
use crate::ToApi;
use crate::ToWire;

wire_hash! {
    wire => WireFsnodeId,
    api  => FsnodeId,
    size => 32,
}

wire_hash! {
    wire => WireContentId,
    api  => ContentId,
    size => 32,
}

wire_hash! {
    wire => WireSha1,
    api  => Sha1,
    size => 20,
}

wire_hash! {
    wire => WireSha256,
    api  => Sha256,
    size => 32,
}

wire_hash! {
    wire => WireBlake3,
    api => Blake3,
    size => 32,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(
        WireFileMetadata,
        WireFileMetadataRequest,
        WireDirectoryMetadata,
        WireDirectoryMetadataRequest,
        WireAnyFileContentId
    );
}


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

//! This module contains wire representation structs for external types we'd
//! like to avoid explicitly depending on. Types will be added as they are
//! used.
//!
//! These types should all be `pub(crate)`. They're used extensively inside the
//! crate, but should never appear outside it. The methods on the request /
//! response objects should accept and return the public types from
//! `eden/scm/lib/types`.
//!
//! To maintain wire-protocol compatibility, we have some important conventions
//! and requirements for all types defined inside this module:
//!
//! 1. Every field should be renamed to a unique natural number using
//! `#[serde(rename = "0")]`. New fields should never re-use a field identifier
//! that has been used before. If a field changes semantically, it should be
//! considered a new field, and be given a new identifier.
//!
//! 2. Every enum should have an "Unknown" variant as the last variant in the
//! enum. This variant should be annotated with `#[serde(other, rename = "0")]`
//!
//! 3. When practical, fields should be annotated with
//! `#[serde(default, skip_serializing_if = "is_default")` to save space on the
//! wire. Do not use `#[serde(default)]` on the container.
//!
//! 4. All fields should be wrapped in `Option` or in a container that may be
//! empty, such as `Vec`. If an empty container has special semantics (other
//! than ignoring the field), please wrap that field in an `Option` as well to
//! distinguish between "empty" and "not present".
//!
//! Things to update when making a change to a wire type:
//!
//! 1. The Wire type definition.
//! 2. If applicable, the API type definition.
//! 3. The `ToWire` and `ToApi` implementations for the wire type.
//! 4. If the API type has changed, the `json` module.
//! 5. The `Arbitrary` implementations for the modified types.
//! 6. If a new type is introduced, add a quickcheck serialize round trip test.
//! 7. If the type has a corresponding API type, add a quickcheck wire-API round
//! trip test.

#[macro_use]
pub mod hash;

pub mod anyid;
pub mod batch;
pub mod bookmark;
pub mod clone;
pub mod commit;
pub mod errors;
pub mod file;
pub mod history;
pub mod land;
pub mod metadata;
pub mod pull;
#[cfg(test)]
pub(crate) mod tests;
pub mod token;
pub mod tree;

use std::collections::HashMap;
use std::convert::Infallible;
use std::fmt;
use std::num::NonZeroU64;

use dag_types::id::Id as DagId;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use revisionstore_types::Metadata as RevisionstoreMetadata;
use serde_derive::Deserialize;
use serde_derive::Serialize;
use thiserror::Error;
use types::path::ParseError as RepoPathParseError;
use types::HgId;
use types::Key;
use types::Parents;
use types::RepoPathBuf;

pub use crate::wire::anyid::WireAnyId;
pub use crate::wire::anyid::WireLookupRequest;
pub use crate::wire::anyid::WireLookupResponse;
pub use crate::wire::anyid::WireLookupResult;
pub use crate::wire::batch::WireBatch;
pub use crate::wire::bookmark::WireBookmarkEntry;
pub use crate::wire::bookmark::WireBookmarkRequest;
pub use crate::wire::bookmark::WireSetBookmarkRequest;
pub use crate::wire::clone::WireCloneData;
pub use crate::wire::clone::WireIdMapEntry;
pub use crate::wire::commit::WireCommitGraphEntry;
pub use crate::wire::commit::WireCommitGraphRequest;
pub use crate::wire::commit::WireCommitHashLookupRequest;
pub use crate::wire::commit::WireCommitHashLookupResponse;
pub use crate::wire::commit::WireCommitHashToLocationRequestBatch;
pub use crate::wire::commit::WireCommitHashToLocationResponse;
pub use crate::wire::commit::WireCommitLocation;
pub use crate::wire::commit::WireCommitLocationToHashRequest;
pub use crate::wire::commit::WireCommitLocationToHashRequestBatch;
pub use crate::wire::commit::WireCommitLocationToHashResponse;
pub use crate::wire::commit::WireEphemeralPrepareRequest;
pub use crate::wire::commit::WireEphemeralPrepareResponse;
pub use crate::wire::commit::WireExtra;
pub use crate::wire::commit::WireFetchSnapshotRequest;
pub use crate::wire::commit::WireFetchSnapshotResponse;
pub use crate::wire::commit::WireHgChangesetContent;
pub use crate::wire::commit::WireHgMutationEntryContent;
pub use crate::wire::commit::WireUploadBonsaiChangesetRequest;
pub use crate::wire::commit::WireUploadHgChangeset;
pub use crate::wire::commit::WireUploadHgChangesetsRequest;
pub use crate::wire::errors::WireError;
pub use crate::wire::errors::WireResult;
pub use crate::wire::file::WireFileEntry;
pub use crate::wire::file::WireFileRequest;
pub use crate::wire::file::WireUploadHgFilenodeRequest;
pub use crate::wire::file::WireUploadTokensResponse;
pub use crate::wire::history::WireHistoryRequest;
pub use crate::wire::history::WireHistoryResponseChunk;
pub use crate::wire::history::WireWireHistoryEntry;
pub use crate::wire::land::WireLandStackRequest;
pub use crate::wire::land::WireLandStackResponse;
pub use crate::wire::land::WirePushVar;
pub use crate::wire::metadata::WireAnyFileContentId;
pub use crate::wire::metadata::WireBlake3;
pub use crate::wire::metadata::WireContentId;
pub use crate::wire::metadata::WireDirectoryMetadata;
pub use crate::wire::metadata::WireDirectoryMetadataRequest;
pub use crate::wire::metadata::WireFileMetadata;
pub use crate::wire::metadata::WireFileMetadataRequest;
pub use crate::wire::metadata::WireFileType;
pub use crate::wire::metadata::WireSha1;
pub use crate::wire::metadata::WireSha256;
pub use crate::wire::token::WireUploadToken;
pub use crate::wire::token::WireUploadTokenData;
pub use crate::wire::token::WireUploadTokenSignature;
pub use crate::wire::tree::WireTreeEntry;
pub use crate::wire::tree::WireTreeRequest;
pub use crate::wire::tree::WireUploadTreeEntry;
pub use crate::wire::tree::WireUploadTreeRequest;
pub use crate::wire::tree::WireUploadTreeResponse;
use crate::EdenApiServerErrorKind;

#[derive(Copy, Clone, Debug, Error)]
#[error("invalid byte slice length, expected {expected_len} found {found_len}")]
pub struct TryFromBytesError {
    pub expected_len: usize,
    pub found_len: usize,
}

#[derive(Debug, Error)]
#[error("Failed to convert from wire to API representation")]
pub enum WireToApiConversionError {
    UnrecognizedEnumVariant(&'static str),
    CannotPopulateRequiredField(&'static str),
    PathValidationError(RepoPathParseError),
    InvalidUploadTokenType(&'static str),
}

impl From<Infallible> for WireToApiConversionError {
    fn from(v: Infallible) -> Self {
        match v {}
    }
}

impl From<RepoPathParseError> for WireToApiConversionError {
    fn from(v: RepoPathParseError) -> Self {
        WireToApiConversionError::PathValidationError(v)
    }
}

/// Convert from an EdenAPI API type to Wire type
pub trait ToWire: Sized {
    type Wire: ToApi<Api = Self> + serde::Serialize + serde::de::DeserializeOwned;

    fn to_wire(self) -> Self::Wire;
}

/// Convert from an EdenAPI Wire type to API type
pub trait ToApi: Send + Sized {
    type Api: ToWire<Wire = Self>;
    type Error: Into<WireToApiConversionError> + Send + Sync + std::error::Error;

    fn to_api(self) -> Result<Self::Api, Self::Error>;
}

impl<A: ToWire> ToWire for Vec<A> {
    type Wire = Vec<<A as ToWire>::Wire>;

    fn to_wire(self) -> Self::Wire {
        let mut out = Vec::with_capacity(self.len());
        for v in self.into_iter() {
            out.push(v.to_wire())
        }
        out
    }
}

impl<W: ToApi> ToApi for Vec<W> {
    type Api = Vec<<W as ToApi>::Api>;
    type Error = <W as ToApi>::Error;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        let mut out = Vec::with_capacity(self.len());
        for v in self.into_iter() {
            out.push(v.to_api()?)
        }
        Ok(out)
    }
}

// if needed, use macros to implement for more tuples
impl<A: ToWire, B: ToWire> ToWire for (A, B) {
    type Wire = (<A as ToWire>::Wire, <B as ToWire>::Wire);

    fn to_wire(self) -> Self::Wire {
        (self.0.to_wire(), self.1.to_wire())
    }
}

impl<A: ToApi, B: ToApi> ToApi for (A, B) {
    type Api = (<A as ToApi>::Api, <B as ToApi>::Api);
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok((
            self.0.to_api().map_err(|e| e.into())?,
            self.1.to_api().map_err(|e| e.into())?,
        ))
    }
}

impl<A: ToWire> ToWire for Option<A> {
    type Wire = Option<<A as ToWire>::Wire>;

    fn to_wire(self) -> Self::Wire {
        self.map(|a| a.to_wire())
    }
}

impl<W: ToApi> ToApi for Option<W> {
    type Api = Option<<W as ToApi>::Api>;
    type Error = <W as ToApi>::Error;
    fn to_api(self) -> Result<Self::Api, Self::Error> {
        self.map(|w| w.to_api()).transpose()
    }
}

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct WireMap<K, V>(Vec<(K, V)>);

// This is a bit more restrictive than usual hashmap, as we require Ord
// That is because in tests we want the order of keys to be consistent
impl<K: ToWire + Eq + std::hash::Hash + Ord, V: ToWire> ToWire for HashMap<K, V> {
    type Wire = WireMap<<K as ToWire>::Wire, <V as ToWire>::Wire>;

    fn to_wire(self) -> Self::Wire {
        let iter = self.into_iter();
        #[cfg(test)]
        let iter = std::collections::BTreeMap::from_iter(iter).into_iter();
        WireMap(iter.map(|(k, v)| (k.to_wire(), v.to_wire())).collect())
    }
}

impl<K: ToApi, V: ToApi> ToApi for WireMap<K, V>
where
    <K as ToApi>::Api: Eq + std::hash::Hash + Ord,
{
    type Api = HashMap<<K as ToApi>::Api, <V as ToApi>::Api>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        self.0
            .into_iter()
            .map(|(k, v)| {
                Ok((
                    k.to_api().map_err(|e| e.into())?,
                    v.to_api().map_err(|e| e.into())?,
                ))
            })
            .collect()
    }
}

// This allows using these objects as pure requests and responses
// Only use it for very simple objects which serializations don't
// incur extra costs
macro_rules! transparent_wire {
    ( $($name: ty),* $(,)? ) => {
        $(
        impl ToWire for $name {
            type Wire = $name;

            fn to_wire(self) -> Self::Wire {
                self
            }
        }

        impl ToApi for $name {
            type Api = $name;
            type Error = std::convert::Infallible;

            fn to_api(self) -> Result<Self::Api, Self::Error> {
                Ok(self)
            }
        }
     )*
    }
}

transparent_wire!(
    bool,
    u8,
    i8,
    u16,
    i16,
    u32,
    i32,
    u64,
    i64,
    usize,
    isize,
    bytes::Bytes,
    String,
    (),
);

#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct WrapNonZero<T>(T);

impl ToWire for NonZeroU64 {
    type Wire = WrapNonZero<u64>;

    fn to_wire(self) -> Self::Wire {
        WrapNonZero(self.get())
    }
}

impl ToApi for WrapNonZero<u64> {
    type Api = NonZeroU64;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        match NonZeroU64::new(self.0) {
            Some(val) => Ok(val),
            None => Err(WireToApiConversionError::CannotPopulateRequiredField(
                "Invalid value provided for NonZeroU64",
            )),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum WireEdenApiServerError {
    #[serde(rename = "1")]
    OpaqueError(String),

    #[serde(other, rename = "0")]
    Unknown,
}

impl ToWire for EdenApiServerErrorKind {
    type Wire = WireEdenApiServerError;

    fn to_wire(self) -> Self::Wire {
        use EdenApiServerErrorKind::*;
        match self {
            OpaqueError(s) => WireEdenApiServerError::OpaqueError(s),
        }
    }
}

impl ToApi for WireEdenApiServerError {
    type Api = EdenApiServerErrorKind;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        use WireEdenApiServerError::*;
        Ok(match self {
            Unknown => {
                return Err(WireToApiConversionError::UnrecognizedEnumVariant(
                    "WireEdenApiServerError",
                ));
            }
            OpaqueError(s) => EdenApiServerErrorKind::OpaqueError(s),
        })
    }
}

wire_hash! {
    wire => WireHgId,
    api  => HgId,
    size => 20,
}

impl fmt::Display for WireHgId {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self.to_api() {
            Ok(api) => fmt::Display::fmt(&api, fmt),
            Err(e) => match e {},
        }
    }
}

#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireRepoPathBuf(
    #[serde(rename = "0", default, skip_serializing_if = "is_default")] String,
);

impl ToWire for RepoPathBuf {
    type Wire = WireRepoPathBuf;

    fn to_wire(self) -> Self::Wire {
        WireRepoPathBuf(self.into_string())
    }
}

impl ToApi for WireRepoPathBuf {
    type Api = RepoPathBuf;
    type Error = RepoPathParseError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(RepoPathBuf::from_string(self.0)?)
    }
}

#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WireKey {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    path: WireRepoPathBuf,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    hgid: WireHgId,
}

impl ToWire for Key {
    type Wire = WireKey;

    fn to_wire(self) -> Self::Wire {
        WireKey {
            path: self.path.to_wire(),
            hgid: self.hgid.to_wire(),
        }
    }
}

impl ToApi for WireKey {
    type Api = Key;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(Key {
            path: self.path.to_api()?,
            hgid: self.hgid.to_api()?,
        })
    }
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum WireParents {
    #[serde(rename = "1")]
    None,

    #[serde(rename = "2")]
    One(WireHgId),

    #[serde(rename = "3")]
    Two(WireHgId, WireHgId),

    #[serde(other, rename = "0")]
    Unknown,
}

impl ToWire for Parents {
    type Wire = WireParents;

    fn to_wire(self) -> Self::Wire {
        use Parents::*;
        match self {
            None => WireParents::None,
            One(id) => WireParents::One(id.to_wire()),
            Two(id1, id2) => WireParents::Two(id1.to_wire(), id2.to_wire()),
        }
    }
}

impl ToApi for WireParents {
    type Api = Parents;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        use WireParents::*;
        Ok(match self {
            Unknown => {
                return Err(WireToApiConversionError::UnrecognizedEnumVariant(
                    "WireParents",
                ));
            }
            None => Parents::None,
            One(id) => Parents::One(id.to_api()?),
            Two(id1, id2) => Parents::Two(id1.to_api()?, id2.to_api()?),
        })
    }
}

impl Default for WireParents {
    fn default() -> Self {
        WireParents::None
    }
}

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireRevisionstoreMetadata {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    size: Option<u64>,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    flags: Option<u64>,
}

impl ToWire for RevisionstoreMetadata {
    type Wire = WireRevisionstoreMetadata;

    fn to_wire(self) -> Self::Wire {
        WireRevisionstoreMetadata {
            size: self.size,
            flags: self.flags,
        }
    }
}

impl ToApi for WireRevisionstoreMetadata {
    type Api = RevisionstoreMetadata;
    type Error = Infallible;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(RevisionstoreMetadata {
            size: self.size,
            flags: self.flags,
        })
    }
}

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[derive(Serialize, Deserialize)]
pub struct WireDagId(u64);

impl ToWire for DagId {
    type Wire = WireDagId;

    fn to_wire(self) -> Self::Wire {
        WireDagId(self.0)
    }
}

impl ToApi for WireDagId {
    type Api = DagId;
    type Error = Infallible;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(DagId(self.0))
    }
}

pub(crate) fn is_default<T: Default + PartialEq>(v: &T) -> bool {
    v == &T::default()
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireRepoPathBuf {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        RepoPathBuf::arbitrary(g).to_wire()
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireKey {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        Key::arbitrary(g).to_wire()
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireParents {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        Parents::arbitrary(g).to_wire()
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireDagId {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        DagId::arbitrary(g).to_wire()
    }
}

#[cfg(test)]
pub mod local_tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(
        WireHgId,
        WireKey,
        WireRepoPathBuf,
        WireParents,
        WireRevisionstoreMetadata,
        WireEdenApiServerError,
        WireDagId,
    );
}


/*
 * 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 type_macros::auto_wire;
use types::HgId;

#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
    any(test, feature = "for-tests"),
    derive(quickcheck_arbitrary_derive::Arbitrary)
)]
pub struct PullFastForwardRequest {
    #[id(1)]
    pub old_master: HgId,
    #[id(2)]
    pub new_master: HgId,
}

/// Pull `missing % common` in the master group.
#[auto_wire]
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
    any(test, feature = "for-tests"),
    derive(quickcheck_arbitrary_derive::Arbitrary)
)]
pub struct PullLazyRequest {
    #[id(1)]
    pub common: Vec<HgId>,
    #[id(2)]
    pub missing: Vec<HgId>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(WirePullFastForwardRequest);
    auto_wire_tests!(WirePullLazyRequest);
}


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

pub(crate) mod support {
    pub(crate) use insta_ext;
    pub(crate) use paste::paste;
    pub(crate) use quickcheck::quickcheck;
    pub(crate) use quickcheck::Arbitrary;
    pub(crate) use quickcheck::Gen;

    pub(crate) use crate::wire::ToApi;
    pub(crate) use crate::wire::ToWire;

    pub(crate) fn check_wire_roundtrip<T>(original: T) -> bool
    where
        T: ToWire + Clone + PartialEq,
        <<T as ToWire>::Wire as ToApi>::Error: std::fmt::Debug,
    {
        let wire = original.clone().to_wire();
        let roundtrip = wire.to_api().unwrap();
        original == roundtrip
    }
}

macro_rules! auto_wire_tests {
    ($wire: ident $(,)?) => {
        $crate::wire::tests::support::paste!{
            #[test]
            fn [<auto_test_ $wire:snake>]() {
                use $crate::wire::tests::support::*;
                let mut g = Gen::from_seed(42);
                g.set_size(5);
                insta_ext::assert_json!($wire::arbitrary(&mut g), [<auto_test_ $wire:snake>]);

                type Api = <$wire as ToApi>::Api;
                println!("Checking wire roundtrip");
                quickcheck(check_wire_roundtrip::<Api> as fn(Api) -> bool);
            }
        }
    };
    ($first: ident, $($rest: ident),* $(,)?) => {
        auto_wire_tests!($first);
        auto_wire_tests!($($rest),*);
    }
}
pub(crate) use auto_wire_tests;


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

pub use crate::token::WireFileContentTokenMetadata;
pub use crate::token::WireIndexableId;
pub use crate::token::WireUploadToken;
pub use crate::token::WireUploadTokenData;
pub use crate::token::WireUploadTokenMetadata;
pub use crate::token::WireUploadTokenSignature;

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(WireUploadToken, WireIndexableId);
}


/*
 * 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 bytes::Bytes;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck::Arbitrary;
#[cfg(any(test, feature = "for-tests"))]
use quickcheck_arbitrary_derive::Arbitrary;
use serde_derive::Deserialize;
use serde_derive::Serialize;

use crate::tree::TreeAttributes;
use crate::tree::TreeChildDirectoryEntry;
use crate::tree::TreeChildEntry;
use crate::tree::TreeChildFileEntry;
use crate::tree::TreeEntry;
use crate::tree::TreeRequest;
pub use crate::tree::WireUploadTreeEntry;
pub use crate::tree::WireUploadTreeRequest;
pub use crate::tree::WireUploadTreeResponse;
use crate::wire::is_default;
use crate::wire::ToApi;
use crate::wire::ToWire;
use crate::wire::WireDirectoryMetadata;
use crate::wire::WireEdenApiServerError;
use crate::wire::WireFileMetadata;
use crate::wire::WireKey;
use crate::wire::WireParents;
use crate::wire::WireToApiConversionError;
use crate::EdenApiServerError;

#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct WireTreeEntry {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    key: Option<WireKey>,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    data: Option<Bytes>,

    #[serde(rename = "2", default, skip_serializing_if = "is_default")]
    parents: Option<WireParents>,

    #[serde(rename = "3", default, skip_serializing_if = "is_default")]
    children: Option<Vec<WireTreeChildEntry>>,

    #[serde(rename = "4", default, skip_serializing_if = "is_default")]
    pub error: Option<WireEdenApiServerError>,
}

impl ToWire for Result<TreeEntry, EdenApiServerError> {
    type Wire = WireTreeEntry;

    fn to_wire(self) -> Self::Wire {
        match self {
            Ok(t) => WireTreeEntry {
                key: Some(t.key.to_wire()),
                data: t.data,
                parents: t.parents.to_wire(),
                children: t.children.to_wire(),
                error: None,
            },
            Err(e) => WireTreeEntry {
                key: e.key.to_wire(),
                error: Some(e.err.to_wire()),
                ..Default::default()
            },
        }
    }
}

impl ToApi for WireTreeEntry {
    type Api = Result<TreeEntry, EdenApiServerError>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(if let (key, Some(err)) = (self.key.clone(), self.error) {
            Err(EdenApiServerError {
                key: key.to_api()?,
                err: err.to_api()?,
            })
        } else {
            Ok(TreeEntry {
                key: self
                    .key
                    .to_api()?
                    .ok_or(WireToApiConversionError::CannotPopulateRequiredField("key"))?,
                data: self.data,
                parents: self.parents.to_api()?,
                children: self.children.to_api()?,
            })
        })
    }
}

#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
pub struct WireTreeChildEntry {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    key: Option<WireKey>,

    #[serde(rename = "3", default, skip_serializing_if = "is_default")]
    file_metadata: Option<WireFileMetadata>,

    #[serde(rename = "4", default, skip_serializing_if = "is_default")]
    directory_metadata: Option<WireDirectoryMetadata>,

    #[serde(rename = "4", default, skip_serializing_if = "is_default")]
    error: Option<WireEdenApiServerError>,
}

impl ToWire for Result<TreeChildEntry, EdenApiServerError> {
    type Wire = WireTreeChildEntry;

    fn to_wire(self) -> Self::Wire {
        match self {
            Ok(TreeChildEntry::File(t)) => WireTreeChildEntry {
                key: Some(t.key.to_wire()),
                file_metadata: t.file_metadata.to_wire(),
                directory_metadata: None,
                error: None,
            },
            Ok(TreeChildEntry::Directory(t)) => WireTreeChildEntry {
                key: Some(t.key.to_wire()),
                file_metadata: None,
                directory_metadata: t.directory_metadata.to_wire(),
                error: None,
            },
            Err(e) => WireTreeChildEntry {
                key: e.key.to_wire(),
                error: Some(e.err.to_wire()),
                ..Default::default()
            },
        }
    }
}

impl ToApi for WireTreeChildEntry {
    type Api = Result<TreeChildEntry, EdenApiServerError>;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(if let (key, Some(err)) = (self.key.clone(), self.error) {
            Err(EdenApiServerError {
                key: key.to_api()?,
                err: err.to_api()?,
            })
        } else {
            Ok(
                if let (key, Some(file_metadata)) = (self.key.clone(), self.file_metadata) {
                    TreeChildEntry::File(TreeChildFileEntry {
                        key: key
                            .to_api()?
                            .ok_or(WireToApiConversionError::CannotPopulateRequiredField("key"))?,
                        file_metadata: Some(file_metadata.to_api()?),
                    })
                } else {
                    TreeChildEntry::Directory(TreeChildDirectoryEntry {
                        key: self
                            .key
                            .to_api()?
                            .ok_or(WireToApiConversionError::CannotPopulateRequiredField("key"))?,
                        directory_metadata: self.directory_metadata.to_api()?,
                    })
                },
            )
        })
    }
}

#[derive(Clone, Default, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireTreeKeysQuery {
    pub keys: Vec<WireKey>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub enum WireTreeQuery {
    #[serde(rename = "1")]
    ByKeys(WireTreeKeysQuery),

    #[serde(other, rename = "0")]
    Other,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireTreeAttributesRequest {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    with_data: bool,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    with_parents: bool,

    #[serde(rename = "4", default, skip_serializing_if = "is_default")]
    with_child_metadata: bool,
}

impl ToWire for TreeAttributes {
    type Wire = WireTreeAttributesRequest;

    fn to_wire(self) -> Self::Wire {
        WireTreeAttributesRequest {
            with_data: self.manifest_blob,
            with_parents: self.parents,
            with_child_metadata: self.child_metadata,
        }
    }
}

impl ToApi for WireTreeAttributesRequest {
    type Api = TreeAttributes;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(TreeAttributes {
            child_metadata: self.with_child_metadata,
            parents: self.with_parents,
            manifest_blob: self.with_data,
        })
    }
}

#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
#[cfg_attr(any(test, feature = "for-tests"), derive(Arbitrary))]
pub struct WireTreeRequest {
    #[serde(rename = "0", default, skip_serializing_if = "is_default")]
    query: Option<WireTreeQuery>,

    #[serde(rename = "1", default, skip_serializing_if = "is_default")]
    attributes: Option<WireTreeAttributesRequest>,
}

impl ToWire for TreeRequest {
    type Wire = WireTreeRequest;

    fn to_wire(self) -> Self::Wire {
        WireTreeRequest {
            query: Some(WireTreeQuery::ByKeys(WireTreeKeysQuery {
                keys: self.keys.to_wire(),
            })),

            attributes: Some(self.attributes.to_wire()),
        }
    }
}

impl ToApi for WireTreeRequest {
    type Api = TreeRequest;
    type Error = WireToApiConversionError;

    fn to_api(self) -> Result<Self::Api, Self::Error> {
        Ok(TreeRequest {
            keys: match self.query {
                Some(WireTreeQuery::ByKeys(kq)) => kq.keys.to_api()?,
                Some(_) => {
                    return Err(WireToApiConversionError::UnrecognizedEnumVariant(
                        "WireTreeQuery",
                    ));
                }
                None => {
                    return Err(WireToApiConversionError::CannotPopulateRequiredField(
                        "keys",
                    ));
                }
            },
            attributes: self.attributes.to_api()?.unwrap_or_default(),
        })
    }
}

#[cfg(any(test, feature = "for-tests"))]
impl Arbitrary for WireTreeEntry {
    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
        let bytes: Option<Vec<u8>> = Arbitrary::arbitrary(g);
        Self {
            key: Arbitrary::arbitrary(g),
            data: bytes.map(|b| Bytes::from(b)),
            parents: Arbitrary::arbitrary(g),
            // Not recursing here because it causes Quickcheck to overflow the stack
            children: None,
            // TODO
            error: None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::wire::tests::auto_wire_tests;

    auto_wire_tests!(
        WireTreeAttributesRequest,
        WireTreeRequest,
        WireTreeEntry,
        WireUploadTreeResponse
    );
}


/*
 * 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 edenapi_types::ToApi;
use edenapi_types::ToWire;
use edenapi_types::WireToApiConversionError;
use quickcheck_arbitrary_derive::Arbitrary;
use type_macros::auto_wire;

// Simulating edenapi wire crate
pub mod wire {
    pub fn is_default<T: Default + PartialEq>(t: &T) -> bool {
        t == &Default::default()
    }
}

#[auto_wire]
#[derive(
    Arbitrary,
    Default,
    Debug,
    serde::Serialize,
    serde::Deserialize,
    Clone,
    PartialEq,
    Eq
)]
struct ApiObj {
    /// Doc comment should work here
    #[id(0)]
    a: i64,
    #[id(1)]
    /// Doc comment should also work here
    b: u8,
}

#[auto_wire]
#[derive(Arbitrary, Default, Debug, Clone, PartialEq, Eq)]
struct ComplexObj {
    #[id(1)]
    inner: ApiObj,
    #[id(2)]
    b: bool,
}

#[auto_wire]
#[derive(Arbitrary, Clone, Debug, PartialEq, Eq)]
enum MyEnum {
    #[id(1)]
    A,
    #[id(2)]
    B(u32),
}

impl Default for MyEnum {
    fn default() -> Self {
        Self::A
    }
}

#[test]
fn main() {
    let x = ApiObj { a: 12, b: 42 };
    let y = WireApiObj { a: 12, b: 42 };
    assert_eq!(x.clone().to_wire(), y);
    assert_eq!(x, y.clone().to_api().unwrap());
    assert_eq!(&serde_json::to_string(&y).unwrap(), r#"{"0":12,"1":42}"#);

    let x = ComplexObj { inner: x, b: true };
    let y = WireComplexObj { inner: y, b: true };
    assert_eq!(x.clone().to_wire(), y);
    assert_eq!(x, y.clone().to_api().unwrap());
    assert_eq!(
        &serde_json::to_string(&y).unwrap(),
        r#"{"1":{"0":12,"1":42},"2":true}"#
    );

    let x = MyEnum::A;
    let y = WireMyEnum::A;
    assert_eq!(x.clone().to_wire(), y);
    assert_eq!(x, y.clone().to_api().unwrap());
    assert_eq!(&serde_json::to_string(&y).unwrap(), r#""1""#);
    assert_eq!(WireMyEnum::default().to_api().unwrap(), MyEnum::A);

    let x = MyEnum::B(12);
    let y = WireMyEnum::B(12);
    assert_eq!(x.clone().to_wire(), y);
    assert_eq!(x, y.clone().to_api().unwrap());
    assert_eq!(&serde_json::to_string(&y).unwrap(), r#"{"2":12}"#);
}


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

//! # Communicating to EdenFS via Thrift

pub mod status;


/*
 * 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::HashMap;
use std::fs::symlink_metadata;
use std::fs::File;
use std::io;
use std::io::BufReader;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::str;
use std::sync::Arc;

use ::io::IsTty;
use ::io::IO;
use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Error;
use anyhow::Result;
use byteorder::BigEndian;
use byteorder::ByteOrder;
use eden::GetScmStatusParams;
use eden::GetScmStatusResult;
use eden::ScmFileStatus;
use eden::ScmStatus;
use fbthrift_socket::SocketTransport;
use serde::Deserialize;
use sha2::Digest;
use sha2::Sha256;
use status::needs_morestatus_extension;
use status::StatusBuilder;
use thrift_types::edenfs as eden;
use thrift_types::edenfs::client::EdenService;
use thrift_types::fbthrift::binary_protocol::BinaryProtocol;
use tokio_uds_compat::UnixStream;
use types::HgId;
use types::RepoPath;
use types::RepoPathBuf;

#[derive(Debug, thiserror::Error)]
#[error("")]
pub struct OperationNotSupported;

/// Standalone status command for edenfs.
///
/// TODO: This does not match the Python implementation. Namely:
/// - No pager.
/// - Colors are hard-coded here.
/// - Global gitignore paths are hard-coded on edenfs side.
/// - No matcher support.
/// - No `--rev` support.
///
/// Consider integrating with `clidispatch` cleanly:
/// - Split into multiple functions.
///   - Communicate with EdenFS via Thrift.
///   - Wrap result with dirstate (Move to another crate?).
///   - Print status (Move to another crate).
/// - Avoid `print!` directly. Use `clidispatch::io` instead.
pub fn maybe_status_fastpath(
    repo_dot_path: &Path,
    io: &IO,
    list_ignored: bool,
) -> Result<(status::Status, HashMap<RepoPathBuf, RepoPathBuf>)> {
    let rt = tokio::runtime::Runtime::new()?;

    rt.block_on(maybe_status_fastpath_internal(
        repo_dot_path,
        io,
        list_ignored,
    ))
}

async fn get_socket_transport(sock_path: &Path) -> Result<SocketTransport<UnixStream>> {
    let sock = UnixStream::connect(&sock_path).await?;
    Ok(SocketTransport::new(sock))
}

pub fn get_status(repo_root: &Path) -> Result<GetScmStatusResult> {
    let rt = tokio::runtime::Runtime::new()?;

    rt.block_on(get_status_internal(repo_root))
}

async fn get_status_internal(repo_root: &Path) -> Result<GetScmStatusResult> {
    let eden_config = EdenConfig::from_root(repo_root)?;

    let transport = get_socket_transport(&eden_config.socket).await?;
    let client = <dyn EdenService>::new(BinaryProtocol, transport);

    let dirstate_data = read_hg_dirstate(eden_config.root.as_ref())?;

    get_status_helper(&client, &eden_config.root, dirstate_data.p1, false).await
}

#[derive(Deserialize)]
struct EdenConfig {
    root: String,
    socket: PathBuf,
}

impl EdenConfig {
    fn from_root(root: &Path) -> Result<Self> {
        let dot_eden = root.join(".eden");

        // Look up the mount point name where Eden thinks this repository is
        // located.  This may be different from repo_root if a parent directory
        // of the Eden mount has been bind mounted to another location, resulting
        // in the Eden mount appearing at multiple separate locations.

        // Windows uses a toml .eden/config file due to lack of symlink support.
        if cfg!(windows) {
            let toml_path = dot_eden.join("config");

            match util::file::read_to_string(&toml_path) {
                Ok(toml_contents) => {
                    #[derive(Deserialize)]
                    struct Outer {
                        #[serde(rename = "Config")]
                        config: EdenConfig,
                    }

                    let outer: Outer = toml::from_str(&toml_contents)?;
                    return Ok(outer.config);
                }
                // Fallthrough and try symlinks just in case.
                Err(err) if err.is_not_found() => {}
                Err(err) => return Err(err.into()),
            }
        }

        let root = util::file::read_link(dot_eden.join("root"))?
            .into_os_string()
            .into_string()
            .map_err(|path| anyhow!("couldn't stringify path {:?}", path))?;
        Ok(Self {
            root,
            socket: util::file::read_link(dot_eden.join("socket"))?,
        })
    }
}

async fn maybe_status_fastpath_internal(
    repo_dot_path: &Path,
    io: &IO,
    list_ignored: bool,
) -> Result<(status::Status, HashMap<RepoPathBuf, RepoPathBuf>)> {
    let repo_root = match repo_dot_path.parent() {
        Some(p) => p,
        None => bail!("invalid dot dir {}", repo_dot_path.display()),
    };

    let eden_config = EdenConfig::from_root(repo_root).map_err(|_| OperationNotSupported)?;

    let transport = get_socket_transport(&eden_config.socket)
        .await
        .map_err(|_| OperationNotSupported)?;
    let client = <dyn EdenService>::new(BinaryProtocol, transport);

    // TODO(mbolin): Run read_hg_dirstate() and core.run() in parallel.
    let dirstate_data = read_hg_dirstate(eden_config.root.as_ref())?;

    // If any of the files are present that should trigger the 'morestatus' extension, bail out of
    // the wrapper here and default to the Python implementation. D9025269 has a prototype
    // implementation of 'morestatus' in Rust, but we should gradually rewrite Mercurial in-place
    // and call out to it here rather than maintain a parallel implementation in the wrapper.
    if needs_morestatus_extension(
        repo_dot_path,
        if HgId::from(dirstate_data.p2).is_null() {
            1
        } else {
            2
        },
    ) {
        return Err(OperationNotSupported.into());
    }

    let use_color = io.output().can_color();

    let status =
        get_status_helper(&client, &eden_config.root, dirstate_data.p1, list_ignored).await?;

    let status_output = group_entries(&repo_root, &status.status, &dirstate_data, io)?;
    let copymap = dirstate_data.copymap;
    print_errors(&status.status, io)?;

    if let Ok(version) = status.version.parse::<u32>() {
        if use_color {
            let _ = io.write_err(BOLD);
        }
        // TODO: in the future we can have this look at some configuration that
        // we ship with the eden server to provide a version check and advice

        use chrono::offset::TimeZone;
        let today = chrono::Local::now();

        // Construct a date object from the version number
        let day = version % 100;
        let month = (version % 10_000) / 100;
        let year = version / 10_000;
        if let Some(version_date) = chrono::Local
            .with_ymd_and_hms(year as i32, month, day, 0, 0, 0)
            .latest()
        {
            if today - version_date > chrono::Duration::days(45) {
                let _ = io.write_err(
                    "
Your running Eden server is more than 45 days old.  You should run
`eden restart --graceful` to update to the current release.\n",
                );
            }

            if use_color {
                let _ = io.write_err(RESET);
            }
        }
    }

    Ok((status_output, copymap))
}

async fn get_status_helper(
    client: &Arc<impl EdenService>,
    eden_root: &String,
    commit: CommitHash,
    ignored: bool,
) -> Result<GetScmStatusResult, Error> {
    client
        .getScmStatusV2(&GetScmStatusParams {
            mountPoint: eden_root.as_bytes().to_vec(),
            commit: commit.to_vec(),
            listIgnored: ignored,
            ..Default::default()
        })
        .await
        .map_err(|err| err.into())
}

fn print_errors(raw_status: &ScmStatus, io: &IO) -> Result<u8> {
    if raw_status.errors.is_empty() {
        Ok(0)
    } else {
        io.write_err("Encountered errors computing status for some paths:\n")?;
        for (path_str, error) in &raw_status.errors {
            let path = RepoPath::from_utf8(path_str)?;
            io.write_err(format!("  {}: {}\n", path, error))?;
        }
        Ok(1)
    }
}

const BOLD: &str = "\u{001B}[1m";
const RESET: &str = "\u{001B}[0m";

fn group_entries(
    repo_root: &Path,
    status: &ScmStatus,
    dirstate_data: &DirstateData,
    io: &IO,
) -> Result<status::Status> {
    let mut modified = vec![];
    let mut added = vec![];
    let mut removed = vec![];
    let mut deleted = vec![];
    let mut unknown = vec![];
    let mut ignored = vec![];
    let clean = vec![];

    let mut dirstates = dirstate_data.tuples.clone();
    for (path_str, status_code) in &status.entries {
        let path = match RepoPath::from_utf8(path_str) {
            Ok(s) => s,
            Err(e) => {
                io.write_err(format!(
                    "skipping invalid utf-8 filename: {} ({})\n",
                    String::from_utf8_lossy(path_str),
                    e
                ))?;
                continue;
            }
        };
        let dirstate = dirstates.remove(path);
        use self::DirstateDataStatus::*;
        let group = match (status_code.clone(), dirstate) {
            (ScmFileStatus::MODIFIED, Some(DirstateDataTuple { status: Remove, .. })) => {
                &mut removed
            }
            (ScmFileStatus::MODIFIED, _) => &mut modified,

            (ScmFileStatus::REMOVED, Some(DirstateDataTuple { status: Remove, .. })) => {
                &mut removed
            }
            (ScmFileStatus::REMOVED, _) => &mut deleted,

            (ScmFileStatus::ADDED, Some(DirstateDataTuple { status: Add, .. }))
            | (
                ScmFileStatus::ADDED,
                Some(DirstateDataTuple {
                    status: Normal,
                    merge_state: DirstateMergeState::OtherParent,
                    ..
                }),
            ) => &mut added,
            (ScmFileStatus::ADDED, _) => &mut unknown,

            (ScmFileStatus::IGNORED, Some(DirstateDataTuple { status: Add, .. })) => &mut added,
            (ScmFileStatus::IGNORED, _) => &mut ignored,

            (ScmFileStatus(_), _) => unreachable!(
                "Illegal state: this should not be reachable \
                 once Thrift enums are translated as Rust enums."
            ),
        };
        group.push(path.to_owned());
    }

    for (path, tuple) in dirstates {
        match tuple.status {
            DirstateDataStatus::Merge => {
                if tuple.merge_state == DirstateMergeState::NotApplicable {
                    eprintln!(
                        "Unexpected Nonnormal file {} has a merge state of \
                         NotApplicable but is marked as 'needs merging'.",
                        path
                    );
                } else {
                    modified.push(path)
                }
            }
            DirstateDataStatus::Add => match symlink_metadata(repo_root.join(path.as_str())) {
                Ok(ref attr) if attr.is_dir() => {
                    eprintln!("Suspicious: dirstate tuple points to a directory: {}", path)
                }
                Ok(_) => added.push(path),
                Err(_) => deleted.push(path),
            },
            DirstateDataStatus::Remove => removed.push(path),
            DirstateDataStatus::Normal => continue,
            DirstateDataStatus::Unknown => continue,
        }
    }

    let status = StatusBuilder::new()
        .modified(modified)
        .added(added)
        .removed(removed)
        .deleted(deleted)
        .unknown(unknown)
        .ignored(ignored)
        .clean(clean)
        .build();
    Ok(status)
}

struct DirstateReader {
    reader: BufReader<File>,
    sha256: Sha256,
}

impl DirstateReader {
    fn hashing_read(&mut self, buf: &mut [u8]) -> Result<(), io::Error> {
        self.reader.read_exact(buf)?;
        self.sha256.update(&buf);
        Ok(())
    }

    fn read_u8(&mut self) -> Result<u8, io::Error> {
        let mut buf = [0; 1];
        self.hashing_read(&mut buf)?;
        Ok(buf[0])
    }

    fn read_u16(&mut self) -> Result<u16, io::Error> {
        let mut buf = [0; 2];
        self.hashing_read(&mut buf)?;
        Ok(BigEndian::read_u16(&buf))
    }

    fn read_u32(&mut self) -> Result<u32, io::Error> {
        let mut buf = [0; 4];
        self.hashing_read(&mut buf)?;
        Ok(BigEndian::read_u32(&buf))
    }

    fn read_path(&mut self) -> Result<RepoPathBuf> {
        let path_length = self.read_u16()?;

        let mut buf = vec![0; path_length as usize];
        self.reader.read_exact(&mut buf)?;
        self.sha256.update(&buf);

        Ok(RepoPathBuf::from_utf8(buf)?)
    }

    fn verify_checksum(&mut self) -> Result<(), io::Error> {
        let mut binary_checksum = [0; 32];
        self.reader.read_exact(&mut binary_checksum)?;

        let observed_digest: [u8; 32] = self.sha256.clone().finalize().into();

        if binary_checksum != observed_digest {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Checksum in .hg/dirstate did not match the hash of the file contents.",
            ));
        }

        let mut buf = [0; 1];
        match self.reader.read_exact(&mut buf) {
            // We expect that there should be nothing left to read after the checksum
            // has been read.
            Err(ref e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(()),
            // Some unexpected type of I/O error was returned.
            Err(e) => Err(e),
            Ok(_) => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "Suspicious data is present after the end of the checksum.",
            )),
        }
    }
}

fn read_hg_dirstate(repo_root: &Path) -> Result<DirstateData> {
    let ident = identity::must_sniff_dir(repo_root)?;
    let dirstate = repo_root.join(ident.dot_dir()).join("dirstate");
    let mut reader = DirstateReader {
        reader: BufReader::new(File::open(dirstate)?),
        sha256: Sha256::new(),
    };

    let mut p1 = [0; 20];
    reader.hashing_read(&mut p1)?;
    let mut p2 = [0; 20];
    reader.hashing_read(&mut p2)?;

    let version = reader.read_u32()?;
    ensure!(version == 1, "Unsupported dirstate version: {}", version);

    let mut tuples: HashMap<RepoPathBuf, DirstateDataTuple> = HashMap::new();
    let mut copymap: HashMap<RepoPathBuf, RepoPathBuf> = HashMap::new();

    loop {
        let header = reader.read_u8()?;
        match header {
            0x01 => {
                let status = match reader.read_u8()? {
                    b'n' => DirstateDataStatus::Normal,
                    b'm' => DirstateDataStatus::Merge,
                    b'r' => DirstateDataStatus::Remove,
                    b'a' => DirstateDataStatus::Add,
                    b'?' => DirstateDataStatus::Unknown,
                    value => bail!("Unknown status type (ASCII value): {}", value),
                };

                // The next four bytes compose an unsigned integer that corresponds to mode_t.
                // This implementation of `hg status` does not need it because it does not
                // currently support path arguments that would require us to do a directory
                // traversal that would have to consider the mode.
                let _mode = reader.read_u32()?;
                let merge_state = match reader.read_u8()? as i8 {
                    0 => DirstateMergeState::NotApplicable,
                    -1 => DirstateMergeState::BothParents,
                    -2 => DirstateMergeState::OtherParent,
                    value => bail!("Unknown merge type: {}", value),
                };
                let path = reader.read_path()?;
                tuples.insert(
                    path,
                    DirstateDataTuple {
                        status,
                        merge_state,
                    },
                );
            }
            0x02 => {
                let dest = reader.read_path()?;
                let source = reader.read_path()?;
                copymap.insert(dest, source);
            }
            0xFF => {
                reader.verify_checksum()?;
                break;
            }
            header => bail!("Unrecognized header byte: {:x}", header),
        };
    }

    Ok(DirstateData {
        p1,
        p2,
        tuples,
        copymap,
    })
}

type CommitHash = [u8; 20];

#[derive(Clone, Default)]
struct DirstateData {
    p1: CommitHash,
    p2: CommitHash,
    tuples: HashMap<RepoPathBuf, DirstateDataTuple>,
    copymap: HashMap<RepoPathBuf, RepoPathBuf>,
}

#[derive(Clone)]
struct DirstateDataTuple {
    status: DirstateDataStatus,
    merge_state: DirstateMergeState,
}

#[derive(Clone, PartialEq)]
enum DirstateDataStatus {
    Normal,
    Merge,
    Remove,
    Add,
    Unknown,
}

#[derive(Clone, PartialEq)]
enum DirstateMergeState {
    NotApplicable,
    BothParents,
    OtherParent,
}

#[cfg(test)]
#[cfg(target_os = "linux")]
mod test {
    use std::collections::BTreeMap;

    use telemetry::test_utils::generate_fixture;
    use telemetry::test_utils::Fixture;

    use super::*;

    fn repo_path_buf(path: &str) -> RepoPathBuf {
        RepoPathBuf::from_string(path.to_string()).unwrap()
    }

    fn extract_output(io: IO) -> (String, String) {
        let stdout = io.with_output(|o| o.as_any().downcast_ref::<Vec<u8>>().unwrap().clone());
        let stdout = str::from_utf8(&stdout).unwrap().to_string();
        let stderr = io.with_error(|e| {
            e.as_ref()
                .unwrap()
                .as_any()
                .downcast_ref::<Vec<u8>>()
                .unwrap()
                .clone()
        });
        let stderr = str::from_utf8(&stderr).unwrap().to_string();
        (stdout, stderr)
    }

    #[derive(Default)]
    struct GroupStatusTestCase {
        p1: [u8; 20],
        p2: [u8; 20],
        entries: BTreeMap<Vec<u8>, ScmFileStatus>,
        errors: BTreeMap<Vec<u8>, String>,
        dirstate_data_tuples: HashMap<RepoPathBuf, DirstateDataTuple>,
        expected: status::Status,
        stdout: String,
        stderr: String,
    }

    /// Helper function for testing `group_entries`.
    fn test_grouping(test_case: GroupStatusTestCase) {
        let repo_root = generate_fixture(vec![]);
        let dirstate_data = DirstateData {
            p1: test_case.p1,
            p2: test_case.p2,
            tuples: test_case.dirstate_data_tuples,
            ..Default::default()
        };
        let status = ScmStatus {
            entries: test_case.entries,
            errors: test_case.errors,
            ..Default::default()
        };

        let tin = "".as_bytes();
        let tout = Vec::new();
        let terr = Vec::new();
        let io = IO::new(tin, tout, Some(terr));
        let actual_status = group_entries(repo_root.path(), &status, &dirstate_data, &io).unwrap();
        let (actual_output, actual_error) = extract_output(io);
        assert_eq!(actual_output, test_case.stdout);
        assert_eq!(actual_error, test_case.stderr);
        assert!(actual_status == test_case.expected);
    }

    #[test]
    fn empty_status() {
        test_grouping(Default::default());
    }

    #[test]
    fn all_status_types() {
        let mut entries = BTreeMap::new();
        let mut dirstate_data_tuples = HashMap::new();

        entries.insert("added.txt".into(), ScmFileStatus::ADDED);
        dirstate_data_tuples.insert(
            repo_path_buf("added.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Add,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert("added_other_parent.txt".into(), ScmFileStatus::ADDED);
        dirstate_data_tuples.insert(
            repo_path_buf("added_other_parent.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Normal,
                merge_state: DirstateMergeState::OtherParent,
            },
        );

        entries.insert("unknown.txt".into(), ScmFileStatus::ADDED);
        dirstate_data_tuples.insert(
            repo_path_buf("unknown.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Normal,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert(
            "added_even_though_normally_ignored.txt".into(),
            ScmFileStatus::IGNORED,
        );
        dirstate_data_tuples.insert(
            repo_path_buf("added_even_though_normally_ignored.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Add,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert(
            "modified_and_marked_for_removal.txt".into(),
            ScmFileStatus::MODIFIED,
        );
        dirstate_data_tuples.insert(
            repo_path_buf("modified_and_marked_for_removal.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Remove,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert("removed.txt".into(), ScmFileStatus::REMOVED);
        dirstate_data_tuples.insert(
            repo_path_buf("removed.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Remove,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert(
            "removed_but_not_marked_for_removal.txt".into(),
            ScmFileStatus::REMOVED,
        );
        dirstate_data_tuples.insert(
            repo_path_buf("removed_but_not_marked_for_removal.txt"),
            DirstateDataTuple {
                status: DirstateDataStatus::Normal,
                merge_state: DirstateMergeState::NotApplicable,
            },
        );

        entries.insert("ignored.txt".into(), ScmFileStatus::IGNORED);
        entries.insert("modified.txt".into(), ScmFileStatus::MODIFIED);

        let expected = status::StatusBuilder::new()
            .modified(vec![repo_path_buf("modified.txt")])
            .added(vec![
                repo_path_buf("added.txt"),
                repo_path_buf("added_even_though_normally_ignored.txt"),
                repo_path_buf("added_other_parent.txt"),
            ])
            .removed(vec![
                repo_path_buf("modified_and_marked_for_removal.txt"),
                repo_path_buf("removed.txt"),
            ])
            .deleted(vec![repo_path_buf(
                "removed_but_not_marked_for_removal.txt",
            )])
            .unknown(vec![repo_path_buf("unknown.txt")])
            .ignored(vec![repo_path_buf("ignored.txt")])
            .build();

        test_grouping(GroupStatusTestCase {
            entries,
            dirstate_data_tuples,
            expected,
            ..Default::default()
        });
    }

    #[test]
    fn do_not_use_morestatus_if_p2_is_unset() {
        let files = vec![(".hg", Fixture::Dir)];
        let repo_root = generate_fixture(files);
        assert!(!needs_morestatus_extension(
            &repo_root.path().join(".hg"),
            1
        ));
    }

    #[test]
    fn use_morestatus_if_p2_is_set() {
        let files = vec![(".hg", Fixture::Dir)];
        let repo_root = generate_fixture(files);
        assert!(needs_morestatus_extension(&repo_root.path().join(".hg"), 2));
    }

    #[test]
    fn use_morestatus_if_histedit_file_exists() {
        let files = vec![
            (".hg", Fixture::Dir),
            (".hg/histedit-state", Fixture::File(b"")),
        ];
        let repo_root = generate_fixture(files);
        assert!(needs_morestatus_extension(&repo_root.path().join(".hg"), 1));
    }

    #[test]
    fn use_morestatus_if_merge_slash_state_file_exists() {
        let files = vec![
            (".hg", Fixture::Dir),
            (".hg/merge", Fixture::Dir),
            (".hg/merge/state2", Fixture::File(b"")),
        ];
        let repo_root = generate_fixture(files);
        assert!(needs_morestatus_extension(&repo_root.path().join(".hg"), 1));
    }

    #[test]
    fn status_with_errors() {
        let mut entries = BTreeMap::new();
        entries.insert("unknown.txt".into(), ScmFileStatus::ADDED);
        entries.insert("modified.txt".into(), ScmFileStatus::MODIFIED);

        let mut errors = BTreeMap::new();
        errors.insert(
            "src/lib".into(),
            "unable to fetch directory data: connection reset".into(),
        );

        let stdout = "";
        let stderr = format!(
            "{}\n  src/lib: {}\n",
            "Encountered errors computing status for some paths:",
            "unable to fetch directory data: connection reset",
        );

        let status = ScmStatus {
            entries,
            errors,
            ..Default::default()
        };
        let tin = "".as_bytes();
        let tout = Vec::new();
        let terr = Vec::new();
        let io = IO::new(tin, tout, Some(terr));
        let return_code = print_errors(&status, &io).unwrap();
        let (actual_output, actual_error) = extract_output(io);
        assert_eq!(actual_output, stdout);
        assert_eq!(actual_error, stderr);
        assert_eq!(return_code, 1);
    }

    #[test]
    fn status_with_invalid_utf8() {
        let mut entries = BTreeMap::new();
        entries.insert(b"\xb0Z\xd0J\x91\x7f.INFO".to_vec(), ScmFileStatus::ADDED);
        entries.insert(b"modified.txt".to_vec(), ScmFileStatus::MODIFIED);
        let errors = BTreeMap::new();
        let stdout = "";
        let stderr = "skipping invalid utf-8 filename: ï¿½Zï¿½Jï¿½\u{7f}.INFO (Failed to parse to Utf8: \"ï¿½Zï¿½Jï¿½\\u{7f}.INFO\". invalid utf-8 sequence of 1 bytes from index 0)\n";
        let expected = status::StatusBuilder::new()
            .modified(vec![repo_path_buf("modified.txt")])
            .build();
        test_grouping(GroupStatusTestCase {
            entries,
            errors,
            expected,
            stdout: stdout.to_string(),
            stderr: stderr.to_string(),
            ..Default::default()
        });
    }
}


/*
 * 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::borrow::Cow;
use std::ffi::OsStr;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;

/// Convert bytes in the local encoding to an `OsStr`.
///
/// On Unix, this is a zero-copy operation and cannot fail.  The encoding of the `OsStr` matches
/// that of the original local bytes.
///
/// On Windows, it uses the ANSI Code Page (ACP) to perform the conversion to UTF-16,
/// which is then stored in an `OsString`.  Note that unlike the function in `local_encoding`,
/// this function does not intermediately convert to a Unicode `String`, therefore it is
/// "more native" from Windows' perspective and is more performant.
#[inline]
pub fn local_bytes_to_osstring(bytes: &[u8]) -> io::Result<Cow<'_, OsStr>> {
    Ok(Cow::Borrowed(OsStr::from_bytes(bytes)))
}

/// Convert bytes in the local encoding to a `Path`.
///
/// On Unix, this is a zero-copy operation and cannot fail.
///
/// On Windows, this converts the local bytes to an `OsString` and then converts the
/// `OsString` to a `PathBuf`, possibly returning the same errors as `local_bytes_to_osstring`.
///
/// Note that local bytes are what Mercurial stores in manifests, and are affected
/// by the "Language for non-Unicode programs" Windows setting at commit time.
/// New programs should normalize paths to UTF-8 before storing them.
#[inline]
pub fn local_bytes_to_path(bytes: &[u8]) -> io::Result<Cow<'_, Path>> {
    Ok(Cow::Borrowed(Path::new(OsStr::from_bytes(bytes))))
}

/// Convert an `OsStr` to bytes in the local encoding.
///
/// On Unix, this is a zero-copy operation and cannot fail.  The encoding of the local bytes
/// matches that of the original `OsStr`.
///
/// On Windows, it uses the ANSI Code Page (ACP) to perform the conversion
/// into bytes.  Note that unlike the function in `local_encoding`, this function
/// does not intermediately convert to a Unicode `String`, therefore it is "more native"
/// from Windows' perspective and is more performant.
#[inline]
pub fn osstring_to_local_bytes(s: &OsStr) -> io::Result<Cow<'_, [u8]>> {
    Ok(Cow::Borrowed(s.as_bytes()))
}

/// Convert a `Path` to bytes in the local encoding.
///
/// On Unix, this is a zero-copy operation and cannot fail.
///
/// On Windows, this converts the path to an `OsString` and then converts the
/// `OsString` to local bytes, possibly returning the same errors as
/// `osstring_to_local_bytes`.
///
/// Note that local bytes are what Mercurial stores in manifests, and are affected
/// by the "Language for non-Unicode programs" Windows setting at commit time.
/// New programs should normalize paths to UTF-8 before storing them.
#[inline]
pub fn path_to_local_bytes(path: &Path) -> io::Result<Cow<'_, [u8]>> {
    Ok(Cow::Borrowed(path.as_os_str().as_bytes()))
}


/*
 * 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::borrow::Cow;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::io;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::ffi::OsStringExt;
use std::path::Path;
use std::path::PathBuf;

use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::LPSTR;
use winapi::um::stringapiset::MultiByteToWideChar;
use winapi::um::stringapiset::WideCharToMultiByte;
use winapi::um::winnls::CP_ACP;

const MB_ERR_INVALID_CHARS: DWORD = 0x00000008;
const WC_COMPOSITECHECK: DWORD = 0x00000200;

/// Convert bytes in the local encoding to an `OsStr`.
///
/// On Unix, this is a zero-copy operation and cannot fail.  The encoding of the `OsStr` matches
/// that of the original local bytes.
///
/// On Windows, it uses the ANSI Code Page (ACP) to perform the conversion to UTF-16,
/// which is then stored in an `OsString`.  Note that unlike the function in `local_encoding`,
/// this function does not intermediately convert to a Unicode `String`, therefore it is
/// "more native" from Windows' perspective and is more performant.
#[inline]
pub fn local_bytes_to_osstring(bytes: &[u8]) -> io::Result<Cow<OsStr>> {
    if bytes.len() == 0 {
        return Ok(Cow::Owned(OsString::new()));
    }
    let codepage = CP_ACP;
    let len = unsafe {
        MultiByteToWideChar(
            codepage,
            MB_ERR_INVALID_CHARS,
            bytes.as_ptr() as LPSTR,
            bytes.len() as i32,
            std::ptr::null_mut(),
            0,
        )
    };
    if len == 0 {
        return Err(io::Error::last_os_error());
    }
    let mut wide: Vec<u16> = Vec::with_capacity(len as usize);
    let len = unsafe {
        wide.set_len(len as usize);
        MultiByteToWideChar(
            codepage,
            MB_ERR_INVALID_CHARS,
            bytes.as_ptr() as LPSTR,
            bytes.len() as i32,
            wide.as_mut_ptr(),
            len,
        )
    };
    if len as usize == wide.len() {
        Ok(Cow::Owned(OsString::from_wide(&wide)))
    } else {
        Err(io::Error::last_os_error())
    }
}

/// Convert bytes in the local encoding to a `Path`.
///
/// On Unix, this is a zero-copy operation and cannot fail.
///
/// On Windows, this converts the local bytes to an `OsString` and then converts the
/// `OsString` to a `PathBuf`, possibly returning the same errors as `local_bytes_to_osstring`.
///
/// Note that local bytes are what Mercurial stores in manifests, and are affected
/// by the "Language for non-Unicode programs" setting on Windows at commit time.
/// New programs should normalize paths to UTF-8 before storing them.
#[inline]
pub fn local_bytes_to_path(bytes: &[u8]) -> io::Result<Cow<Path>> {
    Ok(Cow::Owned(PathBuf::from(
        local_bytes_to_osstring(bytes)?.into_owned(),
    )))
}

/// Convert an `OsStr` to bytes in the local encoding.
///
/// On Unix, this is a zero-copy operation and cannot fail.  The encoding of the local bytes
/// matches that of the original `OsStr`.
///
/// On Windows, it uses the ANSI Code Page (ACP) to perform the conversion
/// into bytes.  Note that unlike the function in `local_encoding`, this function
/// does not intermediately convert to a Unicode `String`, therefore it is "more native"
/// from Windows' perspective and is more performant.
#[inline]
pub fn osstring_to_local_bytes(s: &OsStr) -> io::Result<Cow<[u8]>> {
    let codepage = CP_ACP;
    if s.len() == 0 {
        return Ok(Cow::Owned(Vec::new()));
    }
    let wstr: Vec<u16> = s.encode_wide().collect();
    let len = unsafe {
        WideCharToMultiByte(
            codepage,
            WC_COMPOSITECHECK,
            wstr.as_ptr(),
            wstr.len() as i32,
            std::ptr::null_mut(),
            0,
            std::ptr::null(),
            std::ptr::null_mut(),
        )
    };
    if len == 0 {
        return Err(io::Error::last_os_error());
    }
    let mut astr: Vec<u8> = Vec::with_capacity(len as usize);
    let len = unsafe {
        astr.set_len(len as usize);
        WideCharToMultiByte(
            codepage,
            WC_COMPOSITECHECK,
            wstr.as_ptr(),
            wstr.len() as i32,
            astr.as_mut_ptr() as LPSTR,
            len,
            std::ptr::null(),
            std::ptr::null_mut(),
        )
    };
    if len as usize == astr.len() {
        Ok(Cow::Owned(astr))
    } else {
        Err(io::Error::last_os_error())
    }
}

/// Convert a `Path` to bytes in the local encoding.
///
/// On Unix, this is a zero-copy operation and cannot fail.
///
/// On Windows, this converts the path to an `OsString` and then converts the
/// `OsString` to local bytes, possibly returning the same errors as
/// `osstring_to_local_bytes`.
///
/// Note that local bytes are what Mercurial stores in manifests, and are affected
/// by the "Language for non-Unicode programs" Windows setting at commit time.
/// New programs should normalize paths to UTF-8 before storing them.
#[inline]
pub fn path_to_local_bytes(path: &Path) -> io::Result<Cow<[u8]>> {
    osstring_to_local_bytes(&path.as_os_str())
}


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

use std::collections::BTreeMap;
use std::sync::Arc;

use anyhow::Result;
use async_runtime::block_unless_interrupted as block_on;
use dag::CloneData;
use dag::VertexName;
use edenapi::configmodel::Config;
use edenapi::configmodel::ConfigExt;
use edenapi::EdenApi;
use hgcommits::DagCommits;
use metalog::CommitOptions;
use metalog::MetaLog;
use tracing::instrument;
use types::HgId;

// TODO: move to a bookmarks crate
pub fn convert_to_remote(config: &dyn Config, bookmark: &str) -> Result<String> {
    Ok(format!(
        "{}/{}",
        config.must_get::<String>("remotenames", "hoist")?,
        bookmark
    ))
}

/// Download commit data via lazy pull endpoint. Returns hash of bookmarks, if any.
#[instrument(skip_all, fields(?bookmarks))]
pub fn clone(
    config: &dyn Config,
    edenapi: Arc<dyn EdenApi>,
    metalog: &mut MetaLog,
    commits: &mut Box<dyn DagCommits + Send + 'static>,
    bookmarks: Vec<String>,
) -> Result<BTreeMap<String, HgId>> {
    let bookmarks = block_on(edenapi.bookmarks(bookmarks))?.map_err(|e| e.tag_network())?;
    let bookmarks = bookmarks
        .into_iter()
        .filter_map(|bm| bm.hgid.map(|id| (bm.bookmark, id)))
        .collect::<BTreeMap<String, HgId>>();

    let heads = bookmarks.values().cloned().collect();
    let clone_data = block_on(edenapi.pull_lazy(vec![], heads))?.map_err(|e| e.tag_network())?;
    let idmap: BTreeMap<_, _> = clone_data
        .idmap
        .into_iter()
        .map(|(k, v)| (k, VertexName::copy_from(&v.into_byte_array())))
        .collect();
    let vertex_clone_data = CloneData {
        flat_segments: clone_data.flat_segments,
        idmap,
    };
    block_on(commits.import_clone_data(vertex_clone_data))??;

    let all = block_on(commits.all())??;
    let tip = block_on(all.first())??;
    if let Some(tip) = tip {
        metalog.set("tip", tip.as_ref())?;
    }
    metalog.set(
        "remotenames",
        &refencode::encode_remotenames(
            &bookmarks
                .iter()
                .map(|(bm, id)| Ok((convert_to_remote(config, bm)?, id.clone())))
                .collect::<Result<_>>()?,
        ),
    )?;
    metalog.commit(CommitOptions::default())?;

    Ok(bookmarks)
}


/*
 * 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 thiserror::Error;

#[derive(Debug, Error)]
#[error("unable to find formatter for template {0}")]
pub struct FormatterNotFound(pub String);

#[derive(Debug, Error)]
pub enum FormattingError {
    /// IO error likely caused by failure to write
    #[error("Write Error")]
    WriterError(#[from] std::io::Error),

    /// Error caused when serializing using the JSON formatter
    #[error("Serializing Error")]
    JsonFormatterError(#[from] serde_json::Error),

    /// Non-IO error caused by `format_plain` application code
    #[error(transparent)]
    PlainFormattingError(#[from] anyhow::Error),
}


/*
 * 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::HashMap;
use std::io::Write;

use serde::Serialize;
use serde_json::to_writer_pretty;

use crate::errors::FormatterNotFound;
use crate::errors::FormattingError;

pub type FormatResult<T> = std::result::Result<T, FormattingError>;

#[derive(Default)]
pub struct FormatOptions {
    pub debug: bool,
    pub verbose: bool,
    pub quiet: bool,
    pub color: bool,
    pub debug_color: bool,
}

pub trait JsonFormattable {
    fn format_json(&self, writer: &mut dyn Write) -> Result<(), serde_json::Error>;
}

impl<S: Serialize> JsonFormattable for S {
    fn format_json(&self, writer: &mut dyn Write) -> Result<(), serde_json::Error> {
        to_writer_pretty(writer, self)?;
        Ok(())
    }
}

pub trait StyleWrite: Write {
    fn write_styled(&mut self, style: &str, text: &str) -> anyhow::Result<()>;
}

struct PlainWriter<'a> {
    w: &'a mut dyn Write,
    styler: &'a mut termstyle::Styler,
    styles: &'a HashMap<String, String>,
    should_color: bool,
    debug: bool,
}

impl Write for PlainWriter<'_> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.w.write(buf)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.w.flush()
    }
}

impl StyleWrite for PlainWriter<'_> {
    fn write_styled(&mut self, style: &str, mut text: &str) -> anyhow::Result<()> {
        if self.debug {
            let mut end = "";
            if let Some(stripped) = text.strip_suffix('\n') {
                text = stripped;
                end = "\n";
            }
            write!(self.w, "[{text}|{style}]{end}")?;
            return Ok(());
        }

        if !self.should_color {
            self.w.write_all(text.as_bytes())?;
            return Ok(());
        }

        let style = style
            .split_ascii_whitespace()
            .map(|s| self.styles.get(s).map_or(s, |s| s.as_ref()))
            .collect::<Vec<&str>>()
            .join(" ");
        self.styler.render(self.w, &style, text)?;
        Ok(())
    }
}

pub trait Formattable: JsonFormattable {
    fn format_plain(
        &self,
        options: &FormatOptions,
        writer: &mut dyn StyleWrite,
    ) -> Result<(), anyhow::Error>;
}

pub trait ListFormatter {
    fn format_item(&mut self, item: &dyn Formattable) -> FormatResult<()>;
    fn begin_list(&mut self) -> FormatResult<()>;
    fn end_list(&mut self) -> FormatResult<()>;
}

pub struct PlainFormatter {
    writer: Box<dyn Write>,
    options: FormatOptions,
    styles: HashMap<String, String>,
    styler: termstyle::Styler,
}

pub struct JsonFormatter {
    writer: Box<dyn Write>,
    first_item_formatted: bool,
}

impl ListFormatter for PlainFormatter {
    fn format_item(&mut self, item: &dyn Formattable) -> FormatResult<()> {
        item.format_plain(
            &self.options,
            &mut PlainWriter {
                w: self.writer.as_mut(),
                styler: &mut self.styler,
                styles: &self.styles,
                should_color: self.options.color,
                debug: self.options.debug_color,
            },
        )
        .map_err(|err| match err.downcast::<std::io::Error>() {
            Ok(io_err) => FormattingError::WriterError(io_err),
            Err(err) => FormattingError::PlainFormattingError(err),
        })
    }

    fn begin_list(&mut self) -> FormatResult<()> {
        Ok(())
    }

    fn end_list(&mut self) -> FormatResult<()> {
        Ok(())
    }
}

impl ListFormatter for JsonFormatter {
    fn format_item(&mut self, item: &dyn Formattable) -> FormatResult<()> {
        let prev_separator = if self.first_item_formatted {
            ","
        } else {
            self.first_item_formatted = true;
            ""
        };
        write!(self.writer, "{}\n", prev_separator)?;
        item.format_json(self.writer.as_mut())?;
        Ok(())
    }

    fn begin_list(&mut self) -> FormatResult<()> {
        write!(self.writer, "[")?;
        Ok(())
    }

    fn end_list(&mut self) -> FormatResult<()> {
        write!(self.writer, "\n]\n")?;
        Ok(())
    }
}

pub fn get_formatter(
    config: &dyn configmodel::Config,
    _topic: &str,
    template: &str,
    options: FormatOptions,
    writer: Box<dyn Write>,
) -> anyhow::Result<Box<dyn ListFormatter>> {
    match template {
        "" => {
            let styles: HashMap<String, String> = config
                .keys("color")
                .into_iter()
                .filter_map(|k| {
                    if !k.contains('.') || k.starts_with("color.") {
                        None
                    } else {
                        Some((
                            k.to_string(),
                            config.get("color", &k).unwrap_or_default().to_string(),
                        ))
                    }
                })
                .collect();

            Ok(Box::new(PlainFormatter {
                writer,
                options,
                styles,
                styler: termstyle::Styler::new()?,
            }))
        }
        "json" => Ok(Box::new(JsonFormatter {
            writer,
            first_item_formatted: false,
        })),
        _ => Err(FormatterNotFound(template.into()).into()),
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::collections::BTreeMap;
    use std::io::Result as IoResult;
    use std::rc::Rc;

    use anyhow::bail;
    use serde::Deserialize;
    use serde_json::json;

    use super::*;

    #[derive(Serialize, Deserialize)]
    struct RequestTest<'a> {
        url: &'a str,
        result: u32,
    }

    impl<'a> Formattable for RequestTest<'a> {
        fn format_plain(
            &self,
            _options: &FormatOptions,
            writer: &mut dyn StyleWrite,
        ) -> Result<(), anyhow::Error> {
            write!(writer, "{}: {}", self.url, self.result)?;
            Ok(())
        }
    }

    struct Buffer {
        writer: Rc<RefCell<Vec<u8>>>,
    }

    impl Write for Buffer {
        fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
            self.writer.borrow_mut().write(buf)
        }

        fn flush(&mut self) -> IoResult<()> {
            self.writer.borrow_mut().flush()
        }
    }

    struct FaultyItem;

    impl Formattable for FaultyItem {
        fn format_plain(
            &self,
            _options: &FormatOptions,
            _writer: &mut dyn StyleWrite,
        ) -> Result<(), anyhow::Error> {
            bail!("Nope")
        }
    }

    impl JsonFormattable for FaultyItem {
        fn format_json(&self, _writer: &mut dyn Write) -> Result<(), serde_json::Error> {
            let serialized = json!({
                "x": 1,
                "y": 2,
            })
            .to_string();
            serde_json::from_str::<RequestTest>(serialized.as_str())?;
            Ok(())
        }
    }

    struct FaultyBuffer {
        buf: [u8; 0],
    }

    impl Write for FaultyBuffer {
        fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
            (&mut self.buf[..]).write(buf)
        }

        fn flush(&mut self) -> IoResult<()> {
            (&mut self.buf[..]).flush()
        }
    }

    fn get_trivial_formatter(
        template: &str,
        buffer: Rc<RefCell<Vec<u8>>>,
    ) -> anyhow::Result<Box<dyn ListFormatter>> {
        let mut colors: BTreeMap<&str, &str> = BTreeMap::new();
        colors.insert("color.foo.bar", "green");

        get_formatter(
            &colors,
            "",
            template,
            FormatOptions {
                color: true,
                ..Default::default()
            },
            Box::new(Buffer { writer: buffer }),
        )
    }

    #[test]
    fn test_formatter() {
        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let err = get_trivial_formatter("{node|short}", buf.clone())
            .err()
            .unwrap();
        assert_eq!(
            err.to_string(),
            "unable to find formatter for template {node|short}"
        );

        let item = RequestTest {
            url: "foo://bar",
            result: 200,
        };
        let mut fm = get_trivial_formatter("", buf.clone()).unwrap();
        fm.format_item(&item).unwrap();
        assert_eq!(
            String::from_utf8(buf.as_ref().borrow().clone()).unwrap(),
            "foo://bar: 200".to_string()
        );
    }

    #[derive(Serialize, Deserialize)]
    struct ColorfulItem {}

    impl Formattable for ColorfulItem {
        fn format_plain(
            &self,
            _options: &FormatOptions,
            writer: &mut dyn StyleWrite,
        ) -> Result<(), anyhow::Error> {
            writer.write_all(b"no style\n")?;
            writer.write_styled("unknown-style", "unknown style\n")?;
            writer.write_styled("red", "red\n")?;
            writer.write_styled("foo.bar", "green\n")?;
            Ok(())
        }
    }

    #[test]
    fn test_colors() {
        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let mut fm = get_trivial_formatter("", buf.clone()).unwrap();
        fm.format_item(&ColorfulItem {}).unwrap();
        assert_eq!(
            String::from_utf8(buf.as_ref().borrow().clone()).unwrap(),
            "no style
unknown style
\x1b[31mred\x1b[39m
\x1b[32mgreen\x1b[39m
",
        );
    }

    #[test]
    fn test_json_formatter() {
        let item = RequestTest {
            url: "foo://bar",
            result: 200,
        };

        // Test no items
        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let mut fm = get_trivial_formatter("json", buf.clone()).unwrap();
        fm.begin_list().unwrap();
        fm.end_list().unwrap();
        assert_eq!(
            String::from_utf8(buf.as_ref().borrow().clone()).unwrap(),
            "[\n]\n".to_string()
        );
        // Test single item
        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let mut fm = get_trivial_formatter("json", buf.clone()).unwrap();
        fm.begin_list().unwrap();
        fm.format_item(&item).unwrap();
        fm.end_list().unwrap();
        assert_eq!(
            String::from_utf8(buf.as_ref().borrow().clone()).unwrap(),
            r#"[
{
  "url": "foo://bar",
  "result": 200
}
]
"#
            .to_string()
        );
        // Test more than one item
        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let mut fm = get_trivial_formatter("json", buf.clone()).unwrap();
        fm.begin_list().unwrap();
        fm.format_item(&item).unwrap();
        fm.format_item(&item).unwrap();
        fm.end_list().unwrap();
        assert_eq!(
            String::from_utf8(buf.as_ref().borrow().clone()).unwrap(),
            r#"[
{
  "url": "foo://bar",
  "result": 200
},
{
  "url": "foo://bar",
  "result": 200
}
]
"#
            .to_string()
        );
    }

    #[test]
    fn test_errors() {
        let buf: [u8; 0] = [0; 0];
        let mut fm = get_formatter(
            &BTreeMap::<&str, &str>::new(),
            "",
            "",
            FormatOptions::default(),
            Box::new(FaultyBuffer { buf }),
        )
        .unwrap();

        let item = RequestTest {
            url: "foo://bar",
            result: 200,
        };
        assert!(matches!(
            fm.format_item(&item).err().unwrap(),
            FormattingError::WriterError(_)
        ));

        let item = FaultyItem;
        assert!(matches!(
            fm.format_item(&item).err().unwrap(),
            FormattingError::PlainFormattingError(_)
        ));

        let buf = Rc::new(RefCell::new(Vec::<u8>::new()));
        let mut fm = get_trivial_formatter("json", buf).unwrap();
        assert!(matches!(
            fm.format_item(&item).err().unwrap(),
            FormattingError::JsonFormatterError(_)
        ));
    }
}


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

pub mod errors;
pub mod formatter;

pub use crate::formatter::FormatOptions;
pub use crate::formatter::Formattable;
pub use crate::formatter::ListFormatter;
pub use crate::formatter::StyleWrite;


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

fn main() -> Result<()> {
    for arg in std::env::args().skip(1) {
        println!("{}: {}", &arg, fsinfo::fstype(&arg)?.to_string());
    }
    Ok(())
}