const std = @import("std");
const kmd = @import("kmd.zig");
const Allocator = std.mem.Allocator;

// Work to do
// - Implement --io which takes an input and an output so
//   save the need to spawn the program multiple times.

// Globals
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();

var output: ?[]const u8 = null;
var input: ?[]const u8 = null;
var template: ?[]const u8 = null;
var debug = false;
var insert_br = false;
var show_hidden_comments = false;

fn print(comptime fmt: []const u8, args: anytype) !void {
    try stdout.print(fmt, args);
    try bw.flush();
}

fn usage() noreturn {
    print(
        \\kmd: a set of utilities relating to the kmd format
        \\`kmd convert-to-html`:
        \\    -i INPUT    The input Markdown file. 
        \\       Alternatively specify multiple with --io.
        \\    -o OUTPUT   The file to output to. Alternatively, 
        \\       specify multiple with --io.
        \\    -t TEMPLATE The template to substitute into. $$CONTENTS$$,
        \\       $$DATE$$, $$TITLE$$, $$WC$$, $$TTR$$ are substituted.
        \\    -h Display this help message.
        \\ kmd wc 
        \\    -w, -l, -b
        \\       Retrieves the word, line, or byte count ignoring the header-seq.
        \\    -t
        \\       Allows passing multiple files, and the total is retrieved for these.
        \\    -d 
        \\       Pass a directory and (non-recursively) get total count. Assumes all files in the directory are kmd.
        \\    EXAMPLE    kmd wc -wd $WEBSITE/blog
        \\ kmd generate-index DIRECTORIES
        \\    -s [title|date] [asc|dsc]
        \\       Sort mode.
    , .{}) catch {
        std.process.exit(1);
    };
    std.process.exit(0);
}

pub fn convertToHtml(allocator: Allocator, args: anytype) !void {
    var i: usize = 1;
    while (i < args.len) : (i += 1) {
        if (std.mem.eql(u8, args[i], "-o")) {
            i += 1;
            output = args[i];
        } else if (std.mem.eql(u8, args[i], "-i")) {
            i += 1;
            input = args[i];
        } else if (std.mem.eql(u8, args[i], "-t")) {
            i += 1;
            template = args[i];
        } else if (std.mem.eql(u8, args[i], "-d")) {
            debug = true;
        } else if (std.mem.eql(u8, args[i], "-h")) {
            usage();
        } else if (std.mem.eql(u8, args[i], "-b")) {
            insert_br = true;
        } else if (std.mem.eql(u8, args[i], "-p")) {
            show_hidden_comments = true;
        } else {
            std.debug.print("not a valid arg\n", .{});
        }
    }

    if (input == null) {
        std.debug.print("Did not pass input parameter -i\n", .{});
        std.process.exit(4);
    }
    const dir = std.fs.cwd();
    // const file = try std.fs.openFileAbsolute(input.?, .{});
    const file = try dir.openFile(input.?, .{});
    const bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(bytes);
    var md: kmd.Kmd = undefined;
    md.init(bytes);
    const html = try md.html(allocator, .{
        .br_after_newline = insert_br,
        .show_hidden_comments = show_hidden_comments,
        .debug = debug,
    }, null);
    defer allocator.free(html);

    if (template) |t| {
        // read template
        // const template_file = try std.fs.openFileAbsolute(t, .{});
        const template_file = try dir.openFile(t, .{});
        const template_bytes = try template_file.readToEndAlloc(allocator, std.math.maxInt(usize));
        defer allocator.free(template_bytes);

        // many allocations version
        const r1 = try std.mem.replaceOwned(u8, allocator, template_bytes, "$$CONTENT$$", html);
        const r2 = try std.mem.replaceOwned(u8, allocator, r1, "$$CONTENTS$$", html);
        const r3 = try std.mem.replaceOwned(u8, allocator, r2, "$$DATE$$", md.date);
        const r4 = try std.mem.replaceOwned(u8, allocator, r3, "$$TITLE$$", md.title);
        const r5 = try std.mem.replaceOwned(u8, allocator, r4, "$$TAGS$$", if (std.mem.eql(u8, md.tags, "")) "none" else md.tags);

        const wc_ = md.wc(.words);
        const wc_str = try std.fmt.allocPrint(allocator, "{}", .{wc_});
        defer allocator.free(wc_str);
        const r6 = try std.mem.replaceOwned(u8, allocator, r5, "$$WC$$", wc_str);

        const ttr_str = try std.fmt.allocPrint(allocator, "{}", .{md.averageMinutesTakenToRead(wc_)});
        defer allocator.free(ttr_str);
        const r7 = try std.mem.replaceOwned(u8, allocator, r6, "$$TTR$$", ttr_str);

        defer allocator.free(r1);
        defer allocator.free(r2);
        defer allocator.free(r3);
        defer allocator.free(r4);
        defer allocator.free(r5);
        defer allocator.free(r6);
        try print("{s}\n", .{r7});
        allocator.free(r7);
    } else {
        // output `html` to either stdout or a file
        try print("{s}", .{html});
    }
}

pub fn wc(allocator: Allocator, args: anytype) !void {
    var mode: ?kmd.WcMode = null;
    var file_str: ?[]const u8 = null;
    var direc = false;
    var total = false;

    var i: usize = 1;
    while (i < args.len) : (i += 1) {
        if (std.mem.eql(u8, args[i], "-h")) {
            usage();
        } else if (args[i][0] == '-') {
            // process flags
            for (args[i][1..]) |f| switch (f) {
                'w' => mode = .words,
                'l' => mode = .lines,
                'b' => mode = .bytes,
                'd' => direc = true,
                't' => total = true,
                else => {
                    std.debug.print("invalid flag {}\n", .{f});
                    std.process.exit(5);
                },
            };
        } else {
            file_str = args[i];
        }
    }

    if (file_str == null) {
        std.debug.print("Did not pass file to run wc over\n", .{});
        std.process.exit(4);
    }

    const dir = std.fs.cwd();
    const file = try dir.openFile(file_str.?, .{});
    const bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(bytes);
    var md: kmd.Kmd = undefined;
    md.init(bytes);
    try print("{any}\n", .{md.wc(mode orelse .words)});
    std.process.exit(0);
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const args = try std.process.argsAlloc(allocator);

    if (args.len <= 1) {
        usage();
    }

    if (std.mem.eql(u8, args[1], "convert-to-html")) {
        const rest_of_args = args[1..];
        try convertToHtml(allocator, rest_of_args);
    } else if (std.mem.eql(u8, args[1], "wc")) {
        const rest_of_args = args[1..];
        try wc(allocator, rest_of_args);
    } else {
        usage();
    }
}