use std::io::Read;
use core::error::AzureRequestError;
use hyper::client::Response;
use serde_json;

header! { (BrokerPropertiesHeader, "BrokerProperties") => [String] }

///
/// A list of the properties that the message can have.
/// This is not all the possible properties exposed, but it's
/// some of the common ones.
///
/// These properties are instantiated for every message that is
/// created so properties are omitted to reduce the overhead of
/// creating simple messages.
///
/// Most of these shouldn't be assigned to, but treated as read only.
/// Many of them are generated by the service bus itself when a message is added.
/// They are still writeable to allow for flexibility though in special circumstances.
#[allow(non_snake_case)]
#[derive(Eq,PartialEq,Clone,Debug,Serialize,Deserialize,Default)]
pub struct BrokerProperties {
    // These 3 fields let you complete/abandon a request.
    #[serde(skip_serializing_if="Option::is_none")]
    pub LockToken: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub MessageId: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub SequenceNumber: Option<usize>,

    #[serde(skip_serializing_if="Option::is_none")]
    pub DeliveryCount: Option<usize>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub EnqueuedSequenceNumber: Option<usize>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub EnqueuedTimeUtc: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub Label: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub State: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub TimeToLive: Option<usize>,
}

///
/// Queues and Topics send and receive brokered messages.
/// Brokered messages are an abstraction over the body of the message
/// and the properties of it.
///
/// When creating a brokered message, the content is automatically encoded into
/// XML. This is to make interoperation with many of the existing service bus libaries
/// easier because they expect serialized data. When interoperating with a library that
/// does not serialize data, the raw content can be extracted.
///
/// BrokeredMessage has unique clone behavior. It is possible to clone a message and then
/// perform actions on it that fail because the message no longer exists on the server.
///
/// ````
/// let message = client.receive();
/// let message2 = message.clone();
///
/// // The message is deleted on the server here.
/// client.complete_message(message);
///
/// // Continue working with message2;
///
/// // This will fail because the message has already been completed once.
/// client.complete_message(messsage2)
/// ````
///
#[derive(Clone,Eq,PartialEq,Debug)]
pub struct BrokeredMessage {
    pub props: Box<BrokerProperties>,
    body: String,
}

impl BrokeredMessage {
    /// Serializes the string into a message. Note that the message is wrapped in
    /// XML to be compatible with what .Net and most client letters are expecting.
    ///
    /// Your message will be serialized into:
    ///
    /// ```
    /// <string>Here's your message</string>
    /// ```
    ///
    /// This library will correctly deserialize your message and it is designed to
    /// be deserializeable by a .Net client as well.
    ///
    /// If a more viable XML serialization/deserialization library becomes avaiable,
    /// more general object types may be serialized about as well.
    /// if you need to serialize a complex object, serialize it first and then create
    /// a message out of it.
    pub fn with_body(body: &str) -> BrokeredMessage {
        BrokeredMessage {
            body: format!("<string>{}</string>", body),
            props: Box::new(Default::default()),
        }
    }

    ///
    /// Create a new message with a body and properties.
    /// You usually won't call this method directly.
    pub fn with_body_and_props(body: &str, props: BrokerProperties) -> BrokeredMessage {
        BrokeredMessage {
            body: body.to_string(),
            props: Box::new(props),
        }
    }

    ///
    /// Create a new message from a Hyper Http response.
    /// This deserializes the Message Properties and moves the body into the
    /// message. It doesn't attempt to deserialize the body into XML. This is done
    /// lazily in the event that the body is malformed or not serialized.
    /// The message can still be completed or the body can be parsed from its raw contents.
    ///
    pub fn with_response(mut response: Response) -> BrokeredMessage {
        let mut body = String::new();
        response.read_to_string(&mut body).ok();

        let props = response.headers
            .get::<BrokerPropertiesHeader>()
            .and_then(|header| serde_json::from_str::<BrokerProperties>(header).ok())
            .unwrap_or(Default::default());

        BrokeredMessage {
            body: body,
            props: Box::new(props),
        }
    }

    /// Attempts to deserialize the body into a String loosely based on what the .Net client
    /// will attempt to do when deserialzing the message.
    pub fn get_body(&self) -> Result<String, AzureRequestError> {
        // Get the opening of the first string.
        self.body.trim()
            .find("<string")
            .and_then(|idx| if idx == 0 {Some(self.body.split_at(idx).1) } else { None })
            // Look for the closing brace and take everything in the middle.
            .and_then(|rhs| rhs.rfind("</string>").map(|idx| rhs.split_at(idx).0))
            // Skip the rest of the opening tag. Then build a new string from the rest of the body.
            .map(|inner| inner.chars().skip_while(|&ch| ch != '>').skip(1).collect::<String>())
            // If any of these steps failed, return an error.
            .ok_or(AzureRequestError::NonSerializedBody)
    }

    /// Returns the raw string body. This is useful if the agent who put the message in the
    /// queue didn't serialize the message.
    pub fn get_body_raw(&self) -> &str {
        &self.body
    }

    /// Serializes all of the message properties into JSON. This is mostly used to transmit
    /// over HTTP, but it is exposed to the user of the library as well.
    pub fn props_as_json(&self) -> String {
        serde_json::to_string(&self.props).unwrap()
    }
}

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

    #[test]
    fn message_empty_test() {
        let message = BrokeredMessage::with_body("");
        assert_eq!(Ok(String::from("")), message.get_body());
        assert_eq!("<string></string>", message.get_body_raw());
    }

    #[test]
    fn message_deserializes_test() {
        let message = BrokeredMessage::with_body("<b>Hello World</b>");
        assert_eq!(Ok(String::from("<b>Hello World</b>")), message.get_body());
        assert_eq!("<string><b>Hello World</b></string>",
                   message.get_body_raw());
    }

    #[test]
    fn message_json_test() {
        let message = BrokeredMessage::with_body("{\"Azure\":2}");
        assert_eq!(Ok(String::from("{\"Azure\":2}")), message.get_body());
        assert_eq!("<string>{\"Azure\":2}</string>", message.get_body_raw());
    }
}
