// zem - makes an ELF file - which we can look at with mez

const std = @import("std");

const Bytes = struct {
    mem: [0x2000]u8 = undefined,
    pos: usize = 0,

    pub fn writeNum(self: *Bytes, num: anytype) void {
        const T = @TypeOf(num);
        std.mem.writeIntSliceNative(T, self.mem[self.pos..], num);
//        std.debug.print("Writing {x} ({s})\n", .{num, @typeName(@TypeOf(num))});
        self.pos += @sizeOf(T);
    }

    pub fn writeBytes(self: *Bytes, source_bytes: []const u8) void {
//        std.debug.print("Writing {d} bytes\n", .{source_bytes.len});
        for (source_bytes) |b| {
//            std.debug.print("  0x{x}: {x}\n", .{self.pos, b});
            self.mem[self.pos] = b;
            self.pos += 1;
        }
    }

    pub fn padTo(self: *Bytes, addr: usize) void {
        while (self.pos < addr) {
            self.mem[self.pos] = 0;
            self.pos += 1;
        }
    }

    pub fn print(self: Bytes) void {
        for(self.mem[0..self.pos], 0..self.pos) |b,a| {
            std.debug.print("\x1b[90m{x}:\x1b[39m{x} ", .{a,b});
        }
        std.debug.print("\n", .{});
    }

    pub fn get(self: Bytes) []const u8 {
        return self.mem[0..self.pos];
    }
};

pub fn main() !void {
//    var bytes: [100]u8 = undefined;
//    var pos: usize = 0;

    var bytes = Bytes{};

    // hard-coding for ease!
    const segment_count = 2;
    const program_mem_addr: u32 = 0x8040000;
    const data_mem_addr: u32 = 0x8041000;
    const ph_size = 32; // program header size (bytes)
    const elf_size = 52; // main elf header size (bytes)
    const total_header = elf_size + (ph_size * segment_count);
    const entry_addr = program_mem_addr + total_header;

std.debug.print("total header: {d} bytes\n", .{total_header});

    bytes.writeBytes(&[_]u8{ 0x7F, 'E', 'L', 'F' }); // magic
    bytes.writeNum(@as(u8, 1)); // 32-bit            0x04
    bytes.writeNum(@as(u8, 1)); // little endian     0x05
    bytes.writeNum(@as(u8, 1)); // elf version       0x06
    bytes.writeNum(@as(u8, 0)); // systemV os abi    0x07
    bytes.writeNum(@as(u8, 0)); // abi version       0x08
    bytes.writeBytes(&[_]u8{
        0, // pad 0x09
        0, // pad 0x0a
        0, // pad 0x0b
        0, // pad 0x0c      (7 bytes padding)
        0, // pad 0x0d
        0, // pad 0x0e
        0, // pad 0x0f
    });
    bytes.writeNum(@as(u16, 2)); // executable        0x10
    bytes.writeNum(@as(u16, 3)); // arch intel 80386  0x12
    bytes.writeNum(@as(u32, 1)); // ELF version       0x14
    bytes.writeNum(entry_addr); // entry address!     0x18

    // next is elf_size because program headers start right after
    // main elf header.
    bytes.writeNum(@as(u32, elf_size)); // e_phoff    0x1C

    // Stuff
    bytes.writeNum(@as(u32, 0)); // e_shoff ignore!   0x20
    bytes.writeNum(@as(u32, 0)); // e_flags           0x24
    bytes.writeNum(@as(u16, elf_size)); // e_hsize    0x28

    // Program header size and count
    bytes.writeNum(@as(u16, 32)); // ph size (each)   0x2A
    bytes.writeNum(@as(u16, 2)); // ph count          0x2C

    // More section stuff to ignore:
    bytes.writeNum(@as(u16, 0)); // sh size           0x2E
    bytes.writeNum(@as(u16, 0)); // sh count          0x30
    bytes.writeNum(@as(u16, 0)); // sh str idx        0x32

    // Program Header 1
    // ================================================
    bytes.writeNum(@as(u32, 1)); // ptype (1=LOAD)
    bytes.writeNum(@as(u32, 0)); // file offset to load!
    bytes.writeNum(@as(u32, program_mem_addr)); // write to mem!
    bytes.writeNum(@as(u32, program_mem_addr)); // same (physical)
    bytes.writeNum(@as(u32, 0x1000)); // bytes to write
    bytes.writeNum(@as(u32, 0x1000)); // memory to alloc
    bytes.writeNum(@as(u32, 5)); // flags (RX)
    bytes.writeNum(@as(u32, 0x1000)); // alignment

    // Program Header 2
    // ================================================
    bytes.writeNum(@as(u32, 1)); // ptype (1=LOAD)
    bytes.writeNum(@as(u32, 0x1000)); // file offset to load!
    bytes.writeNum(@as(u32, data_mem_addr)); // write to mem!
    bytes.writeNum(@as(u32, data_mem_addr)); // same (physical)
    bytes.writeNum(@as(u32, 13)); // bytes to write
    bytes.writeNum(@as(u32, 13)); // memory to alloc
    bytes.writeNum(@as(u32, 6)); // flags (RW)
    bytes.writeNum(@as(u32, 0x1000)); // alignment

    // Write program (0x22 bytes)
    // ================================================
    bytes.writeBytes(&[_]u8{
        // comes from hello.asm - then hand-edited address to
        // point to my data segment: 0x8041000
        0xba, 0x0d, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x10, 0x04, 0x08,
        0xbb, 0x01, 0x00, 0x00, 0x00, 0xb8, 0x04, 0x00, 0x00, 0x00,
        0xcd, 0x80, 0xbb, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x00,
        0x00, 0x00, 0xcd, 0x80,
    });

    // Pad to next 0x1000, then write string data
    // ================================================
    bytes.padTo(0x1000);
    bytes.writeBytes("Hello world.\x0a");


    // Print what we're gonna write to compare with mez
    // LOL - not now that there's 1kb padding!
    //bytes.print();

    const file = try std.fs.cwd().createFile("foo", .{
        .mode = 0o777,
    });
    defer file.close();
    _ = try file.write(bytes.get());
}
