/*
 * mdview2_hex.c — HEX-дамп ОРИГИНАЛЬНОГО файла (F4).
 *
 * Показывает исходные байты файла (orig_file_phys), а не активный буфер:
 * hex-дамп декодированного UTF-набора был бы искажением. Printable-колонка
 * интерпретируется ТЕКУЩЕЙ кодировкой: CP866 как есть, CP1251/KOI8 через
 * g_remap, UTF-8 — глиф на позиции лид-байта (continuation-байты → '.').
 *
 * Формат ряда (79 колонок, один атрибут ATTR_TEXT — как RAW):
 *  0x012340 │ XX XX XX XX XX XX XX XX  XX XX XX XX XX XX XX XX │ 16 символов
 *
 * Ряд выровнен на 16 байт и никогда не пересекает границу EMM-страницы
 * (16 делит 16384 нацело) — один bank_read на ряд, fb()/W3-маппинг не нужны.
 * Позиция (hex_top) живёт в байтах ОРИГИНАЛА; конвертацию в/из байт-позиции
 * активного буфера делает ядро (view_pos/view_reanchor через map_off).
 */

#include <stdint.h>
#include <string.h>
#include <conio.h>
#include <bios/text.h>
#include <sprinter_mem.h>   /* bank_read (чтение оригинала по страницам EMM) */
#include "mdview2.h"

#if WITH_HEX   /* весь модуль опционален (mdview2_conf.h); при 0 пустой объектник */

#define HEX_BPR       16u                        /* байт на ряд */
#define HEX_SPAN      ((uint32_t)(VIEW_H - 1) * HEX_BPR)

/* Геометрия ряда (колонки). */
#define HX_OFF         1u                        /* "0x012340" */
#define HX_SEP1       10u                        /* │ */
#define HX_DUMP       12u                        /* пары hex-цифр */
#define HX_SEP2       61u                        /* │ */
#define HX_PRINT      63u                        /* printable */

static uint32_t hex_top;                         /* offset верхнего ряда (кратен 16) */

uint32_t hex_pos(void) { return hex_top; }

/* Максимальный hex_top: последний ряд файла на нижней строке экрана. */
static uint32_t hex_max_top(void)
{
    uint32_t last = (orig_file_size == 0) ? 0
                  : ((orig_file_size - 1u) & ~(uint32_t)(HEX_BPR - 1u));
    return (last > HEX_SPAN) ? (last - HEX_SPAN) : 0;
}

void hex_reanchor(uint32_t orig_off)
{
    uint32_t mt = hex_max_top();
    orig_off &= ~(uint32_t)(HEX_BPR - 1u);
    hex_top = (orig_off > mt) ? mt : orig_off;
}

void hex_screen_init(void)
{
    bios_clearwin(VIEW_TOP_ROW, 0, VIEW_H, SCREEN_W, ATTR_TEXT);
}

/* ---- отрисовка ----------------------------------------------------- */

static uint8_t hexdig(uint8_t v)
{
    v &= 15u;
    return (uint8_t)((v < 10u) ? ('0' + v) : ('A' - 10 + v));
}

/* Один байт оригинала (для UTF-lookahead за границей ряда — редкий путь). */
static uint8_t fbo(uint32_t off)
{
    uint8_t b;
    bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), &b, 1);
    return b;
}

/* Printable-глиф для n байт ряда b[] (off — offset b[0] в файле) → out[]. */
static void hex_print_cells(const uint8_t *b, uint32_t off, uint8_t n, uint8_t *out)
{
    for (uint8_t i = 0; i < n; i++) {
        uint8_t c = b[i];
        uint8_t g;
        if (c < 0x20u) {
            g = '.';
        } else if (c < 0x80u || g_encoding != ENC_UTF8) {
            g = (uint8_t)((c >= 0x80u && g_remap) ? g_remap[c - 0x80u] : c);
        } else if ((c & 0xC0u) == 0x80u) {
            g = '.';                              /* continuation-байт */
        } else {
            /* Лид-байт: собрать кодпойнт (хвост может выйти за ряд → fbo).
             * 4-байтовые (за пределами CP866) и битые последовательности → '.'. */
            uint32_t cp;
            uint8_t  need, ok = 1;
            if      ((c & 0xE0u) == 0xC0u) { cp = (uint32_t)(c & 0x1Fu); need = 1; }
            else if ((c & 0xF0u) == 0xE0u) { cp = (uint32_t)(c & 0x0Fu); need = 2; }
            else { out[i] = '.'; continue; }
            for (uint8_t k = 1; k <= need; k++) {
                uint32_t p = off + i + k;
                if (p >= orig_file_size) { ok = 0; break; }
                uint8_t cb = (uint8_t)((i + k < n) ? b[i + k] : fbo(p));
                if ((cb & 0xC0u) != 0x80u) { ok = 0; break; }
                cp = (cp << 6) | (uint32_t)(cb & 0x3Fu);
            }
            g = ok ? utf_cp_glyph(cp) : '.';
            if (g < 0x20u) g = '.';               /* overlong мог дать управляющий */
        }
        out[i] = g;
    }
}

static uint8_t hex_line[SCREEN_W];   /* собранный ряд (только символы, единый attr) */

static void hex_draw_row(uint32_t off, uint8_t row)
{
    uint8_t buf[HEX_BPR];
    uint8_t n = 0;

    memset(hex_line, ' ', SCREEN_W);
    if (off < orig_file_size) {
        uint32_t rem = orig_file_size - off;
        n = (rem >= HEX_BPR) ? (uint8_t)HEX_BPR : (uint8_t)rem;
        bank_read(orig_file_phys[off >> PAGE_BITS], (uint16_t)(off & PAGE_MASK), buf, n);
    }
    if (n) {                                     /* ряды за EOF — пустые */
        hex_line[HX_OFF]     = '0';
        hex_line[HX_OFF + 1] = 'x';
        hex_line[HX_OFF + 2] = hexdig((uint8_t)(off >> 20));
        hex_line[HX_OFF + 3] = hexdig((uint8_t)(off >> 16));
        hex_line[HX_OFF + 4] = hexdig((uint8_t)(off >> 12));
        hex_line[HX_OFF + 5] = hexdig((uint8_t)(off >> 8));
        hex_line[HX_OFF + 6] = hexdig((uint8_t)(off >> 4));
        hex_line[HX_OFF + 7] = hexdig((uint8_t)off);
        hex_line[HX_SEP1] = TBL_V;
        for (uint8_t i = 0; i < n; i++) {
            uint8_t x = (uint8_t)(HX_DUMP + i * 3u + ((i >= 8u) ? 1u : 0u));
            hex_line[x]     = hexdig((uint8_t)(buf[i] >> 4));
            hex_line[x + 1] = hexdig(buf[i]);
        }
        hex_line[HX_SEP2] = TBL_V;
        hex_print_cells(buf, off, n, hex_line + HX_PRINT);
    }
    bios_set_place(row, 0);
    bios_write_until((const char *)hex_line, SCREEN_W, 0);
}

void hex_draw(void)
{
    uint32_t off = hex_top;
    for (uint8_t r = 0; r < VIEW_H; r++) {
        hex_draw_row(off, (uint8_t)(VIEW_TOP_ROW + r));
        off += HEX_BPR;
    }
}

/* ---- статус-бар: % по байтам (как calc_raw_pct) --------------------- */

static uint8_t calc_hex_pct(void)
{
    return pct16((uint16_t)(hex_top >> 8), (uint16_t)(orig_file_size >> 8));
}

/* ---- навигация (вызывается из главного цикла при g_view == VIEW_HEX) */

uint8_t hex_key(uint8_t scan)
{
    uint32_t mt = hex_max_top();
    switch (scan) {
        /* Одна строка — аппаратный скролл окна + подрисовка одного ряда
         * (как в MD/RAW), без перерисовки всего экрана. */
        case KEY_DOWN:
            if (hex_top < mt) {
                hex_top += HEX_BPR;
                scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 1, 0);   /* содержимое вверх */
                hex_draw_row(hex_top + HEX_SPAN, (uint8_t)(VIEW_TOP_ROW + VIEW_H - 1));
            }
            break;
        case KEY_UP:
            if (hex_top) {
                hex_top -= HEX_BPR;
                scroll(0, VIEW_TOP_ROW, SCREEN_W, VIEW_H, 2, 0);   /* содержимое вниз */
                hex_draw_row(hex_top, VIEW_TOP_ROW);
            }
            break;
        case KEY_PGDN: {
            uint32_t nt = hex_top + (uint32_t)VIEW_H * HEX_BPR;
            if (nt > mt) nt = mt;
            if (nt != hex_top) { hex_top = nt; hex_draw(); }
            break;
        }
        case KEY_PGUP: {
            uint32_t d  = (uint32_t)VIEW_H * HEX_BPR;
            uint32_t nt = (hex_top >= d) ? hex_top - d : 0;
            if (nt != hex_top) { hex_top = nt; hex_draw(); }
            break;
        }
        case KEY_HOME:
            if (hex_top)       { hex_top = 0;  hex_draw(); }
            break;
        case KEY_END:
            if (hex_top != mt) { hex_top = mt; hex_draw(); }
            break;
        default:
            return 0;                            /* не наша клавиша */
    }
    render_percent_progress(calc_hex_pct());
    return 1;
}

#endif /* WITH_HEX */
