const std = @import("std");

pub const ini = @import("zigini");

pub const interop = @import("interop.zig");
pub const UidRange = @import("UidRange.zig");
pub const LogFile = @import("LogFile.zig");
pub const SharedError = @import("SharedError.zig");

pub fn IniParser(comptime Struct: type) type {
    return struct {
        const Self = @This();
        const temporary_allocator = std.heap.page_allocator;

        pub const Error = struct {
            type_name: []const u8,
            key: []const u8,
            value: []const u8,
            error_name: []const u8,
        };
        pub var global_errors: std.ArrayList(Error) = .empty;

        ini_struct: ini.Ini(Struct),
        structure: Struct,
        maybe_load_error: ?anyerror,
        errors: std.ArrayList(Error),

        pub fn init(
            allocator: std.mem.Allocator,
            io: std.Io,
            path: []const u8,
            field_handler: ?fn (allocator: std.mem.Allocator, field: ini.IniField) ?ini.IniField,
        ) !Self {
            var ini_struct = ini.Ini(Struct).init(allocator);
            errdefer ini_struct.deinit();

            var maybe_load_error: ?anyerror = null;

            const structure = ini_struct.readFileToStruct(io, path, .{
                .fieldHandler = field_handler,
                .errorHandler = errorHandler,
                .comment_characters = "#",
            }) catch |err| load_error: {
                maybe_load_error = err;
                break :load_error Struct{};
            };

            return .{
                .ini_struct = ini_struct,
                .structure = structure,
                .maybe_load_error = maybe_load_error,
                .errors = global_errors,
            };
        }

        pub fn deinit(self: *Self) void {
            self.ini_struct.deinit();

            for (0..global_errors.items.len) |i| {
                const err = global_errors.items[i];
                temporary_allocator.free(err.type_name);
                temporary_allocator.free(err.key);
                temporary_allocator.free(err.value);
            }

            global_errors.deinit(temporary_allocator);
        }

        fn errorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void {
            global_errors.append(temporary_allocator, .{
                .type_name = temporary_allocator.dupe(u8, type_name) catch return,
                .key = temporary_allocator.dupe(u8, key) catch return,
                .value = temporary_allocator.dupe(u8, value) catch return,
                .error_name = @errorName(err),
            }) catch return;
        }
    };
}
