use libc;

use codecs;
use ffi;
use ffi::yaml_node_type_t::*;
use error::YamlMark;

use std::ptr;
use std::mem;

pub struct YamlDocument {
    document_mem: ffi::yaml_document_t
}

impl YamlDocument {
    pub unsafe fn parser_load(parser: &mut ffi::yaml_parser_t) -> Option<Box<YamlDocument>> {
        let mut document = Box::new(YamlDocument {
            document_mem: mem::uninitialized()
        });

        if ffi::yaml_parser_load(parser, &mut document.document_mem) == 0 {
            None
        } else {
            Some(document)
        }
    }

    pub fn is_empty(&self) -> bool {
        unsafe {
            ffi::yaml_document_get_root_node(&self.document_mem) == ptr::null()
        }
    }

    unsafe fn load<'r>(&'r self, node_ptr: *const ffi::yaml_node_t) -> YamlNode<'r> {
        if node_ptr == ptr::null() {
            panic!("empty node")
        }
        let node = &*node_ptr;
        match node.node_type {
            YAML_SCALAR_NODE => {
                let scalar_data: &ffi::yaml_scalar_node_t = mem::transmute(&node.data);
                YamlNode::YamlScalarNode(YamlScalarData {
                    node: node,
                    data: scalar_data
                })
            },
            YAML_SEQUENCE_NODE => {
                let sequence_data: &ffi::yaml_sequence_node_t = mem::transmute(&node.data);
                YamlNode::YamlSequenceNode(YamlSequenceData {
                    doc: self,
                    node: node,
                    data: sequence_data
                })
            },
            YAML_MAPPING_NODE => {
                let mapping_data: &ffi::yaml_sequence_node_t = mem::transmute(&node.data);
                YamlNode::YamlMappingNode(YamlMappingData {
                    doc: self,
                    node: node,
                    data: mapping_data
                })
            },
            _ => panic!("invalid node")
        }
    }

    unsafe fn get_node<'r>(&'r self, index: libc::c_int) -> YamlNode<'r> {
        let node_ptr = ffi::yaml_document_get_node(&self.document_mem, index);
        self.load(node_ptr)
    }

    pub fn root<'r>(&'r self) -> Option<YamlNode<'r>> {
        unsafe {
            let node_ptr = ffi::yaml_document_get_root_node(&self.document_mem);
            if node_ptr == ptr::null() {
                None
            } else {
                Some(self.load(node_ptr))
            }
        }
    }
}

impl Drop for YamlDocument {
    fn drop(&mut self) {
        unsafe {
            ffi::yaml_document_delete(&mut self.document_mem);
        }
    }
}

pub enum YamlNode<'r> {
    YamlScalarNode(YamlScalarData<'r>),
    YamlSequenceNode(YamlSequenceData<'r>),
    YamlMappingNode(YamlMappingData<'r>),
}

pub trait YamlNodeData {
    unsafe fn internal_node<'r>(&'r self) -> &'r ffi::yaml_node_t;

    fn tag(&self) -> Option<String> {
        unsafe {
            codecs::decode_c_str(self.internal_node().tag)
        }
    }

    fn start_mark(&self) -> YamlMark {
        unsafe {
            YamlMark::conv(&self.internal_node().start_mark)
        }
    }

    fn end_mark(&self) -> YamlMark {
        unsafe {
            YamlMark::conv(&self.internal_node().end_mark)
        }
    }
}

pub struct YamlScalarData<'r> {
    node: &'r ffi::yaml_node_t,
    data: &'r ffi::yaml_scalar_node_t
}

impl<'r> YamlNodeData for YamlScalarData<'r> {
    unsafe fn internal_node<'a>(&'a self) -> &'a ffi::yaml_node_t {
        self.node
    }
}

impl<'r> YamlScalarData<'r> {
    pub fn get_value(&self) -> String {
        codecs::decode_buf(self.data.value, self.data.length).unwrap()
    }

    pub fn style(&self) -> ffi::YamlScalarStyle {
        self.data.style
    }
}

pub struct YamlSequenceData<'r> {
    doc: &'r YamlDocument,
    node: &'r ffi::yaml_node_t,
    data: &'r ffi::yaml_sequence_node_t
}

impl<'r> YamlNodeData for YamlSequenceData<'r> {
    unsafe fn internal_node<'a>(&'a self) -> &'a ffi::yaml_node_t {
        self.node
    }
}

impl<'r> YamlSequenceData<'r> {
    pub fn values(&self) -> YamlSequenceIter<'r> {
        YamlSequenceIter {
            doc: self.doc,
            top: self.data.items.top as *const libc::c_int,
            ptr: self.data.items.start as *const libc::c_int
        }
    }
}

pub struct YamlSequenceIter<'r> {
    doc: &'r YamlDocument,
    top: *const libc::c_int,
    ptr: *const libc::c_int
}

impl<'r> Iterator for YamlSequenceIter<'r> {
    type Item = YamlNode<'r>;

    fn next(&mut self) -> Option<YamlNode<'r>> {
        if self.ptr == self.top {
            None
        } else {
            unsafe {
                let next_node = self.doc.get_node(*self.ptr);

                self.ptr = self.ptr.offset(1);

                Some(next_node)
            }
        }
    }
}

pub struct YamlMappingData<'r> {
    doc: &'r YamlDocument,
    node: &'r ffi::yaml_node_t,
    data: &'r ffi::yaml_sequence_node_t
}

impl<'r> YamlNodeData for YamlMappingData<'r> {
    unsafe fn internal_node<'a>(&'a self) -> &'a ffi::yaml_node_t {
        self.node
    }
}

impl<'r> YamlMappingData<'r> {
    pub fn pairs(&self) -> YamlMappingIter<'r> {
        YamlMappingIter {
            doc: self.doc,
            top: self.data.items.top as *const ffi::yaml_node_pair_t,
            ptr: self.data.items.start as *const ffi::yaml_node_pair_t
        }
    }
}

pub struct YamlMappingIter<'r> {
    doc: &'r YamlDocument,
    top: *const ffi::yaml_node_pair_t,
    ptr: *const ffi::yaml_node_pair_t
}

impl<'r> Iterator for YamlMappingIter<'r> {
    type Item = (YamlNode<'r>, YamlNode<'r>);

    fn next(&mut self) -> Option<(YamlNode<'r>, YamlNode<'r>)> {
        if self.ptr == self.top {
            None
        } else {
            unsafe {
                let next_key = self.doc.get_node((*self.ptr).key);
                let next_value = self.doc.get_node((*self.ptr).value);

                self.ptr = self.ptr.offset(1);

                Some((next_key, next_value))
            }
        }
    }
}

