use std::fmt;
use std::str::FromStr;

use crate::error::{Error, ErrorKind};
use crate::result::Result;

// Common HTTP methods.
//
// Unless otherwise noted, these are defined in RFC 7231 section 4.3.
#[derive(Debug, Clone, Copy)]
pub enum Method {
    Get,
    Head,
    Post,
    Put,
    Patch, // RFC 5789
    Delete,
    Connect,
    Options,
    Trace,
}

impl Method {
    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
        match bytes {
            b"GET" | b"get" => Ok(Method::Get),
            b"HEAD" | b"head" => Ok(Method::Head),
            b"POST" | b"post" => Ok(Method::Post),
            b"PUT" | b"put" => Ok(Method::Put),
            b"PATCH" | b"patch" => Ok(Method::Patch),
            b"DELETE" | b"delete" => Ok(Method::Delete),
            b"CONNECT" | b"connect" => Ok(Method::Connect),
            b"OPTIONS" | b"options" => Ok(Method::Options),
            b"TRACE" | b"trace" => Ok(Method::Trace),
            _ => Err(Error::simple(ErrorKind::HttpMethod)),
        }
    }
}

impl FromStr for Method {
    type Err = Error;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "GET" | "get" => Ok(Method::Get),
            "HEAD" | "head" => Ok(Method::Head),
            "POST" | "post" => Ok(Method::Post),
            "PUT" | "put" => Ok(Method::Put),
            "PATCH" | "patch" => Ok(Method::Patch),
            "DELETE" | "delete" => Ok(Method::Delete),
            "CONNECT" | "connect" => Ok(Method::Connect),
            "OPTIONS" | "options" => Ok(Method::Options),
            "TRACE" | "trace" => Ok(Method::Trace),
            _ => Err(Error::simple(ErrorKind::HttpMethod)),
        }
    }
}

impl fmt::Display for Method {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Method::Get => write!(f, "GET"),
            Method::Head => write!(f, "HEAD"),
            Method::Post => write!(f, "POST"),
            Method::Put => write!(f, "PUT"),
            Method::Patch => write!(f, "PATCH"),
            Method::Delete => write!(f, "DELETE"),
            Method::Connect => write!(f, "CONNECT"),
            Method::Options => write!(f, "OPTIONS"),
            Method::Trace => write!(f, "TRACE"),
        }
    }
}
