// Copyright (c) 2016 The Rouille developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

/// Dispatches between blocks depending on the value of the `Accept` header.
///
/// This macro takes as first parameter the request object, and then each additional parameter must
/// be of the form `mime => value` where `mime` is a MIME type in quotes and `value` is an
/// expression of any type.
///
/// The macro returns the value corresponding to the MIME type that has the highest priority in
/// the request's `Accept` header. If multiple MIME types have the same priority, the earliest in
/// the list passed to the macro is chosen. If no MIME matches the request, the first in the list
/// is chosen. If there is no `Accept` header in the request, it is as if the header's value
/// was `*/*`.
///
/// You can also use `*` in the MIME types you pass to the macro. The MIME `*/*` can be used as a
/// default handler.
///
/// > **Note**: Using `|` just like in real match expressions is not yet supported because the
/// > author didn't find a way to make it work with Rust macros.
///
/// # Basic example
///
/// ```
/// # #[macro_use] extern crate rouille;
/// use rouille::Request;
/// use rouille::Response;
///
/// fn handle(request: &Request) -> Response {
///     accept!(request,
///         "text/html" => Response::html("<p>Hello world</p>"),
///         "text/plain" => Response::text("Hello world"),
///     )
/// }
/// # fn main() {}
/// ```
///
/// # Example with a default handler
///
/// ```
/// # #[macro_use] extern crate rouille;
/// use rouille::Request;
/// use rouille::Response;
///
/// fn handle(request: &Request) -> Response {
///     accept!(request,
///         "text/html" => Response::html("<p>Hello world</p>"),
///         "text/plain" => Response::text("Hello world"),
///         "*/*" => Response::empty_406()
///     )
/// }
/// # fn main() {}
/// ```
#[macro_export]
macro_rules! accept {
    ($request:expr, $($mime:expr => $val:expr),+ $(,)*) => ({
        use $crate::input;
        use std::iter;

        let header = $request.header("Accept").unwrap_or("*/*");

        let handled = {
            let i = iter::empty();
            $(let i = i.chain(iter::once($mime));)+
            i
        };

        let mut preferred = input::priority_header_preferred(header, handled).unwrap_or(0);

        let mut outcome = None;

        preferred += 1;
        $(
            if preferred >= 1 {
                preferred -= 1;
                if preferred == 0 {
                    outcome = Some($val);
                }
            }
        )+

        outcome.unwrap()    // unwrap() can only panic if priority_header_preferred has a bug or
                            // if the list of mimes is empty (which can't happen)
    });
}

#[cfg(test)]
mod tests {
    use Request;

    #[test]
    fn basic() {
        let request = Request::fake_http("GET", "/", vec![
            ("Accept".to_owned(), "text/plain, */*".to_owned())
        ], vec![]);

        let result = accept!(&request,
            "text/plain" => 5,
            "*/*" => 12,
        );

        assert_eq!(result, 5);
    }

    #[test]
    fn wildcard() {
        let request = Request::fake_http("GET", "/", vec![
            ("Accept".to_owned(), "image/gif".to_owned())
        ], vec![]);

        let result = accept!(&request,
            "text/plain" => 5,
            "*/*" => 12,
        );

        assert_eq!(result, 12);
    }

    #[test]
    fn no_match() {
        let request = Request::fake_http("GET", "/", vec![
            ("Accept".to_owned(), "image/gif".to_owned())
        ], vec![]);

        let result = accept!(&request,
            "text/plain" => 5,
            "image/svg+xml" => 12,
        );

        assert_eq!(result, 5);
    }

    #[test]
    fn multimatch_first() {
        let request = Request::fake_http("GET", "/", vec![
            ("Accept".to_owned(), "image/gif".to_owned())
        ], vec![]);

        let result = accept!(&request,
            "text/plain" => 5,
            "text/plain" => 12,
            "text/plain" => 28,
        );

        assert_eq!(result, 5);
    }

    #[test]
    fn no_header_first() {
        let request = Request::fake_http("GET", "/", vec![], vec![]);

        let result = accept!(&request,
            "image/gif" => 5,
            "text/plain" => 12,
            "text/html" => 28,
        );

        assert_eq!(result, 5);
    }

    #[test]
    fn no_header_wildcard() {
        let request = Request::fake_http("GET", "/", vec![
            ("Accept".to_owned(), "image/tiff".to_owned())
        ], vec![]);

        let result = accept!(&request,
            "image/gif" => 5,
            "text/plain" => 12,
            "text/html" => 28,
            "*/*" => 37
        );

        assert_eq!(result, 37);
    }
}
