use ffi;
use error::YamlError;
use event::{YamlVersionDirective, YamlTagDirective};

use std::str;
use std::slice;
use std::ptr;
use std::mem;
use std::ffi::{CStr, CString};
use std::io;
use std::io::Write;
use libc;

pub struct YamlBaseEmitter {
    emitter_mem: ffi::yaml_emitter_t
}

impl YamlBaseEmitter {
    unsafe fn new() -> YamlBaseEmitter {
        YamlBaseEmitter {
            emitter_mem: mem::uninitialized()
        }
    }
}

impl Drop for YamlBaseEmitter {
    fn drop(&mut self) {
        unsafe {
            ffi::yaml_emitter_delete(&mut self.emitter_mem);
        }
    }
}

pub struct YamlEmitter<'r> {
    base_emitter: YamlBaseEmitter,
    writer: &'r mut (Write+'r),
    io_error: Option<io::Error>,
}

fn to_c_str_opt(s: Option<&str>) -> Result<Option<CString>, YamlError> {
    match s {
        None => Ok(None),
        Some(s) => match CString::new(s.as_bytes()) {
            Ok(cstr) => Ok(Some(cstr)),
            Err(_) => Err(YamlError::new(
                        ffi::YamlErrorType::YAML_EMITTER_ERROR,
                        Some("Nul bytes in string".to_string())
                    ))
        }
    }
}

fn to_c_str(s: &str) -> Result<CString, YamlError> {
    match CString::new(s.as_bytes()) {
        Ok(cstr) => Ok(cstr),
        Err(_) => Err(YamlError::new(
                    ffi::YamlErrorType::YAML_EMITTER_ERROR,
                    Some("Nul bytes in string".to_string())
                ))
    }
}

impl<'r> YamlEmitter<'r> {
    pub fn init<'a>(writer: &'a mut Write) -> Box<YamlEmitter<'a>> {
        unsafe {
            let mut emitter = Box::new(YamlEmitter {
                base_emitter: YamlBaseEmitter::new(),
                writer: writer,
                io_error: None
            });

            if ffi::yaml_emitter_initialize(&mut emitter.base_emitter.emitter_mem) == 0 {
                panic!("failed to initialize yaml_emitter_t");
            }

            ffi::yaml_emitter_set_output(&mut emitter.base_emitter.emitter_mem, handle_writer_cb, mem::transmute(&mut *emitter));

            emitter
        }
    }

    fn get_error(&mut self) -> YamlError {
        let emitter_mem = &self.base_emitter.emitter_mem;
        unsafe {
            let c_problem = CStr::from_ptr(emitter_mem.problem);
            let mut error = YamlError {
                kind: emitter_mem.error,
                problem: str::from_utf8(c_problem.to_bytes()).map(|s| s.to_string()).ok(),
                io_error: None,
                context: None
            };

            mem::swap(&mut self.io_error, &mut error.io_error);

            return error;
        }
    }

    pub fn emit_stream<F>(&mut self, encoding: ffi::YamlEncoding, f: F) -> Result<(), YamlError>
        where F: Fn(&mut YamlEmitter) -> Result<(), YamlError>
    {
        try!(self.emit_stream_start_event(encoding));
        try!(f(self));
        try!(self.emit_stream_end_event());
        self.flush()
    }

    fn emit_stream_start_event(&mut self, encoding: ffi::YamlEncoding) -> Result<(), YamlError> {
        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_stream_start_event_initialize(&mut event, encoding) == 0 {
                panic!("yaml_stream_start_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    fn emit_stream_end_event(&mut self) -> Result<(), YamlError> {
        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_stream_end_event_initialize(&mut event) == 0 {
                panic!("yaml_stream_end_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn emit_document<F>(&mut self,
            version_directive: Option<YamlVersionDirective>,
            tag_directives: &[YamlTagDirective],
            implicit: bool,
            f: F) -> Result<(), YamlError> where
        F: Fn(&mut YamlEmitter) -> Result<(), YamlError>
    {
        try!(self.emit_document_start_event(version_directive, tag_directives, implicit));
        try!(f(self));
        self.emit_document_end_event(implicit)
    }

    fn emit_document_start_event(&mut self,
            version_directive: Option<YamlVersionDirective>,
            tag_directives: &[YamlTagDirective],
            implicit: bool)
        -> Result<(), YamlError>
    {
        let mut vsn_dir = ffi::yaml_version_directive_t { major: 0, minor: 0 };
        let c_vsn_dir = match version_directive {
            Some(directive) => {
                vsn_dir.major = directive.major as libc::c_int;
                vsn_dir.minor = directive.minor as libc::c_int;
                &vsn_dir as *const ffi::yaml_version_directive_t
            },
            None => ptr::null()
        };

        let c_tag_dirs: Vec<ffi::yaml_tag_directive_t> =
            match tag_directives.iter().map(|tag| tag.to_tag_directive_t()).collect() {
                Ok(dirs) => dirs,
                Err(_) => return Err(YamlError::new(
                        ffi::YamlErrorType::YAML_EMITTER_ERROR,
                        Some("Nul bytes in tag directives".to_string())
                    ))
            };
        let tag_dir_start = c_tag_dirs.as_ptr();
        unsafe {
            let mut event = mem::uninitialized();
            let tag_dir_end = tag_dir_start.offset(c_tag_dirs.len() as isize);
            let c_implicit = if implicit { 1 } else { 0 };

            if ffi::yaml_document_start_event_initialize(&mut event, c_vsn_dir, tag_dir_start, tag_dir_end, c_implicit) == 0 {
                panic!("yaml_document_start_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    fn emit_document_end_event(&mut self, implicit: bool) -> Result<(), YamlError> {
        let c_implicit = if implicit { 1 } else { 0 };
        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_document_end_event_initialize(&mut event, c_implicit) == 0 {
                panic!("yaml_stream_end_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn emit_alias_event(&mut self, anchor: &str) -> Result<(), YamlError> {
        let c_anchor = try!(to_c_str(anchor));

        unsafe {
            let mut event = mem::uninitialized();

            let ptr = c_anchor.as_ptr();
            if ffi::yaml_alias_event_initialize(&mut event, ptr as *const ffi::yaml_char_t) != 0 {
                panic!("yaml_stream_end_event_initialize failed!")
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn emit_scalar_event(&mut self, anchor: Option<&str>, tag: Option<&str>,
        value: &str, plain_implicit: bool, quoted_implicit: bool,
        style: ffi::YamlScalarStyle) -> Result<(), YamlError>
    {
        let c_anchor = try!(to_c_str_opt(anchor));
        let anchor_ptr = match c_anchor {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_tag = try!(to_c_str_opt(tag));
        let tag_ptr = match c_tag {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_plain_implicit = if plain_implicit { 1 } else { 0 };
        let c_quoted_implicit = if quoted_implicit { 1 } else { 0 };

        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_scalar_event_initialize(&mut event,
                    anchor_ptr as *const ffi::yaml_char_t, tag_ptr as *const ffi::yaml_char_t,
                    value.as_ptr(), value.len() as libc::c_int,
                    c_plain_implicit, c_quoted_implicit,
                    style) == 0
            {
                panic!("yaml_scalar_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn emit_sequence<F>(&mut self, anchor: Option<&str>, tag: Option<&str>, implicit: bool,
            style: ffi::YamlSequenceStyle,
            f: F) -> Result<(), YamlError> where
        F: Fn(&mut YamlEmitter) -> Result<(), YamlError>
    {
        try!(self.emit_sequence_start_event(anchor, tag, implicit, style));
        try!(f(self));
        self.emit_sequence_end_event()
    }

    fn emit_sequence_start_event(&mut self, anchor: Option<&str>, tag: Option<&str>, implicit: bool,
        style: ffi::YamlSequenceStyle) -> Result<(), YamlError>
    {
        let c_anchor = try!(to_c_str_opt(anchor));
        let anchor_ptr = match c_anchor {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_tag = try!(to_c_str_opt(tag));
        let tag_ptr = match c_tag {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_implicit = if implicit { 1 } else { 0 };

        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_sequence_start_event_initialize(&mut event,
                    anchor_ptr as *const ffi::yaml_char_t, tag_ptr as *const ffi::yaml_char_t,
                    c_implicit, style) == 0
            {
                panic!("yaml_sequence_start_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    fn emit_sequence_end_event(&mut self) -> Result<(), YamlError> {
        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_sequence_end_event_initialize(&mut event) == 0 {
                panic!("yaml_sequence_end_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn emit_mapping<F>(&mut self, anchor: Option<&str>, tag: Option<&str>, implicit: bool,
            style: ffi::YamlSequenceStyle,
            f: F) -> Result<(), YamlError> where
        F: Fn(&mut YamlEmitter) -> Result<(), YamlError>
    {
        try!(self.emit_mapping_start_event(anchor, tag, implicit, style));
        try!(f(self));
        self.emit_mapping_end_event()
    }

    fn emit_mapping_start_event(&mut self, anchor: Option<&str>, tag: Option<&str>, implicit: bool,
        style: ffi::YamlSequenceStyle) -> Result<(), YamlError>
    {
        let c_anchor = try!(to_c_str_opt(anchor));
        let anchor_ptr = match c_anchor {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_tag = try!(to_c_str_opt(tag));
        let tag_ptr = match c_tag {
            Some(s) => s.as_ptr(),
            None => ptr::null()
        };
        let c_implicit = if implicit { 1 } else { 0 };

        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_mapping_start_event_initialize(&mut event,
                    anchor_ptr as *const ffi::yaml_char_t, tag_ptr as *const ffi::yaml_char_t,
                    c_implicit, style) == 0
            {
                panic!("yaml_mapping_start_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    fn emit_mapping_end_event(&mut self) -> Result<(), YamlError> {
        unsafe {
            let mut event = mem::uninitialized();

            if ffi::yaml_mapping_end_event_initialize(&mut event) == 0 {
                panic!("yaml_mapping_end_event_initialize failed!");
            }

            if ffi::yaml_emitter_emit(&mut self.base_emitter.emitter_mem, &mut event) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }

    pub fn flush(&mut self) -> Result<(), YamlError> {
        unsafe {
            if ffi::yaml_emitter_flush(&mut self.base_emitter.emitter_mem) != 0 {
                Ok(())
            } else {
                Err(self.get_error())
            }
        }
    }
}

extern fn handle_writer_cb(data: *mut YamlEmitter, buffer: *const u8, size: libc::size_t) -> libc::c_int {
    unsafe {
        let buf = slice::from_raw_parts(buffer, size as usize);
        let emitter = &mut *data;
        match emitter.writer.write_all(buf) {
            Ok(()) => 1,
            Err(err) => {
                emitter.io_error = Some(err);
                0
            }
        }
    }
}

#[cfg(test)]
mod test {
    use emitter::YamlEmitter;
    use ffi::YamlEncoding::YamlUtf8Encoding;
    use ffi::YamlScalarStyle::*;
    use ffi::YamlSequenceStyle::*;

    #[test]
    #[allow(unused_must_use)]
    fn event_emitter_sequence_test() {
        let mut writer = Vec::new();
        {
            let mut emitter = YamlEmitter::init(&mut writer);
            emitter.emit_stream(YamlUtf8Encoding, |e| {
                e.emit_document(None, &[], true, |e| {
                    e.emit_sequence(None, None, true, YamlFlowSequenceStyle, |e| {
                        try!(e.emit_scalar_event(None, None, "1", true, false, YamlPlainScalarStyle));
                        e.emit_scalar_event(None, None, "2", true, false, YamlPlainScalarStyle)
                    })
                })
            });
            emitter.flush();
        }
        assert_eq!(&writer[..], b"[1, 2]\n");
    }

    #[test]
    #[allow(unused_must_use)]
    fn event_emitter_mapping_test() {
        let mut writer = Vec::new();
        {
            let mut emitter = YamlEmitter::init(&mut writer);
            emitter.emit_stream(YamlUtf8Encoding, |e| {
                e.emit_document(None, &[], true, |e| {
                    e.emit_mapping(None, None, true, YamlFlowSequenceStyle, |e| {
                        try!(e.emit_scalar_event(None, None, "a", true, false, YamlPlainScalarStyle));
                        try!(e.emit_scalar_event(None, None, "1", true, false, YamlPlainScalarStyle));
                        try!(e.emit_scalar_event(None, None, "b", true, false, YamlPlainScalarStyle));
                        e.emit_scalar_event(None, None, "2", true, false, YamlPlainScalarStyle)
                    })
                })
            });
            emitter.flush();
        }
        assert_eq!(&writer[..], b"{a: 1, b: 2}\n");
    }
}
