/* projector_crc.c
 *
 * Build:
 *   cc -std=c99 -Wall -Wextra -O2 projector_crc.c -o projector_crc
 *
 * The first entry calibrates the unknown initial CRC value.
 * Every subsequent entry is checked against a newly calculated CRC.
 */
#include <stdint.h>
#include <stdio.h>

typedef struct {
    const char *name;
    uint8_t stated0, stated1; /* CRC bytes printed in the table */
    uint8_t action;
    uint8_t type0, type1;
    uint8_t setting0;
} Command;

/* In these entries, the second action and setting bytes are 00. */
#define C(n, c0, c1, a, t0, t1, s0) \
    {n, c0, c1, a, t0, t1, s0}

static const Command commands[] = {
    C("Power off",            0x2A,0xD3, 0x01,0x00,0x60,0x00),
    C("Power on",             0xBA,0xD2, 0x01,0x00,0x60,0x01),
    C("Power get",            0x19,0xD3, 0x02,0x00,0x60,0x00),

    C("Input COMPUTER IN1",   0xFE,0xD2, 0x01,0x00,0x20,0x00),
    C("Input COMPUTER IN2",   0x3E,0xD0, 0x01,0x00,0x20,0x04),
    C("Input HDMI",           0x0E,0xD2, 0x01,0x00,0x20,0x03),
    C("Input COMPONENT",      0xAE,0xD1, 0x01,0x00,0x20,0x05),
    C("Input S-VIDEO",        0x9E,0xD3, 0x01,0x00,0x20,0x02),
    C("Input VIDEO",          0x6E,0xD3, 0x01,0x00,0x20,0x01),
    C("Input USB TYPE A",     0x5E,0xD1, 0x01,0x00,0x20,0x06),
    C("Input LAN",            0xCE,0xD5, 0x01,0x00,0x20,0x0B),
    C("Input USB TYPE B",     0xFE,0xD7, 0x01,0x00,0x20,0x0C),
    C("Input get",            0xCD,0xD2, 0x02,0x00,0x20,0x00),

    C("Error status get",     0xD9,0xD8, 0x02,0x20,0x60,0x00),

    C("Magnify get",          0x7C,0xD2, 0x02,0x07,0x30,0x00),
    C("Magnify increment",    0x1A,0xD2, 0x04,0x07,0x30,0x00),
    C("Magnify decrement",    0xCB,0xD3, 0x05,0x07,0x30,0x00),

    C("Freeze normal",        0x83,0xD2, 0x01,0x02,0x30,0x00),
    C("Freeze on",            0x13,0xD3, 0x01,0x02,0x30,0x01),
    C("Freeze get",           0xB0,0xD2, 0x02,0x02,0x30,0x00),

    C("Picture normal",       0x23,0xF6, 0x01,0xBA,0x30,0x00),
    C("Picture cinema",       0xB3,0xF7, 0x01,0xBA,0x30,0x01),
    C("Picture dynamic",      0xE3,0xF4, 0x01,0xBA,0x30,0x04),
    C("Picture blackboard",   0xE3,0xEF, 0x01,0xBA,0x30,0x20),
    C("Picture greenboard",   0x73,0xEE, 0x01,0xBA,0x30,0x21),
    C("Picture whiteboard",   0x83,0xEE, 0x01,0xBA,0x30,0x22),
    C("Picture daytime",      0xE3,0xC7, 0x01,0xBA,0x30,0x40),
    C("Picture get",          0x10,0xF6, 0x02,0xBA,0x30,0x00),

    C("Brightness get",       0x89,0xD2, 0x02,0x03,0x20,0x00),
    C("Brightness increment", 0xEF,0xD2, 0x04,0x03,0x20,0x00),
    C("Brightness decrement", 0x3E,0xD3, 0x05,0x03,0x20,0x00),
    C("Brightness reset",     0x58,0xD3, 0x06,0x00,0x70,0x00),

    C("Contrast get",         0xFD,0xD3, 0x02,0x04,0x20,0x00),
    C("Contrast increment",   0x9B,0xD3, 0x04,0x04,0x20,0x00),
    C("Contrast decrement",   0x4A,0xD2, 0x05,0x04,0x20,0x00),
    C("Contrast reset",       0xA4,0xD2, 0x06,0x01,0x70,0x00),

    C("Gamma 1 default",      0x07,0xE9, 0x01,0xA1,0x30,0x20),
    C("Gamma 1 custom",       0x07,0xFD, 0x01,0xA1,0x30,0x10),
    C("Gamma 2 default",      0x97,0xE8, 0x01,0xA1,0x30,0x21),
    C("Gamma 2 custom",       0x97,0xFC, 0x01,0xA1,0x30,0x11),
    C("Gamma 3 default",      0x67,0xE8, 0x01,0xA1,0x30,0x22),
    C("Gamma 3 custom",       0x67,0xFC, 0x01,0xA1,0x30,0x12),
    C("Gamma 4 default",      0xF7,0xE9, 0x01,0xA1,0x30,0x23),
    C("Gamma 4 custom",       0xF7,0xFD, 0x01,0xA1,0x30,0x13),
    C("Gamma 5 default",      0xC7,0xEB, 0x01,0xA1,0x30,0x24),
    C("Gamma 5 custom",       0xC7,0xFF, 0x01,0xA1,0x30,0x14),
    C("Gamma 6 default",      0x57,0xEA, 0x01,0xA1,0x30,0x25),
    C("Gamma 6 custom",       0x57,0xFE, 0x01,0xA1,0x30,0x15),
    C("Gamma get",            0xF4,0xF0, 0x02,0xA1,0x30,0x00),

    C("Mute off",             0x46,0xD3, 0x01,0x02,0x20,0x00),
    C("Mute on",              0xD6,0xD2, 0x01,0x02,0x20,0x01),
    C("Mute get",             0x75,0xD3, 0x02,0x02,0x20,0x00)
};
#undef C

static const size_t command_count =
    sizeof commands / sizeof commands[0];

/* Reflected polynomial 0xA001 corresponds to normal polynomial 0x8005. */
static uint16_t crc16_a001(const uint8_t *data, size_t length,
                           uint16_t initial)
{
    uint16_t crc = initial;

    for (size_t i = 0; i < length; ++i) {
        crc ^= data[i];
        for (unsigned bit = 0; bit < 8; ++bit)
            crc = (uint16_t)((crc >> 1) ^
                   ((crc & 1u) ? 0xA001u : 0u));
    }
    return crc;
}

static uint16_t calculated_crc(const Command *c, uint16_t initial)
{
    const uint8_t data[6] = {
        c->action, 0x00,
        c->type0, c->type1,
        c->setting0, 0x00
    };
    return crc16_a001(data, sizeof data, initial);
}

/*
 * Find an initial value that makes the calculated CRC of Power off
 * equal its stated value, interpreting the printed bytes as high-first.
 * This calibrates ONE entry; it does not verify that entry.
 */
static int find_initial_value(uint16_t *result)
{
    const uint16_t target =
        (uint16_t)(((uint16_t)commands[0].stated0 << 8) |
                    commands[0].stated1);

    for (uint32_t candidate = 0; candidate <= 0xFFFFu; ++candidate) {
        if (calculated_crc(&commands[0], (uint16_t)candidate)
            == target) {
            *result = (uint16_t)candidate;
            return 1;
        }
    }
    return 0;
}

int main(void)
{
    uint16_t initial;
    size_t matches = 0, mismatches = 0;

    if (!find_initial_value(&initial)) {
        fputs("Could not calibrate this CRC model.\n", stderr);
        return 1;
    }

    printf("Polynomial: 0x8005 (reflected implementation: 0xA001)\n");
    printf("Initial value fitted from Power off: 0x%04X\n", initial);
    puts("Power off is calibration data, NOT an independent match.\n");

    for (size_t i = 1; i < command_count; ++i) {
        const Command *c = &commands[i];
        uint16_t actual = calculated_crc(c, initial);
        uint16_t stated =
            (uint16_t)(((uint16_t)c->stated0 << 8) | c->stated1);
        int match = (actual == stated);

        printf("%-24s stated %02X %02X  calculated %02X %02X  %s\n",
               c->name, c->stated0, c->stated1,
               (unsigned)(actual >> 8), (unsigned)(actual & 0xFFu),
               match ? "MATCH" : "MISMATCH");

        if (match) ++matches;
        else       ++mismatches;
    }

    printf("\nIndependent checks: %zu matched, %zu mismatched.\n",
           matches, mismatches);
    return mismatches ? 1 : 0;
}
