/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This crate provides a client to the watchman file watching service.
//!
//! Start with the [Connector](struct.Connector.html) struct and use
//! it to connect and return a [Client](struct.Client.html) struct,
//! [Client::resolve_root](struct.Client.html#method.resolve_root) to
//! resolve a path and initiate a watch, and then
//! [Client::query](struct.Client.html#method.query) to perform
//! a query, or [Client::subscribe](struct.Client.html#method.subscribe)
//! to subscribe to file changes in real time.
//!
//! This example shows how to connect and expand a glob from the
//! current working directory:
//!
//! ```no_run
//! use watchman_client::prelude::*;
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!   let mut client = Connector::new().connect().await?;
//!   let resolved = client
//!      .resolve_root(CanonicalPath::canonicalize(".")?)
//!      .await?;
//!
//!   // Basic globs -> names
//!   let files = client.glob(&resolved, &["**/*.rs"]).await?;
//!   println!("files: {:#?}", files);
//!   Ok(())
//! }
//! ```
#![deny(warnings)]

pub mod expr;
pub mod fields;
mod named_pipe;
pub mod pdu;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::io;
use std::io::Write;
use std::marker::PhantomData;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;

use bytes::Bytes;
use bytes::BytesMut;
use futures::future::FutureExt;
use futures::stream::StreamExt;
use serde_bser::de::Bunser;
use serde_bser::de::SliceRead;
use serde_bser::value::Value;
use thiserror::Error;
use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
#[cfg(unix)]
use tokio::net::UnixStream;
use tokio::process::Command;
use tokio::sync::mpsc::Receiver;
use tokio::sync::mpsc::Sender;
use tokio::sync::mpsc::UnboundedReceiver;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::Mutex;
use tokio_util::codec::Decoder;
use tokio_util::codec::FramedRead;

/// The next id number to use when generating a subscription name
static SUB_ID: AtomicUsize = AtomicUsize::new(1);

/// `use watchman_client::prelude::*` for convenient access to the types
/// provided by this crate
pub mod prelude {
    pub use crate::expr::*;
    pub use crate::fields::*;
    pub use crate::pdu::*;
    pub use crate::query_result_type;
    pub use crate::CanonicalPath;
    pub use crate::Client;
    pub use crate::Connector;
    pub use crate::ResolvedRoot;
}

use prelude::*;

#[derive(Error, Debug)]
pub enum ConnectionLost {
    #[error("Client task exited")]
    ClientTaskExited,

    #[error("Client task failed: {0}")]
    Error(String),
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("Failed to connect to Watchman: {0}")]
    ConnectionError(tokio::io::Error),

    #[error("Lost connection to watchman")]
    ConnectionLost(#[from] ConnectionLost),

    #[error(
        "While invoking the {watchman_path} CLI to discover the server connection details: {reason}, stderr=`{stderr}`"
    )]
    ConnectionDiscovery {
        watchman_path: PathBuf,
        reason: String,
        stderr: String,
    },
    #[error("The watchman server reported an error: \"{}\", while executing command: {}", .message, .command)]
    WatchmanServerError { message: String, command: String },
    #[error("The watchman server reported an error: \"{}\"", .message)]
    WatchmanResponseError { message: String },
    #[error("The watchman server didn't return a value for field `{}` in response to a `{}` command. {:?}", .fieldname, .command, .response)]
    MissingField {
        fieldname: &'static str,
        command: String,
        response: String,
    },

    #[error("Deserialization error (data: {data:x?})")]
    Deserialize {
        data: Vec<u8>,
        #[source]
        source: anyhow::Error,
    },

    #[error("Seriaization error")]
    Serialize {
        #[source]
        source: anyhow::Error,
    },

    #[error("Failed to connect to {endpoint}")]
    Connect {
        endpoint: PathBuf,
        #[source]
        source: Box<std::io::Error>,
    },
}

#[derive(Error, Debug)]
enum TaskError {
    #[error("IO Error: {0}")]
    Io(#[from] std::io::Error),

    #[error("Task is shutting down")]
    Shutdown,

    #[error("EOF on Watchman socket")]
    Eof,

    #[error("Received a unilateral PDU from the server")]
    UnilateralPdu,

    #[error("Deserialization error (data: {data:x?})")]
    Deserialize {
        #[source]
        source: anyhow::Error,
        data: Vec<u8>,
    },
}

/// The Connector defines how to connect to the watchman server.
/// You will typically use `Connector::new` to set up the connection with
/// the environmental defaults.  You might want to override those defaults
/// in situations such as integration testing environments, or in extremely
/// latency sensitive environments where the cost of performing discovery
/// is a measurable overhead.
#[derive(Default)]
pub struct Connector {
    watchman_cli_path: Option<PathBuf>,
    unix_domain: Option<PathBuf>,
}

impl Connector {
    /// Set up the connector with the system defaults.
    /// If `WATCHMAN_SOCK` is set in the environment it will preset the
    /// local IPC socket path.
    /// Otherwise the connector will invoke the watchman CLI to perform
    /// discovery.
    pub fn new() -> Self {
        let connector = Self::default();

        if let Some(val) = std::env::var_os("WATCHMAN_SOCK") {
            connector.unix_domain_socket(val)
        } else {
            connector
        }
    }

    /// If the watchman CLI is installed in a location that is not present
    /// in the PATH environment variable, this method is used to inform
    /// the connector of its location.
    pub fn watchman_cli_path<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.watchman_cli_path = Some(path.as_ref().to_path_buf());
        self
    }

    /// Specify the unix domain socket path
    pub fn unix_domain_socket<P: AsRef<Path>>(mut self, path: P) -> Self {
        self.unix_domain = Some(path.as_ref().to_path_buf());
        self
    }

    /// Resolve the unix domain socket path, taking either the override
    /// or performing discovery.
    async fn resolve_unix_domain_path(&self) -> Result<PathBuf, Error> {
        if let Some(path) = self.unix_domain.as_ref() {
            Ok(path.clone())
        } else {
            let watchman_path = self
                .watchman_cli_path
                .as_ref()
                .map(|p| p.as_ref())
                .unwrap_or_else(|| Path::new("watchman"));

            let mut cmd = Command::new(watchman_path);
            cmd.args(&["--output-encoding", "bser-v2", "get-sockname"]);

            #[cfg(windows)]
            cmd.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);

            let output = cmd
                .output()
                .await
                .map_err(|source| Error::ConnectionDiscovery {
                    watchman_path: watchman_path.to_path_buf(),
                    reason: source.to_string(),
                    stderr: "".to_string(),
                })?;

            let info: GetSockNameResponse =
                serde_bser::from_slice(&output.stdout).map_err(|source| {
                    Error::ConnectionDiscovery {
                        watchman_path: watchman_path.to_path_buf(),
                        reason: source.to_string(),
                        stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
                    }
                })?;

            let debug = format!("{:#?}", info);

            if let Some(message) = info.error {
                return Err(Error::WatchmanServerError {
                    message,
                    command: "get-sockname".into(),
                });
            }

            info.sockname.ok_or_else(|| Error::MissingField {
                fieldname: "sockname",
                command: "get-sockname".into(),
                response: debug,
            })
        }
    }

    /// Establish a connection to the watchman server.
    /// If the connector was configured to perform discovery (which is
    /// the default configuration), then this will attempt to start
    /// the watchman server.
    pub async fn connect(&self) -> Result<Client, Error> {
        let sock_path = self.resolve_unix_domain_path().await?;

        #[cfg(unix)]
        let stream = UnixStream::connect(sock_path)
            .await
            .map_err(Error::ConnectionError)?;

        #[cfg(windows)]
        let stream = named_pipe::NamedPipe::connect(sock_path).await?;

        let stream: Box<dyn ReadWriteStream> = Box::new(stream);

        let (reader, writer) = tokio::io::split(stream);

        let (request_tx, request_rx) = tokio::sync::mpsc::channel(128);

        let mut task = ClientTask {
            writer,
            reader: FramedRead::new(reader, BserSplitter),
            request_rx,
            request_queue: VecDeque::new(),
            waiting_response: false,
            subscriptions: HashMap::new(),
        };
        tokio::spawn(async move {
            if let Err(err) = task.run().await {
                let _ignored = writeln!(io::stderr(), "watchman client task failed: {}", err);
            }
        });

        let inner = Arc::new(Mutex::new(ClientInner { request_tx }));

        Ok(Client { inner })
    }
}

/// Represents a canonical path in the filesystem.
#[derive(Debug, Clone)]
pub struct CanonicalPath(PathBuf);

impl CanonicalPath {
    /// Construct the canonical version of the supplied path.
    /// This function will canonicalize the path and return the
    /// result, if successful.
    /// If you have already canonicalized the path, it is preferable
    /// to use the `with_canonicalized_path` function instead.
    pub fn canonicalize<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
        let path = std::fs::canonicalize(path)?;
        Ok(Self(Self::strip_unc_escape(path)))
    }

    /// Construct from an already canonicalized path.
    /// This function will panic if the supplied path is not an absolute
    /// path!
    pub fn with_canonicalized_path(path: PathBuf) -> Self {
        assert!(
            path.is_absolute(),
            "attempted to call \
             CanonicalPath::with_canonicalized_path on a non-canonical path! \
             You probably want to call CanonicalPath::canonicalize instead!"
        );
        Self(Self::strip_unc_escape(path))
    }

    /// Watchman doesn't like the UNC prefix being present for incoming paths
    /// in its current implementation: we should fix that, but in the meantime
    /// we want clients to be able to connect to existing versions, so let's
    /// strip off the UNC escape
    #[cfg(windows)]
    #[inline]
    fn strip_unc_escape(path: PathBuf) -> PathBuf {
        match path.to_str() {
            Some(s) if s.starts_with("\\\\?\\") => PathBuf::from(&s[4..]),
            _ => path,
        }
    }

    #[cfg(unix)]
    #[inline]
    fn strip_unc_escape(path: PathBuf) -> PathBuf {
        path
    }

    /// Consume self yielding the canonicalized PathBuf.
    pub fn into_path_buf(self) -> PathBuf {
        self.0
    }
}

/// Data that describes a watched filesystem location.
/// Watchman performs watch aggregation to project boundaries, so a request
/// to watch a subdirectory will resolve to the higher level root path
/// and a relative path offset.
/// This struct encodes both pieces of information.
#[derive(Debug, Clone)]
pub struct ResolvedRoot {
    root: PathBuf,
    relative: Option<PathBuf>,
    watcher: String,
}

impl ResolvedRoot {
    /// Returns the name of the watcher that the server is using to
    /// monitor the path.  The watcher is generally system dependent,
    /// but some systems offer multipler watchers.
    /// You generally don't care too much about the watcher that is
    /// in use, but if the watcher is a virtualized filesystem such as
    /// `eden` then you may wish to use to alternative queries to get the
    /// best performance.
    pub fn watcher(&self) -> &str {
        self.watcher.as_str()
    }

    /// Returns the root of the watchman project that is being watched
    pub fn project_root(&self) -> &Path {
        &self.root
    }

    /// Returns the absolute path to the directory that you requested be resolved.
    pub fn path(&self) -> PathBuf {
        if let Some(relative) = self.relative.as_ref() {
            self.root.join(relative)
        } else {
            self.root.clone()
        }
    }

    /// Returns the path to the directory that you requested be resolved,
    /// relative to the `project_root`.
    pub fn project_relative_path(&self) -> Option<&Path> {
        self.relative.as_ref().map(PathBuf::as_ref)
    }
}

trait ReadWriteStream: AsyncRead + AsyncWrite + std::marker::Unpin + Send {}

#[cfg(unix)]
impl ReadWriteStream for UnixStream {}

struct SendRequest {
    /// The serialized request to send to the server
    buf: Vec<u8>,
    /// to pass the response back to the requstor
    tx: tokio::sync::oneshot::Sender<Result<Bytes, String>>,
}

impl SendRequest {
    fn respond(self, result: Result<Bytes, String>) {
        let _ = self.tx.send(result);
    }
}

enum SubscriptionNotification {
    Pdu(Bytes),
    Canceled,
}

enum TaskItem {
    QueueRequest(SendRequest),
    RegisterSubscription(String, UnboundedSender<SubscriptionNotification>),
}

/// Splits BSER mesages out of a stream. Does not attempt to actually decode them.
struct BserSplitter;

impl Decoder for BserSplitter {
    type Item = Bytes;
    type Error = TaskError;

    fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
        let mut bunser = Bunser::new(SliceRead::new(buf.as_ref()));

        let pdu = match bunser.read_pdu() {
            Ok(pdu) => pdu,
            Err(source) => {
                // We know that the smallest full PDU returned by the server won't ever be smaller
                // than this size. So, if we have less than BUF_SIZE bytes, ask for more data.
                const BUF_SIZE: usize = 16;

                let missing = BUF_SIZE.saturating_sub(buf.len());

                if missing > 0 {
                    buf.reserve(missing);
                    return Ok(None);
                }

                // We should have succeded in reading some data here, but we didn't. Return an
                // error.
                return Err(TaskError::Deserialize {
                    source: source.into(),
                    data: buf.to_vec(),
                });
            }
        };

        let total_size = (pdu.start + pdu.len) as usize;

        let missing = total_size.saturating_sub(buf.len());
        if missing > 0 {
            buf.reserve(missing);
            return Ok(None);
        }

        let ret = buf.split_to(total_size);
        Ok(Some(ret.freeze()))
    }
}

/// A live connection to a watchman server.
/// Use [Connector](struct.Connector.html) to establish a connection.
pub struct Client {
    inner: Arc<Mutex<ClientInner>>,
}

/// The client task coordinates sending requests with processing
/// unilateral results
struct ClientTask {
    writer: tokio::io::WriteHalf<Box<dyn ReadWriteStream>>,
    reader: FramedRead<tokio::io::ReadHalf<Box<dyn ReadWriteStream>>, BserSplitter>,
    request_rx: Receiver<TaskItem>,
    request_queue: VecDeque<SendRequest>,
    waiting_response: bool,
    subscriptions: HashMap<String, UnboundedSender<SubscriptionNotification>>,
}

impl Drop for ClientTask {
    fn drop(&mut self) {
        self.fail_all(&TaskError::Shutdown)
    }
}

impl ClientTask {
    async fn run(&mut self) -> Result<(), TaskError> {
        // process things, and if we encounter an error, ensure that
        // we fail all outstanding requests
        match self.run_loop().await {
            Err(err) => {
                self.fail_all(&err);
                Err(err)
            }
            ok => ok,
        }
    }

    async fn run_loop(&mut self) -> Result<(), TaskError> {
        loop {
            futures::select_biased! {
                pdu = self.reader.next().fuse() => {
                    match pdu {
                        Some(pdu) => self.process_pdu(pdu?).await?,
                        None => return Err(TaskError::Eof),
                    }
                }
                task = self.request_rx.recv().fuse() => {
                    match task {
                        Some(TaskItem::QueueRequest(request)) => self.queue_request(request).await?,
                        Some(TaskItem::RegisterSubscription(name, tx)) => {
                            self.register_subscription(name, tx)
                        }
                        None => break,
                    }
                }
            }
        }

        Ok(())
    }

    fn register_subscription(
        &mut self,
        name: String,
        tx: UnboundedSender<SubscriptionNotification>,
    ) {
        self.subscriptions.insert(name, tx);
    }

    /// Generate an error for each queued request.
    /// This is called in situations where the state of the connection
    /// to the serve is non-recoverable.
    fn fail_all(&mut self, err: &TaskError) {
        while let Some(request) = self.request_queue.pop_front() {
            request.respond(Err(err.to_string()));
        }
    }

    /// If we're not waiting for the response to a request,
    /// then send the next one!
    async fn send_next_request(&mut self) -> Result<(), TaskError> {
        if !self.waiting_response && !self.request_queue.is_empty() {
            match self
                .writer
                .write_all(&self.request_queue.front().expect("not empty").buf)
                .await
            {
                Err(err) => {
                    // A failed write breaks our world; we don't want to
                    // try to continue
                    return Err(err.into());
                }
                Ok(_) => self.waiting_response = true,
            }
        }
        Ok(())
    }

    /// Queue up a new request from the client code, and then
    /// check to see if we can send a queued request to the server.
    async fn queue_request(&mut self, request: SendRequest) -> Result<(), TaskError> {
        self.request_queue.push_back(request);
        self.send_next_request().await?;
        Ok(())
    }

    /// Dispatch a PDU that we just read to the appropriate client code.
    async fn process_pdu(&mut self, pdu: Bytes) -> Result<(), TaskError> {
        use serde::Deserialize;
        #[derive(Deserialize, Debug)]
        pub struct Unilateral {
            pub unilateral: bool,
            pub subscription: String,
            #[serde(default)]
            pub canceled: bool,
        }

        if let Ok(unilateral) = bunser::<Unilateral>(&pdu) {
            if let Some(subscription) = self.subscriptions.get_mut(&unilateral.subscription) {
                let msg = if unilateral.canceled {
                    SubscriptionNotification::Canceled
                } else {
                    SubscriptionNotification::Pdu(pdu)
                };

                if subscription.send(msg).is_err() || unilateral.canceled {
                    // The `Subscription` was dropped; we don't need to
                    // treat this as terminal for this client session,
                    // so just de-register the handler
                    self.subscriptions.remove(&unilateral.subscription);
                }
            }
        } else if self.waiting_response {
            let request = self
                .request_queue
                .pop_front()
                .expect("waiting_response is only true when request_queue is not empty");
            self.waiting_response = false;

            request.respond(Ok(pdu));
        } else {
            // This should never happen as we're not doing any subscription stuff
            return Err(TaskError::UnilateralPdu);
        }

        self.send_next_request().await?;
        Ok(())
    }
}

fn bunser<T>(buf: &[u8]) -> Result<T, Error>
where
    T: serde::de::DeserializeOwned,
{
    let response: T = serde_bser::from_slice(&buf).map_err(|source| Error::Deserialize {
        source: source.into(),
        data: buf.to_vec(),
    })?;
    Ok(response)
}

struct ClientInner {
    request_tx: Sender<TaskItem>,
}

impl ClientInner {
    /// This method will send a request to the watchman server
    /// and wait for its response.
    /// This is really an internal method, but it is made public in case a
    /// consumer of this crate needs to issue a command for which we haven't
    /// yet made an ergonomic wrapper.
    pub(crate) async fn generic_request<Request, Response>(
        &mut self,
        request: Request,
    ) -> Result<Response, Error>
    where
        Request: serde::Serialize + std::fmt::Debug,
        Response: serde::de::DeserializeOwned,
    {
        // Step 1: serialize into a bser byte buffer
        let mut request_data = vec![];
        serde_bser::ser::serialize(&mut request_data, &request).map_err(|source| {
            Error::Serialize {
                source: source.into(),
            }
        })?;

        // Step 2: ask the client task to send it for us
        let (tx, rx) = tokio::sync::oneshot::channel();
        self.request_tx
            .send(TaskItem::QueueRequest(SendRequest {
                buf: request_data,
                tx,
            }))
            .await
            .map_err(|_| ConnectionLost::ClientTaskExited)?;

        // Step 3: wait for the client task to give us the response
        let pdu_data = rx
            .await
            .map_err(|_| ConnectionLost::ClientTaskExited)?
            .map_err(ConnectionLost::Error)?;

        // Step 4: sniff for an error response in the deserialized data
        use serde::Deserialize;
        #[derive(Deserialize, Debug)]
        struct MaybeError {
            #[serde(default)]
            error: Option<String>,
        }

        // Step 5: deserialize into the caller-desired format
        let maybe_err: MaybeError = bunser(&pdu_data)?;
        if let Some(message) = maybe_err.error {
            return Err(Error::WatchmanServerError {
                message,
                command: format!("{:#?}", request),
            });
        }

        let response: Response = bunser(&pdu_data)?;
        Ok(response)
    }
}

/// Returned by [Subscription::next](struct.Subscription.html#method.next)
/// as events are observed by Watchman.
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub enum SubscriptionData<F>
where
    F: serde::de::DeserializeOwned + std::fmt::Debug + Clone + QueryFieldList,
{
    /// The Subscription was canceled.
    /// This could be for a number of reasons that are not knowable
    /// to the client:
    /// * The user may have issued the `watch-del` command
    /// * The containing watch root may have been deleted or
    ///   un-mounted
    /// * The containing watch may no longer be accessible
    ///   to the watchman user/process
    /// * Some other error condition that renders the project
    ///   unwatchable may have occurred
    /// * The server may have been gracefully shutdown
    ///
    /// A Canceled subscription will deliver no further results.
    Canceled,

    /// Files matching your criteria have changed.
    /// The QueryResult contains the details.
    /// Pay attention to the
    /// [is_fresh_instance](pdu/struct.QueryResult.html#structfield.is_fresh_instance) field!
    FilesChanged(QueryResult<F>),

    /// Some other watchman client has broadcast that the watched
    /// project is entering a new named state.
    /// For example, `hg.update` may be generated by the FB
    /// internal source control system to indicate that the
    /// working copy is about to be updated to a new revision.
    /// The metadata field contains data specific to the named
    /// state.
    StateEnter {
        state_name: String,
        metadata: Option<Value>,
    },
    /// Some other watchman client has broadcast that the watched
    /// project is no longer in the named state.
    /// This event can also be generated if the watchman client
    /// that entered the state disconnects unexpectedly from
    /// the watchman server.
    /// The `metadata` field will be `None` in that situation.
    StateLeave {
        state_name: String,
        metadata: Option<Value>,
    },
}

/// A handle to a subscription initiated via `Client::subscribe`.
/// Repeatedly call `Subscription::next().await` to yield the next
/// set of subscription results.
/// Use the `cancel` method to gracefully halt this subscription
/// if you have a program that creates and destroys subscriptions
/// throughout its lifetime.
pub struct Subscription<F>
where
    F: serde::de::DeserializeOwned + std::fmt::Debug + Clone + QueryFieldList,
{
    name: String,
    inner: Arc<Mutex<ClientInner>>,
    root: ResolvedRoot,
    responses: UnboundedReceiver<SubscriptionNotification>,
    _phantom: PhantomData<F>,
}

impl<F> Subscription<F>
where
    F: serde::de::DeserializeOwned + std::fmt::Debug + Clone + QueryFieldList,
{
    /// Returns the assigned name for this subscription instance.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Yield the next set of subscription data.
    /// An error is generated if the subscription is disconnected
    /// from the server.
    #[allow(clippy::should_implement_trait)]
    pub async fn next(&mut self) -> Result<SubscriptionData<F>, Error> {
        let msg = self
            .responses
            .recv()
            .await
            .ok_or(ConnectionLost::ClientTaskExited)?;

        match msg {
            SubscriptionNotification::Pdu(pdu) => {
                let response: QueryResult<F> = bunser(&pdu)?;

                if let Some(state_name) = response.state_enter {
                    Ok(SubscriptionData::StateEnter {
                        state_name,
                        metadata: response.state_metadata,
                    })
                } else if let Some(state_name) = response.state_leave {
                    Ok(SubscriptionData::StateLeave {
                        state_name,
                        metadata: response.state_metadata,
                    })
                } else {
                    Ok(SubscriptionData::FilesChanged(response))
                }
            }
            SubscriptionNotification::Canceled => {
                self.responses.close();
                Ok(SubscriptionData::Canceled)
            }
        }
    }

    /// Gracefully cancel this subscription.
    /// If you are imminently about to drop the associated client then you
    /// need not call this method.
    /// However, if the associated client is going to live much longer
    /// than a Subscription that you are about to drop,
    /// then it is recommended that you call `cancel` so that the server
    /// will stop delivering data about it.
    pub async fn cancel(self) -> Result<(), Error> {
        let mut inner = self.inner.lock().await;
        let _: UnsubscribeResponse = inner
            .generic_request(Unsubscribe("unsubscribe", self.root.root, self.name))
            .await?;
        Ok(())
    }
}

impl Client {
    /// This method will send a request to the watchman server
    /// and wait for its response.
    /// This is really an internal method, but it is made public in case a
    /// consumer of this crate needs to issue a command for which we haven't
    /// yet made an ergonomic wrapper.
    #[doc(hidden)]
    pub async fn generic_request<Request, Response>(
        &self,
        request: Request,
    ) -> Result<Response, Error>
    where
        Request: serde::Serialize + std::fmt::Debug,
        Response: serde::de::DeserializeOwned,
    {
        let mut inner = self.inner.lock().await;
        let response: Response = inner.generic_request(request).await?;
        Ok(response)
    }

    pub async fn version(&self) -> Result<GetVersionResponse, Error> {
        self.generic_request(&["version"]).await
    }

    pub async fn watch_list(&self) -> Result<WatchListResponse, Error> {
        self.generic_request(&["watch-list"]).await
    }

    /// This method will attempt to assert the state named `state_name`
    /// on the watchman server. This is used to facilitate advanced settling
    /// in subscriptions.
    ///
    /// Only one client can assert a given named state for a given root at
    /// a given time; an error will be returned if another client owns the
    /// requested state assertion.
    ///
    /// If successful, the state will remain asserted until the owning client
    /// either issues a `state-leave` or disconnects from the server.
    ///
    /// The optional `metadata` will be published to all subscribers of the
    /// root and made visible via `SubscriptionData::StateEnter::metadata`.
    ///
    /// See also: <https://facebook.github.io/watchman/docs/cmd/state-enter.html>
    pub async fn state_enter(
        &self,
        root: &ResolvedRoot,
        state_name: &str,
        sync_timeout: SyncTimeout,
        metadata: Option<Value>,
    ) -> Result<(), Error> {
        let request = StateEnterLeaveRequest(
            "state-enter",
            root.root.clone(),
            StateEnterLeaveParams {
                name: state_name,
                metadata,
                sync_timeout,
            },
        );

        let _response: StateEnterLeaveResponse = self.generic_request(request).await?;
        Ok(())
    }

    /// This method will attempt to release an owned state assertion for the
    /// state named `state_name` on the watchman server. This is used to facilitate
    /// advanced settling in subscriptions.
    ///
    /// The optional `metadata` will be published to all subscribers of the
    /// root and made visible via `SubscriptionData::StateLeave::metadata`.
    ///
    /// See also: <https://facebook.github.io/watchman/docs/cmd/state-leave.html>
    pub async fn state_leave(
        &self,
        root: &ResolvedRoot,
        state_name: &str,
        sync_timeout: SyncTimeout,
        metadata: Option<Value>,
    ) -> Result<(), Error> {
        let request = StateEnterLeaveRequest(
            "state-leave",
            root.root.clone(),
            StateEnterLeaveParams {
                name: state_name,
                metadata,
                sync_timeout,
            },
        );

        let _response: StateEnterLeaveResponse = self.generic_request(request).await?;
        Ok(())
    }

    /// This is typically the first method invoked on a client.
    /// Its purpose is to ensure that the watchman server is watching the specified
    /// path and to resolve it to a `ResolvedRoot` instance.
    ///
    /// The path to resolve must be a canonical path; watchman performs strict name
    /// resolution to detect TOCTOU issues and will generate an error if the path
    /// is not the canonical name.
    ///
    /// Note that for regular filesystem watches, if the requested path is not
    /// yet being watched, this method will not yield until the watchman server
    /// has completed a recursive crawl of that portion of the filesystem.
    /// In other words, the worst case performance of this is
    /// `O(recursive-number-of-files)` and is impacted by the underlying storage
    /// device and its performance characteristics.
    pub async fn resolve_root(&self, path: CanonicalPath) -> Result<ResolvedRoot, Error> {
        let response: WatchProjectResponse = self
            .generic_request(WatchProjectRequest("watch-project", path.0.clone()))
            .await?;

        Ok(ResolvedRoot {
            root: response.watch,
            relative: response.relative_path,
            watcher: response.watcher,
        })
    }

    /// Perform a generic watchman query.
    /// The `F` type is a struct defined by the
    /// [query_result_type!](macro.query_result_type.html) macro,
    /// or, if you want only the file name from the results, the
    /// [NameOnly](struct.NameOnly.html) struct.
    ///
    /// ```
    /// use watchman_client::prelude::*;
    /// use serde::Deserialize;
    ///
    /// query_result_type! {
    ///     struct NameAndType {
    ///         name: NameField,
    ///         file_type: FileTypeField,
    ///     }
    /// }
    ///
    /// async fn query(
    ///    client: &mut Client,
    ///    resolved: &ResolvedRoot
    /// ) -> Result<(), Box<dyn std::error::Error>> {
    ///    let response: QueryResult<NameAndType> = client
    ///        .query(
    ///            &resolved,
    ///               QueryRequestCommon {
    ///                glob: Some(vec!["**/*.rs".to_string()]),
    ///                ..Default::default()
    ///            },
    ///        )
    ///        .await?;
    ///    println!("response: {:#?}", response);
    ///    Ok(())
    /// }
    /// ```
    ///
    /// When constructing your result type, you can select from the
    /// following fields:
    ///
    /// * [CTimeAsFloatField](struct.CTimeAsFloatField.html)
    /// * [CTimeField](struct.CTimeField.html)
    /// * [ContentSha1HexField](struct.ContentSha1HexField.html)
    /// * [CreatedClockField](struct.CreatedClockField.html)
    /// * [DeviceNumberField](struct.DeviceNumberField.html)
    /// * [ExistsField](struct.ExistsField.html)
    /// * [FileTypeField](struct.FileTypeField.html)
    /// * [InodeNumberField](struct.InodeNumberField.html)
    /// * [MTimeAsFloatField](struct.MTimeAsFloatField.html)
    /// * [MTimeField](struct.MTimeField.html)
    /// * [ModeAndPermissionsField](struct.ModeAndPermissionsField.html)
    /// * [NameField](struct.NameField.html)
    /// * [NewField](struct.NewField.html)
    /// * [NumberOfLinksField](struct.NumberOfLinksField.html)
    /// * [ObservedClockField](struct.ObservedClockField.html)
    /// * [OwnerGidField](struct.OwnerGidField.html)
    /// * [OwnerUidField](struct.OwnerUidField.html)
    /// * [SizeField](struct.SizeField.html)
    /// * [SymlinkTargetField](struct.SymlinkTargetField.html)
    ///
    /// (See [the fields module](fields/index.html) for a definitive list)
    ///
    /// The file names are all relative to the `root` parameter.
    pub async fn query<F>(
        &self,
        root: &ResolvedRoot,
        query: QueryRequestCommon,
    ) -> Result<QueryResult<F>, Error>
    where
        F: serde::de::DeserializeOwned + std::fmt::Debug + Clone + QueryFieldList,
    {
        let query = QueryRequest(
            "query",
            root.root.clone(),
            QueryRequestCommon {
                relative_root: root.relative.clone(),
                fields: F::field_list(),
                ..query
            },
        );

        let response: QueryResult<F> = self.generic_request(query.clone()).await?;

        Ok(response)
    }

    /// Create a Subscription that will yield file changes as they occur in
    /// real time.
    /// The `F` type is a struct defined by the
    /// [query_result_type!](macro.query_result_type.html) macro,
    /// or, if you want only the file name from the results, the
    /// [NameOnly](struct.NameOnly.html) struct.
    ///
    /// Returns two pieces of information:
    /// * A [Subscription](struct.Subscription.html) handle that can be used to yield changes
    ///   as they are observed by watchman
    /// * A [SubscribeResponse](pdu/struct.SubscribeResponse.html) that contains some data about the
    ///   state of the watch at the time the subscription was
    ///   initiated
    pub async fn subscribe<F>(
        &self,
        root: &ResolvedRoot,
        query: SubscribeRequest,
    ) -> Result<(Subscription<F>, SubscribeResponse), Error>
    where
        F: serde::de::DeserializeOwned + std::fmt::Debug + Clone + QueryFieldList,
    {
        let name = format!(
            "sub-[{}]-{}",
            std::env::args()
                .next()
                .unwrap_or_else(|| "<no-argv-0>".to_string()),
            SUB_ID.fetch_add(1, Ordering::Relaxed)
        );

        let query = SubscribeCommand(
            "subscribe",
            root.root.clone(),
            name.clone(),
            SubscribeRequest {
                relative_root: root.relative.clone(),
                fields: F::field_list(),
                ..query
            },
        );

        let (tx, responses) = tokio::sync::mpsc::unbounded_channel();

        {
            let inner = self.inner.lock().await;
            inner
                .request_tx
                .send(TaskItem::RegisterSubscription(name.clone(), tx))
                .await
                .map_err(|_| ConnectionLost::ClientTaskExited)?;
        }

        let subscription = Subscription::<F> {
            name,
            inner: Arc::clone(&self.inner),
            root: root.clone(),
            responses,
            _phantom: PhantomData,
        };

        let response: SubscribeResponse = self.generic_request(query).await?;

        Ok((subscription, response))
    }

    /// Expand a set of globs into the set of matching file names.
    /// The globs must be relative to the `root` parameter.
    /// The returned file names are all relative to the `root` parameter.
    pub async fn glob(&self, root: &ResolvedRoot, globs: &[&str]) -> Result<Vec<PathBuf>, Error> {
        let response: QueryResult<NameOnly> = self
            .query(
                root,
                QueryRequestCommon {
                    relative_root: root.relative.clone(),
                    glob: Some(globs.iter().map(|&s| s.to_string()).collect()),
                    ..Default::default()
                },
            )
            .await?;
        Ok(response
            .files
            .unwrap_or_else(Vec::new)
            .into_iter()
            .map(|f| f.name.into_inner())
            .collect())
    }

    /// Returns the current clock value for a watched root.
    /// If `sync_timeout` is `SyncTimeout::DisableCookie` then the instantaneous
    /// clock value is returned without using a sync cookie.
    ///
    /// Otherwise, a sync cookie will be created and the server will wait
    /// for up to the associated `sync_timeout` duration to observe it.
    /// If that timeout is reached, this method will yield an error.
    ///
    /// When should you use a cookie?  If you need to a clock value that is
    /// guaranteed to reflect any filesystem changes that happened before
    /// a given point in time you should use a sync cookie.
    ///
    /// ## See also:
    ///  * <https://facebook.github.io/watchman/docs/cmd/clock.html>
    ///  * <https://facebook.github.io/watchman/docs/cookies.html>
    pub async fn clock(
        &self,
        root: &ResolvedRoot,
        sync_timeout: SyncTimeout,
    ) -> Result<ClockSpec, Error> {
        let response: ClockResponse = self
            .generic_request(ClockRequest(
                "clock",
                root.root.clone(),
                ClockRequestParams { sync_timeout },
            ))
            .await?;
        Ok(response.clock)
    }

    /// Returns the current configuration for a watched root.
    pub async fn get_config(&self, root: &ResolvedRoot) -> Result<WatchmanConfig, Error> {
        let response: GetConfigResponse = self
            .generic_request(GetConfigRequest("get-config", root.root.clone()))
            .await?;
        Ok(response.config)
    }
}

#[cfg(test)]
mod tests {
    use std::io;

    use futures::stream;
    use futures::stream::TryStreamExt;
    use serde::Deserialize;
    use serde::Serialize;
    use tokio_util::io::StreamReader;

    use super::*;

    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct TestStruct {
        value: i32,
    }

    #[test]
    fn connection_builder_paths() {
        let builder = Connector::new().unix_domain_socket("/some/path");
        assert_eq!(builder.unix_domain, Some(PathBuf::from("/some/path")));
    }

    #[tokio::test]
    async fn test_decoder() {
        async fn read_bser(buf: &[u8], chunk_size: usize) -> Vec<TestStruct> {
            let chunks = buf
                .chunks(chunk_size)
                .map(|c| Result::<_, io::Error>::Ok(Bytes::copy_from_slice(c)));

            let reader = StreamReader::new(stream::iter(chunks));

            let decoded = FramedRead::new(reader, BserSplitter)
                .map_err(TaskError::from)
                .and_then(|bytes| async move {
                    // We unwrap this since a) this is a test and b) serde_bser's errors aren't
                    // easily propagated into en error type like anyhow::Error without losing the
                    // message.
                    Ok(serde_bser::from_slice::<TestStruct>(&bytes).unwrap())
                })
                .try_collect()
                .await
                .unwrap();

            decoded
        }

        let msgs = vec![
            TestStruct { value: 1 },
            TestStruct { value: 2 },
            TestStruct { value: 3 },
        ];

        let mut buf = vec![];

        for msg in msgs.iter() {
            serde_bser::ser::serialize(&mut buf, msg).expect("Failed to write to a Vec");
        }

        // Read it with various sizes
        assert_eq!(msgs, read_bser(&buf, 1).await);
        assert_eq!(msgs, read_bser(&buf, 2).await);
        assert_eq!(msgs, read_bser(&buf, 10).await);
        assert_eq!(msgs, read_bser(&buf, buf.len()).await);
    }

    #[test]
    fn test_decoder_err() {
        let mut bytes = BytesMut::new();

        // We don't error if there isn't much data yet
        bytes.extend_from_slice(&[0; 10]);
        let r1 = BserSplitter.decode(&mut bytes);
        assert!(r1.is_ok());
        assert!(r1.unwrap().is_none());

        // We do if there is enough
        bytes.extend_from_slice(&[0; 10]);
        let r1 = BserSplitter.decode(&mut bytes);
        assert!(r1.is_err());
    }

    #[test]
    fn test_bounds() {
        fn assert_bounds<T: std::error::Error + Sync + Send + 'static>() {}
        assert_bounds::<Error>();
        assert_bounds::<TaskError>();
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![cfg(windows)]

use std::io::Error as IoError;
use std::os::windows::ffi::OsStrExt;
use std::path::PathBuf;
use std::pin::Pin;
use std::task::Context;
use std::task::Poll;

use tokio::io::AsyncRead;
use tokio::io::AsyncWrite;
use tokio::io::ReadBuf;
use tokio::net::windows::named_pipe::NamedPipeClient;
use winapi::um::fileapi::CreateFileW;
use winapi::um::fileapi::OPEN_EXISTING;
use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
use winapi::um::winnt::GENERIC_READ;
use winapi::um::winnt::GENERIC_WRITE;

use crate::Error;

/// Wrapper around a tokio [`NamedPipeClient`]
pub struct NamedPipe {
    io: NamedPipeClient,
}

impl NamedPipe {
    pub async fn connect(path: PathBuf) -> Result<Self, Error> {
        let win_path = path
            .as_os_str()
            .encode_wide()
            .chain(Some(0))
            .collect::<Vec<_>>();

        let handle = unsafe {
            CreateFileW(
                win_path.as_ptr(),
                GENERIC_READ | GENERIC_WRITE,
                0,
                std::ptr::null_mut(),
                OPEN_EXISTING,
                FILE_FLAG_OVERLAPPED,
                std::ptr::null_mut(),
            )
        };
        if handle.is_null() {
            let err = IoError::last_os_error();
            return Err(Error::Connect {
                endpoint: path,
                source: Box::new(err),
            });
        }

        let io = unsafe {
            NamedPipeClient::from_raw_handle(handle).map_err(|err| Error::Connect {
                endpoint: path,
                source: Box::new(err),
            })?
        };
        Ok(Self { io })
    }
}

impl AsyncRead for NamedPipe {
    fn poll_read(
        self: Pin<&mut Self>,
        ctx: &mut Context,
        buf: &mut ReadBuf,
    ) -> Poll<Result<(), IoError>> {
        AsyncRead::poll_read(Pin::new(&mut self.get_mut().io), ctx, buf)
    }
}

impl AsyncWrite for NamedPipe {
    fn poll_write(
        self: Pin<&mut Self>,
        ctx: &mut Context,
        buf: &[u8],
    ) -> Poll<Result<usize, IoError>> {
        AsyncWrite::poll_write(Pin::new(&mut self.get_mut().io), ctx, buf)
    }

    fn poll_flush(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Result<(), IoError>> {
        AsyncWrite::poll_flush(Pin::new(&mut self.get_mut().io), ctx)
    }

    fn poll_shutdown(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Result<(), IoError>> {
        AsyncWrite::poll_shutdown(Pin::new(&mut self.get_mut().io), ctx)
    }
}

impl crate::ReadWriteStream for NamedPipe {}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! This module defines the request and response PDU types used by the
//! watchman protocol.

use std::path::PathBuf;

use serde::Deserialize;
use serde::Serialize;
use serde_bser::value::Value;

use crate::expr::Expr;

#[derive(Deserialize, Debug)]
pub struct GetVersionResponse {
    pub version: String,
}

#[derive(Deserialize, Debug)]
pub struct WatchListResponse {
    pub roots: Vec<PathBuf>,
}

/// The `get-sockname` command response
#[derive(Deserialize, Debug)]
pub struct GetSockNameResponse {
    pub version: String,
    pub sockname: Option<PathBuf>,
    pub error: Option<String>,
}

/// The `clock` command response
#[derive(Deserialize, Debug)]
pub struct ClockResponse {
    pub version: String,
    pub clock: ClockSpec,
}

/// The `clock` command request.
#[derive(Serialize, Debug)]
pub struct ClockRequest(pub &'static str, pub PathBuf, pub ClockRequestParams);

#[derive(Serialize, Debug)]
pub struct ClockRequestParams {
    #[serde(skip_serializing_if = "SyncTimeout::is_disabled", default)]
    pub sync_timeout: SyncTimeout,
}

/// The `get-config` command request
#[derive(Serialize, Debug)]
pub struct GetConfigRequest(pub &'static str, pub PathBuf);

/// An incomplete, but typed, representation of the Watchman config file,
/// which usually lives in /etc/watchman.json. Add new fields as they're
/// needed, and it might be worth someday exposing the serde_bser::Value
/// directly so callers can parse it however they want.
#[derive(Deserialize, Debug)]
pub struct WatchmanConfig {
    pub ignore_dirs: Option<Vec<PathBuf>>,
}

/// The `get-config` command response
#[derive(Deserialize, Debug)]
pub struct GetConfigResponse {
    pub version: String,
    pub config: WatchmanConfig,
}

/// Parameters used by `state-enter` and `state-leave` commands.
#[derive(Serialize, Debug)]
pub struct StateEnterLeaveParams<'a> {
    /// The name of the state being asserted
    pub name: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Value>,
    #[serde(skip_serializing_if = "SyncTimeout::is_default")]
    pub sync_timeout: SyncTimeout,
}

/// The `state-enter` or `state-leave` request.
/// You should use `Client::state_enter` rather than directly constructing
/// this type.
#[derive(Serialize, Debug)]
pub struct StateEnterLeaveRequest<'a>(pub &'static str, pub PathBuf, pub StateEnterLeaveParams<'a>);

/// The `state-enter` response
#[derive(Deserialize, Debug)]
pub struct StateEnterLeaveResponse {
    /// The watchman server version
    pub version: String,
}

/// The `watch-project` command request.
/// You should use `Client::resolve_root` rather than directly
/// constructing this type.
#[derive(Serialize, Debug)]
pub struct WatchProjectRequest(pub &'static str, pub PathBuf);

/// The `watch-project` response
#[derive(Deserialize, Debug)]
pub struct WatchProjectResponse {
    /// The watchman server version
    pub version: String,
    /// The path relative to the root of the project; if not none,
    /// this must be passed to QueryRequestCommon::relative_root
    pub relative_path: Option<PathBuf>,
    /// The root of the watched project
    pub watch: PathBuf,
    /// The watcher that the server is using to monitor this path
    pub watcher: String,
}

/// When using the `path` generator, this specifies a path to be
/// examined.
/// <https://facebook.github.io/watchman/docs/file-query.html#path-generator>
#[derive(Serialize, Clone, Debug)]
#[serde(untagged)]
pub enum PathGeneratorElement {
    RecursivePath(PathBuf),
    ConstrainedDepth { path: PathBuf, depth: i64 },
}

/// The `query` request
#[derive(Serialize, Clone, Debug)]
pub struct QueryRequest(pub &'static str, pub PathBuf, pub QueryRequestCommon);

#[allow(clippy::trivially_copy_pass_by_ref)]
fn is_false(v: &bool) -> bool {
    !*v
}

#[derive(Serialize, Clone, Debug)]
#[serde(into = "i64")]
pub struct SettleDurationMs(pub std::time::Duration);

impl From<std::time::Duration> for SettleDurationMs {
    fn from(duration: std::time::Duration) -> Self {
        Self(duration)
    }
}

impl Into<i64> for SettleDurationMs {
    fn into(self) -> i64 {
        self.0.as_millis() as i64
    }
}

#[derive(Serialize, Clone, Debug)]
#[serde(into = "i64")]
pub enum SyncTimeout {
    /// Use the default cookie synchronization timeout
    Default,
    /// Disable the use of a sync cookie.
    /// This can save ~15ms of latency, but may result in
    /// results from an outdated view of the filesystem.
    /// It is safe to use after you have performed a synchronized
    /// query or clock call, so that you can guarantee that the
    /// server is at least as current as the time that you started
    /// your processing.
    DisableCookie,
    /// Specify a timeout for the sync cookie.  You should not
    /// need to override the default value in most cases.
    /// Note that the server has millisecond level granularity
    /// for the timeout.
    Duration(std::time::Duration),
}

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

impl SyncTimeout {
    fn is_default(&self) -> bool {
        match self {
            Self::Default => true,
            _ => false,
        }
    }

    fn is_disabled(&self) -> bool {
        match self {
            Self::DisableCookie => true,
            _ => false,
        }
    }
}

impl From<std::time::Duration> for SyncTimeout {
    fn from(duration: std::time::Duration) -> Self {
        let millis = duration.as_millis();
        if millis == 0 {
            Self::DisableCookie
        } else {
            Self::Duration(duration)
        }
    }
}

impl Into<i64> for SyncTimeout {
    fn into(self) -> i64 {
        match self {
            // This is only really here because the `ClockRequestParams` PDU
            // treats a missing sync_timeout as `DisableCookie`, whereas
            // the `QueryRequestCommon` PDU treats it as `Default`.
            // We don't really know for sure what the server will use for
            // its default value as it may potentially be changed in a future
            // revision of the server, but for the sake of having reasonable
            // default behavior, we use the current default sync timeout here.
            // We're honestly not likely to change this, so this should be fine.
            // The server uses 1 minute; the value here is expressed in milliseconds.
            Self::Default => 60_000,
            Self::DisableCookie => 0,
            Self::Duration(d) => d.as_millis() as i64,
        }
    }
}

/// The query parameters.
/// There are a large number of fields that influence the behavior.
///
/// A query consists of three phases:
/// 1. Candidate generation
/// 2. Result filtration (using the `expression` term)
/// 3. Result rendering
///
/// The generation phase is explained in detail here:
/// <https://facebook.github.io/watchman/docs/file-query.html#generators>
///
/// Note that it is legal to combine multiple generators but that it
/// is often undesirable to do so.
/// Not specifying a generator results in the default "all-files" generator
/// being used to iterate all known files.
///
/// The filtration and expression syntax is explained here:
/// <https://facebook.github.io/watchman/docs/file-query.html#expressions>
#[derive(Serialize, Default, Clone, Debug)]
pub struct QueryRequestCommon {
    /// If set, enables the glob generator and specifies a set of globs
    /// that will be expanded into a list of file names and then filtered
    /// according to the expression field.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub glob: Option<Vec<String>>,

    /// If using the glob generator and set to true, do not treat the backslash
    /// character as an escape sequence
    #[serde(default, skip_serializing_if = "is_false")]
    pub glob_noescape: bool,

    /// If using the glob generator and set to true, include files whose basename
    /// starts with `.` in the results. The default behavior for globs is to
    /// exclude those files from the results.
    #[serde(default, skip_serializing_if = "is_false")]
    pub glob_includedotfiles: bool,

    /// If set, enables the use of the `path` generator.
    /// <https://facebook.github.io/watchman/docs/file-query.html#path-generator>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub path: Option<Vec<PathGeneratorElement>>,

    /// If set, enables the use of the `suffix` generator, and specifies the
    /// list of filename suffixes.
    /// In virtualized filesystems this can result in an expensive O(project)
    /// filesystem walk, so it is strongly recommended that you scope this to
    /// a relatively shallow subdirectory.
    ///
    /// <https://facebook.github.io/watchman/docs/file-query.html#suffix-generator>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub suffix: Option<Vec<PathBuf>>,

    /// If set, enables the use of the `since` generator and specifies the last
    /// time you queried the server and for which you wish to receive a delta of
    /// changes.
    /// You will typically thread the QueryResult.clock field back to a subsequent
    /// since query to process the continuity of matching file changes.
    /// <https://facebook.github.io/watchman/docs/file-query.html#since-generator>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub since: Option<Clock>,

    /// if set, indicates that all input paths are relative to this subdirectory
    /// in the project, and that all returned filenames will also be relative to
    /// this subdirectory.
    /// In large virtualized filesystems it is undesirable to leave this set to
    /// None as it makes it more likely that you will trigger an O(project)
    /// filesystem walk.
    /// This field is set automatically from the ResolvedRoot when you perform queries
    /// using Client::query.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub relative_root: Option<PathBuf>,

    /// If set, specifies the expression to use to filter the candidate matches
    /// produced by the selected query generator.
    /// Each candidate is visited in turn and has the expression applied.
    /// Candidates for which the expression evaluates as true will be included
    /// in the returned list of files.
    /// If left unspecified, the server will assume `Expr::True`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expression: Option<Expr>,

    /// Specifies the list of fields names returned by the server.
    /// The `name` field should be considered a required field and is the cheapest
    /// field to return.
    /// Depending on the watcher implementation, other metadata has varying cost.
    /// In general, avoid querying `size` and `mode` fields and instead prefer to
    /// query `content.sha1hex` and `type` instead to avoid materializing inodes
    /// in a virtualized filesystem.
    pub fields: Vec<&'static str>,

    /// If true you indicate that you know how to 100% correctly deal with a fresh
    /// instance result set.  It is strongly recommended that you leave this
    /// option alone as it is a common source of cache invalidation and divergence
    /// issues for clients.
    #[serde(default, skip_serializing_if = "is_false")]
    pub empty_on_fresh_instance: bool,

    /// If true you indicate that you don't want watchman to query for or
    /// return the changed files. This is most helpful if you'd quickly
    /// like the saved-state but don't care about the changed files.
    #[serde(default, skip_serializing_if = "is_false")]
    pub omit_changed_files: bool,

    /// When requesting saved state information via SavedStateClockData, if
    /// `fail_if_no_saved_state` is set to true, the server will generate a
    /// query error in the case that the merge base change and no appropriate
    /// saved state could be found.
    /// Otherwise, the default behavior is to perform a normal watchman since
    /// query that may return a large number of changed files.
    #[serde(default, skip_serializing_if = "is_false")]
    pub fail_if_no_saved_state: bool,

    /// If true, treat filenames as case sensitive even on filesystems that otherwise
    /// appear to be case insensitive.
    /// This can improve performance of directory traversal in queries by turning
    /// O(directory-size) operations into an O(1) hash lookup.
    /// <https://facebook.github.io/watchman/docs/cmd/query.html#case-sensitivity>
    #[serde(default, skip_serializing_if = "is_false")]
    pub case_sensitive: bool,

    /// If set, override the default synchronization timeout.
    /// The timeout controls how long the server will wait to observe a cookie
    /// file through the notification stream.
    /// If the timeout is reached, the query will fail.
    ///
    /// Specify `SyncTimeout::DisableCookie` to tell the server not to use a sync
    /// cookie.  **Disabling sync cookies means that your query results may be
    /// slightly out of date**.  You can safely perform a query with sync cookies
    /// disabled if you have explicitly synchronized.  For example, you can perform a
    /// synchronized `Client::clock` call at the start of your processing run
    /// to ensure that the server is current up to that point in time,
    /// and then issue a large volume of additional queries with the sync cookie
    /// disabled and save approximately ~15ms of latency per query.
    ///
    /// ## See also:
    /// * <https://facebook.github.io/watchman/docs/cookies.html>
    /// * <https://facebook.github.io/watchman/docs/cmd/query.html#synchronization-timeout-since-21>
    #[serde(skip_serializing_if = "SyncTimeout::is_default", default)]
    pub sync_timeout: SyncTimeout,

    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub settle_period: Option<SettleDurationMs>,

    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub settle_timeout: Option<SettleDurationMs>,

    /// If set to true, when mixing generators (not recommended), dedup results by filename.
    /// This defaults to false.  When not enabled, if multiple generators match
    /// the same file, it will appear twice in the result set.
    /// Turning on dedup_results will increase the memory cost of processing a query
    /// and build an O(result-size) hash map to dedup the results.
    #[serde(default, skip_serializing_if = "is_false")]
    pub dedup_results: bool,

    /// Controls the duration that the server will wait to obtain a lock on the
    /// filesystem view.
    /// You should not normally need to change this.
    /// <https://facebook.github.io/watchman/docs/cmd/query.html#lock-timeout>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lock_timeout: Option<i64>,

    /// If set, records the request_id in internal performance sampling data.
    /// It is also exported through the environment as HGREQUESTID so that
    /// the context of the request can be passed down to any child mercurial
    /// processes that might be spawned as part of processing source control
    /// aware queries.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub request_id: Option<String>,

    /// If this is set Watchman should guarantee that events are sent for
    /// directories. When this is not set watchman is known to skip sending
    /// events for directories on EdenFS repos that change across commits.
    /// Collecting these events can be slow when there were many recent commit
    /// transitions.
    #[serde(default, skip_serializing_if = "is_false")]
    pub always_include_directories: bool,
}

#[derive(Deserialize, Clone, Debug)]
pub struct QueryDebugInfo {
    pub cookie_files: Option<Vec<PathBuf>>,
}

/// Holds the result of a query.
/// The result is generic over a `F` type that you define.
/// The `F` should deserialize the list of fields in your QueryRequestCommon
/// struct.
#[derive(Deserialize, Clone, Debug)]
pub struct QueryResult<F>
where
    F: std::fmt::Debug + Clone,
{
    /// The version of the watchman server
    pub version: String,
    /// If true, indicates that this result set represents the
    /// total set of possible matches.  Otherwise the results should be
    /// considered to be incremental since your last since query.
    /// If is_fresh_instance is true you MUST arrange to forget about
    /// any files not included in the list of files in this QueryResult
    /// otherwise you risk diverging your state.
    #[serde(default)]
    pub is_fresh_instance: bool,

    /// Holds the list of matching files from the query
    pub files: Option<Vec<F>>,

    /// The clock value at the time that these results were generated
    pub clock: Clock,

    #[serde(rename = "state-enter")]
    #[doc(hidden)]
    pub state_enter: Option<String>,

    #[serde(rename = "state-leave")]
    #[doc(hidden)]
    pub state_leave: Option<String>,
    #[serde(rename = "metadata")]
    pub state_metadata: Option<Value>,

    /// When using source control aware queries with saved
    /// state configuration, this field holds metadata from
    /// the save state storage engine.
    #[serde(rename = "saved-state-info")]
    pub saved_state_info: Option<Value>,

    pub debug: Option<QueryDebugInfo>,
}

#[derive(Serialize, Default, Clone, Debug)]
pub struct SubscribeRequest {
    /// If set, enables the use of the `since` generator and specifies the last
    /// time you queried the server and for which you wish to receive a delta of
    /// changes.
    /// You will typically thread the QueryResult.clock field back to a subsequent
    /// since query to process the continuity of matching file changes.
    /// <https://facebook.github.io/watchman/docs/file-query.html#since-generator>
    #[serde(skip_serializing_if = "Option::is_none")]
    pub since: Option<Clock>,

    /// if set, indicates that all input paths are relative to this subdirectory
    /// in the project, and that all returned filenames will also be relative to
    /// this subdirectory.
    /// In large virtualized filesystems it is undesirable to leave this set to
    /// None as it makes it more likely that you will trigger an O(project)
    /// filesystem walk.
    /// This field is set automatically from the ResolvedRoot when you perform queries
    /// using Client::query.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub relative_root: Option<PathBuf>,

    /// If set, specifies the expression to use to filter the candidate matches
    /// produced by the selected query generator.
    /// Each candidate is visited in turn and has the expression applied.
    /// Candidates for which the expression evaluates as true will be included
    /// in the returned list of files.
    /// If left unspecified, the server will assume `Expr::True`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub expression: Option<Expr>,

    /// Specifies the list of fields names returned by the server.
    /// The `name` field should be considered a required field and is the cheapest
    /// field to return.
    /// Depending on the watcher implementation, other metadata has varying cost.
    /// In general, avoid querying `size` and `mode` fields and instead prefer to
    /// query `content.sha1hex` and `type` instead to avoid materializing inodes
    /// in a virtualized filesystem.
    pub fields: Vec<&'static str>,

    /// If true you indicate that you know how to 100% correctly deal with a fresh
    /// instance result set.  It is strongly recommended that you leave this
    /// option alone as it is a common source of cache invalidation and divergence
    /// issues for clients.
    #[serde(default, skip_serializing_if = "is_false")]
    pub empty_on_fresh_instance: bool,

    /// If true, treat filenames as case sensitive even on filesystems that otherwise
    /// appear to be case insensitive.
    /// This can improve performance of directory traversal in queries by turning
    /// O(directory-size) operations into an O(1) hash lookup.
    /// <https://facebook.github.io/watchman/docs/cmd/query.html#case-sensitivity>
    #[serde(default, skip_serializing_if = "is_false")]
    pub case_sensitive: bool,

    /// In some circumstances it is desirable for a client to observe the creation of
    /// the control files at the start of a version control operation. You may specify
    /// that you want this behavior by passing the defer_vcs flag to your
    /// subscription command invocation
    /// <https://facebook.github.io/watchman/docs/cmd/subscribe.html#filesystem-settling>
    #[serde(default, skip_serializing_if = "is_false")]
    pub defer_vcs: bool,

    /// The `defer` field specifies a list of state names for which the subscriber
    /// wishes to defer the notification stream.
    /// <https://facebook.github.io/watchman/docs/cmd/subscribe.html#defer>
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub defer: Vec<&'static str>,

    /// The `drop` field specifies a list of state names for which the subscriber
    /// wishes to discard the notification stream.
    /// <https://facebook.github.io/watchman/docs/cmd/subscribe.html#drop>
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub drop: Vec<&'static str>,
}

#[derive(Serialize, Clone, Debug)]
pub struct SubscribeCommand(
    pub &'static str,
    pub PathBuf,
    pub String,
    pub SubscribeRequest,
);

/// Returns information about the state of the watch at the time the
/// subscription was initiated.
#[derive(Deserialize, Debug)]
pub struct SubscribeResponse {
    pub version: String,
    #[allow(unused)] // TODO unused warning after rustc upgrade
    subscribe: String,

    /// The clock at initiation time.
    pub clock: Clock,

    /// The set of asserted states at watch initiation time.
    /// This is useful in the case where you need to reason
    /// about the states and may have connected after the
    /// StateEnter was generated but prior to the StateLeave
    #[serde(default, rename = "asserted-states")]
    pub asserted_states: Vec<String>,

    /// When using source control aware queries with saved
    /// state configuration, this field holds metadata from
    /// the save state storage engine.
    #[serde(rename = "saved-state-info")]
    pub saved_state_info: Option<Value>,
}

#[derive(Serialize, Debug)]
pub struct Unsubscribe(pub &'static str, pub PathBuf, pub String);

#[derive(Deserialize, Debug)]
pub struct UnsubscribeResponse {
    pub version: String,
    pub unsubscribe: String,
}

/// A `Clock` is used to refer to a logical point in time.
/// Internally, watchman maintains a monotonically increasing tick counter
/// along with some additional data to detect A-B-A style situations if
/// eg: the watchman server is restarted.
///
/// Clocks are important when using the recency index with the `since`
/// generator.
///
/// A clock can also encoded metadata describing the state of source
/// control to work with source control aware queries:
/// <https://facebook.github.io/watchman/docs/scm-query.html>
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Clock {
    /// Just a basic ClockSpec
    Spec(ClockSpec),
    /// A clock embedding additional source control information
    ScmAware(FatClockData),
}

/// The fundamental clock specifier string.
/// The contents of the string should be considered to be opaque to
/// the client as the server occasionally evolves the meaning of
/// the clockspec and its format is expressly not a stable API.
///
/// In particular, there is no defined way for a client to reason
/// about the relationship between any two ClockSpec's.
///
/// <https://facebook.github.io/watchman/docs/clockspec.html>
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ClockSpec {
    StringClock(String),
    UnixTimestamp(i64),
}

/// Construct a null clockspec
impl Default for ClockSpec {
    fn default() -> Self {
        Self::null()
    }
}

impl ClockSpec {
    /// Construct a null clockspec.
    /// This indicates a time before any changes occurred and will
    /// cause a `since` generator based query to emit a fresh instance
    /// result set that contains all possible matches.
    /// It is appropriate to use a null clock in cases where you are
    /// starting up from scratch and don't have a saved clock value
    /// to use as the basis for your query.
    pub fn null() -> Self {
        Self::StringClock("c:0:0".to_string())
    }

    /// Construct a named cursor clockspec.
    ///
    /// Using a named cursor causes the server to maintain the name -> clock
    /// value mapping on the behalf of your tool.  This frees your client
    /// from the need to manage storing of the clock between queries but
    /// does require an exclusive lock for the duration of the query, which
    /// serializes the query with all other clients.
    ///
    /// The namespace is per watched project so your cursor name must be
    /// unique enough to not collide with other tools that use this same
    /// feature.
    ///
    /// There is no way to clear the value associated with a named cursor.
    ///
    /// The first time you use a named cursor, it has an effective value
    /// of the null clock.
    ///
    /// We do not recommend using named cursors because of the exclusive
    /// lock requirement.
    pub fn named_cursor(cursor: &str) -> Self {
        Self::StringClock(format!("n:{}", cursor))
    }

    /// A clock specified as a unix timestamp.
    /// The watchman server will never generate a clock in this form,
    /// but will accept them in `since` generator based queries.
    /// Using UnixTimeStamp is discouraged as it has granularity of
    /// 1 second and will often result in over-reporting the same events
    /// when they happen in the same second.
    pub fn unix_timestamp(time_t: i64) -> Self {
        Self::UnixTimestamp(time_t)
    }
}

impl Into<Value> for ClockSpec {
    fn into(self) -> Value {
        match self {
            Self::StringClock(st) => Value::Utf8String(st),
            Self::UnixTimestamp(ts) => Value::Integer(ts),
        }
    }
}

/// Holds extended clock data that includes source control aware
/// query metadata.
/// <https://facebook.github.io/watchman/docs/scm-query.html>
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FatClockData {
    pub clock: ClockSpec,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scm: Option<ScmAwareClockData>,
}

/// Holds extended clock data that includes source control aware
/// query metadata.
/// <https://facebook.github.io/watchman/docs/scm-query.html>
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ScmAwareClockData {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mergebase: Option<String>,
    #[serde(rename = "mergebase-with")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mergebase_with: Option<String>,

    #[serde(rename = "saved-state")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub saved_state: Option<SavedStateClockData>,
}

/// Holds extended clock data that includes source control aware
/// query metadata.
/// <https://facebook.github.io/watchman/docs/scm-query.html>
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SavedStateClockData {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub storage: Option<String>,
    #[serde(rename = "commit-id")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub commit: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub config: Option<Value>,
}

/// Reports the content SHA1 hash for a file.
/// Since computing the hash can fail, this struct can also represent
/// the error that happened during hash computation.
#[derive(Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum ContentSha1Hex {
    /// The 40-hex-digit SHA1 content hash of the file contents
    Hash(String),
    /// The error that occured while trying to determine the hash
    Error { error: String },
    /// Value if the file was deleted.
    /// Note that this is distinct from the case where watchman believes
    /// that the file exists and where some other process unlinks it before
    /// watchman can compute the hash: in that racy scenario, the value
    /// will be `ContentSha1Hex::Error(_)`.
    None,
}

/// Encodes the file type field returned in query results and
/// specified in expression terms.
///
/// <https://facebook.github.io/watchman/docs/expr/type.html>
///
/// Use this in your query file struct like this:
///
/// ```
/// use serde::Deserialize;
/// use watchman_client::prelude::*;
/// #[derive(Deserialize, Debug, Clone)]
/// struct NameAndType {
///    name: std::path::PathBuf,
///    #[serde(rename = "type")]
///    file_type: FileType,
/// }
/// ```
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(from = "String", into = "String")]
pub enum FileType {
    BlockSpecial,
    CharSpecial,
    Directory,
    Regular,
    Fifo,
    Symlink,
    Socket,
    SolarisDoor,
    Unknown,
}

impl std::string::ToString for FileType {
    fn to_string(&self) -> String {
        (*self).into()
    }
}

impl From<String> for FileType {
    fn from(s: String) -> Self {
        match s.as_ref() {
            "b" => Self::BlockSpecial,
            "c" => Self::CharSpecial,
            "d" => Self::Directory,
            "f" => Self::Regular,
            "p" => Self::Fifo,
            "l" => Self::Symlink,
            "s" => Self::Socket,
            "D" => Self::SolarisDoor,
            "?" => Self::Unknown,
            unknown => panic!("Watchman Server returned impossible file type {}", unknown),
        }
    }
}

impl Into<String> for FileType {
    fn into(self) -> String {
        match self {
            Self::BlockSpecial => "b",
            Self::CharSpecial => "c",
            Self::Directory => "d",
            Self::Regular => "f",
            Self::Fifo => "p",
            Self::Symlink => "l",
            Self::Socket => "s",
            Self::SolarisDoor => "D",
            Self::Unknown => "?",
        }
        .to_string()
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use serde_bser::value::Value;

    use super::*;
    use crate::bunser;

    fn convert_bser_value<T>(input: Value) -> T
    where
        T: serde::de::DeserializeOwned,
    {
        let binary = serde_bser::ser::serialize(Vec::new(), input).unwrap();
        bunser(&binary).unwrap()
    }

    #[test]
    fn test_content_sha1hex_hash() {
        let value: ContentSha1Hex =
            convert_bser_value("e820c2c600a36f05ba905cf1bf32c4834e804e22".into());
        assert_eq!(
            value,
            ContentSha1Hex::Hash("e820c2c600a36f05ba905cf1bf32c4834e804e22".into())
        );
    }

    #[test]
    fn test_content_sha1hex_error() {
        let mut error_obj: HashMap<String, Value> = HashMap::new();
        error_obj.insert("error".to_string(), "out of cookies".into());

        let value: ContentSha1Hex = convert_bser_value(error_obj.into());
        assert_eq!(
            value,
            ContentSha1Hex::Error {
                error: "out of cookies".into()
            }
        );
    }

    #[test]
    fn test_content_sha1hex_none() {
        let value: ContentSha1Hex = convert_bser_value(Value::Null);
        assert_eq!(value, ContentSha1Hex::None);
    }
}


// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use core::fmt;

// ASSERTION ERROR
// ================================================================================================
/// Represents an error returned during assertion evaluation.
#[derive(Debug, PartialEq, Eq)]
pub enum AssertionError {
    /// This error occurs when an assertion is evaluated against an execution trace which does not
    /// contain a column specified by the assertion.
    TraceWidthTooShort(usize, usize),
    /// This error occurs when an assertion is evaluated against an execution trace with length
    /// which is not a power of two.
    TraceLengthNotPowerOfTwo(usize),
    /// This error occurs when an assertion is evaluated against an execution trace which does not
    /// contain a step against which the assertion is placed.
    TraceLengthTooShort(usize, usize),
    /// This error occurs when a `Sequence` assertion is placed against an execution trace with
    /// length which conflicts with the trace length implied by the assertion.
    TraceLengthNotExact(usize, usize),
}

impl fmt::Display for AssertionError {
    #[rustfmt::skip]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::TraceWidthTooShort(expected, actual) => {
                write!(f, "expected trace width to be at least {expected}, but was {actual}")
            }
            Self::TraceLengthNotPowerOfTwo(actual) => {
                write!(f, "expected trace length to be a power of two, but was {actual}")
            }
            Self::TraceLengthTooShort(expected, actual) => {
                write!(f, "expected trace length to be at least {expected}, but was {actual}")
            }
            Self::TraceLengthNotExact(expected, actual) => {
                write!(f, "expected trace length to be exactly {expected}, but was {actual}")
            }
        }
    }
}


// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

//! This crate contains components need to describe arbitrary computations in a STARK-specific
//! format.
//!
//! Before we can generate proofs attesting that some computations were executed correctly, we
//! need to describe these computations in a way that can be understood by the Winterfell prover
//! and verifier.
//!
//! More formally, we need to reduce our computations to algebraic statements involving a set of
//! bounded-degree polynomials. This step is usually called *arithmetization*. STARK arithmetization
//! reduces computations to an *algebraic intermediate representation* or AIR for short. For basics
//! of AIR arithmetization please refer to the excellent posts from StarkWare:
//!
//! * [Arithmetization I](https://medium.com/starkware/arithmetization-i-15c046390862)
//! * [Arithmetization II](https://medium.com/starkware/arithmetization-ii-403c3b3f4355)
//! * [StarkDEX Deep Dive: the STARK Core Engine](https://medium.com/starkware/starkdex-deep-dive-the-stark-core-engine-497942d0f0ab)
//!
//! Coming up with efficient arithmetizations for computations is highly non-trivial, and
//! describing arithmetizations could be tedious and error-prone. The [Air] trait aims to help
//! with the latter, which, hopefully, also makes the former a little simpler. For additional
//! details, please refer to the documentation of the [Air] trait itself.
//!
//! This crate also contains components describing STARK protocol parameters ([ProofOptions]) and
//! proof structure ([StarkProof](proof::StarkProof)).

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;

pub mod proof;

mod errors;
pub use errors::AssertionError;

mod options;
pub use options::{FieldExtension, ProofOptions};

mod air;
pub use air::{
    Air, AirContext, Assertion, AuxTraceRandElements, BoundaryConstraint, BoundaryConstraintGroup,
    BoundaryConstraints, ConstraintCompositionCoefficients, ConstraintDivisor,
    DeepCompositionCoefficients, EvaluationFrame, TraceInfo, TraceLayout,
    TransitionConstraintDegree, TransitionConstraints,
};


// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use fri::FriOptions;
use math::{StarkField, ToElements};
use utils::{
    collections::Vec, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};

// CONSTANTS
// ================================================================================================

// most of these constants are set so that values fit into a u8 integer.

const MAX_NUM_QUERIES: usize = 255;

const MIN_BLOWUP_FACTOR: usize = 2;
const MAX_BLOWUP_FACTOR: usize = 128;

const MAX_GRINDING_FACTOR: u32 = 32;

const FRI_MIN_FOLDING_FACTOR: usize = 2;
const FRI_MAX_FOLDING_FACTOR: usize = 16;
const FRI_MAX_REMAINDER_DEGREE: usize = 255;

// TYPES AND INTERFACES
// ================================================================================================

/// Defines an extension field for the composition polynomial.
///
/// Choice of a field for a composition polynomial may impact proof soundness, and can also have
/// a non-negligible impact on proof generation time and proof size. Specifically, for small
/// fields, security offered by the base field itself may be inadequate or insufficient, and an
/// extension of the base field may need to be used.
///
/// For example, if the size of base field is ~64-bits, a quadratic extension must be use to
/// achieve ~100 bits of soundness, and a cubic extension must be used to achieve 128+ bits
/// of soundness.
///
/// However, increasing extension degree will increase proof generation time and proof size by
/// as much as 50%.
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FieldExtension {
    /// Composition polynomial is constructed in the base field.
    None = 1,
    /// Composition polynomial is constructed in the quadratic extension of the base field.
    Quadratic = 2,
    /// Composition polynomial is constructed in the cubic extension of the base field.
    Cubic = 3,
}

/// STARK protocol parameters.
///
/// These parameters have a direct impact on proof soundness, proof generation time, and proof
/// size. Specifically:
///
/// 1. Finite field - proof soundness depends on the size of finite field used by the protocol.
///    This means, that for small fields (e.g. smaller than ~128 bits), field extensions must be
///    used to achieve adequate security. And even for ~128 bit fields, to achieve security over
///    100 bits, a field extension may be required.
/// 2. Number of queries - higher values increase proof soundness, but also increase proof size.
/// 3. Blowup factor - higher values increase proof soundness, but also increase proof generation
///    time and proof size. However, higher blowup factors require fewer queries for the same
///    security level. Thus, it is frequently possible to increase blowup factor and at the same
///    time decrease the number of queries in such a way that the proofs become smaller.
/// 4. Grinding factor - higher values increase proof soundness, but also may increase proof
///    generation time. More precisely, conjectured proof soundness is bounded by
///    `num_queries * log2(blowup_factor) + grinding_factor`.
///
/// Another important parameter in defining STARK security level, which is not a part of [ProofOptions]
/// is the hash function used in the protocol. The soundness of a STARK proof is limited by the
/// collision resistance of the hash function used by the protocol. For example, if a hash function
/// with 128-bit collision resistance is used, soundness of a STARK proof cannot exceed 128 bits.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ProofOptions {
    num_queries: u8,
    blowup_factor: u8,
    grinding_factor: u8,
    field_extension: FieldExtension,
    fri_folding_factor: u8,
    fri_remainder_max_degree: u8,
}

// PROOF OPTIONS IMPLEMENTATION
// ================================================================================================
impl ProofOptions {
    // CONSTANTS
    // --------------------------------------------------------------------------------------------

    /// Smallest allowed blowup factor which is currently set to 2.
    ///
    /// The smallest allowed blowup factor for a given computation is derived from degrees of
    /// constraints defined for that computation and may be greater than 2. But no computation may
    /// have a blowup factor smaller than 2.
    pub const MIN_BLOWUP_FACTOR: usize = MIN_BLOWUP_FACTOR;

    // CONSTRUCTORS
    // --------------------------------------------------------------------------------------------
    /// Returns a new instance of [ProofOptions] struct constructed from the specified parameters.
    ///
    /// # Panics
    /// Panics if:
    /// - `num_queries` is zero or greater than 255.
    /// - `blowup_factor` is smaller than 2, greater than 128, or is not a power of two.
    /// - `grinding_factor` is greater than 32.
    /// - `fri_folding_factor` is not 2, 4, 8, or 16.
    /// - `fri_remainder_max_degree` is greater than 255 or is not a power of two minus 1.
    #[rustfmt::skip]
    pub fn new(
        num_queries: usize,
        blowup_factor: usize,
        grinding_factor: u32,
        field_extension: FieldExtension,
        fri_folding_factor: usize,
        fri_remainder_max_degree: usize,
    ) -> ProofOptions {
        // TODO: return errors instead of panicking
        assert!(num_queries > 0, "number of queries must be greater than 0");
        assert!(num_queries <= MAX_NUM_QUERIES, "number of queries cannot be greater than {MAX_NUM_QUERIES}");

        assert!(blowup_factor.is_power_of_two(), "blowup factor must be a power of 2");
        assert!(blowup_factor >= MIN_BLOWUP_FACTOR, "blowup factor cannot be smaller than {MIN_BLOWUP_FACTOR}");
        assert!(blowup_factor <= MAX_BLOWUP_FACTOR, "blowup factor cannot be greater than {MAX_BLOWUP_FACTOR}");

        assert!(grinding_factor <= MAX_GRINDING_FACTOR, "grinding factor cannot be greater than {MAX_GRINDING_FACTOR}");

        assert!(fri_folding_factor.is_power_of_two(), "FRI folding factor must be a power of 2");
        assert!(fri_folding_factor >= FRI_MIN_FOLDING_FACTOR, "FRI folding factor cannot be smaller than {FRI_MIN_FOLDING_FACTOR}");
        assert!(fri_folding_factor <= FRI_MAX_FOLDING_FACTOR, "FRI folding factor cannot be greater than {FRI_MAX_FOLDING_FACTOR}");

        assert!(
            (fri_remainder_max_degree + 1).is_power_of_two(),
            "FRI polynomial remainder degree must be one less than a power of two"
        );
        assert!(
            fri_remainder_max_degree <= FRI_MAX_REMAINDER_DEGREE,
            "FRI polynomial remainder degree cannot be greater than {FRI_MAX_REMAINDER_DEGREE}"
        );

        ProofOptions {
            num_queries: num_queries as u8,
            blowup_factor: blowup_factor as u8,
            grinding_factor: grinding_factor as u8,
            field_extension,
            fri_folding_factor: fri_folding_factor as u8,
            fri_remainder_max_degree: fri_remainder_max_degree as u8,
        }
    }

    // PUBLIC ACCESSORS
    // --------------------------------------------------------------------------------------------

    /// Returns number of queries for a STARK proof.
    ///
    /// This directly impacts proof soundness as each additional query adds roughly
    /// `log2(blowup_factor)` bits of security to a proof. However, each additional query also
    /// increases proof size.
    pub fn num_queries(&self) -> usize {
        self.num_queries as usize
    }

    /// Returns trace blowup factor for a STARK proof.
    ///
    /// This is the factor by which the execution trace is extended during low-degree extension. It
    /// has a direct impact on proof soundness as each query adds roughly `log2(blowup_factor)`
    /// bits of security to a proof. However, higher blowup factors also increases prover runtime,
    /// and may increase proof size.
    pub fn blowup_factor(&self) -> usize {
        self.blowup_factor as usize
    }

    /// Returns query seed grinding factor for a STARK proof.
    ///
    /// Grinding applies Proof-of-Work to the query position seed. An honest prover needs to
    /// perform this work only once, while a dishonest prover will need to perform it every time
    /// they try to change a commitment. Thus, higher grinding factor makes it more difficult to
    /// forge a STARK proof. However, setting grinding factor too high (e.g. higher than 20) will
    /// adversely affect prover time.
    pub fn grinding_factor(&self) -> u32 {
        self.grinding_factor as u32
    }

    /// Specifies whether composition polynomial should be constructed in an extension field
    /// of STARK protocol.
    ///
    /// Using a field extension increases maximum security level of a proof, but also has
    /// non-negligible impact on prover performance.
    pub fn field_extension(&self) -> FieldExtension {
        self.field_extension
    }

    /// Returns the offset by which the low-degree extension domain is shifted in relation to the
    /// trace domain.
    ///
    /// Currently, this is hard-coded to the primitive element of the underlying base field.
    pub fn domain_offset<B: StarkField>(&self) -> B {
        B::GENERATOR
    }

    /// Returns options for FRI protocol instantiated with parameters from this proof options.
    pub fn to_fri_options(&self) -> FriOptions {
        let folding_factor = self.fri_folding_factor as usize;
        let remainder_max_degree = self.fri_remainder_max_degree as usize;
        FriOptions::new(self.blowup_factor(), folding_factor, remainder_max_degree)
    }
}

impl<E: StarkField> ToElements<E> for ProofOptions {
    fn to_elements(&self) -> Vec<E> {
        // encode field extension and FRI parameters into a single field element
        let mut buf = self.field_extension as u32;
        buf = (buf << 8) | self.fri_folding_factor as u32;
        buf = (buf << 8) | self.fri_remainder_max_degree as u32;

        vec![
            E::from(buf),
            E::from(self.grinding_factor),
            E::from(self.blowup_factor),
            E::from(self.num_queries),
        ]
    }
}

impl Serializable for ProofOptions {
    /// Serializes `self` and writes the resulting bytes into the `target`.
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        target.write_u8(self.num_queries);
        target.write_u8(self.blowup_factor);
        target.write_u8(self.grinding_factor);
        target.write(self.field_extension);
        target.write_u8(self.fri_folding_factor);
        target.write_u8(self.fri_remainder_max_degree);
    }
}

impl Deserializable for ProofOptions {
    /// Reads proof options from the specified `source` and returns the result.
    ///
    /// # Errors
    /// Returns an error of a valid proof options could not be read from the specified `source`.
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        Ok(ProofOptions::new(
            source.read_u8()? as usize,
            source.read_u8()? as usize,
            source.read_u8()? as u32,
            FieldExtension::read_from(source)?,
            source.read_u8()? as usize,
            source.read_u8()? as usize,
        ))
    }
}

// FIELD EXTENSION IMPLEMENTATION
// ================================================================================================

impl FieldExtension {
    /// Returns `true` if this field extension is set to `None`.
    pub fn is_none(&self) -> bool {
        matches!(self, Self::None)
    }

    /// Returns extension degree of this field extension.
    pub fn degree(&self) -> u32 {
        match self {
            Self::None => 1,
            Self::Quadratic => 2,
            Self::Cubic => 3,
        }
    }
}

impl Serializable for FieldExtension {
    /// Serializes `self` and writes the resulting bytes into the `target`.
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        target.write_u8(*self as u8);
    }
}

impl Deserializable for FieldExtension {
    /// Reads a field extension enum from the specified `source`.
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        match source.read_u8()? {
            1 => Ok(FieldExtension::None),
            2 => Ok(FieldExtension::Quadratic),
            3 => Ok(FieldExtension::Cubic),
            value => Err(DeserializationError::InvalidValue(format!(
                "value {value} cannot be deserialized as FieldExtension enum"
            ))),
        }
    }
}

// TESTS
// ================================================================================================

#[cfg(test)]
mod tests {
    use super::{FieldExtension, ProofOptions, ToElements};
    use math::fields::f64::BaseElement;

    #[test]
    fn proof_options_to_elements() {
        let field_extension = FieldExtension::None;
        let fri_folding_factor = 8;
        let fri_remainder_max_degree = 127;
        let grinding_factor = 20;
        let blowup_factor = 8;
        let num_queries = 30;

        let ext_fri = u32::from_le_bytes([
            fri_remainder_max_degree,
            fri_folding_factor,
            field_extension as u8,
            0,
        ]);
        let expected = vec![
            BaseElement::from(ext_fri),
            BaseElement::from(grinding_factor as u32),
            BaseElement::from(blowup_factor as u32),
            BaseElement::from(num_queries as u32),
        ];

        let options = ProofOptions::new(
            num_queries,
            blowup_factor,
            grinding_factor,
            field_extension,
            fri_folding_factor as usize,
            fri_remainder_max_degree as usize,
        );
        assert_eq!(expected, options.to_elements());
    }
}