encrusted 1.0.0

A z-machine (interpreter) for Infocom-era text adventure games
use std::boxed::Box;
use std::ffi::CString;
use std::fmt::Write;

use serde_json;

use traits::UI;
use js_message;

#[derive(Debug)]
enum Token {
    Newline,
    Text(String),
    Object(String),
    Debug(String),
}

#[derive(Debug)]
pub struct WebUI {
    buffer: Vec<Token>,
}

impl UI for WebUI {
    fn new() -> Box<WebUI> {
        Box::new(WebUI {
            buffer: Vec::new(),
        })
    }

    fn print(&mut self, text: &str) {
        if text.is_empty() {
            return;
        }

        if text == "\n" {
            self.buffer.push(Token::Newline);
            return;
        }

        if !text.contains('\n') {
            self.buffer.push(Token::Text(String::from(text)));
            return;
        }

        let lines = text.lines().collect::<Vec<_>>();

        for (index, line) in lines.iter().enumerate() {
            if !line.is_empty() {
                self.buffer.push(Token::Text(String::from(*line)));
            }

            if let Some(_) = lines.get(index + 1) {
                self.buffer.push(Token::Newline);
            }
        }

        if text.ends_with('\n') {
            self.buffer.push(Token::Newline);
        }
    }

    fn debug(&mut self, text: &str) {
        self.buffer.push(Token::Debug(String::from(text)));
    }

    fn print_object(&mut self, obj: &str) {
        self.buffer.push(Token::Object(String::from(obj)));
    }

    fn flush(&mut self) {
        if self.buffer.is_empty() {
            return;
        }

        let mut html = String::new();

        for (index, item) in self.buffer.iter().enumerate() {
            let prev = if index == 0 { None } else { self.buffer.get(index - 1) };
            let next = self.buffer.get(index + 1);

            match *item {
                Token::Newline => {
                    html.push_str("<br>");
                }
                Token::Text(ref text) => {
                    match prev {
                        Some(&Token::Text(_)) => (),
                        _ => html.push_str("<span>"),
                    }

                    html.push_str(&text);

                    match next {
                        Some(&Token::Text(_)) => (),
                        _ => html.push_str("</span>"),
                    }
                }
                Token::Object(ref obj) => {
                    let class = match (prev, next) {
                        (None, Some(&Token::Newline)) => "room",
                        (Some(&Token::Newline), Some(&Token::Newline)) => "room",
                        _ => "object",
                    };

                    write!(html, r#"<span class="{}">{}</span>"#, class, obj).unwrap();
                }
                Token::Debug(ref text) => {
                    write!(html, r#"<span class="debug">{}</span>"#, text).unwrap();
                }
            }
        }

        self.message("print", &html);
        self.buffer.clear();
    }

    fn set_status_bar(&self, left: &str, right: &str) {
        let msg = serde_json::to_string(&(left, right)).unwrap();
        self.message("header", &msg)
    }

    fn message(&self, mtype: &str, msg: &str) {
        let type_ptr = CString::new(mtype).unwrap().into_raw();
        let msg_ptr = CString::new(msg).unwrap().into_raw();

        unsafe {
            js_message(type_ptr, msg_ptr);
            CString::from_raw(type_ptr); // free memory
            CString::from_raw(msg_ptr);
        }
    }

    fn clear(&self) {}
    fn reset(&self) {}
    fn get_user_input(&self) -> String { unimplemented!(); }
}