/* Native MAKI02 decoder.  It does not execute or interpret MAG comments. */
static char g_loadimage_mag_error[256];

static void loadimage_mag_set_error(const char *message) {
    snprintf(g_loadimage_mag_error, sizeof(g_loadimage_mag_error), "%s", message != NULL ? message : "MAG decode error");
    g_loadimage_mag_error[sizeof(g_loadimage_mag_error) - 1U] = '\0';
}

static unsigned int loadimage_mag_u16le(const unsigned char *p) {
    return (unsigned int)p[0] | ((unsigned int)p[1] << 8);
}

static unsigned int loadimage_mag_u32le(const unsigned char *p) {
    return (unsigned int)p[0] | ((unsigned int)p[1] << 8) |
           ((unsigned int)p[2] << 16) | ((unsigned int)p[3] << 24);
}

static int loadimage_is_mag_bytes(const unsigned char *data, size_t size) {
    return data != NULL && size >= 8U && memcmp(data, "MAKI02  ", 8U) == 0;
}

static unsigned char loadimage_mag_expand_component(unsigned char value, int significant_bits) {
    unsigned int sample;
    unsigned int max_sample;
    if (significant_bits >= 8) return value;
    if (significant_bits <= 0) return 0U;
    sample = (unsigned int)value >> (8U - (unsigned int)significant_bits);
    max_sample = (1U << (unsigned int)significant_bits) - 1U;
    return (unsigned char)((sample * 255U + max_sample / 2U) / max_sample);
}

static int loadimage_mag_decode_from_bytes(
    const unsigned char *data,
    size_t size,
    int pixel_mode,
    int *out_width,
    int *out_height,
    double **out_values)
{
    static const int copy_dx[16] = {0, -1, -2, -4, 0, -1, 0, -1, -2, 0, -1, -2, 0, -1, -2, -4};
    static const int copy_dy[16] = {0,  0,  0,  0,-1, -1,-2, -2, -2,-4, -4, -4,-8, -8, -8,-16};
    const unsigned char *header;
    size_t header_pos;
    size_t flag_a_pos, flag_b_pos, pixel_pos;
    size_t flag_a_available, flag_b_available, pixel_available;
    size_t flag_a_needed, action_count, output_bytes;
    size_t flag_a_bit = 0U, flag_b_used = 0U, pixel_used = 0U;
    size_t action_index, output_pos = 0U;
    unsigned char *action = NULL;
    unsigned char *packed = NULL;
    double *values = NULL;
    unsigned char palette[256][3];
    unsigned int model_code, model_flags, screen_mode;
    unsigned int x1, y1, x2, y2;
    unsigned int off_a, off_b, off_pixel;
    int is_256, palette_count, palette_bits, pixels_per_byte;
    int logical_width, logical_height, padded_byte_width, crop_left;
    int padded_left_byte, padded_right_byte;
    int x, y;
    if (out_width == NULL || out_height == NULL || out_values == NULL) return 0;
    *out_width = 0;
    *out_height = 0;
    *out_values = NULL;
    g_loadimage_mag_error[0] = '\0';
    if (data == NULL || size < 8U || memcmp(data, "MAKI02  ", 8U) != 0) {
        if (data != NULL && size >= 6U && memcmp(data, "MAKI01", 6U) == 0)
            loadimage_mag_set_error("MAKI01/MKI is not supported / 旧MAKI01・MKI形式には対応していません");
        else
            loadimage_mag_set_error("invalid MAKI02 signature / MAKI02シグネチャーがありません");
        return 0;
    }
    header_pos = 8U;
    while (header_pos < size && data[header_pos] != 0x1AU) ++header_pos;
    if (header_pos >= size || size - header_pos <= 32U) {
        loadimage_mag_set_error("MAG comment terminator or header is missing / MAGコメント終端またはヘッダーがありません");
        return 0;
    }
    ++header_pos;
    header = data + header_pos;
    if (header[0] != 0U) {
        loadimage_mag_set_error("invalid MAG header marker / MAGヘッダー開始マーカーが不正です");
        return 0;
    }
    model_code = (unsigned int)header[1];
    model_flags = (unsigned int)header[2];
    screen_mode = (unsigned int)header[3];
    if (model_code == 0x03U && (model_flags == 0x24U || model_flags == 0x34U || model_flags == 0x44U)) {
        loadimage_mag_set_error("MSX2+ YJK MAG is not supported / MSX2+ YJK形式のMAGには対応していません");
        return 0;
    }
    x1 = loadimage_mag_u16le(header + 4U);
    y1 = loadimage_mag_u16le(header + 6U);
    x2 = loadimage_mag_u16le(header + 8U);
    y2 = loadimage_mag_u16le(header + 10U);
    off_a = loadimage_mag_u32le(header + 12U);
    off_b = loadimage_mag_u32le(header + 16U);
    off_pixel = loadimage_mag_u32le(header + 24U);
    if (x2 < x1 || y2 < y1) {
        loadimage_mag_set_error("invalid MAG image coordinates / MAG画像座標が不正です");
        return 0;
    }
    if ((uintmax_t)x2 - (uintmax_t)x1 + 1U > (uintmax_t)INT_MAX ||
        (uintmax_t)y2 - (uintmax_t)y1 + 1U > (uintmax_t)INT_MAX) {
        loadimage_mag_set_error("MAG image dimensions overflow / MAG画像サイズが上限を超えています");
        return 0;
    }
    logical_width = (int)(x2 - x1 + 1U);
    logical_height = (int)(y2 - y1 + 1U);
    if (!loadimage_size_valid((size_t)logical_width, (size_t)logical_height)) {
        loadimage_mag_set_error("MAG image is too large / MAG画像サイズが大きすぎます");
        return 0;
    }
    is_256 = (screen_mode & 0x80U) != 0U;
    palette_count = is_256 ? 256 : 16;
    palette_bits = 4;
    if (model_code == 0x03U) palette_bits = 3;
    else if (model_code == 0x68U) palette_bits = 5;
    else if (model_code == 0x99U || (is_256 && model_code != 0x88U)) palette_bits = 8;
    pixels_per_byte = is_256 ? 1 : 2;
    padded_left_byte = ((int)(x1 / (unsigned int)pixels_per_byte)) & ~3;
    padded_right_byte = ((int)(x2 / (unsigned int)pixels_per_byte) + 4) & ~3;
    padded_byte_width = padded_right_byte - padded_left_byte;
    crop_left = (int)x1 - padded_left_byte * pixels_per_byte;
    if (padded_byte_width <= 0 || (padded_byte_width & 3) != 0 || crop_left < 0 ||
        crop_left + logical_width > padded_byte_width * pixels_per_byte) {
        loadimage_mag_set_error("invalid MAG padded geometry / MAG画像のパディング座標が不正です");
        return 0;
    }
    if (off_a < 32U + (unsigned int)palette_count * 3U || off_b < off_a || off_pixel < off_b ||
        (uintmax_t)header_pos + (uintmax_t)off_pixel > (uintmax_t)size) {
        loadimage_mag_set_error("invalid MAG stream offsets / MAGデータのオフセットが不正です");
        return 0;
    }
    flag_a_pos = header_pos + (size_t)off_a;
    flag_b_pos = header_pos + (size_t)off_b;
    pixel_pos = header_pos + (size_t)off_pixel;
    if (flag_a_pos > size || flag_b_pos > size || pixel_pos > size) {
        loadimage_mag_set_error("MAG stream offset exceeds file size / MAGデータ位置がファイル範囲を超えています");
        return 0;
    }
    flag_a_available = flag_b_pos - flag_a_pos;
    flag_b_available = pixel_pos - flag_b_pos;
    pixel_available = size - pixel_pos;
    if ((size_t)padded_byte_width > SIZE_MAX / (size_t)logical_height) {
        loadimage_mag_set_error("MAG decoded size overflow / MAG展開サイズが上限を超えています");
        return 0;
    }
    output_bytes = (size_t)padded_byte_width * (size_t)logical_height;
    action_count = output_bytes / 4U;
    flag_a_needed = (action_count + 7U) / 8U;
    if (action_count == 0U || flag_a_available < flag_a_needed) {
        loadimage_mag_set_error("MAG flag A stream is truncated / MAGのFlag Aデータが途中で切れています");
        return 0;
    }
    memset(palette, 0, sizeof(palette));
    for (x = 0; x < palette_count; ++x) {
        size_t p = header_pos + 32U + (size_t)x * 3U;
        palette[x][0] = loadimage_mag_expand_component(data[p + 1U], palette_bits);
        palette[x][1] = loadimage_mag_expand_component(data[p + 0U], palette_bits);
        palette[x][2] = loadimage_mag_expand_component(data[p + 2U], palette_bits);
    }
    action = (unsigned char *)calloc((size_t)padded_byte_width / 4U, 1U);
    packed = (unsigned char *)calloc(output_bytes, 1U);
    if (action == NULL || packed == NULL) {
        loadimage_mag_set_error("out of memory while decoding MAG / MAG展開用のメモリを確保できません");
        goto fail;
    }
    for (action_index = 0U; action_index < action_count; ++action_index) {
        size_t action_slot = action_index % ((size_t)padded_byte_width / 4U);
        unsigned char flag_a_byte = data[flag_a_pos + flag_a_bit / 8U];
        unsigned int flag_a_set = ((unsigned int)flag_a_byte >> (7U - (unsigned int)(flag_a_bit & 7U))) & 1U;
        unsigned char action_byte;
        int half;
        ++flag_a_bit;
        if (flag_a_set != 0U) {
            if (flag_b_used >= flag_b_available) {
                loadimage_mag_set_error("MAG flag B stream is truncated / MAGのFlag Bデータが途中で切れています");
                goto fail;
            }
            action[action_slot] ^= data[flag_b_pos + flag_b_used++];
        }
        action_byte = action[action_slot];
        for (half = 0; half < 2; ++half) {
            unsigned int code = half == 0 ? (unsigned int)(action_byte >> 4) : (unsigned int)(action_byte & 0x0FU);
            if (output_pos + 2U > output_bytes) {
                loadimage_mag_set_error("MAG output stream overflow / MAG展開先を超えるデータがあります");
                goto fail;
            }
            if (code == 0U) {
                if (pixel_used + 2U > pixel_available) {
                    loadimage_mag_set_error("MAG color index stream is truncated / MAGの画素データが途中で切れています");
                    goto fail;
                }
                packed[output_pos + 0U] = data[pixel_pos + pixel_used + 0U];
                packed[output_pos + 1U] = data[pixel_pos + pixel_used + 1U];
                pixel_used += 2U;
            } else {
                long long source = (long long)output_pos + (long long)copy_dx[code] * 2LL +
                                   (long long)copy_dy[code] * (long long)padded_byte_width;
                if (source < 0LL || (uintmax_t)source + 1U >= (uintmax_t)output_pos) {
                    loadimage_mag_set_error("MAG copy reference is invalid / MAG圧縮データの参照先が不正です");
                    goto fail;
                }
                packed[output_pos + 0U] = packed[(size_t)source + 0U];
                packed[output_pos + 1U] = packed[(size_t)source + 1U];
            }
            output_pos += 2U;
        }
    }
    if (output_pos != output_bytes) {
        loadimage_mag_set_error("MAG decoded byte count mismatch / MAG展開後のデータ長が一致しません");
        goto fail;
    }
    values = (double *)malloc((size_t)logical_width * (size_t)logical_height * 4U * sizeof(double));
    if (values == NULL) {
        loadimage_mag_set_error("out of memory while expanding MAG pixels / MAG画素展開用のメモリを確保できません");
        goto fail;
    }
    for (y = 0; y < logical_height; ++y) {
        for (x = 0; x < logical_width; ++x) {
            int padded_x = crop_left + x;
            unsigned int index;
            size_t dst = ((size_t)y * (size_t)logical_width + (size_t)x) * 4U;
            if (is_256) {
                index = (unsigned int)packed[(size_t)y * (size_t)padded_byte_width + (size_t)padded_x];
            } else {
                unsigned char byte_value = packed[(size_t)y * (size_t)padded_byte_width + (size_t)padded_x / 2U];
                index = ((padded_x & 1) == 0) ? (unsigned int)(byte_value >> 4) : (unsigned int)(byte_value & 0x0FU);
            }
            if ((int)index >= palette_count) index = 0U;
            if (pixel_mode == 0) {
                values[dst + 0U] = (double)palette[index][0] / 255.0;
                values[dst + 1U] = (double)palette[index][1] / 255.0;
                values[dst + 2U] = (double)palette[index][2] / 255.0;
                values[dst + 3U] = 1.0;
            } else if (pixel_mode == 1) {
                values[dst + 0U] = (double)palette[index][0];
                values[dst + 1U] = (double)palette[index][1];
                values[dst + 2U] = (double)palette[index][2];
                values[dst + 3U] = 255.0;
            } else {
                values[dst + 0U] = (double)((unsigned int)palette[index][0] * 257U);
                values[dst + 1U] = (double)((unsigned int)palette[index][1] * 257U);
                values[dst + 2U] = (double)((unsigned int)palette[index][2] * 257U);
                values[dst + 3U] = 65535.0;
            }
        }
    }
    free(action);
    free(packed);
    *out_width = logical_width;
    *out_height = logical_height;
    *out_values = values;
    return 1;
fail:
    free(values);
    free(action);
    free(packed);
    return 0;
}
