const std = @import("std");
const logu = @import("logu");
const Com = @import("com.zig");
const simurai = @cImport({
    @cInclude("swsim.h");
});
const argsParser = @import("args");

fn signalExitHandler(signal_number: c_int) callconv(.C) void {
    _ = signal_number;
    logu.logzScope(logu.logz.info(), @This(), @src()).string("message", "Shutting down...").log();
    simurai.swicc_net_client_destroy(&state__net);
    std.process.exit(0);
}

var state__net: simurai.swicc_net_client_st = undefined;

pub fn usage(arg_0: ?[]const u8) void {
    logu.logzScope(logu.logz.info(), @This(), @src()).fmt("usage", "{s} --log-path log_file_path --fs-load-path fs_json_file_path --fs-save-path fs_gen_save_file_path --capdu-path capdu_save_file_path --rapdu-path rapdu_save_file_path", .{arg_0 orelse "exe"}).log();
}

const MainError = error{
    ArgProcessFailed,
    ArgMissing,
};

var com: Com = undefined;

pub fn main() !u8 {
    var state__swsim: simurai.swsim_st = undefined;
    var state__swicc: simurai.swicc_st = undefined;

    var allocator_gp = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = allocator_gp.allocator();

    try logu.logz.setup(allocator, .{});
    defer logu.logz.deinit();

    const options = argsParser.parseForCurrentProcess(struct {
        @"log-path": ?[]const u8 = null,
        @"fs-load-path": ?[]const u8 = null,
        @"fs-save-path": ?[]const u8 = null,
        @"capdu-path": ?[]const u8 = null,
        @"rapdu-path": ?[]const u8 = null,
    }, allocator, .print) catch {
        return MainError.ArgProcessFailed;
    };
    defer options.deinit();

    if (options.options.@"log-path" == null) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Argument missing: Path to log.").log();
        usage(options.executable_name);
        return MainError.ArgMissing;
    }
    try logu.logz.setup(allocator, .{ .output = .{ .file = options.options.@"log-path".? } });
    if (options.options.@"fs-load-path" == null) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Argument missing: Path to FS json definition.").log();
        usage(options.executable_name);
        return MainError.ArgMissing;
    }
    if (options.options.@"fs-save-path" == null) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Argument missing: Path where to save the generated SIM FS.").log();
        usage(options.executable_name);
        return MainError.ArgMissing;
    }
    if (options.options.@"capdu-path" == null) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Argument missing: Path where to write C-APDUs.").log();
        usage(options.executable_name);
        return MainError.ArgMissing;
    }
    if (options.options.@"rapdu-path" == null) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Argument missing: Path where to find R-APDUs.").log();
        usage(options.executable_name);
        return MainError.ArgMissing;
    }

    const file__capdu = try std.fs.cwd().openFile(options.options.@"capdu-path".?, .{ .mode = .write_only });
    defer file__capdu.close();
    const file__rapdu = try std.fs.cwd().openFile(options.options.@"rapdu-path".?, .{ .mode = .read_only });
    defer file__rapdu.close();
    com = Com{
        .file__capdu = file__capdu,
        .file__rapdu = file__rapdu,
        .verbose = true,
    };

    const ret__init = simurai.swsim_init(&state__swsim, &state__swicc, options.options.@"fs-load-path".?.ptr, options.options.@"fs-save-path".?.ptr);
    defer simurai.swicc_terminate(&state__swicc);
    if (ret__init != 0) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Failed to init swSIM.").int("err", ret__init).log();
        return 1;
    }
    state__swsim.proactive.app_default_enable = false;
    state__swsim.proactive.app_proprietary_enable = false;

    const ret__apduh_register = simurai.swicc_apduh_pro_register(&state__swicc, &apduh);
    if (ret__apduh_register != simurai.SWICC_RET_SUCCESS) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Failed register a proprietary APDU handler.").int("err", ret__apduh_register).log();
        return 1;
    }

    const ret__sig = simurai.swicc_net_client_sig_register(signalExitHandler);
    if (ret__sig != simurai.SWICC_RET_SUCCESS) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Failed register a signal handler.").int("err", ret__sig).log();
        return 1;
    }

    const ret__net_client_create = simurai.swicc_net_client_create(&state__net, "127.0.0.1", "37324");
    defer simurai.swicc_net_client_destroy(&state__net);
    if (ret__net_client_create != simurai.SWICC_RET_SUCCESS) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Failed to create a network client.").int("err", ret__net_client_create).log();
        return 1;
    }

    logu.logzScope(logu.logz.info(), @This(), @src()).string("message", "Press ctrl-c to exit.").log();

    const ret__net_client = simurai.swicc_net_client(&state__swicc, &state__net);
    if (ret__net_client != simurai.SWICC_RET_NET_DISCONNECTED) {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Failed to execute network client.").int("err", ret__net_client).log();
        return 1;
    } else {
        logu.logzScope(logu.logz.err(), @This(), @src()).string("message", "Client disconnected from server.").log();
    }

    return 0;
}

fn apduh(state__swicc: [*c]simurai.swicc_st, apdu_cmd: [*c]const simurai.swicc_apdu_cmd_st, apdu_res: [*c]simurai.swicc_apdu_res_st, procedure_count: u32) callconv(.C) simurai.swicc_ret_et {
    _ = state__swicc;

    logu.logzScope(logu.logz.info(), @This(), @src()).fmt("capdu", "{X:0>2} {X:0>2} {X:0>2} {X:0>2} {X:0>2} {}", .{ apdu_cmd.*.hdr.*.cla.raw, apdu_cmd.*.hdr.*.ins, apdu_cmd.*.hdr.*.p1, apdu_cmd.*.hdr.*.p2, apdu_cmd.*.p3[0], std.fmt.fmtSliceHexUpper(apdu_cmd.*.data.*.b[0..apdu_cmd.*.data.*.len]) }).log();

    if (apdu_cmd.*.hdr.*.cla.raw == 0x00 and
        apdu_cmd.*.hdr.*.ins == 0xA4 and
        apdu_cmd.*.hdr.*.p1 == 0x04 and
        apdu_cmd.*.hdr.*.p2 == 0x00 and
        apdu_cmd.*.p3[0] == 0x08)
    {
        if (procedure_count == 0) {
            apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_PROC_ACK_ALL;
            apdu_res.*.sw2 = 0;
            apdu_res.*.data.len = apdu_cmd.*.p3[0];
            return simurai.SWICC_RET_SUCCESS;
        }
        if (std.mem.eql(u8, apdu_cmd.*.data.*.b[0..apdu_cmd.*.data.*.len], &[_]u8{ 0xF1, 0x93, 0x57, 0x11, 0x03, 0xB9, 0x05, 0x1A })) {
            apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_NORM_NONE;
            apdu_res.*.sw2 = 0x00;
            apdu_res.*.data.len = 0;
            return simurai.SWICC_RET_SUCCESS;
        } else {
            logu.logzScope(logu.logz.warn(), @This(), @src()).string("message", "SELECT with AID did not contained expected AID.").log();
            return simurai.SWICC_RET_APDU_UNHANDLED;
        }
    }

    if (apdu_cmd.*.hdr.*.cla.raw != 0xA0) {
        logu.logzScope(logu.logz.warn(), @This(), @src()).string("message", "CLA not supported.").int("cla", apdu_cmd.*.hdr.*.cla.raw).log();
        return simurai.SWICC_RET_APDU_UNHANDLED;
    }

    var rapdu: [256 + 2]u8 = undefined;
    var capdu: [5 + 256]u8 = undefined;
    capdu[0] = apdu_cmd.*.hdr.*.cla.raw;
    capdu[1] = apdu_cmd.*.hdr.*.ins;
    capdu[2] = apdu_cmd.*.hdr.*.p1;
    capdu[3] = apdu_cmd.*.hdr.*.p2;
    capdu[4] = apdu_cmd.*.p3[0];
    std.mem.copyForwards(u8, capdu[5..], apdu_cmd.*.data.*.b[0..apdu_cmd.*.data.*.len]);
    const capdu__length = 5 + apdu_cmd.*.data.*.len;

    switch (apdu_cmd.*.hdr.*.ins) {
        0xB0, 0xB1 => {
            if (apdu_cmd.*.p3[0] != Com.PACKET_BUFFER_LENGTH) {
                logu.logzScope(logu.logz.info(), @This(), @src()).string("message", "P3 not indicating PACKET_BUFFER_LENGTH as Le.").log();
                apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_CHER_LE;
                apdu_res.*.sw2 = 0x00;
                apdu_res.*.data.len = 0;
                return simurai.SWICC_RET_SUCCESS;
            }

            const le = Com.PACKET_BUFFER_LENGTH;
            const rapdu__length = le + 2;
            if (procedure_count == 0) {
                logu.logzScope(logu.logz.info(), @This(), @src()).string("message", "Sending back procedure.").log();
                apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_PROC_ACK_ALL;
                apdu_res.*.sw2 = 0x00;
                apdu_res.*.data.len = 0;
                return simurai.SWICC_RET_SUCCESS;
            }
            apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_NORM_NONE;
            apdu_res.*.sw2 = 0x00;
            apdu_res.*.data.len = 0;

            const res = com.apduTransceive(capdu[0..capdu__length], &rapdu, rapdu__length) catch {
                logu.logzScope(logu.logz.info(), @This(), @src()).string("message", "APDU exchange with Java failed.").log();
                apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_EXER_NVM_CHGN;
                apdu_res.*.sw2 = 0x00;
                apdu_res.*.data.len = 0;
                return simurai.SWICC_RET_SUCCESS;
            };

            apdu_res.*.sw1 = res[res.len - 2];
            apdu_res.*.sw2 = res[res.len - 1];
            apdu_res.*.data.len = @intCast(res.len - 2);
            std.mem.copyForwards(u8, &apdu_res.*.data.b, res[0..apdu_res.*.data.len]);
            logu.logzScope(logu.logz.info(), @This(), @src()).fmt("rapdu", "{} {X:0>2} {X:0>2}", .{ std.fmt.fmtSliceHexUpper(apdu_res.*.data.b[0..apdu_res.*.data.len]), apdu_res.*.sw1, apdu_res.*.sw2 }).log();
            return simurai.SWICC_RET_SUCCESS;
        },
        else => {
            apdu_res.*.sw1 = simurai.SWICC_APDU_SW1_CHER_INS;
            apdu_res.*.sw2 = 0x00;
            apdu_res.*.data.len = 0;
            return simurai.SWICC_RET_SUCCESS;
        },
    }
}

test {
    std.testing.refAllDeclsRecursive(@This());
}
