/* Run doctest for sample codes in README */
#![cfg_attr(feature="DEBUG", doc = include_str!("../../README.md"))]

//! <div align="center">
//!     <h1>Ohkami</h1>
//!     Ohkami <em>- [狼] wolf in Japanese -</em> is a performant, declarative, and runtime-flexible web framework for Rust.
//! </div>
//!
//! <br>
//!
//! - *macro-less and type-safe* APIs for declarative, ergonomic code
//! - *runtime-flexible* ： `tokio`, `smol`, `nio`, `glommio`, `monoio`, `compio` and `worker` (Cloudflare Workers), `lambda` (AWS Lambda)
//! - good performance, no-network testing, well-structured middlewares, Server-Sent Events, WebSocket, highly integrated OpenAPI document generation, ...
//!
//! See [GitHub repo](https://github.com/ohkami-rs/ohkami) for details!

#![cfg_attr(docsrs, feature(doc_cfg))]
#![allow(incomplete_features)]
#![cfg_attr(
    feature = "nightly",
    feature(specialization, try_trait_v2, impl_trait_in_assoc_type,)
)]
#![allow(
    clippy::non_minimal_cfg, // for `cfg(any(...))` for various `rt_*` features
    clippy::result_large_err, // TODO: reduce the size of `Response` (and also `Request`) (most parts are their headers!)
)]

#[cfg(any(
    all(
        feature = "rt_tokio",
        any(
            feature = "rt_smol",
            feature = "rt_nio",
            feature = "rt_glommio",
            feature = "rt_monoio",
            feature = "rt_compio",
            feature = "rt_worker"
        )
    ),
    all(
        feature = "rt_smol",
        any(
            feature = "rt_nio",
            feature = "rt_glommio",
            feature = "rt_monoio",
            feature = "rt_compio",
            feature = "rt_worker",
            feature = "rt_tokio"
        )
    ),
    all(
        feature = "rt_nio",
        any(
            feature = "rt_glommio",
            feature = "rt_monoio",
            feature = "rt_compio",
            feature = "rt_worker",
            feature = "rt_tokio",
            feature = "rt_smol"
        )
    ),
    all(
        feature = "rt_glommio",
        any(
            feature = "rt_monoio",
            feature = "rt_compio",
            feature = "rt_worker",
            feature = "rt_tokio",
            feature = "rt_smol",
            feature = "rt_nio"
        )
    ),
    all(
        feature = "rt_monoio",
        any(
            feature = "rt_compio",
            feature = "rt_worker",
            feature = "rt_tokio",
            feature = "rt_smol",
            feature = "rt_nio",
            feature = "rt_glommio"
        )
    ),
    all(
        feature = "rt_compio",
        any(
            feature = "rt_worker",
            feature = "rt_tokio",
            feature = "rt_smol",
            feature = "rt_nio",
            feature = "rt_glommio",
            feature = "rt_monoio"
        )
    ),
    all(
        feature = "rt_worker",
        any(
            feature = "rt_tokio",
            feature = "rt_smol",
            feature = "rt_nio",
            feature = "rt_glommio",
            feature = "rt_monoio",
            feature = "rt_compio"
        )
    ),
))]
compile_error! {"
    Can't activate multiple `rt_*` features at once!
"}

#[cfg(feature = "__rt_native__")]
mod __rt__ {
    #![allow(unused)]

    #[cfg(feature = "__io_futures__")]
    pub(crate) use futures_util::{AsyncReadExt as AsyncRead, AsyncWriteExt as AsyncWrite};
    #[cfg(feature = "__io_tokio__")]
    pub(crate) use tokio::io::{AsyncReadExt as AsyncRead, AsyncWriteExt as AsyncWrite};

    #[cfg(feature = "rt_compio")]
    pub(crate) use compio::net::{TcpListener, ToSocketAddrsAsync as ToSocketAddrs};
    #[cfg(feature = "rt_smol")]
    pub(crate) use smol::net::{AsyncToSocketAddrs as ToSocketAddrs, TcpListener, TcpStream};
    #[cfg(feature = "rt_tokio")]
    pub(crate) use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
    #[cfg(feature = "rt_glommio")]
    pub(crate) use {
        glommio::net::{TcpListener, TcpStream},
        std::net::ToSocketAddrs,
    };
    #[cfg(feature = "rt_monoio")]
    pub(crate) use {
        monoio::net::TcpListener, monoio_compat::TcpStreamCompat as TcpStream,
        std::net::ToSocketAddrs,
    };
    #[cfg(feature = "rt_nio")]
    pub(crate) use {
        nio::net::{TcpListener, TcpStream},
        std::net::ToSocketAddrs,
    };

    #[cfg(feature = "rt_compio")]
    pub(crate) type TcpStream = compio::io::compat::AsyncStream<compio::net::TcpStream>;

    #[cfg(not(feature = "rt_nio"))]
    #[inline(always)]
    pub(crate) async fn accept(
        listener: &TcpListener,
    ) -> std::io::Result<(TcpStream, std::net::SocketAddr)> {
        #[cfg(any(feature = "rt_tokio", feature = "rt_smol"))]
        {
            listener.accept().await
        }
        #[cfg(any(feature = "rt_glommio"))]
        {
            let connection = listener.accept().await?;
            let addr = connection.peer_addr()?;
            Ok((connection, addr))
        }
        #[cfg(any(feature = "rt_monoio", feature = "rt_compio"))]
        {
            let (conn, addr) = listener.accept().await?;
            Ok((TcpStream::new(conn), addr))
        }
    }

    pub trait IntoTcpListener<T> {
        fn into_tcp_listener(self) -> impl Future<Output = TcpListener>;
    }
    impl IntoTcpListener<()> for TcpListener {
        async fn into_tcp_listener(self) -> TcpListener {
            self
        }
    }
    impl<A: ToSocketAddrs> IntoTcpListener<A> for A {
        async fn into_tcp_listener(self) -> TcpListener {
            let binded = TcpListener::bind(self);

            #[cfg(not(any(feature = "rt_glommio", feature = "rt_monoio")))]
            let binded = binded.await;

            binded.expect("Failed to bind TCP listener")
        }
    }

    #[cfg(feature = "rt_tokio")]
    pub(crate) use tokio::time::sleep;
    #[cfg(feature = "rt_smol")]
    pub(crate) async fn sleep(duration: std::time::Duration) {
        smol::Timer::after(duration).await;
    }
    #[cfg(feature = "rt_compio")]
    pub(crate) use compio::runtime::time::sleep;
    #[cfg(feature = "rt_glommio")]
    pub(crate) use glommio::timer::sleep;
    #[cfg(feature = "rt_monoio")]
    pub(crate) use monoio::time::sleep;
    #[cfg(feature = "rt_nio")]
    pub(crate) use nio::sleep;

    #[cfg(feature = "__rt_threaded__")]
    mod task {
        pub trait Task: std::future::Future<Output: Send + 'static> + Send + 'static {}
        impl<F: std::future::Future<Output: Send + 'static> + Send + 'static> Task for F {}
    }
    #[cfg(not(feature = "__rt_threaded__"))]
    mod task {
        pub trait Task: std::future::Future + 'static {}
        impl<F: std::future::Future + 'static> Task for F {}
    }

    pub(crate) fn spawn(task: impl task::Task) {
        #[cfg(feature = "rt_tokio")]
        tokio::task::spawn(task);

        #[cfg(feature = "rt_smol")]
        smol::spawn(task).detach();

        #[cfg(feature = "rt_glommio")]
        glommio::spawn_local(task).detach();

        #[cfg(feature = "rt_monoio")]
        monoio::spawn(task);

        #[cfg(feature = "rt_compio")]
        compio::runtime::spawn(task).detach();
    }

    #[cfg(test)]
    pub(crate) mod testing {
        pub(crate) fn block_on<F: Future>(future: F) -> F::Output {
            #[cfg(feature = "rt_tokio")]
            return tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(future);

            #[cfg(feature = "rt_smol")]
            return smol::block_on(future);

            #[cfg(feature = "rt_nio")]
            return nio::RuntimeBuilder::new().build().unwrap().block_on(future);

            #[cfg(feature = "rt_glommio")]
            return glommio::LocalExecutor::default().run(future);

            #[cfg(feature = "rt_monoio")]
            return monoio::RuntimeBuilder::<monoio::FusionDriver>::new()
                .enable_all()
                .build()
                .unwrap()
                .block_on(future);

            #[cfg(feature = "rt_compio")]
            return compio::runtime::Runtime::new().unwrap().block_on(future);
        }

        pub(crate) const PORT: u16 = {
            #[cfg(feature = "rt_tokio")]
            {
                3001
            }
            #[cfg(feature = "rt_smol")]
            {
                3003
            }
            #[cfg(feature = "rt_nio")]
            {
                3004
            }
            #[cfg(feature = "rt_glommio")]
            {
                3005
            }
            #[cfg(feature = "rt_monoio")]
            {
                3006
            }
            #[cfg(feature = "rt_compio")]
            {
                3007
            }
        };
    }
}

pub mod util;

mod config;
pub use config::Config;

#[cfg(debug_assertions)]
#[cfg(feature = "__rt__")]
pub mod testing;

mod request;
pub use ::ohkami_macros::FromRequest;
pub use request::{FromRequest, Method, Request};

mod response;
pub use response::{IntoResponse, Response, Status};

#[cfg(feature = "__rt_native__")]
mod session;

#[cfg(feature = "__rt__")]
mod router;

#[cfg(feature = "__rt__")]
mod ohkami;
#[cfg(feature = "__rt__")]
pub use ohkami::{Ohkami, Route};

pub mod fang;
pub use fang::{Fang, FangAction, FangProc, handler};

pub mod header;

pub mod claw;
pub use claw::{Cookie, Json, Path, Query};

#[cfg(feature = "sse")]
#[cfg_attr(docsrs, doc(cfg(feature = "sse")))]
pub mod sse;

#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
pub mod ws;

#[cfg(feature = "rt_lambda")]
mod x_lambda;
/* TODO
#[cfg(feature="rt_lambda")]
pub use x_lambda::*;
*/

#[cfg(feature = "rt_worker")]
mod x_worker;
#[cfg(feature = "rt_worker")]
pub use x_worker::*;

pub mod prelude {
    pub use crate::fang::Context;
    pub use crate::serde::{Deserialize, Serialize};
    pub use crate::util::FangAction;
    pub use crate::{IntoResponse, Json, Method, Path, Query, Request, Response, Status};

    #[cfg(feature = "__rt__")]
    pub use crate::{Ohkami, Route};
}

/// Somthing almost [serde](https://crates.io/crates/serde) + [serde_json](https://crates.io/crates/serde_json).
///
/// ---
/// *not_need_serde_in_your_dependencies.rs*
/// ```
/// use ohkami::serde::{json, Serialize};
///
/// #[derive(Serialize)]
/// struct User {
///     #[serde(rename = "username")]
///     name: String,
///     age:  u8,
/// }
///
/// # fn _user() {
/// let user = User {
///     name: String::from("ABC"),
///     age:  200,
/// };
/// assert_eq!(json::to_string(&user).unwrap(), r#"
///     {"age":200,"username":"ABC"}
/// "#);
/// # }
/// ```
/// ---
pub mod serde {
    pub use ::ohkami_macros::{Deserialize, Serialize};
    pub use ::serde::de::{self, Deserialize, Deserializer};
    pub use ::serde::ser::{self, Serialize, Serializer};
    pub use ::serde_json as json;
}

#[cfg(feature = "openapi")]
#[cfg_attr(docsrs, doc(cfg(feature = "openapi")))]
/// # Highly integrated OpenAPI support for Ohkami
pub mod openapi {
    pub use ::ohkami_macros::{Schema, operation};
    pub use ::ohkami_openapi::document::Server;
    pub use ::ohkami_openapi::*;

    #[cfg(feature = "__rt__")]
    #[derive(Clone)]
    pub struct OpenAPI<'s> {
        pub title: &'static str,
        pub version: &'static str,
        pub servers: &'s [Server],
    }

    /// A fang to set OpenAPI tag for handlers of a `Ohkami`
    ///
    /// ## note
    ///
    /// * OpenAPI tags are inherited and stacked for child `Ohkami`s (if any).
    /// * This is a fang, but introduces NO runtime overhead.
    ///
    /// ## example
    ///
    /// ```ignore
    /// use ohkami::prelude::*;
    /// use ohkami::openapi;
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let users_ohkami = Ohkami::new((
    ///         openapi::Tag("users"),
    ///         "/"
    ///             .GET(list_users),
    ///             .POST(create_user),
    ///         "/:id"
    ///             .GET(get_user_profile),
    ///     ));
    ///
    ///     Ohkami::new((
    ///         "/users".By(users_ohkami),
    ///
    ///         // ...
    ///     )).howl("localhost:5050").await
    /// }
    /// # async fn list_users() {}
    /// # async fn create_user() {}
    /// # async fn get_user_profile() {}
    /// ```
    pub struct Tag(pub &'static str);
    impl<I: crate::FangProc> crate::Fang<I> for Tag {
        /// just pass `inner` through
        type Proc = I;
        fn chain(&self, inner: I) -> Self::Proc {
            inner
        }

        /// add tag for operations of `Ohkami` having this `Tag`
        fn openapi_map_operation(&self, operation: Operation) -> Operation {
            operation.with_tag(self.0)
        }
    }
}

#[doc(hidden)]
pub mod __internal__ {
    pub use ::serde;

    pub use ohkami_macros::consume_struct;

    pub use crate::fang::Fangs;

    /* for benchmarks */
    #[cfg(feature = "DEBUG")]
    pub use crate::{
        request::{RequestHeader, RequestHeaders},
        response::{ResponseHeader, ResponseHeaders},
    };
}
