#include <stdint.h>
#include "mcurses.h"

#define FIRST_LINE      1
#define LAST_LINE       (LINES - 1)

#define BYTES_PER_ROW   16
#define FIRST_BYTE_COL  7
#define LAST_BYTE_COL   (FIRST_BYTE_COL + 3 * BYTES_PER_ROW)

/*---------------------------------------------------------------------------------------------------------------------------------------------------
 * itox: convert a decimal value 0-15 into hexadecimal digit
 *---------------------------------------------------------------------------------------------------------------------------------------------------
 */
static void
itox (uint8_t val)
{
    uint8_t ch;

    val &= 0x0F;

    if (val <= 9)
    {
        ch = val + '0';
    }
    else
    {
        ch = val - 10 + 'A';
    }
    addch (ch);
}

/*---------------------------------------------------------------------------------------------------------------------------------------------------
 * itoxx: convert a decimal value 0-255 into 2 hexadecimal digits
 *---------------------------------------------------------------------------------------------------------------------------------------------------
 */
static void
itoxx (unsigned char i)
{
    itox (i >> 4);
    itox (i & 0x0F);
}

static void
print_hex_line (uint8_t line, uint8_t * addr, uint16_t off)
{
    uint8_t         col;
    uint8_t         ch;

    move (line, 0);
    itoxx (off >> 8);
    itoxx (off & 0xFF);

    move (line, FIRST_BYTE_COL);
    for (col = 0; col < BYTES_PER_ROW; col++)
    {
        itoxx (*(addr + off));
        addch (' ');
        off++;
    }

    addch (' ');
    addch (' ');

    off -= BYTES_PER_ROW;

    for (col = 0; col < BYTES_PER_ROW; col++)
    {
        ch = (*(addr + off));

        if (ch >= 32 && ch < 0x7F)
        {
            addch (ch);
        }
        else
        {
            addch ('.');
        }
        off++;
    }
}

/*---------------------------------------------------------------------------------------------------------------------------------------------------
 * hexdit: hex editor
 *---------------------------------------------------------------------------------------------------------------------------------------------------
 */
static void
hexedit (uint8_t * addr, uint16_t offset)
{
    uint8_t         ch;
    uint8_t         line;
    uint8_t         col;
    uint8_t         value;
    uint16_t        off;
    uint8_t         byte;

    clear ();
    setscrreg (FIRST_LINE, LAST_LINE);

    off = offset;

    move (0, 0);
    attrset (A_REVERSE);

    for (col = 0; col < FIRST_BYTE_COL; col++)
    {
        addch (' ');
    }

    for (byte = 0; byte < BYTES_PER_ROW; byte++)
    {
        itoxx (byte);
        addch (' ');
    }

    addch (' ');
    addch (' ');

    for (byte = 0; byte < BYTES_PER_ROW; byte++)
    {
        itox (byte);
    }

    attrset (A_NORMAL);

    for (line = FIRST_LINE; line < LAST_LINE; line++)
    {
        print_hex_line (line, addr, off);
        off += BYTES_PER_ROW;
    }

    off = offset;
    line = FIRST_LINE;
    col = FIRST_BYTE_COL;
    byte = 0;

    do
    {
        move (line, col);
        ch = getch ();

        switch (ch)
        {
            case KEY_RIGHT:
            {
                if (byte < BYTES_PER_ROW - 1)
                {
                    byte++;
                    col += 3;
                }
                break;
            }
            case KEY_LEFT:
            {
                if (byte > 0)
                {
                    byte--;
                    col -= 3;
                }
                break;
            }
            case KEY_DOWN:
            {
                if (off < 65520)
                {
                    off += BYTES_PER_ROW;

                    if (line == LAST_LINE - 1)
                    {
                        scroll ();
                        print_hex_line (line, addr, off);
                    }
                    else
                    {
                        line++;
                    }
                }
                break;
            }
            case KEY_UP:
            {
                if (off >= BYTES_PER_ROW)
                {
                    off -= BYTES_PER_ROW;

                    if (line == FIRST_LINE)
                    {
                        insertln ();
                        print_hex_line (line, addr, off);
                    }
                    else
                    {
                        line--;
                    }
                }
                break;
            }
        }
    } while (ch != 'q');
}

int
main ()
{
    uint16_t    offset = 0x2000;    // change here

#ifdef unix // only for test
    static uint8_t  p[65536];
    int             i;

    for (i = 0; i < 65536; i++)
    {
        p[i] = i & 0xFF;
    }

#else // SDCC

    char * p = 0;       // forever 0 on SDCC

#endif

    initscr ();
    hexedit (p, offset);
    endwin ();
    return 0;
}
