/** About this program
Charmsec7-5c : This program is a demonstration program for an error correcting characterization
code based on the Charmsec CoDec library Rev. 1.9x, an example application of JP-A2021-202714.
This code is capable of correcting single-character errors. Charmsec is an error-correcting code
that uses the Chinese remainder theorem to encode information data into a set of characters -
especially ASCII characters.
http://www.finetune.co.jp/~lyuka/technote/charmsec/
*/
#include <stdint.h>
#include <inttypes.h>
/* Charmsec7 (9c, 44) */
#define NUM_COPRIME     9
#define DATA_LEN       44
#define NUM_SUBSET    (NUM_COPRIME - 2)
#define CHARMS_LEN    (NUM_COPRIME + 3)
typedef struct {
    uint64_t           status;
    uint64_t           data;
    char               code[CHARMS_LEN];
    const int          data_len;
    const uint64_t     data_mask;
    const int          charms_len;                      // Length of the code string encoded by Charmsec
    const int          num_coprime;                     // number of coprime numbers
    const int          num_subset;                      // number of subset of the coprime number set
    const uint8_t      ascii_mask;
    const char         cmap[93];                        // charactor mapping table (92 characters + \0)
    const uint8_t      rmap[128];                       // charactor reverse mapping table
    const uint64_t     coprime[NUM_COPRIME];            // coprimes
    const uint64_t     modmax[NUM_COPRIME];             // Maximum value of the modulus system
    const uint64_t     minmodmax;
    const uint64_t     invmat[NUM_COPRIME][NUM_COPRIME];// Mudulus invert of the modulus system
    const uint64_t     corrected;                       // decode result flag : corrected
    const uint64_t     detected;                        // decode result flag : detected
    const uint64_t     superdata;                       // decode result flag : super code
    const char        *title;
    const char        *version;
    const char        *copyright;
} charms_t;
int          ischarms(char);
char        *encode(uint64_t);
uint64_t     decode(char *);
charms_t    charms = {
    0,                                             //.status
    0,                                             //.data
    {0, 0, 0, 0, 0, 0, 0, 0, 0},                   //.code
    DATA_LEN,                                      //.data_len
    ~0UL >> (64 - DATA_LEN),                       //.data_mask
    CHARMS_LEN,                                    //.charms_len
    NUM_COPRIME,                                   //.num_coprime
    NUM_SUBSET,                                    //.num_subset
    0x7f,                                          //.ascii_mask
    "!\"#$%&'()+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~",//.cmap
    {   127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,\
        127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,\
        127, 0, 1, 2, 3, 4, 5, 6, 7, 8, 127, 9, 10, 11, 12, 13,\
        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,\
        30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,\
        46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 127, 58, 59, 60,\
        61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76,\
        77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 127},//.rmap
    /* Charmsec7-5c */
    { 71,  73,  79,  83,  85,  87,  88,  89,  91}, //.coprime
    { 22116033697560, 27722915480040, 34558702858680, 31059087379320, 27317028658920, 25388767812408, 24221468142872, \
        23395736274365, 22869989391795},            //.modmax
    0x0000141d4a551718,                             //.minmodmax
    {   { 9033309538440,  4544390485800,  3639347317320,  11457704204760,  9366790742496,  13981400613400,  14325158190465,  0,  0}, \
        { 0,  23545489859760,  12984150288120,  26720882390400,  14350685660256,  26129644475440,  27407882349585,  7475842376640,  0}, \
        { 0,  0,  9186490633320,  27480414321360,  33338983934256,  24230814648040,  1963562662425,  29122502409000,  12912042826320}, \
        { 15748269657120,  0,  0,  8232529184880,  8404223643816,  26418074322640,  4588274271945,  4187742118560,  25598148939000}, \
        { 1154240647560,  25071793426680,  0,  0,  5784782539536,  14443486417360,  19556509153545,  15039712407720,  900561384360}, \
        { 8224530418104,  21215271733656,  16068840387600,  0,  0,  16050370456120,  6058683227961,  20824494947256,  13112880078936}, \
        { 2388032070424,  8958625203528,  6438618113928,  6128323265064,  0,  0,  15688905501633,  14696171682192,  18365728591848}, \
        { 14498766141860,  16665455976260,  21026547790885,  3100639747205,  2477195605521,  0,  0,  15246659594530,  20567680241200}, \
        { 4509575373030,  15351088769835,  19975054025745,  15981438370170,  4843056577086,  20767001861515,  0,  0,  10052742589800}
    },                                              //.invmat
    /* decode status at bit 63..61 */
    1ULL << 61,                                     //.corrected
    1ULL << 62,                                     //.detected
    1ULL << 63,                                     //.superdata
    "charmsec8_5c - a subset of the Charmsec CoDec library.",//.title
    "Rev.1.97 (Aug. 25, 2024)",                     //.version
    "(c) 2004 Takayuki HOSODA, Finetune co., ltd."  //.copyright
};
int ischarms(char c) {
    return charms.rmap[(int)c] == charms.ascii_mask ? 0 : charms.ascii_mask;
}
char * encode(uint64_t data) {
    int    i;
    if (data >= charms.minmodmax) data = (charms.minmodmax - 1UL);
    for (i = 0; i < charms.num_coprime; i++) {
        charms.code[i] = charms.cmap[data % charms.coprime[i]];
    }
    charms.code[i] = '\0';
    return charms.code;
}
/** decode() return value
no error   : data, superdata
corrected  : data, superdate | charms.corrected
detected   :                   charms.detected
*/
uint64_t decode(char *code) {
    int         i, j;
    uint8_t     a[NUM_COPRIME];
    uint64_t    b[NUM_COPRIME];
    uint64_t    data = 0;
    /* decompose code */
    for (i = 0; i < charms.num_coprime; i++) {
        a[i] = charms.rmap[(int)code[i] & charms.ascii_mask];
    }
    /* majority decision */
    /* subdecode 0 to 2 */
    for (i = 0; i < 3; i++) {
        /* subdecode */
        data = 0;
        /* mul-add in uint64_t followed by remainder calculation */
        for (j = 0; j < charms.num_coprime; j++) {
            data = data + a[j] * charms.invmat[i][j];
        }
        data %= charms.modmax[i];
        b[i] = data;
    }
    data = b[0];
    charms.status = charms.detected;
    if ((data == b[1]) && (data == b[2])) {
        charms.status = 0;
    } else {
        /* subdecode 3 to charms.num_coprime */
        for (i = 3; i < charms.num_coprime; i++) {
            /* subdecode*/
            data = 0;
            /* mul-add in uint64_t followed by remainder calculation */
            for (j = 0; j < charms.num_coprime; j++) {
                data = data + a[j] * charms.invmat[i][j];
            }
            data %= charms.modmax[i];
            b[i] = data;
        }
        data = b[0];
        /* compare adjacent data to find correct data. */
        if (data == b[charms.num_coprime - 1]) {
            charms.status = charms.corrected;
        } else {
            for (i = 0; i < charms.num_coprime - 1; i++) {
                if (b[i] == b[i + 1]) {
                    charms.status = charms.corrected;
                    data = b[i];
                    break;
                }
            }
        }
    }
    if (data > charms.data_mask) charms.status |= charms.superdata;
    return data;
}
#ifdef DEBUG
#include <stdio.h>
#ifndef PRIx64
#define PRIx64 "llx"// uint64_t
#endif
const char    *myname  = "Charmsec7_5c_demo";
int main() {
    int        i, j;
    char       rxcode[CHARMS_LEN];
    char       rxdisp[CHARMS_LEN];
    uint64_t   rxdata, data;
    rxdata = 0UL;
    for (i = 0; i < charms.charms_len; i++) rxcode[i] = rxdisp[i] = '\0';
    printf("%s : %s %s\n", myname, charms.version, charms.copyright);
    printf("%s : Using %s\n", myname, charms.title);
    printf("%s : '\\' is to be used as the error position indicator here.\n", myname);
    for (i = 0; i < charms.charms_len; i++) rxcode[i] = '\0';// initialize rxcode. Note that '\0' is not a Charmsec code charactor.
    data = 0xbadcafebabe;// A test data of 44-bit unsigned integer.
    printf("%s : txdata = 0x%015" PRIx64 " -> txcode = %s%s\n", myname, \
        data, encode(data), data > charms.data_mask ? data < charms.minmodmax - 1UL ? " (super data)" : " (saturated)" : "");
    for (j = 0; j < charms.num_coprime; j++) {
        for (i = 0; i < charms.charms_len; i++) rxcode[i] = charms.code[i];
        rxcode[j] = '\t';// insert single character error. Note that '\t' is not a Charmsec code character.
        rxdata = decode(rxcode);
        for (i = 0; i < charms.num_coprime; i++) rxdisp[i] = ischarms(rxcode[i]) ? rxcode[i] : '\\';
        printf("%s : rxdata = 0x%015" PRIx64 " <- rxcode = %s%s - %s\n", myname, rxdata, rxdisp,
              charms.status & charms.superdata ? " (superdata)" : "",
              charms.status & charms.corrected ? "Corrected an error"
            : charms.status & charms.detected  ? "Unrecoverable error detected" : "No error");
    }
    data = charms.minmodmax - 1UL;// the maximum value for superdata
    printf("%s : txdata = 0x%015" PRIx64 " -> txcode = %s%s\n", myname, \
        data, encode(data), data > charms.data_mask ? data < charms.minmodmax - 1UL ? " (super data)" : " (saturated)" : "");
    for (j = 0; j < charms.num_coprime; j++) {
        for (i = 0; i < charms.charms_len; i++) rxcode[i] = charms.code[i];
        rxcode[j] = '\t';// insert single character error. Note that '\t' is not a Charmsec code character.
        rxdata = decode(rxcode);
        for (i = 0; i < charms.num_coprime; i++) rxdisp[i] = ischarms(rxcode[i]) ? rxcode[i] : '\\';
        printf("%s : rxdata = 0x%015" PRIx64 " <- rxcode = %s%s - %s\n", myname, rxdata, rxdisp,
              charms.status & charms.superdata ? " (superdata)" : "",
              charms.status & charms.corrected ? "Corrected an error"
            : charms.status & charms.detected  ? "Unrecoverable error detected" : "No error");
    }
    return 0;
}
#endif