use std::io::{Write, Result as IoResult};

use uri::{Host, Uri};
use time::{Utc};

use crate::{HttpVersion, Method, parse::ParseError, header::Header};

///For Serialization. Does no extra processing of the contents otherwise.
/// Pre-fill with things like required headers, etc.
pub struct Request {
    pub method: Method,
    pub version: HttpVersion,
    pub target: RequestTarget,
    pub headers: Vec<Header>,
    pub body: Vec<u8>,
}


pub enum RequestTarget {
    Origin(String),
    Absolute(Uri),
    Authority(Host,u16),
    Asterisk,
}

impl RequestTarget {
    //TODO: Write
    pub fn from_bytes(bytes: &[u8]) -> Result<RequestTarget, ParseError> {
        todo!()
    }
    
    /// Write to a Writer. Outputs in the format expected by HTTP 1.1 for the request target in the request line.
    fn write_to<W: Write>(&self, w: &mut W) -> IoResult<()> {
         match self {
            RequestTarget::Origin(o) => w.write_all(b"/").and(w.write_all(o.as_bytes())),
            RequestTarget::Absolute(uri) => uri.write_to(w),
            RequestTarget::Authority(host,port) => host.write_to(w).and(w.write_all(b":")).and(w.write_all(port.to_string().as_bytes())),
            RequestTarget::Asterisk => w.write_all(b"*"),
        }.and(w.write_all(b"/"))
    }
}


impl Request {
    fn write_request_line<W: Write>(&self, w: &mut W) -> IoResult<()> {
        w.write_all(self.method.to_string().as_bytes())?;
        w.write_all(b" ")?;
        self.target.write_to(w);
        w.write_all(b" ")?;
        w.write_all(self.version.to_string().as_bytes())?;
        w.write_all(b"\r\n")
    }
    
    pub fn write_to<W: Write>(&self, w: &mut W) -> IoResult<()> {
        self.write_request_line(w)?;
        for header in &self.headers {
            header.write_to(w)?;
            w.write_all(b"\r\n")?;
        }
        w.write_all(b"\r\n")?;
        w.write_all(self.body.as_slice())
    }
}

impl Header {
    //WIP: More variants
    /// Returns true if the header is permitted to be sent in a request.
    fn is_request_header(&self) -> bool {
        use Header::*;
        match self {
            Host(_) | UserAgent(_) | Accept(_) => true,
            _ => false,
        }
    }
    
    //WIP: More variants
    /// Returns true if the header is permitted to be sent in a response.
    fn is_response_header(&self) -> bool {
        use Header::*;
        match self {
            ContentEncoding(_) | ContentType(_) => true,
            _ => false,
        }
    }

    fn name_as_bytes(&self) -> &[u8] {
        match self {
            Header::Host(_) => b"Host",
            Header::UserAgent(_) => b"User-Agent",
            Header::Accept(_) => b"Accept",
            Header::ContentType(_) => b"Content-Type",
            Header::AcceptLanguage(_) => b"Accept-Language",
            Header::AcceptEncoding(_) => b"Accept-Encoding",
            Header::ContentEncoding(_) => b"Content-Encoding",
            Header::Cookie(_) => b"Cookie",
            Header::Priority(_) => b"Priority",
            Header::IfModifiedSince(_) => b"If-Modified-Since",
            Header::Other(name, _) => name.as_str().as_bytes(),
        }
    }

    fn value_as_bytes(&self) -> &[u8] {
        match self {
            Header::Host(host) => todo!(),
            Header::UserAgent(agent) => todo!(),
            Header::Accept(items) => todo!(),
            Header::ContentType(content_type) => todo!(),
            Header::AcceptLanguage(items) => todo!(),
            Header::AcceptEncoding(items) => todo!(),
            Header::ContentEncoding(encodings) => todo!(),
            Header::Cookie(items) => todo!(),
            Header::Priority(priority) => todo!(),
            Header::IfModifiedSince(utc) => todo!(),
            Header::Other(_, value) => value.as_str().as_bytes(),
        }
    }

    //TODO: Write
    fn write_value_to<W: Write>(&self, w: &mut W) -> IoResult<()> {
        todo!();
    }

    fn write_to<W: Write>(&self, w: &mut W) -> IoResult<()> {
        w.write_all(self.name_as_bytes())?;
        w.write_all(b": ")?;
        w.write_all(self.value_as_bytes())?;
        Ok(())
    }
}
