// @todo fsave: fputs(DNA), then fwrite
// @todo fread: fgets(DNA), abort if DNA != read; then fread

#include <stdio.h>

// load/save whole struct acording to its DNA structure
int fload(FILE *infile, void *structure, const char *dna);
int fsave(FILE *outfile, const void *structure, const char *dna);

// set alignment for next fload/fsave call. resets back to 0 after every fload/fsave call.
// value: 0 (auto, default), 1,2,4,8,16 bytes [...]
void falign(unsigned alignment); 

// override default DNA handlers and switch to a different schema and/or serialization format
typedef int (*size_operator)(char fmt, void *addr, int readmode);
typedef int (*call_operator)(void *out, void *addr, char fmt, int count, int readmode);
void foverride(size_operator size, call_operator call);

#if 0
Example Usage
-------------

    struct fat_bootsector {
        uint8_t jump_instruction[3];
        uint8_t oem_name[8];
        uint16_t bytes_per_sector;
        uint8_t sectors_per_cluster;
        uint16_t reserved_sectors;
        uint8_t fat_copies;
        uint16_t max_dirs;
        uint16_t sector_count;
        uint8_t media_descriptor;
        uint16_t sectors_per_fat;
        uint16_t sectors_per_head;
        uint16_t heads;
        uint32_t hidden_sectors;
        uint32_t sector_count2;
    } fat_struct;

Now we can read a binary image of the MBR into this structure:

    mreadf(mbr, "i3c8chchchhchhhdd", &fat_struct);

Directives
----------

Supported directives:

    regex   meaning

    (blank) ignored

    b       read / write uint8_t
    w       read / write uint16_t (uppercase: use vli encoding; as uint8? )
    u       read / write uint32_t (uppercase: use vli encoding; as uint8,16? )
    q       read / write uint64_t (uppercase: use vli encoding; as uint8,16,32?)

    m       read / write micro
    h       read / write half     (uppercase: use smallest representation; as micro?)
    f       read / write float    (uppercase: use smallest representation; as micro,half?)
    d       read / write double   (uppercase: use smallest representation; as micro,half,float?)

    s       read / write string   (uppercase: use smallest representation; quarks?)
    []      read / write buffer

    <       switch to Intel (little endian) byte order
    >       switch to Motorola (big endian) byte order
    (       begin of tight group
    )       end of tight group
    [0-9]+  next item repeated n times
    z       skip one byte of input / emit \0
    *       consume next structure item but do not read / write
#endif

// ----------------------------------------------------------------------------

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#ifndef __thread
#define __thread __declspec(thread)
#endif

static __thread int pragma_pack_alignment = 0;

void falign(unsigned alignment) {
    pragma_pack_alignment = alignment;
}

static
int size_fn(char fmt, uint8_t* addr8, int rd) {
    if(addr8) {
        // sizeof pointee data & align operator
        /**/ if(fmt == 'c' || fmt == 'b') return 1;
        else if(fmt == 'w'              ) return 2;
        else if(fmt == 'i' || fmt == 'u') return 4;
        else if(fmt == 'l' || fmt == 'q') return 8;
        else if(fmt == 'f'              ) return 4;
        else if(fmt == 'd'              ) return 8;
        else if(fmt == 's'              ) return !*(char**)addr8 ? 0+1 : !rd ? strlen(*(char**)addr8) + 1 : strcspn(*(char**)addr8,"\x1") + 1;
        return -1;
    } else {
        // sizeof member
        /**/ if(fmt == 'w'              ) return 2;
        else if(fmt == 'i' || fmt == 'u') return 4;
        else if(fmt == 'l' || fmt == 'q') return 8;
        else if(fmt == 'f'              ) return 4;
        else if(fmt == 'd'              ) return 8;
        else if(fmt == 's'              ) return sizeof(void*);
        return 1;
    }
}

static
int call_fn(void *out, uint8_t *addr8, char fmt, int count, int rd) { // rd/wr operator
    FILE *fp = (FILE*)out;
    int iterated_bytes = 0, processed_bytes = 0;
    while( --count >= 0 ) {
        int slot = size_fn(fmt, 0, rd);
        int size = size_fn(fmt, addr8, rd);
        if(rd)
        switch (fmt) {
            default: return -1;
            break; case 'c': case 'b': fscanf(fp, "%c,", (uint8_t*)addr8);
            break; case 'w':           fscanf(fp, "%#04llx,", (uint16_t*)addr8);
            break; case 'i': case 'u': fscanf(fp, "%#08llx,", (uint32_t*)addr8);
            break; case 'l': case 'q': fscanf(fp, "%#16llx,", (uint64_t*)addr8);
            break; case 'f':           fscanf(fp, "%f,", (float*)addr8);
            break; case 'd':           fscanf(fp, "%llf,", (double*)addr8);
            break; case 's':           fscanf(fp, "%[^\x1],", (char*)addr8);
        }
        else
        switch(fmt) {
            default: return -1;
            break; case 'c': case 'b': fprintf(fp, "%c,", (int)*(uint8_t*)addr8);
            break; case 'w':           fprintf(fp, "%#04llx,", (uint64_t)*(uint16_t*)addr8);
            break; case 'i': case 'u': fprintf(fp, "%#08llx,", (uint64_t)*(uint32_t*)addr8);
            break; case 'l': case 'q': fprintf(fp, "%#16llx,", (uint64_t)*(uint64_t*)addr8);
            break; case 'f':           fprintf(fp, "%f,", (float)*(float*)addr8);
            break; case 'd':           fprintf(fp, "%f,", (double)*(double*)addr8);
            break; case 's':           fprintf(fp, "%s\x1,", *(char**)addr8);
        }
        addr8 += slot;
        iterated_bytes += slot;
        processed_bytes += size;
    }
    return iterated_bytes;
}

static size_operator fsize_fn = size_fn;
static call_operator fcall_fn = call_fn;

void foverride(size_operator size, call_operator call) {
    fsize_fn = size;
    fcall_fn = call;
}

int fdump(FILE *out, void *addr, const char *dna, int rd) {
    unsigned pragma_pack = pragma_pack_alignment; pragma_pack_alignment = 0; // reset alignment
    uint8_t *addr8 = (uint8_t*)addr;
    int size = 0, count = 1, skip = 0;
    int last_type = 0;
    int written = 0;
    int align = 0;
    for( int i = 0; dna[i]; ++i) {
        char fmt = dna[i];
        /**/ if(fmt <= 32) continue;
        else if(fmt >= '0' && fmt <= '9') continue;
        else if(fmt == 'z') skip = 1;
        else { 

            // member alignment
            if( last_type != fmt ) { // check if next struct member was found (may need re-alignment)
                if( pragma_pack != 1 ) { // forced (>1) or auto-alignment (0)?
                    //printf("*%p ->", addr8);
                    align = pragma_pack == 0 ? fsize_fn(fmt, 0, rd) : pragma_pack;
                    // Round up to N-byte boundary
                    addr8 = (uint8_t*)(((uintptr_t)(addr8) + ((align) - 1)) & -(align));
                    //printf(" %p\n", addr8);
                }
            }

            last_type = fmt;

            size = fsize_fn(fmt, addr8, rd);
            if( size < 0 ) {
                fprintf(stderr, "parse error, unknown dna sequence '%c'!\n", fmt);
                return -i-1; 
            }
        }

        if( skip ) { skip = 0; continue; }

        char next = dna[i+1];
        if( next >= '0' && next <= '9' ) {
            count = next - '0';
        }
        int bytes = skip || count == 0 ? 0 : call_fn(out, addr8, dna[i], count, rd);
        if( bytes < 0 ) {
            fprintf(stderr, "stream fail. rc: %d\n", bytes);
            return bytes;
        }
        written += bytes;
        addr8 += bytes;
        count = 1;

        fprintf(out, "\n");
    }
    return written;
}

int fsave(FILE *out, const void *structure, const char *dna) {
    return fdump(out, (void*)structure, dna, 0);
}
int fload(FILE *inf, void *structure, const char *dna) {
    return fdump(inf, structure, dna, 1);
}
int fsavefile(const char *outfile, const void *structure, const char *dna) {
	FILE *fp = fopen(outfile, "wb");
	if( !fp ) return 0;
    int bytes = fdump(fp, (void*)structure, dna, 0);
    fclose(fp);
    return bytes;
}
int floadfile(const char *infile, void *structure, const char *dna) {
	FILE *fp = fopen(infile, "rb");
	if( !fp ) return 0;
    int bytes = fdump(fp, (void*)structure, dna, 1);
    fclose(fp);
    return bytes;
}

// ---

#include <assert.h>


//  #pragma pack(1)

struct fat_mbr {
    uint8_t jmp[3]; // b3
    uint8_t oem[8];         // b8
    const char* str; // s
    uint16_t bytes_per_sector;   // w
    uint8_t sectors_per_cluster; // b
    uint16_t reserved_sectors;   // w
    uint8_t fat_copies;          // b
    uint16_t max_dirs;           // w
    uint16_t sector_count;       // w
    uint8_t media_descriptor;    // b
    uint16_t sectors_per_fat;    // w
    uint16_t sectors_per_head;   // w
    uint16_t heads;              // w
    uint32_t hidden_sectors;     // u
    uint32_t sector_count2;      // u
    float    pi;
    char break_alignment;  // b
    double   phi;
};

    #define FAT_MBR_DNA "b3b8s wb wb w wb www uu fbd"

//  #pragma pack(pop)


int main() {

//    foverride(size_fn, write_fn);

    struct fat_mbr mbr = {
        {'a','b','c'},
        {'d','e','f','g','h','i','j','k'},
        "hello 'escaped' \"world\"",
        0x100,'l',
        0x101,'m',
        0x102,
        0x103,'n',
        0x104,
        0x105,
        0x106,
        0x01234567,
        0x89abcdef,
        3.14159f,'o',
        1.6069,
    };

//    fdump(stdout, &mbr, FAT_MBR_DNA); exit(0);
//  printf("%p\n", &mbr.jmp);
//  printf("%p\n", &mbr.oem);
//  printf("%p\n", &mbr.str);
//  printf("%p\n", &mbr.sector_count);

//  falign(0);
    int bytes = fsave(stdout, &mbr, FAT_MBR_DNA);
    printf("%d bytes written\n", bytes);


    typedef struct entitystate {
        struct pos {
            short trTime;
            float trBase[3];
        } pos;
    } entitystate;
    entitystate e = { 123, 3.14159f, 4.14159f, 5.14159f };
    bytes = fsave(stdout, &e, "wfff");
    printf("%d bytes written\n", bytes);


//    exit(0);

    struct fat_mbr zero = {0};
    struct fat_mbr src = mbr;
    struct fat_mbr dst = zero;

    dst = src;
    assert(0 == memcmp(&src,&dst,sizeof(struct fat_mbr)));

    dst = zero;
    assert(0 != memcmp(&src,&dst,sizeof(struct fat_mbr)));

    int sbytes = fsavefile(".temp", &src, FAT_MBR_DNA);
    int lbytes = floadfile(".temp", &dst, FAT_MBR_DNA);
    assert( sbytes == lbytes );
    assert(0 != memcmp(&src,&dst,sizeof(struct fat_mbr)));

    assert(!puts("Ok"));
}