shine-testutils 0.2.0

SHINE. Unit test helpers for the shine engine.
use crate::webserver::service::AppData;
use actix_web::{error, web, Error as ActixWebError, HttpResponse};
use log::info;
use serde::Deserialize;
use serde_json;
use std::collections::HashMap;
use svg::node::{self, element};
use svg::{Document, Node};
use tera;

pub trait IntoD2Data {
    fn into_data(self) -> String;
}

enum Container {
    Root(Document),
    Group(element::Group),
}

impl Container {
    fn add_node<N: Node>(&mut self, node: N) {
        match *self {
            Container::Group(ref mut group) => group.append(node),
            Container::Root(ref mut doc) => doc.append(node),
        }
    }
}

struct Text {
    text: String,
    color: String,
    size: f32,
}

struct Group {
    container: Container,
    texts: HashMap<(i32, i32), Vec<Text>>,
}

impl Group {
    fn new_root() -> Group {
        let doc = Document::new().set("group-name", "root");
        Group {
            container: Container::Root(doc),
            texts: HashMap::new(),
        }
    }

    fn new_group(name: Option<String>) -> Group {
        let mut group = element::Group::new();
        if let Some(name) = name {
            group = group.set("group-name", name);
        }
        Group {
            container: Container::Group(group),
            texts: HashMap::new(),
        }
    }

    fn add_text(&mut self, p: (f64, f64), text: String, color: String, size: f32) {
        let key = ((p.0 * 65536.) as i32, (p.1 * 65546.) as i32);
        let size = size * 0.05;
        self.texts
            .entry(key)
            .or_insert_with(Vec::new)
            .push(Text { text, color, size });
    }

    fn add_node<N: Node>(&mut self, node: N) {
        self.container.add_node(node);
    }

    fn finalize(mut self) -> Container {
        if !self.texts.is_empty() {
            for (pos, texts) in self.texts.iter() {
                let p = (pos.0 as f32 / 65536., pos.1 as f32 / 65536.);
                let mut group = element::Group::new()
                    .set("preserve-size", "true")
                    .set("group-name", "*")
                    .set("transform", format!("translate({},{}) scale(1)", p.0, p.1));

                let mut y = 0.;
                for text in texts.iter() {
                    for line in text.text.split('\n') {
                        y += text.size;
                        let mut node = element::Text::new()
                            .set("x", 0)
                            .set("y", y)
                            .set("font-size", text.size)
                            .set("xml:space", "preserve")
                            .set("fill", text.color.clone());
                        node.append(node::Text::new(line));
                        group.append(node);
                    }
                }
                self.container.add_node(group);
            }
        }

        self.container
    }
}

/// Trace 2D geometry object through the web service
pub struct D2Trace {
    groups: Vec<Group>,
    scale: (f64, f64, f64, f64),
}

impl D2Trace {
    pub fn new() -> D2Trace {
        D2Trace {
            groups: vec![Group::new_root()],
            scale: (1., -1., 0., 0.),
        }
    }

    pub fn push_group(&mut self) {
        self.groups.push(Group::new_group(None));
    }

    pub fn push_group_with_name<S: Into<String>>(&mut self, name: S) {
        self.groups.push(Group::new_group(Some(name.into())));
    }

    pub fn pop_group(&mut self) {
        let group = self.groups.pop().unwrap();
        match group.finalize() {
            Container::Group(group) => self.add_node(group),
            _ => panic!("Poping root group"),
        }
    }

    pub fn pop_all_groups(&mut self) {
        while self.groups.len() > 1 {
            self.pop_group();
        }
    }

    pub fn set_scale(&mut self, minx: f64, miny: f64, maxx: f64, maxy: f64) {
        let w = maxx - minx;
        let h = maxy - miny;
        let w = if w == 0. { 1. } else { w };
        let h = if h == 0. { 1. } else { h };

        self.scale.0 = 2. / w;
        self.scale.1 = -2. / h;
        self.scale.2 = -(minx + maxx) / w;
        self.scale.3 = (miny + maxy) / h;
    }

    pub fn scale_position(&self, p: &(f64, f64)) -> (f64, f64) {
        (p.0 * self.scale.0 + self.scale.2, p.1 * self.scale.1 + self.scale.3)
    }

    pub fn add_point(&mut self, p: &(f64, f64), color: String) {
        let p = self.scale_position(p);
        let node = element::Line::new()
            .set("x1", p.0)
            .set("y1", p.1)
            .set("x2", p.0)
            .set("y2", p.1)
            .set("vector-effect", "non-scaling-stroke")
            .set("stroke-linecap", "round")
            .set("stroke", color)
            .set("stroke-width", "4");
        self.add_node(node);
    }

    pub fn add_line(&mut self, a: &(f64, f64), b: &(f64, f64), color: String) {
        let a = self.scale_position(a);
        let b = self.scale_position(b);
        let node = element::Line::new()
            .set("x1", a.0)
            .set("y1", a.1)
            .set("x2", b.0)
            .set("y2", b.1)
            .set("vector-effect", "non-scaling-stroke")
            .set("stroke-linecap", "round")
            .set("stroke", color)
            .set("stroke-width", "2");
        self.add_node(node);
    }

    pub fn add_text<S: Into<String>>(&mut self, p: &(f64, f64), msg: S, color: String, size: f32) {
        let p = self.scale_position(p);

        let group = self.groups.last_mut().unwrap();
        group.add_text(p, msg.into(), color, size);
    }

    fn add_node<N: Node>(&mut self, node: N) {
        let group = self.groups.last_mut().unwrap();
        group.add_node(node);
    }
}

impl Default for D2Trace {
    fn default() -> D2Trace {
        D2Trace::new()
    }
}

impl IntoD2Data for D2Trace {
    fn into_data(mut self) -> String {
        self.pop_all_groups();

        let group = self.groups.pop().unwrap();
        match group.finalize() {
            Container::Root(mut document) => {
                //document.assign("width", "640");
                document.assign("viewbox", "-1 -1 2 2");
                document.to_string()
            }

            _ => panic!("Poping root group"),
        }
    }
}

#[derive(Deserialize)]
pub(crate) struct D2DataRequest {
    id: usize,
}

pub(crate) fn handle_d2data_request(state: web::Data<AppData>, req: web::Query<D2DataRequest>) -> HttpResponse {
    // convert to 0 based
    let id = if req.id == 0 { usize::max_value() } else { req.id - 1 };
    let image = {
        info!("Getting d2data for {}", id);
        let img = state.d2datas.lock().unwrap();
        if id >= img.len() {
            "<svg xmlns=\"http://www.w3.org/2000/svg\" group-name=\"root\" viewbox=\"-1 -1 2 2\"></svg>".into()
        } else {
            img[id].clone()
        }
    };

    HttpResponse::Ok().content_type("image/svg+xml").body(image)
}

pub(crate) fn handle_d2datas_request(state: web::Data<AppData>) -> HttpResponse {
    info!("Getting all d2datas");
    let data = {
        let img = state.d2datas.lock().unwrap();
        serde_json::to_string(&*img).unwrap()
    };

    HttpResponse::Ok().content_type("application/json").body(data)
}

pub(crate) fn handle_d2view_request(state: web::Data<AppData>) -> Result<HttpResponse, ActixWebError> {
    let all_data = {
        let img = state.d2datas.lock().unwrap();
        serde_json::to_string(&*img).unwrap()
    };

    let mut ctx = tera::Context::new();
    ctx.insert("svg_list", &all_data);

    let body = state.template.render("d2view.html", &ctx).map_err(|e| {
        log::error!("Template error: {}", e);
        error::ErrorInternalServerError(format!("Template error: {}", e))
    })?;

    Ok(HttpResponse::Ok().content_type("text/html").body(body))
}