Documentation
//! # 📡 HTTP Request API
//!
//! API to be able to issue HTTP GET, POST, PUT, DELETE, PATCH requests.
//!
//! This is useful and powerful to be able to access and communicate with local or internet services.
//!
//! ## Security note
//!
//! Currently there are no limitations on what domains and services can be accessed.
//! But this will change later as more of the Ark capability-based security gets put into place
//! and where modules have to opt-in and request access to minimal possible specific domains and URLs.
//!
//! To prepare for this, avoid connecting to arbitrary URLs or allow user input of what to connect to when possible.
//! Prefer domains and URLs the module can know statically at compile time, as it may need to declare them in its
//! manifest later to get access to it.
//!
//! ## Example usage
//!
//! ```rust,no_run
//! require_http_request_api!();
//!
//! // Get Google's frontpage
//! let html_bytes = http_get("https://google.com").unwrap();
//! ```

use crate::{ffi::http_request_v1 as ffi, Error, ErrorCode};
use std::{
    borrow::Cow,
    future::Future,
    pin::Pin,
    string::FromUtf8Error,
    task::{Context, Poll},
};

#[doc(hidden)]
pub use ffi::API as FFI_API;

pub use ffi::Method;

/// An HTTP response object sent back from a server.
pub struct Response {
    /// HTTP status code. If it's different from the 200 range (from 200 to 299), indicates a
    /// failure that needs to be handled.
    pub status_code: u16,

    bytes: Vec<u8>,
}

impl Response {
    /// Consumes the `Response` object into the bytes it contains.
    pub fn into_bytes(self) -> Vec<u8> {
        self.bytes
    }

    /// Bytes sent back from the server.
    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes
    }

    /// Bytes sent back from the server, interpreted as a string. Can fail if the bytes don't
    /// represent a valid utf8 encoding.
    pub fn as_text(&self) -> Result<String, FromUtf8Error> {
        String::from_utf8(self.bytes.clone())
    }

    /// Bytes sent back from the server, interpreted as a string. Will interpret invalid utf8
    /// encodings loosely, so as to return something.
    pub fn as_text_lossy(&self) -> Cow<'_, str> {
        String::from_utf8_lossy(&self.bytes)
    }
}

struct RequestFuture(Result<ffi::RequestHandle, ErrorCode>);

/// The response to any http request served by this API.
pub type ResponseResult = Result<Response, Error>;

impl Future for RequestFuture {
    type Output = ResponseResult;
    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        match self.0 {
            Ok(handle) => {
                let mut status: u32 = 0;
                if let Err(error_code) = ffi::is_ready(handle, &mut status) {
                    return Poll::Ready(Err(Error::from(error_code)));
                }
                if status != ffi::STATUS_PENDING {
                    match ffi::retrieve(handle) {
                        Ok(bytes) => {
                            assert!(u16::try_from(status).is_ok());
                            let response = Response {
                                bytes,
                                status_code: status as u16,
                            };
                            Poll::Ready(Ok(response))
                        }
                        Err(error_code) => Poll::Ready(Err(Error::from(error_code))),
                    }
                } else {
                    Poll::Pending
                }
            }
            Err(error_code) => Poll::Ready(Err(Error::from(error_code))),
        }
    }
}

/// Issue async HTTP request of the specific method
///
/// It is generally preferred to use the more explicit short-hand functions such as `http_get`, `http_post` instead.
///
/// # Returns
///
/// On badly-formed URL or
/// On 404 error this will return `Error::NotFound`
pub fn http_request(
    method: Method,
    url: &str,
    body: &[u8],
) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(method, url, body))
}

/// Issue async HTTP GET request
pub fn http_get(url: &str) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Get, url, &[]))
}

/// Issue async HTTP POST request
pub fn http_post(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Post, url, body))
}

/// Issue async HTTP PUT request
pub fn http_put(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Put, url, body))
}

/// Issue async HTTP DELETE request
pub fn http_delete(url: &str) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Delete, url, &[]))
}

/// Issue async HTTP PATCH request
pub fn http_patch(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Patch, url, body))
}

/// Issue async HTTP HEAD request
pub fn http_head(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Head, url, body))
}

/// Issue async HTTP OPTIONS request
pub fn http_options(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Options, url, body))
}

/// Issue async HTTP TRACE request
pub fn http_trace(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Trace, url, body))
}

/// Issue async HTTP CONNECT request
pub fn http_connect(url: &str, body: &[u8]) -> impl Future<Output = ResponseResult> {
    RequestFuture(ffi::request(ffi::Method::Connect, url, body))
}