#include "debug.h"
#include <SDL2/SDL.h>
#include <cstring>
#include "sm83.h"
#include "sm83dis.h"
#include "mem.h"
#include "APU.h"
//#include "z80analyze.h"
#include "ui.h"
#include "ui_window.h"
#include "gbscreen.h"

#define RESIZING_NONE       0
#define RESIZING_MEMORY     1
#define RESIZING_CONSOLE    2
#define RESIZING_SYMBOLS    3
#define RESIZING_X          4

namespace debug
{
    int midx = 58;
    int mem_y = 20;
    int con_y = 28;
    int sym_y = 24;
    int win_h = 44;
    int mem_h = 8;
    int con_h = 6;
    int sym_h = 14;

    int resizing_type = RESIZING_MEMORY;
    bool resizing = false;

    namespace history
    {
        uint16_t buffer[65536];
        uint8_t top=0;
        uint8_t pos=0;
    }

    uint16_t oAF, oBC, oDE, oHL, oSP, oPC;

    bool mem_modified[65536]; //[CHECK]

    SDL_Window *win = nullptr;
    SDL_Renderer *ren = nullptr;
    SDL_Texture *tex = nullptr;
    SDL_Cursor *cur_df = nullptr;
    SDL_Cursor *cur_ns = nullptr;
    SDL_Cursor *cur_we = nullptr;

    bool is_debugging=false;
    bool is_paused=false;
    uint16_t mem_viewer_pos = 0;

    char console_log[256][256];
    char console_history[256][256];
    uint8_t console_log_pos = 255;
    uint8_t console_history_pos = 0;
    uint8_t console_history_nav = 0;

    uint8_t breakpoints[65536];

    uint16_t cursor = 0;

    uint8_t use[7][256];

    uint16_t line_address[256];
    uint16_t line_breakpoint_address[256];
    int num_breakpoint_lines=0;
    bool sync_mem_with_cursor = false;

    uint8_t search_sequence[32];
    int search_sequence_len=0;
    int search_pos=0;

    uint16_t inspected_value = 255;

    char temp[256];
    const char *tohex(int value, int numdigits)
    {
        if (numdigits==2) sprintf(temp, "%02X", value);
        else if (numdigits==4) sprintf(temp, "%04X", value);
        return temp;
    }
    const char *todec(int value, bool sign)
    {
        if (sign)
            sprintf(temp, "%i", int8_t(value));
        else
            sprintf(temp, "%u", value);
        return temp;
    }

    bool eventHandler(SDL_Event *e)
    {
        if (e->type == SDL_MOUSEMOTION) {
            SDL_ShowCursor(SDL_ENABLE);
        }
        if (debug::debugging()) {
            if (e->type==SDL_WINDOWEVENT) {
                if ((e->window.event==SDL_WINDOWEVENT_SHOWN) || (e->window.event==SDL_WINDOWEVENT_EXPOSED)) {
                    int w; int h;
                    SDL_GetWindowSize(win, &w, &h);
                    midx = (w/CHR_W) - 25;
                    win_h = (h/CHR_H);
                    mem_y = win_h - mem_h - con_h;
                    con_y = win_h - con_h;
                    sym_h = win_h - sym_y;
                    debug::refresh();
                    gbscreen::redraw();
                } else if (e->window.event == SDL_WINDOWEVENT_CLOSE) {
                    hide();
                    gbscreen::focus();
                }
            }
            if (e->type == SDL_MOUSEWHEEL) {
                if (e->wheel.mouseX<midx*CHR_W && e->wheel.mouseY<mem_y*CHR_H) {
                    if (e->wheel.y>0) {
                        debug::cursorback();
                        debug::refresh();
                    } else if (e->wheel.y<0) {
                        debug::cursorfwd();
                        debug::refresh();
                    }
                } else if (e->wheel.mouseX<midx*CHR_W && e->wheel.mouseY>=mem_y*CHR_H && e->wheel.mouseY<con_y*CHR_H) {
                    if (e->wheel.y>0) {
                        mem_viewer_pos-=0x10;
                        debug::refresh();
                    } else if (e->wheel.y<0) {
                        mem_viewer_pos+=0x10;
                        debug::refresh();
                    }
                }
            }
            if (e->type == SDL_KEYDOWN) {
                if (e->key.keysym.scancode==SDL_SCANCODE_ESCAPE) {
                    return false;
                } else if (e->key.keysym.scancode==SDL_SCANCODE_F1) {
                    debug::history::gototop();
                    debug::refresh();
                    //z80analyze::refresh();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_F2) {
                    debug::history::goback();
                    debug::refresh();
                    //z80analyze::refresh();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_F3) {
                    debug::history::goforward();
                    debug::refresh();
                    //z80analyze::refresh();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_F9) {
                    const uint16_t address = cursor;
                    if (breakpoints[line_address[address]]==0)
                        breakpoints[line_address[address]]=1;
                    else
                        breakpoints[line_address[address]]=0;
                    refresh();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_UP) {
                    if (console_history_nav != console_history_pos+1 && console_history[console_history_nav-1][0]!=0) console_history_nav--;
                    //z80debug::cursorback();
                    debug::refresh();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_DOWN) {
                    if (console_history_nav != console_history_pos) console_history_nav++;
                    //z80debug::cursorfwd();
                    debug::refresh();
                } else if ( (e->key.keysym.scancode==SDL_SCANCODE_RETURN) || (e->key.keysym.scancode==SDL_SCANCODE_KP_ENTER)) {
                    debug::executeConsole();
                } else if (e->key.keysym.scancode==SDL_SCANCODE_BACKSPACE) {
                    debug::DeleteCharConsole();
                }
            }
            if (e->type == SDL_MOUSEMOTION) {
                if (!resizing) {
                    if ( (e->motion.y > (mem_y*CHR_H)-8) && (e->motion.y < (mem_y*CHR_H)+8) && ( e->motion.x < midx*CHR_W ) ) {
                        if (resizing_type != RESIZING_MEMORY) SDL_SetCursor(cur_ns);
                        resizing_type = RESIZING_MEMORY;
                    } else if ( (e->motion.y > (sym_y*CHR_H)-8) && (e->motion.y < (sym_y*CHR_H)+8) && ( e->motion.x >= midx*CHR_W ) ) {
                        if (resizing_type != RESIZING_SYMBOLS) SDL_SetCursor(cur_ns);
                        resizing_type = RESIZING_SYMBOLS;
                    } else if ( (e->motion.y > (con_y*CHR_H)-8) && (e->motion.y < (con_y*CHR_H)+8) && (e->motion.x < midx*CHR_W) ) {
                        if (resizing_type != RESIZING_CONSOLE) SDL_SetCursor(cur_ns);
                        resizing_type = RESIZING_CONSOLE;
                    } else {
                        if (resizing_type != RESIZING_NONE) SDL_SetCursor(cur_df);
                        resizing_type = RESIZING_NONE;
                    }

                    const int chrx = e->motion.x/CHR_W;
                    const int chry = e->motion.y/CHR_H;
                    //printf("%ix%i\n", chrx, chry);
                    // Si pasa per damunt de l'adreça en el desensamblador
                    if ( (chrx>1) && (chrx<6) && (chry<mem_y-1) && (chry>0) ) {
                        inspected_value = line_address[chry-1];
                        refresh();
                    // Si pasa per damunt dels bytes que formen el opcode en el desensamblador
                    } else if ((chrx>=19) && (chrx<30) && (chry<mem_y-1) && (chry>0)) {
                        const uint16_t address = line_address[chry-1];
                        const int opcodesize = sm83dis::getOpcodeSize(address);
                        const int byte = (chrx-19)/3;
                        if (byte < opcodesize) {
                            inspected_value = mem::readMem(address+byte);
                            refresh();
                        }
                    // Si pasa per damunt de l'adreça en el visor de memòria
                    } else if ((chrx>1) && (chrx<6) && (chry>mem_y) && (chry<mem_y+mem_h-1)) {
                        inspected_value = mem_viewer_pos + (chry-mem_y-1)*16;
                        refresh();
                    // Si pasa per damunt d'un byte en el visor de memòria
                    } else if ((chrx>6) && (chrx<55) && (chry>mem_y) && (chry<mem_y+mem_h-1)) {
                        const uint16_t address = mem_viewer_pos + (chry-mem_y-1)*16 + (chrx-7)/3;
                        inspected_value = mem::readMem(address);
                        refresh();
                    // Si pasa per damunt d'un registre
                    } else if ((chrx>=midx+4)  && (chrx<midx+8)  && (chry==1)) { inspected_value = sm83::getAF(); refresh();
                    } else if ((chrx>=midx+4)  && (chrx<midx+8)  && (chry==2)) { inspected_value = sm83::getBC(); refresh();
                    } else if ((chrx>=midx+4)  && (chrx<midx+8)  && (chry==3)) { inspected_value = sm83::getDE(); refresh();
                    } else if ((chrx>=midx+4)  && (chrx<midx+8)  && (chry==4)) { inspected_value = sm83::getHL(); refresh();
                    } else if ((chrx>=midx+20) && (chrx<midx+24) && (chry==3)) { inspected_value = sm83::getSP(); refresh();
                    } else if ((chrx>=midx+20) && (chrx<midx+24) && (chry==4)) { inspected_value = sm83::getPC(); refresh();

                    } else if ((chrx>=midx+6)  && (chrx<midx+8)  && (chry==5)) { inspected_value = mem::readMem(sm83::getBC()); refresh();
                    } else if ((chrx>=midx+14) && (chrx<midx+16) && (chry==5)) { inspected_value = mem::readMem(sm83::getDE()); refresh();
                    } else if ((chrx>=midx+22) && (chrx<midx+24) && (chry==5)) { inspected_value = mem::readMem(sm83::getHL()); refresh();

                    }

                } else {
                    if (resizing_type==RESIZING_MEMORY) {
                        const int new_mem_y = e->motion.y/CHR_H;
                        const int new_mem_h = win_h - new_mem_y - con_h;
                        if (new_mem_y>=5 && new_mem_h>=3) {
                            mem_y = new_mem_y;
                            mem_h = new_mem_h;
                            refresh();
                        }
                    } else if (resizing_type==RESIZING_CONSOLE) {
                        const int new_con_y = e->motion.y/CHR_H;
                        const int new_con_h = win_h - new_con_y;
                        const int new_mem_y = win_h - mem_h - new_con_h;
                        if (new_mem_y>=5 && new_con_h>=3) {
                            mem_y = new_mem_y;
                            con_y = new_con_y;
                            con_h = new_con_h;
                            refresh();
                        }
                    } else if (resizing_type==RESIZING_SYMBOLS) {
                        const int new_sym_y = e->motion.y/CHR_H;
                        const int new_sym_h = win_h - new_sym_y;
                        if (new_sym_y>=12 && new_sym_h >=3) {
                            sym_y = new_sym_y;
                            sym_h = new_sym_h;
                            refresh();
                        }
                    }
                }
            }
            if (e->type == SDL_MOUSEBUTTONDOWN) {
                const int chrx = e->button.x/CHR_W;
                const int chry = e->button.y/CHR_H;
                //printf("%ix%i\n", chrx, chry);
                if ( (e->button.x<midx*CHR_W) && (e->button.y<(mem_y-1)*CHR_H) && (e->button.y>CHR_H) ) {
                    const uint16_t address = ((e->button.y)/CHR_H)-1;
                    if (breakpoints[line_address[address]]==0)
                        breakpoints[line_address[address]]=1;
                    else
                        breakpoints[line_address[address]]=0;
                    refresh();
                }
                if (chrx>=midx+12 && chry>=9 && chry<sym_y-1) {
                    const int breakpoint_selected = chry-9;
                    if (num_breakpoint_lines>breakpoint_selected) {
                        cursor = line_breakpoint_address[breakpoint_selected];
                        refresh();
                    }
                }

                if (chrx>=10 && chrx<=12 && chry==mem_y) {
                    sync_mem_with_cursor = ! sync_mem_with_cursor;
                    refresh();
                }

                if (chrx>=midx+2 && chry>=sym_y+1) {
                    const int line = chry-(sym_y+1);
                    if (line<sm83dis::getNumSymbols()) {
                        cursor = sm83dis::getSymbolAddress(line);
                        refresh();
                    }
                }

                if (!resizing && resizing_type != RESIZING_NONE) {
                    resizing = true;
                }
            }
            if (e->type == SDL_MOUSEBUTTONUP) {
                if (resizing) {
                    resizing = false;
                }
            }
            if (e->type == SDL_TEXTINPUT) {
                debug::sendToConsole(e->text.text);
            }
        }
        return true;
    }

    void processCommand();

    void init()
    {
        is_debugging = is_paused = false;
        for (int i=0; i<65536; ++i) breakpoints[i]=0;
        if (!cur_df) cur_df = SDL_GetCursor();
        if (!cur_ns) cur_ns = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
        if (!cur_we) cur_we = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
        
        memchr(console_log, 0, 65536);
        memchr(console_history, 0, 65536);
        //show();        
    }

    void show()
    {
        if (!win) {
            win = SDL_CreateWindow("Z80 Debugger", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 98*CHR_W, 44*CHR_H, SDL_WINDOW_RESIZABLE);
            ren = SDL_CreateRenderer(win, -1, 0);
            ui::window::registerWindow(SDL_GetWindowID(win), eventHandler);
            tex = ui::createtexture(ren);
        }
        focus();
        //z80analyze::refresh();
    }

    void focus()
    {
        if (win) {
            SDL_RaiseWindow(win);
            debug::refresh();
        }
    }

    void hide()
    {
        ui::window::unregisterWindow(SDL_GetWindowID(win));
        if (tex) SDL_DestroyTexture(tex);
        if (ren) SDL_DestroyRenderer(ren);
        if (win) SDL_DestroyWindow(win);
        tex = NULL;
        ren = NULL;
        win = NULL;
    }

    void pause()
    {
        APU::silence();
        is_paused = true;
        breakpoints[sm83::getPC()] &= ~8;
    }

    void stop()
    {
        //history::gototop();
        gbscreen::setTitle(" (stopped)");
        pause();
        is_debugging = true;
        show();
        gbscreen::redraw(true);
    }

    void cont() {
        gbscreen::setTitle("");
        is_debugging = is_paused = false;
        refresh();
        gbscreen::focus();
        APU::resume();
    }
    
    const bool debugging() { return is_debugging; }
    const bool paused() { return is_paused; }


    uint16_t find_previous_opcode(uint16_t pc)
    {
        pc--;
        uint8_t tag = mem::getTag(pc);
        if ( !(tag & (MEMTAG_CODE | MEMTAG_INST) ) ) return pc;

        while ( !(mem::getTag(pc) & MEMTAG_INST) ) pc--;

        return pc;
        
        /*if (sm83dis::getOpcodeSize(pc-3) == 3) return pc-3;
        if (sm83dis::getOpcodeSize(pc-3) == 3) return pc-3;
        if (sm83dis::getOpcodeSize(pc-2) == 2) return pc-2;
        return pc-1;*/

        /*
        pc-=4;
        if (sm83dis::getOpcodeSize(pc) == 4) return pc;
        if (sm83dis::getOpcodeSize(pc) == 3) return pc+3;
        pc++;
        if (sm83dis::getOpcodeSize(pc) == 3) return pc;
        if (sm83dis::getOpcodeSize(pc) == 2) return pc+2;
        pc++;
        if (sm83dis::getOpcodeSize(pc) == 2) return pc;

        return pc+1;
        */
    }

    void printDissasemblerLine(const uint16_t address, const int line, const bool heuristics=false)
    {
        uint8_t colors[4] = { COLOR_RED, COLOR_CYAN, COLOR_WHITE, COLOR_WHITE};
        const uint8_t tag = mem::getTag(address);
        if ( !(tag & (MEMTAG_TINST | MEMTAG_TREPEAT)) ) colors[3]=COLOR_GRAY;
        if ( !(tag & MEMTAG_TOUCHED) ) colors[1]=COLOR_GRAY;

        if (is_debugging && (address == sm83::getPC())) {
            for (int i=0; i<4;++i) colors[i]=COLOR_YELLOW;
            ui::printrect(0,line, midx-2,1, COLOR_BLUE);
        }

        if (breakpoints[address]&9) ui::printtxt(0,line,"*", colors[0]);
        ui::printtxt(1,line,tohex(address,4), colors[1]);

        if ( (tag & MEMTAG_INST) || heuristics ) {
            const char *sym = sm83dis::getSymbol(address);
            if (sym[0]!=0) ui::printtxt(7,line, sym, COLOR_YELLOW);

            const int opcodesize = sm83dis::getOpcodeSize(address);
            for (int i=0; i<opcodesize; ++i) {
                const uint8_t tag = mem::getTag(address+i);
                const uint32_t color = !(tag & MEMTAG_KNOWN) ? COLOR_GRAY : (tag & MEMTAG_DATA) ? ( (tag & (MEMTAG_CODE|MEMTAG_INST)) ? COLOR_MAGENTA : COLOR_BLUE ) : COLOR_GREEN;
                ui::printrect(19+i*3,line,2,1,color);
            }

            ui::printtxt(19,line, sm83dis::getOpcode(address), colors[2]);
            ui::printtxt(31,line, sm83dis::getAsm(address), colors[3]);
        } else {
            ui::printrect(19,line,2,1,COLOR_GRAY);
            ui::printtxt(19,line, tohex(mem::readMem(address),2), COLOR_WHITE);
            ui::printtxt(31,line, "?????????", COLOR_GRAY);
        }
    }

    void refresh()
    {
        ui::setrenderer(ren, tex);
        SDL_SetRenderDrawColor(ren, 30, 30, 30, 255);
        SDL_RenderClear(ren);

        // DISSASSEBLY
        // ******************************************
        ui::setoffset(0, 0);

        ui::box(0,0,midx,mem_y,COLOR_WHITE);
        ui::printrect(2,0, 12,1, COLOR_DARK);
        ui::printtxt(3,0, "ASSEMBLER:", COLOR_WHITE);
        
        ui::setoffset(1, 1);

        uint16_t pc = cursor; //sm83::getPC();
        if (sync_mem_with_cursor) mem_viewer_pos = cursor;
        uint16_t pos = pc;
        int num_lines = mem_y-2;
        line_address[(num_lines/2)-1] = pos;
        ui::printvoidrect(0,(num_lines/2)-1, midx-2,1, COLOR_BLUE);
        printDissasemblerLine(pc, (num_lines/2)-1, true);

        for (int i=num_lines/2;i<num_lines;++i) {
            pos += sm83dis::getOpcodeSize(pos);
            line_address[i] = pos;
            printDissasemblerLine(pos, i, true);
        }

        pos = pc;
        for (int i=(num_lines/2)-2;i>=0;--i) {
            pos = find_previous_opcode(pos);
            line_address[i] = pos;
            printDissasemblerLine(pos, i);
        }

        // REGISTERS
        // ******************************************

        ui::setoffset(0, 0);
        ui::box(midx,0,25,8,COLOR_WHITE);
        ui::printrect(midx+2,0, 12,1, COLOR_DARK);
        ui::printtxt(midx+3,0, "REGISTERS:", COLOR_WHITE);

        ui::setoffset(midx+1, 1);
        ui::printtxt(0,0, "AF      AF'     IX     ", COLOR_WHITE);
        ui::printtxt(0,1, "BC      BC'     IY     ", COLOR_WHITE);
        ui::printtxt(0,2, "DE      DE'     SP     ", COLOR_WHITE);
        ui::printtxt(0,3, "HL      HL'     PC     ", COLOR_WHITE);
        ui::printtxt(0,4, "(BC)    (DE)    (HL)   ", COLOR_WHITE);
        ui::printtxt(0,5, "              I    R   ", COLOR_WHITE);

        ui::printtxt(3,0, tohex(sm83::getAF(), 4), oAF != sm83::getAF() ? COLOR_RED : COLOR_GRAY);
        //ui::printtxt(11,0, tohex(sm83::getAF(true), 4), oAF2 != sm83::getAF(true) ? COLOR_RED : COLOR_GRAY);
        //ui::printtxt(19,0, tohex(sm83::getIX(), 4), oIX != sm83::getIX() ? COLOR_RED : COLOR_GRAY);
        ui::printtxt(3,1, tohex(sm83::getBC(), 4), oBC != sm83::getBC() ? COLOR_RED : COLOR_GRAY);
        //ui::printtxt(11,1, tohex(sm83::getBC(true), 4), oBC2 != sm83::getBC(true) ? COLOR_RED : COLOR_GRAY);
        //ui::printtxt(19,1, tohex(sm83::getIY(), 4), oIY != sm83::getIY() ? COLOR_RED : COLOR_GRAY);
        ui::printtxt(3,2, tohex(sm83::getDE(), 4), oDE != sm83::getDE() ? COLOR_RED : COLOR_GRAY);
        //ui::printtxt(11,2, tohex(sm83::getDE(true), 4), oDE2 != sm83::getDE(true) ? COLOR_RED : COLOR_GRAY);
        ui::printtxt(19,2, tohex(sm83::getSP(), 4), oSP != sm83::getSP() ? COLOR_RED : COLOR_GRAY);
        ui::printtxt(3,3, tohex(sm83::getHL(), 4), oHL != sm83::getHL() ? COLOR_RED : COLOR_GRAY);
        ui::printtxt(19,3, tohex(sm83::getPC(), 4), oPC != sm83::getPC() ? COLOR_RED : COLOR_GRAY);

        ui::printtxt(5,4, tohex(mem::readMem(sm83::getBC()), 2), COLOR_GRAY);
        ui::printtxt(13,4, tohex(mem::readMem(sm83::getDE()), 2), COLOR_GRAY);
        ui::printtxt(21,4, tohex(mem::readMem(sm83::getHL()), 2), COLOR_GRAY);

        const uint8_t flags = (sm83::getAF() & 0xFF);
        const uint8_t mod_flags = flags ^ (oAF & 0xFF);
        ui::printtxt(1,5,"Z", flags&0x80 ? (mod_flags&0x80 ? COLOR_RED : COLOR_WHITE) : (mod_flags&0x80 ? COLOR_BROWN : COLOR_GRAY) );
        ui::printtxt(6,5,"N", flags&0x40 ? (mod_flags&0x40 ? COLOR_RED : COLOR_WHITE) : (mod_flags&0x40 ? COLOR_BROWN : COLOR_GRAY) );
        ui::printtxt(3,5,"H", flags&0x20 ? (mod_flags&0x20 ? COLOR_RED : COLOR_WHITE) : (mod_flags&0x20 ? COLOR_BROWN : COLOR_GRAY) );
        ui::printtxt(7,5,"C", flags&0x10 ? (mod_flags&0x10 ? COLOR_RED : COLOR_WHITE) : (mod_flags&0x10 ? COLOR_BROWN : COLOR_GRAY) );


        // STACK
        // ******************************************
        ui::setoffset(0, 0);
        ui::box(midx,8,11,12,COLOR_WHITE);
        ui::printrect(midx+2,8, 8,1, COLOR_DARK);
        ui::printtxt(midx+3,8, "STACK:", COLOR_WHITE);
        ui::setoffset(midx+1, 9);
        uint16_t sp = sm83::getSP();//-(((sym_y-12)>>1)<<1);
        for (int i=0; i<10; ++i) {
            uint8_t c1=COLOR_CYAN, c2=COLOR_GRAY;
            if (sp == sm83::getSP()) {
                ui::printrect(0,i,9,1,COLOR_BLUE);
                c1 = c2 = COLOR_YELLOW;
            }
            ui::printtxt(0,i, tohex(sp, 4), c1);
            uint16_t value = mem::readMem(sp) + (mem::readMem(sp+1)<<8);
            ui::printtxt(5,i, tohex(value, 4), c2);
            sp+=2;
        }


        // BREAKPOINTS
        // ******************************************

        ui::setoffset(0, 0);
        ui::box(midx+11,8,14,12,COLOR_WHITE);
        ui::printrect(midx+13,8, 9,1, COLOR_DARK);
        ui::printtxt(midx+14,8, "BREAKS:", COLOR_WHITE);
        ui::setoffset(midx+12, 9);

        int line=0;
        num_breakpoint_lines=0;
        for (int i=0; i<65536; ++i) {
            if (breakpoints[i]&7) {
                line_breakpoint_address[line] = i;
                num_breakpoint_lines++;
                uint8_t colors[2] = {COLOR_WHITE, COLOR_GRAY};
                if (i==cursor) {
                    ui::printrect(1,line,10,1,COLOR_BLUE);
                    colors[0]=colors[1]=COLOR_YELLOW;
                }
                ui::printtxt(2,line, tohex(i,4), colors[0]);
                ui::printtxt(7,line, breakpoints[i]&1?"x":"-", colors[1]);
                ui::printtxt(8,line, breakpoints[i]&2?"r":"-", colors[1]);
                ui::printtxt(9,line, breakpoints[i]&4?"w":"-", colors[1]);
                line++;
                if (line>9) break;
            }
        }


        // INSPECTOR
        // ******************************************

        ui::setoffset(0, 0);
        ui::box(midx,20,25,4,COLOR_WHITE);
        ui::printrect(midx+2,20,12,1, COLOR_DARK);
        ui::printtxt(midx+3,20, "INSPECTOR:", COLOR_WHITE);
        ui::setoffset(midx+1, 21);

        ui::printtxt(1,0,"VALUE:", COLOR_GRAY); ui::printtxt(8,0,"$", COLOR_CYAN);
        ui::printtxt(9,0,tohex(inspected_value,4), COLOR_CYAN);
        ui::printtxt(14,0,todec(inspected_value,false), COLOR_WHITE);
        ui::printtxt(20,0,todec(inspected_value,true), COLOR_WHITE);
        ui::printtxt(1,1,"H:", COLOR_GRAY); ui::printtxt(4,1,"$", COLOR_CYAN);
        ui::printtxt(5,1,tohex(inspected_value>>8,2), COLOR_CYAN);
        ui::printtxt(8,1,todec(inspected_value>>8,false), COLOR_WHITE);
        ui::printtxt(12,1,"L:", COLOR_GRAY); ui::printtxt(15,1,"$", COLOR_CYAN);
        ui::printtxt(16,1,tohex(inspected_value&0xff,2), COLOR_CYAN);
        ui::printtxt(19,1,todec(inspected_value&0xff,false), COLOR_WHITE);

        // MEMORY
        // ******************************************

        ui::setoffset(0, 0);
        ui::box(0,mem_y,midx,mem_h,COLOR_WHITE);
        ui::printrect(2,mem_y, 11,1, COLOR_DARK);
        ui::printtxt(3,mem_y, "MEMORY:", COLOR_WHITE);

        ui::printvoidrect(10,mem_y,3,1,COLOR_WHITE);
        if (sync_mem_with_cursor) ui::printchar(11,mem_y,'X',COLOR_WHITE);
        
        ui::setoffset(1, mem_y+1);

        uint16_t mem_viewer_cursor = mem_viewer_pos;
        for (int i=0; i<mem_h-2; ++i) {
            ui::printtxt(1,i, tohex(mem_viewer_cursor, 4), COLOR_CYAN);
            for (int j=0; j<16; ++j) {

                const uint8_t tag = mem::getTag(mem_viewer_cursor);
                const uint32_t color = !(tag & MEMTAG_KNOWN) ? COLOR_GRAY : (tag & MEMTAG_DATA) ? ( (tag & (MEMTAG_CODE|MEMTAG_INST)) ? COLOR_MAGENTA : COLOR_BLUE ) : COLOR_GREEN;
                ui::printrect(6+j*3,i,2,1,color);
                ui::printrect(54+j,i,1,1,color);
                ui::printtxt(6+j*3, i, tohex(mem::readMem(mem_viewer_cursor), 2), ( tag & MEMTAG_MODIFIED) ? COLOR_RED : COLOR_WHITE);
                ui::printchar(54+j, i, mem::readMem(mem_viewer_cursor), ( tag & MEMTAG_MODIFIED) ? COLOR_BROWN : COLOR_GRAY);
                mem_viewer_cursor++;
            }
            //printtxt(5,0, "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", COLOR_WHITE);
            //printtxt(53,0, "0123456789AB\tDEF", COLOR_GRAY);
        }


        // CONSOLE
        // ******************************************

        ui::setoffset(0, 0);
        ui::box(0,con_y,midx,con_h,COLOR_WHITE);
        ui::printrect(2,con_y, 10,1, COLOR_DARK);
        ui::printtxt(3,con_y, "CONSOLE:", COLOR_WHITE);

        ui::setoffset(1, con_y+1);
        line = con_h-3;

        ui::printtxt(0,line, ">", COLOR_WHITE);
        ui::printtxt(1,line, console_history[console_history_nav], COLOR_WHITE);
        ui::printtxt(strlen(console_history[console_history_nav])+1,line, "\x7F", COLOR_WHITE);
        uint8_t i = console_log_pos;
        while (line>0) {
            line--;
            ui::printtxt(1,line, console_log[i--], COLOR_GRAY);
        }


        // SYMBOLS
        // *****************************************

        ui::setoffset(0, 0);
        ui::box(midx,sym_y,25,sym_h,COLOR_WHITE);
        ui::printrect(midx+2,sym_y, 9,1, COLOR_DARK);
        ui::printtxt(midx+3,sym_y, "SYMBOLS:", COLOR_WHITE);

        ui::setoffset(midx+1, sym_y+1);
        const int num_symbols = sm83dis::getNumSymbols();
        for (int i=0;i<num_symbols;++i) {
            uint8_t colors[2] = {COLOR_GRAY, COLOR_WHITE};
            const uint16_t address = sm83dis::getSymbolAddress(i);
            if (address==cursor) {
                ui::printrect(0,line,23,1,COLOR_BLUE);
                colors[0]=colors[1]=COLOR_YELLOW;
            }

            ui::printtxt(1, i, tohex(address,4), colors[0]);
            ui::printtxt(6, i, sm83dis::getSymbol(address), colors[1]);
        }


        if (!is_debugging) {
            SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
            SDL_SetRenderDrawColor(ren, 0, 0, 0, 128);
            SDL_Rect rect {0,0,83*CHR_W, 34*CHR_H};
            SDL_GetWindowSize(win, &rect.w, &rect.h);
            SDL_RenderFillRect(ren, &rect);
        }

        SDL_RenderPresent(ren);

        for (int i=0; i<65536; ++i) mem_modified[i] = false;

        oAF = sm83::getAF();
        oBC = sm83::getBC();
        oDE = sm83::getDE();
        oHL = sm83::getHL();
        oSP = sm83::getSP();
        oPC = sm83::getPC();
    }

    void sendToConsole(const char* text)
    {
        if (console_history_nav != console_history_pos) {
            strcpy(console_history[console_history_pos], console_history[console_history_nav]);
            console_history_nav=console_history_pos;
        }

        if (strlen(console_history[console_history_pos])+strlen(text)<256) strcat(console_history[console_history_pos], text);
        refresh();
    }

    void sendToConsoleLog(const char *text)
    {
        strcpy(console_log[++console_log_pos], text);
    }

    void sendMoreToConsoleLog(const char *text)
    {
        strcat(console_log[console_log_pos], text);
    }

    void DeleteCharConsole()
    {
        if (console_history_nav != console_history_pos) {
            strcpy(console_history[console_history_pos], console_history[console_history_nav]);
            console_history_nav=console_history_pos;
        }

        const int len = strlen(console_history[console_history_pos]);
        console_history[console_history_pos][len-1] = 0;
        refresh();
    }

    void executeConsole()
    {
        if (console_history_nav != console_history_pos) {
            strcpy(console_history[console_history_pos], console_history[console_history_nav]);
            console_history_nav=console_history_pos;
        }
        strcpy(console_log[++console_log_pos], ">");
        strcat(console_log[console_log_pos], console_history[console_history_pos]);
        processCommand();
        console_history_pos++;
        console_history_nav=console_history_pos;
        console_history[console_history_pos][0] = 0;
        refresh();
    }

    const char integer_digits[] = "0123456789";
    const char hex_digits[] = "0123456789ABCDEF";

    const int getnum(const char *str)
    {
        int i=0;
        int return_value = 0;
        if (str[0]=='$') {
            i++;
            while(str[i]!=0) {
                int num = str[i]-48;
                if (num<0) return -1;
                if (num>9) {
                    num-=7;
                    if (num>15) {
                        num-=32;
                        if (num<10 || num>15) return -1;
                    }
                }
                return_value = (return_value<<4)+num;
                i++;
            }
        } else {
            while(str[i]!=0) {
                int num = str[i]-48;
                if (num<0 || num>9) return -1;
                return_value = (return_value*10)+num;
                i++;
            }
        }
        return return_value;
    }

#define getcmd() {while (*console_ptr==32) console_ptr++;while (console_ptr[i]!=0 && console_ptr[i]!=32) { cmd[i]=console_ptr[i]; i++; } cmd[i]=0; console_ptr = &console_ptr[i]; i=0;}

    void processCommand()
    {
        char cmd[256];
        int i=0;
        char *console_ptr = console_history[console_history_pos];

        getcmd();

        if (strcmp(cmd, "s")==0 || strcmp(cmd, "step")==0) {
            uint8_t dt = sm83::step();
            //zx_tape::update(dt);
            //zx_ula::sound_update(dt);
            gbscreen::refresh(dt);
            gbscreen::redraw();
            //z80analyze::refresh();
        } else if (strcmp(cmd, "c")==0 || strcmp(cmd, "cont")==0) {
            sm83::step();
            debug::cont();
            //z80analyze::refresh();
        } else if (strcmp(cmd, "r")==0 || strcmp(cmd, "reset")==0) {
            sm83::reset();
            debug::refresh();
            //z80analyze::refresh();
        } else if (strcmp(cmd, "b")==0 || strcmp(cmd, "break")==0) {
            getcmd();
            if (cmd[0] == 0) { breakpoints[sm83::getPC()]=1; return; }
            if (cmd[0] == 'i') { sm83::setOption(SM83_OPTION_BREAK_ON_INTERRUPT, !sm83::getOption(SM83_OPTION_BREAK_ON_INTERRUPT)); sendToConsoleLog("Break on interrupt."); return; }
            int address = getnum(cmd);
            if (address<0 || address>65536) { sendToConsoleLog("Illegal break address"); return; }
            getcmd();
            uint8_t break_type = 1;
            if (cmd[0]!=0) {
                if (strcmp(cmd, "x")==0 || strcmp(cmd, "exec")==0) {
                    break_type = 1;
                } else if (strcmp(cmd, "r")==0 || strcmp(cmd, "read")==0) {
                    break_type = 2;
                } else if (strcmp(cmd, "w")==0 || strcmp(cmd, "write")==0) {
                    break_type = 4;
                } else {
                    sendToConsoleLog("Illegal break type");
                    return;
                }
            }
            breakpoints[address] |= break_type;
        } else if (strcmp(cmd, "d")==0 || strcmp(cmd, "delete")==0) {
            getcmd();
            if (strcmp(cmd, "all")==0) { for (int i=0;i<65536;++i) breakpoints[i]=0; return; }
            if (cmd[0] == 0) { breakpoints[sm83::getPC()]=0; return; }
            int address = getnum(cmd);
            if (address<0 || address>65536) { sendToConsoleLog("Illegal address"); return; }
            getcmd();
            uint8_t break_type = 7;
            if (cmd[0]!=0) {
                if (strcmp(cmd, "x")==0 || strcmp(cmd, "exec")==0) {
                    break_type = 1;
                } else if (strcmp(cmd, "r")==0 || strcmp(cmd, "read")==0) {
                    break_type = 2;
                } else if (strcmp(cmd, "w")==0 || strcmp(cmd, "write")==0) {
                    break_type = 4;
                } else {
                    sendToConsoleLog("Illegal break type");
                    return;
                }
            }
            breakpoints[address] &= ~break_type;
        } else if (strcmp(cmd, "m")==0 || strcmp(cmd, "mem")==0) {
            getcmd();
            int address = getnum(cmd);
            if (address<0 || address>65536) { sendToConsoleLog("Illegal memory address"); return; }
            mem_viewer_pos = address;
        } else if (strcmp(cmd, "st")==0 || strcmp(cmd, "save")==0) {
            getcmd();
            char filename[256];
            strcpy(filename, cmd);
            savestate(filename);
        } else if (strcmp(cmd, "l")==0 || strcmp(cmd, "load")==0) {
            getcmd();
            char filename[256];
            strcpy(filename, cmd);
            loadstate(filename);
            //z80analyze::refresh();
        } else if (strcmp(cmd, "t")==0 || strcmp(cmd, "tape")==0) {
            getcmd();
            if (strcmp(cmd, "load")==0) {
                getcmd();
                char filename[256];
                strcpy(filename, cmd);
                //zx_tape::load(filename);
            } else if (strcmp(cmd, "play")==0) {
                //zx_tape::play();
            }
        } else if (strcmp(cmd, "poke")==0) {
            getcmd();
            int address = getnum(cmd);
            getcmd();
            int value = getnum(cmd);
            mem::writeMem(address, value);
        } else if (strcmp(cmd, "peek")==0) {
            getcmd();
            int address = getnum(cmd);
            int value = mem::readMem(address);
            char tmp[10];
            sendToConsoleLog(SDL_itoa(value, tmp, 10));
        } else if (strcmp(cmd, "reg")==0) {
            getcmd();
            if (strcmp(cmd, "f")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[0] = value; }
            else if (strcmp(cmd, "a")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[1] = value; }
            else if (strcmp(cmd, "c")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[2] = value; }
            else if (strcmp(cmd, "b")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[3] = value; }
            else if (strcmp(cmd, "e")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[4] = value; }
            else if (strcmp(cmd, "d")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[5] = value; }
            else if (strcmp(cmd, "l")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[6] = value; }
            else if (strcmp(cmd, "h")==0) { getcmd(); int value = getnum(cmd); sm83::getRegs()[7] = value; }
            else { sendToConsoleLog("Syntax error: invalid register"); return; }
        } else if (strcmp(cmd, "g")==0 || strcmp(cmd, "goto")==0) {
            getcmd();
            int address = getnum(cmd);
            if (address<0 || address>=65536) { sendToConsoleLog("Illegal address"); return; }
            if ( !(mem::getTag(address) & MEMTAG_INST) ) address = find_previous_opcode(address);
            debug::setcursor(address);
        } else if (strcmp(cmd, "sym")==0 || strcmp(cmd, "symbol")==0) {
            getcmd();
            if (strcmp(cmd, "add")==0) {
                getcmd();
                char tmp[12]; strcpy(tmp, cmd);
                getcmd();
                if (cmd[0]==0) {
                    sm83dis::setSymbol(sm83::getPC(), tmp);
                } else {
                    int value = getnum(cmd);
                    sm83dis::setSymbol(value, tmp);
                }
                sendToConsoleLog("Symbol added");
            } else if (strcmp(cmd, "del")==0) {
                getcmd();
                if (cmd[0]==0) {
                    sm83dis::setSymbol(sm83::getPC(), NULL);
                } else {
                    int value = getnum(cmd);
                    sm83dis::setSymbol(value, NULL);
                }
                sendToConsoleLog("Symbol removed");
            }
        } else if (strcmp(cmd, "ula")==0) {
            getcmd();
            if (strcmp(cmd, "keydown")==0) {
                getcmd();
                //zx_ula::keydown(cmd[0]);
                sendToConsoleLog("Keydown sent for key: ");
                sendMoreToConsoleLog(cmd);
            } else if (strcmp(cmd, "keyup")==0) {
                getcmd();
                //zx_ula::keyup(cmd[0]);
                sendToConsoleLog("Keyup sent for key: ");
                sendMoreToConsoleLog(cmd);
            }
        } else if (strcmp(cmd, "opcodes")==0) {
            getcmd();
            if (strcmp(cmd, "clear")==0 || strcmp(cmd, "reset")==0) {
                clearUsedOpcodes();
            } else if (strcmp(cmd, "mark")==0) {
                markUsedOpcodes();
            } else if (strcmp(cmd, "count")==0) {
                printf("Num opcodes used: %i\n", getNumOpcodesUsed());
            } else if (strcmp(cmd, "print")==0) {
                printOpcodesUsed();
            }
        } else if (strcmp(cmd, "ignore")==0) {
            getcmd();
            const int address = getnum(cmd);
            mem::setTag(address, mem::getTag(address) | MEMTAG_IGNORE);
        } else if (strcmp(cmd, "search")==0) {
            getcmd();
            if (strcmp(cmd, "next")==0) {
                search();
            } else {
                search(cmd);
            }
        } else if (strcmp(cmd, "show")==0) {
            getcmd();
            if (strcmp(cmd, "analyzer")==0) {
                //z80analyze::show();
            } else {
                sendToConsoleLog("Unrecognized window. Usage: Show [analyzer]");
            }
        } else {
            sendToConsoleLog("Unrecognized command.");
        }
    }  

    const bool isbreak(const uint16_t address, const uint8_t type)
    {
        return (breakpoints[address]&type);
    }

    uint32_t next()
    {
        breakpoints[sm83::getPC()+sm83dis::getOpcodeSize(sm83::getPC())] |= 8;
        const uint32_t t = sm83::step();
        cont();
        return t;
    }

    uint32_t stepout()
    {
        sm83::setOption(SM83_OPTION_BREAK_ON_RET, true);
        const uint32_t t = sm83::step();
        cont();
        return t;
    }

    void setmemmodified(const uint16_t addr)
    {
        mem_modified[addr] = true;
    }

    void loadngo(const char* filename, const char* addr)
    {
        /*
        int address = getnum(addr);
        if (address<0 || address>65536) { sendToConsoleLog("Illegal offset"); return; }

        FILE *f = fopen(filename, "rb");
        fseek(f, 0, SEEK_END);
        int size = ftell(f);
        fseek(f, 0, SEEK_SET);
        uint32_t memsize;
        uint8_t *memory = mem::getRawPointer(&memsize);
        fread(memory+address, size, 1, f);
        fclose(f);
        sm83::setPC(address);
        is_debugging = is_paused = false;
        */
    }

    void savestate(const char *filename)
    {
        uint8_t *regs = sm83::getRegs();
        FILE *f = fopen(filename, "wb");
        fwrite(regs, 31, 1, f);
        mem::saveState(f);
        fclose(f);
    }

    void loadstate(const char *filename)
    {
        uint8_t *regs = sm83::getRegs();
        FILE *f = fopen(filename, "rb");
        fread(regs, 31, 1, f);
        mem::loadState(f);
        fclose(f);
        history::store();
        history::gototop();
    }

    void setcursor(const uint16_t address)
    {
        //if (!debugging()) return;
        if ( !(mem::getTag(address) & MEMTAG_INST) )
            cursor = find_previous_opcode(address);
        else
            cursor = address;
    }

    void cursorfwd()
    {
        cursor += sm83dis::getOpcodeSize(cursor);
    }

    void cursorback()
    {
        cursor = find_previous_opcode(cursor);
    }

    void useOpcode(const uint8_t opcode, const uint8_t base)
    {
        if (use[base][opcode]<255) use[base][opcode]++;
    }

    void clearUsedOpcodes()
    {
        for (int i=0; i<7; ++i) {
            for (int j=0; j<256; ++j) {
                use[i][j]=0;
            }
        }
    }

    void markUsedOpcodes()
    {
        for (int i=0; i<7; ++i) {
            for (int j=0; j<256; ++j) {
                if (use[i][j]==1) use[i][j]++;
            }
        }
    }

    const int getNumOpcodesUsed()
    {
        int count = 0;
        for (int i=0; i<7; ++i) {
            for (int j=0; j<256; ++j) {
                if (use[i][j]==1) count++;
            }
        }
        return count;
    }

    void printOpcodesUsed()
    {
        for (int i=0; i<7; ++i) {
            for (int j=0; j<256; ++j) {
                if (use[i][j]==1) {
                    switch (i) {
                        case 1: printf("ED "); break;
                        case 2: printf("CB "); break;
                        case 3: printf("DD "); break;
                        case 4: printf("DD CB "); break;
                        case 5: printf("FD "); break;
                        case 6: printf("FD CB "); break;
                    }
                    printf("%2X\n",j);
                }
            }
        }
    }

    uint8_t hexchartonum(char chr)
    {
        int num = chr-48;
        if (num<0) return 0;
        if (num>9) {
            num-=7;
            if (num>15) {
                num-=32;
                if (num<10 || num>15) return 0;
            }
        }
        return uint8_t(num);
    }
    void search(const char *seq)
    {
        if (seq) {
            search_pos = 0;
            search_sequence_len=0;
            while (*seq!=0) search_sequence[search_sequence_len++] = (hexchartonum(*(seq++))<<4) + hexchartonum(*(seq++));
        }
        int num_found=0;
        while (search_pos<65536) {
            if (search_sequence[num_found] == mem::readMem(search_pos)) {
                num_found++; search_pos++;
                if (num_found==search_sequence_len) {
                    mem_viewer_pos=search_pos-search_sequence_len;
                    return;
                }
            } else {
                num_found=0; search_pos++;
            }
        }
        sendToConsoleLog("Sequence not found.");
    }


    namespace history
    {
        void store()
        {
            buffer[++top] = sm83::getPC();
            pos=top;
        }

        void gototop()
        {
            pos = top;
            sm83::setPC(buffer[pos]);
            cursor = sm83::getPC();
        }

        void goforward()
        {
            if (pos == top) return;
            sm83::setPC(buffer[++pos]);
            cursor = sm83::getPC();
        }

        void goback()
        {
            if (uint8_t(pos-1) == top) return;
            sm83::setPC(buffer[--pos]);
            cursor = sm83::getPC();
        }

    }

}