const std = @import("std");

pub const log_level: std.log.Level = .debug;

const ProgramOptions = struct {
    outputFile: ?[]u8 = null,
    inputFile: ?[]u8 = null,
    allocator: std.mem.Allocator,

    pub fn deinit(self: @This()) void {
        if (self.outputFile) |f| {
            self.allocator.free(f);
        }

        if (self.inputFile) |f| {
            self.allocator.free(f);
        }
    }

    fn matchArgOpt(arg: []const u8, long: []const u8, short: ?[]const u8) bool {
        if (arg.len < 2) {
            return false;
        }

        if (arg[0] == '-') {
            if (arg[1] == '-') {
                return std.mem.eql(u8, arg[2..], long);
            } else if (short) |s| {
                return arg[1] == s[0];
            }
        }

        return false;
    }

    pub fn parseArgs(allocator: std.mem.Allocator) !@This() {
        var rv: @This() = .{ .allocator = allocator };

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

        var i: usize = 0;
        while (i < args.len) : (i += 1) {
            if (i <= 0) {
                continue;
            }

            if (matchArgOpt(args[i], "input", "i")) {
                i += 1;
                rv.inputFile = try allocator.dupe(u8, args[i]);
            } else if (matchArgOpt(args[i], "output", "o")) {
                i += 1;
                rv.outputFile = try allocator.dupe(u8, args[i]);
            }
        }

        if (rv.outputFile == null) {
            rv.outputFile = try allocator.dupe(u8, "imgui.zig");
        }

        return rv;
    }
};

// Parses out all of the cimgui functions and sorts them by calling type.

const ParserContext = struct {
    arena: std.heap.ArenaAllocator,
    backingAllocator: std.mem.Allocator,
    opts: *const ProgramOptions,

    read: []const u8 = undefined,
    buffer: []const u8 = undefined,

    state: enum {
        Empty,
        Label,
        Struct,
        Function,
    } = .Empty,

    pub fn create(allocator: std.mem.Allocator, options: *const ProgramOptions) !*@This() {
        const self = try allocator.create(@This());

        self.* = .{
            .backingAllocator = allocator,
            .arena = std.heap.ArenaAllocator.init(allocator),
            .opts = options,
        };

        return self;
    }

    const ParseResults = struct {};

    pub fn getAllocator(self: *@This()) std.mem.Allocator {
        return self.arena.allocator();
    }

    fn matchString(self: *@This(), str: []const u8) ?usize {
        if (self.read.len < str.len) {
            return null;
        }

        if (std.mem.eql(u8, self.read[0..str.len], str)) {
            return str.len - 1;
        }
        return null;
    }

    fn advanceEmpty(self: *@This()) !void {
        if (self.matchString("struct")) |len| {
            std.log.info("STRUCT FOUND! {s}", .{self.read[0 .. len + 1]});
            self.advance(len);
        }
    }

    inline fn next(self: *@This()) !void {
        switch (self.state) {
            .Empty => {
                try self.advanceEmpty();
            },
            .Label => {},
            .Struct => {},
            .Function => {},
        }

        self.advance(1);
    }

    inline fn advance(self: *@This(), count: usize) void {
        self.read = self.read[count..];
    }

    pub fn parseHeader(self: *@This()) !ParseResults {
        var file = try std.fs.cwd().openFile(self.opts.inputFile.?, .{});
        defer file.close();

        self.buffer = try file.reader().readAllAlloc(self.getAllocator(), 1 * 1024 * 1024 * 1024);
        self.read = self.buffer;

        while (self.read.len > 0) {
            try self.next();
        }

        return .{};
    }

    pub fn destroy(self: *@This()) void {
        self.arena.deinit();
        self.backingAllocator.destroy(self);
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const allocator = gpa.allocator();

    const opts = try ProgramOptions.parseArgs(allocator);
    defer opts.deinit();

    std.log.info("inputFile={?s}, outputFile={?s}", .{
        opts.inputFile,
        opts.outputFile,
    });

    const tokenizer = try Tokenizer.create(allocator, &opts);
    defer tokenizer.destroy();

    const tokenizeResults = try tokenizer.tokenize();
}
