use crate::errors::*;
use crate::types::*;
use bytes::*;
use std::cell::RefCell;
use std::convert::{TryFrom, TryInto};
use std::mem;
use std::rc::Rc;

pub const MARKER: u8 = 0xB3;
pub const SIGNATURE: u8 = 0x50;

#[derive(Debug, PartialEq, Clone)]
pub struct BoltPath {
    pub nodes: BoltList,
    pub rels: BoltList,
    pub ids: BoltList,
}

impl BoltPath {
    pub fn can_parse(input: Rc<RefCell<Bytes>>) -> bool {
        let input = input.borrow();
        input.len() > 1 && input[0] == MARKER && input[1] == SIGNATURE
    }
}

impl BoltPath {
    pub fn nodes(&self) -> Vec<BoltNode> {
        let mut nodes = Vec::with_capacity(self.nodes.len());
        for bolt_type in self.nodes.iter() {
            if let BoltType::Node(node) = bolt_type {
                nodes.push(node.clone());
            }
        }
        nodes
    }

    pub fn rels(&self) -> Vec<BoltUnboundedRelation> {
        let mut rels = Vec::with_capacity(self.rels.len());
        for bolt_type in self.rels.iter() {
            if let BoltType::UnboundedRelation(rel) = bolt_type {
                rels.push(rel.clone());
            }
        }
        rels
    }

    pub fn ids(&self) -> Vec<BoltInteger> {
        let mut ids = Vec::with_capacity(self.ids.len());
        for bolt_type in self.ids.iter() {
            if let BoltType::Integer(id) = bolt_type {
                ids.push(id.clone());
            }
        }
        ids
    }
}

impl TryFrom<Rc<RefCell<Bytes>>> for BoltPath {
    type Error = Error;

    fn try_from(input: Rc<RefCell<Bytes>>) -> Result<BoltPath> {
        let marker = input.borrow_mut().get_u8();
        let tag = input.borrow_mut().get_u8();
        match (marker, tag) {
            (MARKER, SIGNATURE) => {
                let nodes: BoltList = input.clone().try_into()?;
                let rels: BoltList = input.clone().try_into()?;
                let ids: BoltList = input.clone().try_into()?;
                Ok(BoltPath { nodes, rels, ids })
            }
            _ => Err(Error::InvalidTypeMarker(format!(
                "invalid path marker/tag ({}, {})",
                marker, tag
            ))),
        }
    }
}

impl TryInto<Bytes> for BoltPath {
    type Error = Error;
    fn try_into(self) -> Result<Bytes> {
        let nodes: Bytes = self.nodes.try_into()?;
        let rels: Bytes = self.rels.try_into()?;
        let ids: Bytes = self.ids.try_into()?;

        let mut bytes = BytesMut::with_capacity(
            mem::size_of::<u8>() + mem::size_of::<u32>() + nodes.len() + rels.len() + ids.len(),
        );
        bytes.put_u8(MARKER);
        bytes.put_u8(SIGNATURE);
        bytes.put(nodes);
        bytes.put(rels);
        bytes.put(ids);
        Ok(bytes.freeze())
    }
}

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

    #[test]
    fn should_serialize_a_path() {
        let mark = BoltNode::new(
            42.into(),
            vec!["Person".into()].into(),
            vec![("name".into(), "Mark".into())].into_iter().collect(),
        );
        let james = BoltNode::new(
            43.into(),
            vec!["Person".into()].into(),
            vec![("name".into(), "James".into())].into_iter().collect(),
        );
        let friend = BoltUnboundedRelation::new(
            22.into(),
            "friend".into(),
            vec![("key".into(), "value".into())].into_iter().collect(),
        );

        let path = BoltPath {
            nodes: vec![mark.into(), james.into()].into(),
            rels: vec![friend.into()].into(),
            ids: vec![22.into(), 42.into()].into(),
        };

        let bytes: Bytes = path.try_into().unwrap();

        assert_eq!(
            bytes,
            Bytes::from_static(&[
                0xB3, 0x50, 0x92, 0xB3, 0x4E, 0x2A, 0x91, 0x86, 0x50, 0x65, 0x72, 0x73, 0x6F, 0x6E,
                0xA1, 0x84, 0x6E, 0x61, 0x6D, 0x65, 0x84, 0x4D, 0x61, 0x72, 0x6B, 0xB3, 0x4E, 0x2B,
                0x91, 0x86, 0x50, 0x65, 0x72, 0x73, 0x6F, 0x6E, 0xA1, 0x84, 0x6E, 0x61, 0x6D, 0x65,
                0x85, 0x4A, 0x61, 0x6D, 0x65, 0x73, 0x91, 0xB3, 0x72, 0x16, 0x86, 0x66, 0x72, 0x69,
                0x65, 0x6E, 0x64, 0xA1, 0x83, 0x6B, 0x65, 0x79, 0x85, 0x76, 0x61, 0x6C, 0x75, 0x65,
                0x92, 0x16, 0x2A,
            ])
        );
    }

    #[test]
    fn should_deserialize_a_path() {
        let input = Rc::new(RefCell::new(Bytes::from_static(&[
            0xB3, 0x50, 0x92, 0xB3, 0x4E, 0x2A, 0x91, 0x86, 0x50, 0x65, 0x72, 0x73, 0x6F, 0x6E,
            0xA1, 0x84, 0x6E, 0x61, 0x6D, 0x65, 0x84, 0x4D, 0x61, 0x72, 0x6B, 0xB3, 0x4E, 0x2B,
            0x91, 0x86, 0x50, 0x65, 0x72, 0x73, 0x6F, 0x6E, 0xA1, 0x84, 0x6E, 0x61, 0x6D, 0x65,
            0x85, 0x4A, 0x61, 0x6D, 0x65, 0x73, 0x91, 0xB3, 0x72, 0x16, 0x86, 0x66, 0x72, 0x69,
            0x65, 0x6E, 0x64, 0xA1, 0x83, 0x6B, 0x65, 0x79, 0x85, 0x76, 0x61, 0x6C, 0x75, 0x65,
            0x92, 0x16, 0x2A,
        ])));

        let path: BoltPath = input.try_into().unwrap();

        let nodes = path.nodes();
        let rels = path.rels();
        let ids = path.ids();
        assert_eq!(nodes.len(), 2);
        assert_eq!(rels.len(), 1);
        assert_eq!(ids.len(), 2);
        assert_eq!(
            nodes,
            vec![
                BoltNode::new(
                    42.into(),
                    vec!["Person".into()].into(),
                    vec![("name".into(), "Mark".into())].into_iter().collect(),
                ),
                BoltNode::new(
                    43.into(),
                    vec!["Person".into()].into(),
                    vec![("name".into(), "James".into())].into_iter().collect(),
                )
            ]
        );
        assert_eq!(
            rels,
            vec![BoltUnboundedRelation::new(
                22.into(),
                "friend".into(),
                vec![("key".into(), "value".into())].into_iter().collect(),
            )]
        );
        assert_eq!(ids, vec![22.into(), 42.into()]);
    }
}
