/// generated
use askama::Template;
use serde::Serialize;
use std::error::Error;


const EMPTY: &'static [u8] = &[];

// todo: should be able to configure which filters a template uses, to decrease wasm size
mod filters {
    #[askama::filter_fn]
    pub fn md_to_html(s: &str, _: &dyn askama::Values) -> askama::Result<String> {
        Ok(markdown::to_html_with_options(
            s,
            &markdown::Options {
                parse: markdown::ParseOptions {
                    constructs: markdown::Constructs {
                        math_flow: true,
                        math_text: true,
                        ..markdown::Constructs::gfm()
                    },
                    ..markdown::ParseOptions::gfm()
                },
                compile: markdown::CompileOptions {
                    ..markdown::CompileOptions::gfm()
                },
            },
        )
        .expect("failed to parse markdown"))
    }

    #[askama::filter_fn]
    pub fn timestamp_s_to_rfc_2822(
        timestamp_s: &i64,
        _: &dyn askama::Values,
    ) -> askama::Result<String> {
        if let Some(date_time) = chrono::DateTime::from_timestamp(*timestamp_s, 0) {
            Ok(date_time.to_rfc2822())
        } else {
            Err(askama::Error::Custom(
                "failed to get timestamp".to_string().into(),
            ))
        }
    }

    fn inner_timestamp_s_to_formatted(timestamp_s: &i64, format: &str) -> askama::Result<String> {
        if let Some(date_time) = chrono::DateTime::from_timestamp(*timestamp_s, 0) {
            let formatted = date_time.format(format);
            let mut string = String::new();
            if formatted.write_to(&mut string).is_ok() {
                Ok(string)
            } else {
                Err(askama::Error::Custom(
                    "failed to write formatted to string".to_string().into(),
                ))
            }
        } else {
            Err(askama::Error::Custom(
                "failed to get timestamp".to_string().into(),
            ))
        }
    }

    #[askama::filter_fn]
    pub fn timestamp_s_to_formatted(
        timestamp_s: &i64,
        _: &dyn askama::Values,
        format: &str,
    ) -> askama::Result<String> {
        inner_timestamp_s_to_formatted(timestamp_s, format)
    }

    #[askama::filter_fn]
    pub fn uuid_str(bytes: &[u8; 16], _: &dyn askama::Values) -> askama::Result<String> {
        Ok(uuid::Uuid::from_bytes(*bytes).to_string())
    }

    #[askama::filter_fn]
    pub fn uuid_to_formatted(
        bytes: &[u8; 16],
        _: &dyn askama::Values,
        format: &str,
    ) -> askama::Result<String> {
        let uuid = uuid::Uuid::from_bytes(*bytes);

        match uuid.get_timestamp() {
            Some(ts) => inner_timestamp_s_to_formatted(&(ts.to_unix().0 as i64), format),
            None => Ok(uuid.to_string()),
        }
    }
}

unsafe extern "C" {
    fn host_get_input() -> i64;
    fn host_set_output(ptr: i32, len: i32) -> i32;
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn alloc(len: usize) -> *mut u8 {
    let mut buf: Vec<u8> = Vec::with_capacity(len);

    let ptr = buf.as_mut_ptr();
    std::mem::forget(buf);

    ptr
}

pub fn recv_in<'a>() -> Result<Option<&'a [u8]>, Box<dyn Error>> {
    let input = unsafe { host_get_input() };

    if input == 0 {
        return Ok(None);
    }

    let input_ptr = (input >> 32) as i32;
    let input_len = input as i32;

    let input = unsafe { core::slice::from_raw_parts(input_ptr as *mut u8, input_len as usize) };

    Ok(Some(input))
}

pub fn send_out(output: Option<Vec<u8>>) -> Result<(), Box<dyn Error>> {
    let res = match output {
        Some(mut output) => {
            let output_len = output.len();
            let output_ptr = output.as_mut_ptr();

            std::mem::forget(output);
            unsafe { host_set_output(output_ptr as i32, output_len as i32) }
        }
        None => unsafe { host_set_output(1, 0) },
    };

    if res == 1 {
        Ok(())
    } else {
        Err("failed to write to output".into())
    }
}
