#define __BSD_VISIBLE 1
#include <errno.h>
#include <libgen.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifndef __FreeBSD__
#include <bsd/stdlib.h>
#include <bsd/string.h>
#endif

// jgm/cmark
#include "config.h"
#include "memory.h"
#include "cmark.h"
#include "node.h"

// libgit2
#include "git2.h"

#include "cgi.h"
#include "html.h"

#include "vcsid.h"
__VCSID("$Id: jwiki.c 537 2016-11-08 01:37:31Z jashank $");

#define WIKI_ROOT "/web/jashankj/_wiki/"

const char *page404 =
"\n"
"You've attempted to load a nonexistent or illegal page.\n"
"Try going [home](.) instead.\n\n";

static void render_404 (cmark_parser *);
static void render_file (git_repository *, git_commit *, const char *, cmark_parser *);
static void render_git_history (git_repository *, git_commit *, cmark_parser *);
static void render_finish (const char *, cmark_parser *);

static void cgi_header (void);
static void cgi_err (int, const char *, ...);
static void cgi_verr (int, int, const char *, va_list);

#define JW_CMARK_OPTIONS \
(CMARK_OPT_DEFAULT | CMARK_OPT_SMART | CMARK_OPT_NORMALIZE)

int
main (int argc, char *argv[]) {
struct request req;
request_populate (&req);

if (req.gateway_interface == NULL)
    if (argc >= 2)
	req.path_info = argv[1];

cgi_header ();

cmark_parser *parser = cmark_parser_new (JW_CMARK_OPTIONS);

int error; // probably should check this sometime...

git_repository *repo;
error = git_repository_open (&repo, WIKI_ROOT);

git_object *head_commit;
error = git_revparse_single (&head_commit, repo, "HEAD^{commit}");
git_commit *commit = (git_commit *)head_commit;

char *filename = "FrontPage";
if (req.path_info != NULL &&
    strcmp (req.path_info, "/_history") == 0) {
    // history rendering
    render_git_history (repo, commit, parser);
    render_finish ("_history", parser);
    return EXIT_SUCCESS;

} else if (req.path_info != NULL && strcmp (req.path_info, "/") != 0) {
    // lose the leading '/'
    filename = &req.path_info[1];
}

render_file (repo, commit, filename, parser);
render_finish (filename, parser);

git_commit_free (commit);
git_repository_free (repo);

return EXIT_SUCCESS;
}

static void
render_404 (cmark_parser *parser) {
cmark_parser_feed (parser, page404, strlen (page404));
return;
}

static void
render_file (git_repository *repo, git_commit *commit, const char *filename, cmark_parser *parser) {
int error;

cmark_parser_feed (parser, filename, strlen (filename));
cmark_parser_feed (parser, "\n================\n\n", 19);

git_tree *tree;
error = git_commit_tree (&tree, commit);

const git_tree_entry *entry = git_tree_entry_byname (tree, filename);
if (entry == NULL) {
    render_404 (parser);

    git_tree_free (tree);
    return;
}

git_object *obj;
error = git_tree_entry_to_object (&obj, repo, entry);

cmark_parser_feed (
    parser,
    git_blob_rawcontent ((git_blob *)obj),
    git_blob_rawsize ((git_blob *)obj));

return;
}

static void
render_git_history (git_repository *repo, git_commit *commit, cmark_parser *parser) {
// code borrowed from
// https://git-scm.com/book/en/v2/Embedding-Git-in-your-Applications-Libgit2
// https://libgit2.github.com/libgit2/ex/HEAD/general.html
int error;

git_revwalk *walk;
git_revwalk_new (&walk, repo);
git_revwalk_sorting (walk, GIT_SORT_TOPOLOGICAL);
git_revwalk_push (walk, git_commit_id (commit));

git_oid oid;
while (git_revwalk_next (&oid, walk) == 0) {
    git_commit *commit;
    error = git_commit_lookup (&commit, repo, &oid);

    const git_signature *author = git_commit_author (commit);

    char *parser_fodder;

    time_t t_ = git_commit_time (commit);
    struct tm *t = localtime (&t_);
    char time_str[21] = {0,};
    strftime (time_str, 20, "%Y-%m-%d %H:%M:%S", t);

#define maybe_str(v) ((v != NULL) ? (v) : "")
    int len = asprintf (
	&parser_fodder, commit_message,
	maybe_str (author->name),
	maybe_str (time_str),
	maybe_str (git_oid_tostr_s (git_commit_id (commit))),
	maybe_str (git_commit_summary (commit)),
	maybe_str (git_commit_body (commit))
	);
    cmark_parser_feed (parser, parser_fodder, len);
    free (parser_fodder);

    git_commit_free (commit);
}

git_revwalk_free (walk);

return;
}

static void
render_finish (const char *title, cmark_parser *parser) {
cmark_node *document = cmark_parser_finish (parser);
cmark_parser_free (parser);

char *result = cmark_render_html (document, JW_CMARK_OPTIONS);
printf (page, title, result);

cmark_node_mem (document)->free (result);
cmark_node_free (document);
}

void
cgi_header (void) {
fputs (
    "Content-type: text/html; charset=utf-8\r\n"
    "\r\n",
    stdout);
}

// cgi_err, cgi_verr derived from FreeBSD's libc
// https://svnweb.freebsd.org/base/head/lib/libc/gen/err.c
void
cgi_err (int eval, const char *fmt, ...) {
va_list ap;
va_start (ap, fmt);
cgi_verr (eval, errno, fmt, ap);
va_end (ap);
}

void
cgi_verr (int eval, int code, const char *fmt, va_list ap) {
fprintf (stderr, "%s: ", getprogname ());
if (fmt != NULL) {
    vfprintf (stderr, fmt, ap);
    fprintf (stderr, ": ");
}
fprintf (stderr, "%s\n", strerror (code));
exit (eval);
}