// Copyright 2020-2022 Michael Aaron Murphy <michael@mmurphy.dev>
// SPDX-License-Identifier: MIT OR Apache-2.0

//! Fastest concatenation of strings and vectors on Crates.io
//!
//! The goal of these macros are to reduce the amount of allocations that are required
//! when concatenating string buffers and vectors; with a macro that makes it simple to
//! achieve in practice.
//!
//! # Implementation Notes
//!
//! - Inputs do not require to implement `AsRef<str>` or `AsRef<[T]>`.
//! - This crate is compatible with `no_std` environments.
//! - Consider it a bug if this is not the fastest concatenation library.

/// Appends any number of string slices onto a string buffer
///
/// ```rust
/// use concat_in_place::strcat;
///
/// let domain = "domain.com";
/// let endpoint = "inventory/parts";
/// let id = "10512";
///
/// // Efficiently concatenate a new string
/// let mut url = strcat!(domain "/" endpoint "/" id);
/// assert_eq!(&*url, "domain.com/inventory/parts/10512");
///
/// let get = "get";
/// let status = "status";
///
/// // Concatenate to existing string.
/// let slice = strcat!(&mut url, "/" get "/" status);
/// assert_eq!(slice, "domain.com/inventory/parts/10512/get/status");
///
/// ```
///
/// # Implementation Notes
///
/// Technically works with any string type that has the following methods:
///
/// - `capacity`
/// - `len`
/// - `push_str`
/// - `reserve`
/// - `with_capacity`
#[macro_export]
macro_rules! strcat {
    ($input:expr, $($element:expr)*) => {{
        let out = $input;
        let mut required = 0;

        $(
            required += $element.len();
        )*

        let free = out.capacity() - out.len();
        if (free < required) {
            out.reserve(required - free);
        }

        $(
            out.push_str($element);
        )*

        &*out
    }};

    ($($element:expr)+) => {{
        let mut required = 0;
        $(required += $element.len();)+
        let mut out = String::with_capacity(required);
        $(out.push_str($element);)+
        out
    }}
}

/// Appends any number of slices onto a vector
///
/// ```rust
/// use concat_in_place::veccat;
///
/// let domain = b"domain.com";
/// let endpoint = b"inventory/parts";
/// let id = b"10512";
///
/// // Efficiently concatenate a new vector.
/// let mut url = veccat!(domain b"/" endpoint b"/" id);
/// assert_eq!(&*url, b"domain.com/inventory/parts/10512");
///
/// let get = b"get";
/// let status = b"status";
///
/// // Concatenate to an existing vector.
/// let slice = veccat!(&mut url, b"/" get b"/" status);
/// assert_eq!(slice, b"domain.com/inventory/parts/10512/get/status");
/// ```
///
/// # Implementation Notes
///
/// Technically works with any type that has the following methods:
///
/// - `capacity`
/// - `extend_from_slice`
/// - `len`
/// - `reserve`
/// - `with_capacity`
#[macro_export]
macro_rules! veccat {
    ($input:expr, $($element:expr)*) => {{
        let out = $input;
        let mut required = 0;

        $(
            required += $element.len();
        )*

        let free = out.capacity() - out.len();
        if (free < required) {
            out.reserve(required - free);
        }

        $(
            out.extend_from_slice($element);
        )*

        &*out
    }};

    ($($element:expr)+) => {{
        let mut required = 0;
        $(required += $element.len();)+
        let mut out = Vec::with_capacity(required);
        $(out.extend_from_slice($element);)+
        out
    }}
}

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

    #[test]
    fn bytes() {
        let mut out = Vec::new();

        let expected = b"domain.com/endpoint/id";
        let actual: &[u8] = veccat!(&mut out, b"domain.com" b"/" b"endpoint" b"/" b"id");
        assert_eq!(actual, expected, "{}", String::from_utf8_lossy(actual));
    }

    #[test]
    fn strings() {
        let mut out = String::new();

        let expected = "domain.com/endpoint/id";
        let actual = strcat!(&mut out, "domain.com" "/" "endpoint" "/" "id");
        assert_eq!(actual, expected);
    }
}
