/* FML: Fantastic Markup Language */
/* A C-Based HTML authoring tool */

/*
See:
https://www.timtimestim.com/b/fml.html
for extended information.
*/

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FML_TOP_OF_OUTPUT \
"<!DOCTYPE html>\n"\
"<html lang=\"en\">\n"\
"<head>\n"\
"<meta charset=\"UTF-8\"/>\n"\
"<title>FML: Fantastic Markup Language</title>\n"\
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n"\
"<style>\n"\
"body {max-width: 50em;}\n"\
"</style>\n"\
"</head>\n"\
"<body>\n"\

#define FML_BOTTOM_OF_OUTPUT \
"</body>\n"\
"</html>\n"\

/* Tags */
enum {
    FML_NORMAL,

    /*
    .PG for paragraph breaks
    .HORIZONTAL_RULE for, you know, horizontal rules
    */

    FML_ITALIC,
    FML_BOLD,
    FML_STRIKETHROUGH,
    FML_CODE,
    FML_SUPERSCRIPT,

    FML_TITLE,
    FML_SECTION,

    FML_RED,
    FML_BLUE,
    FML_GREEN,

    FML_LINK,
    FML_IMG,

    /* Block level elements */
    FML_CODE_BLOCK,
    FML_BLOCKQUOTE,
    FML_UNORDERED_LIST,
    FML_ORDERED_LIST,
    FML_END_BLOCK
};

static void fml_die(char *s1, char *s2) {
    fprintf(stderr, "\n!!!!!!!\nFML Error in string starting with \"%.16s\"\n", s2);
    fprintf(stderr, "%s", s1);
    exit(1);
}

static void fml_print_escaped(FILE *output, char *s) {
    while (*s) {
        switch (*s) {
        case '&':
            fprintf(output, "&amp;");
            break;
        case '>':
            fprintf(output, "&gt;");
            break;
        case '<':
            fprintf(output, "&lt;");
            break;
        case '"':
            fprintf(output, "&quot;");
            break;
        case '\'':
            fprintf(output, "&#39;");
            break;
        default:
            putc(*s, output);
            break;
        }

        ++s;
    }
}

static void fml_print_with_tags(FILE *output, char *ltag, char *s, char *rtag) {
    fprintf(output, "%s", ltag);
    fml_print_escaped(output, s);
    fprintf(output, "%s", rtag);
}

static void fml(FILE *output, ...) {
    char *s;
    int slen = 0;
    int state = FML_NORMAL;
    int in_block = 0;

    va_list args;
    va_start(args, output);

    fprintf(output, "%s", FML_TOP_OF_OUTPUT);
    fprintf(output, "<p>");

    for (;;) {
        s = va_arg(args, char *);
        if (s == NULL) break;

        slen = strlen(s);
        if (slen > 509) {
            fml_die(
            "According to ANSI C, string literals can't be over 509 characters long.\n"
            "For maximum compatibility, FML intentionally breaks when inputs are longer.\n",
            s);
        }

        if (slen == 0) fml_die("Can't have an empty string\n", s);

        if (*s == '.' && *(s + 1) != '.') {
            if (state != FML_NORMAL && !in_block) fml_die("Can't have two tags in a row.\n", s);

            #define FML_CMP(str) (strcmp(s, str) == 0)

            if (in_block) {
                if (FML_CMP(".END_BLOCK")) {
                    in_block = 0;

                    switch (state) {
                        case FML_CODE_BLOCK:
                            fprintf(output, "</pre>\n<p>");
                            break;
                        case FML_BLOCKQUOTE:
                            fprintf(output, "</blockquote>\n<p>");
                            break;
                        case FML_UNORDERED_LIST:
                            fprintf(output, "</ul>\n<p>");
                            break;
                        case FML_ORDERED_LIST:
                            fprintf(output, "</ol>\n<p>");
                            break;
                    }

                    state = FML_NORMAL;
                } else {
                    fml_die("Invalid. You must end a block level element with .END_BLOCK", s);
                }
            } else {
                if (FML_CMP(".PG")) {
                    fprintf(output, "</p>\n<p>");
                } else if (FML_CMP(".HORIZONTAL_RULE")) {
                    fprintf(output, "</p>\n<hr>\n<p>");
                } else if (FML_CMP(".ITALIC")) {
                    state = FML_ITALIC;
                } else if (FML_CMP(".BOLD")) {
                    state = FML_BOLD;
                } else if (FML_CMP(".STRIKETHROUGH")) {
                    state = FML_STRIKETHROUGH;
                } else if (FML_CMP(".CODE")) {
                    state = FML_CODE;
                } else if (FML_CMP(".TITLE")) {
                    state = FML_TITLE;
                } else if (FML_CMP(".SECTION")) {
                    state = FML_SECTION;
                } else if (FML_CMP(".RED")) {
                    state = FML_RED;
                } else if (FML_CMP(".BLUE")) {
                    state = FML_BLUE;
                } else if (FML_CMP(".GREEN")) {
                    state = FML_GREEN;
                } else if (FML_CMP(".SUPERSCRIPT")) {
                    state = FML_SUPERSCRIPT;
                } else if (FML_CMP(".LINK")) {
                    state = FML_LINK;
                } else if (FML_CMP(".IMG")) {
                    state = FML_IMG;
                } else if (FML_CMP(".CODE_BLOCK")) {
                    state = FML_CODE_BLOCK;
                    in_block = 1;
                    fprintf(output, "</p>\n<pre>");
                } else if (FML_CMP(".BLOCKQUOTE")) {
                    state = FML_BLOCKQUOTE;
                    in_block = 1;
                    fprintf(output, "</p>\n<blockquote>");
                } else if (FML_CMP(".UNORDERED_LIST")) {
                    state = FML_UNORDERED_LIST;
                    in_block = 1;
                    fprintf(output, "</p>\n<ul>\n");
                } else if (FML_CMP(".ORDERED_LIST")) {
                    state = FML_ORDERED_LIST;
                    in_block = 1;
                    fprintf(output, "</p>\n<ol>\n");
                } else {
                    fml_die("Invalid option argument.\n", s);
                }
            }

            #undef FML_CMP

            continue;
        }

        if (*s == '.') ++s;

        switch (state) {
        case FML_ITALIC:
            fml_print_with_tags(output, "<em>", s, "</em>");
            break;
        case FML_BOLD:
            fml_print_with_tags(output, "<strong>", s, "</strong>");
            break;
        case FML_STRIKETHROUGH:
            fml_print_with_tags(output, "<del>", s, "</del>");
            break;
        case FML_CODE:
            fml_print_with_tags(output, "<code>", s, "</code>");
            break;
        case FML_TITLE:
            fml_print_with_tags(output, "</p>\n<h1>", s, "</h1>\n<p>");
            break;
        case FML_SECTION:
            fml_print_with_tags(output, "</p>\n<h2>", s, "</h2>\n<p>");
            break;
        case FML_RED:
            fml_print_with_tags(output, "<span style=\"color:red;\">", s, "</span>");
            break;
        case FML_BLUE:
            fml_print_with_tags(output, "<span style=\"color:blue;\">", s, "</span>");
            break;
        case FML_GREEN:
            fml_print_with_tags(output, "<span style=\"color:green;\">", s, "</span>");
            break;
        case FML_SUPERSCRIPT:
            fml_print_with_tags(output, "<sup>", s, "</sup>");
            break;
        case FML_LINK:
            /* Links have to pull in another arg. */
            fprintf(output, "<a href=\"%s\">", s);
            fml_print_escaped(output, va_arg(args, char *));
            fprintf(output, "</a>");
            break;
        case FML_IMG:
            /* Img has to pull in another arg. */
            fprintf(output, "</p>\n<img src=\"%s\" alt=\"", s);
            fml_print_escaped(output, va_arg(args, char *));
            fprintf(output, "\">\n<p>");
            break;
        case FML_CODE_BLOCK:
            fml_print_escaped(output, s);
            break;
        case FML_BLOCKQUOTE:
            fml_print_escaped(output, s);
            fprintf(output, "<br>\n");
            break;
        case FML_UNORDERED_LIST:
            fprintf(output, "<li>");
            fml_print_escaped(output, s);
            fprintf(output, "</li>\n");
            break;
        case FML_ORDERED_LIST:
            fprintf(output, "<li>");
            fml_print_escaped(output, s);
            fprintf(output, "</li>\n");
            break;
        default:
            fml_print_escaped(output, s);
            break;
        }

        if (!in_block) state = FML_NORMAL;
    }

    fprintf(output, "</p>");
    putc('\n', output);
    fprintf(output, "%s", FML_BOTTOM_OF_OUTPUT);
    
    va_end(args);
}
