/******************************************************************************
*
* project name: TIGCC Tools Suite
* file name:    tthedit.c
* initial date: 29/08/2000
* author:       TenorDave@Hotmail.com
*               thomas.nussbaumer@gmx.net (bug fixes/optimizations/TI92p port)
*               marcos.lopez@wol.es (key input routine/bug fix)
*               xdanger1@caramail.com (graphics speedup by using trick from
*                                      ebook.c, optimizations by using separate
*                                      builds)
*
* description:  oncalc hex editor for variables
*
* $Id: tthedit.c,v 1.13 2002/04/17 10:33:58 tnussb Exp $
*
******************************************************************************/

#define TTHEDIT_VERSION "1.12"

#if defined(USE_TI89) && defined(USE_TI92P)
#error cannot produce both versions at once
#elif defined(USE_TI89)
#define C89_92(x,y) (x)
#elif defined(USE_TI92P)
#define C89_92(x,y) (y)
#else
#error Please specify either -DUSE_TI89 or -DUSE_TI92P on commandline
#endif

#define OPTIMIZE_ROM_CALLS
#define NO_EXIT_SUPPORT
#include <tigcclib.h>

#define fatal(s) {ST_helpMsg(s); return;}


#define NR_ROWS    C89_92(15,20)
#define NR_COLUMNS C89_92(8,12)
#define NR_BYTES   (NR_ROWS * NR_COLUMNS)


unsigned char* charset     = NULL;
void*          backb       = NULL;
short*         charwidth   = NULL;  // will be used to speedup length calculation



/*===========================================================================*/
/* gets input key with additional features (APD timer/idle mode/repeat fix)  */
/*===========================================================================*/
short GetInputKey(void) {
    void           *kbq = kbd_queue();
    unsigned short key;

    while (OSdequeue(&key, kbq)) {
        if (OSTimerExpired(APD_TIMER)) off();
        idle();
    }
    OSTimerRestart(APD_TIMER);

#if defined(USE_TI89)
    if (key == '=') key = 'A';
    if (key == '(') key = 'B';
    if (key == ')') key = 'C';
    if (key == ',') key = 'D';
    if (key == '/') key = 'E';
    if (key == '|') key = 'F';
#endif
    key = toupper(key);

    return key & ~(unsigned short)0x800; // map out key repeat flag
}



/*===========================================================================*/
/* using XOR and a simple sprite for cursor drawing                          */
/*===========================================================================*/
void XOR_Cursor(short x,short y) {
    long addr = ((long)backb)+(y<<5)-(y<<1)+((x>>3)&0xfffe);
    long mask = (long)(0xF8)<<(24-(x&15));

    // unrolled loop for more speed ...
    *(long*)addr^=mask;addr+=30;*(long*)addr^=mask;addr+=30;
    *(long*)addr^=mask;addr+=30;*(long*)addr^=mask;addr+=30;
    *(long*)addr^=mask;addr+=30;*(long*)addr^=mask;addr+=30;
    *(long*)addr^=mask;
}



/*===========================================================================*/
/* setup charset                                                             */
/*===========================================================================*/
static inline void SetupCharSet(void) {
    short i;

    FontSetSys(F_4x6);
    memset(charset,0,256*5);
    PortSet(charset,7,5*256-1);

    // now draw all characters into the buffer so that we can use it later
    // with FastString...
    for (i=0;i<256;i++) {
        charwidth[i] = FontCharWidth(i);
        DrawChar(0,i*5,i,A_REPLACE);
    }
    PortRestore();
}



/*===========================================================================*/
/* draws string using own sprites (about 4 times faster than DrawStr)        */
/*===========================================================================*/
void FastString(short x,short y,const unsigned char* s) {
    long           addr_offset = ((long)backb)+(y<<5)-(y<<1);
    long           addr;
    unsigned short cnt;
    unsigned char* sprite;
    long           mask;

    while (*s) {
        sprite = &charset[(short)(*s)*5];
        addr   = addr_offset+((x>>3)&0xfffe);
        cnt    = 24-(x&15);
        mask = ~((long)(0xf8)<<cnt);

        // unrolled loop for more speed ...
        *(long*)addr&=mask; *(long*)addr|=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr&=mask; *(long*)addr|=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr&=mask; *(long*)addr|=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr&=mask; *(long*)addr|=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr&=mask; *(long*)addr|=(long)(*sprite)<<cnt;

        x+=charwidth[*s++];
    }
}



/*===========================================================================*/
/* draws info bar                                                            */
/*===========================================================================*/
void DrawInfoBar(short locked_flag, unsigned short varsize,short save_flag) {
    const char* savestr;
    char        tempstr[100];

    if (locked_flag)    savestr = "Locked";
    else if (save_flag) savestr = "Yes";
    else                savestr = "No";

    sprintf(tempstr, "Size: %x(%u) Modified:%s",varsize,varsize,savestr);
    FastString(C89_92(130,210),C89_92(94,122),"[X]:Help");
    FastString(1,C89_92(94,122),tempstr);
#if defined(USE_TI89)
asm("movem.l %d0/%a0,-(%sp)\n"
    "movea.l backb,%a0\n"
    "moveq   #6,%d0\n"
    "lea     2790(%a0),%a0\n"
"__loop__:\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "lea     10(%a0),%a0\n"
    "dbf     %d0,__loop__\n"
    "movem.l (%sp)+,%d0/%a0");
#else
asm("movem.l %d0/%a0,-(%sp)\n"
    "movea.l backb,%a0\n"
    "moveq   #6,%d0\n"
    "lea     3630(%a0),%a0\n"
"__loop__:\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.l   (%a0)+\n"
    "not.w   (%a0)+\n"
    "dbf     %d0,__loop__\n"
    "movem.l (%sp)+,%d0/%a0");
#endif
}



/*===========================================================================*/
/* outputs character in the right screen part                                */
/*===========================================================================*/
#define PrintRightSideChar(x,y,c) \
  ({char s[2] = {c,0}; FastString(x,y,s);})



/*===========================================================================*/
/* outputs hexcode in the left screen part                                   */
/*===========================================================================*/
static inline void PrintLeftSideNumber(short x,short y,unsigned c) {
    static const unsigned char hex[] = "0123456789ABCDEF";
    char s[3];
    s[0] = hex[(c >> 4) & 0x0f];
    s[1] = hex[c & 0x0f];
    s[2] = 0;
    FastString(x,y,s);
}


/*===========================================================================*/
/* prints address (format=%04X)                                              */
/*===========================================================================*/
static inline void PrintAddress(short x,short y,unsigned short c) {
    static const unsigned char hex[] = "0123456789ABCDEF";
    char s[5];

    s[3] = hex[c & 0x0f]; c>>=4;
    s[2] = hex[c & 0x0f]; c>>=4;
    s[1] = hex[c & 0x0f]; c>>=4;
    s[0] = hex[c];
    s[4] = 0;
    FastString(x,y,s);
}


/*===========================================================================*/
/* outputs helppage                                                          */
/*===========================================================================*/
static inline void DrawHelpPage(void) {
    static const char* help[] = {
        "TIGCC Tools Hex Editor v "TTHEDIT_VERSION,
        "",
        "by tenordave@hotmail.com",
        "&   thomas.nussbaumer@gmx.net",
        "&   xdanger1@caramail.com",
        "",
        "Arrow Keys: Move Cursor",
        "ESC: Cancel / Quit & Save",
        "F1: Page Down",
        "F2: Page Up",
        "F3: Last Page",
        "F4: First Page",
        "F5: Boss Key *DOES NOT SAVE!*",
        "X: Help Menu (this screen)",
        "0-9 & A-F: Change current number"};

    short i;
    for (i=0;i<15;i++) FastString(0, i*6, help[i]);
}



/*===========================================================================*/
/* main function                                                             */
/*===========================================================================*/
void _main(void) {
    ESI            argptr;
    unsigned char* src;
    char           tmpstr[50];
    char*          fname;
    unsigned char* hdata;
    short          save_flag    = 0;
    short          done         = 0;
    short          updatescreen = 1;
    short          changeflag   = 0;
    short          curx         = 0; // cursor x coordinate
    short          cury         = 0; // cursor y coordinate
    short          curab        = 0; // var for high or low part of byte
    short          cursorx      = 0; // where to draw the cursor in x
    short          cursory      = 0; // where to draw the cursor in y
    unsigned short varplace     = 0; // Where we are in the variable
    SYM_ENTRY*     symptr;
    HANDLE         symptr_handle;
    unsigned short varsize;
    short          key;
    short          i,j;
    void*          scrsave = NULL;
    short          locked_flag;
    short          archived_flag;

    //-----------------------------------------------------
    // get commandline argument (name of variable to edit)
    //-----------------------------------------------------
    InitArgPtr(argptr);

    if (GetArgType(argptr) != STR_TAG) fatal("Usage: tthedit(\"name_of_var\")");

    fname = GetStrnArg(argptr);
    tmpstr[0] = 0;
    sprintf(tmpstr+1,"%s",fname);

    if (!(symptr = DerefSym(SymFind(tmpstr+strlen(fname)+1)))) {
        sprintf(tmpstr,"%s not found",fname);
        fatal(tmpstr); // THIS returns !!
    }

    symptr_handle = symptr->handle;
    locked_flag   = symptr->flags.bits.locked;
    archived_flag = symptr->flags.bits.archived;

    if (!(src=HLock(symptr_handle))) {
        fatal("ERROR: Cannot lock variable"); // THIS returns !!
    }

    if (!(charset=malloc(5*256+2*256+LCD_SIZE+LCD_SIZE))) {
        HeapUnlock(symptr_handle);
        fatal("ERROR: out of memory"); // THIS returns !!
    }

    backb   = (void *)(charset + 5*256 + 2*256);
    scrsave = (void *)(charset + 5*256 + 2*256 + LCD_SIZE);

    // This typecast, even if it looks superfluous, is the way to go !
    // (because of pointer arithmetics)...
    charwidth = (short *)((void *)(charset + 5*256));
    SetupCharSet();

    //------------------------------------------------------
    // get size of variable and generate length string (hex)
    //------------------------------------------------------
    varsize = (src[0]<<8 | src[1]);

    //------------------------------------------------------------------
    // skip "length of variable" bytes and prefix first byte of variable
    //------------------------------------------------------------------
    src+=2;

    //------------------------------------------------------------------
    // generate a local copy of the variable content so we can "undo"
    // all modifications simple
    //------------------------------------------------------------------
    if (!(hdata = malloc(varsize))) {
        free(charset);
        HeapUnlock(symptr_handle);
        fatal("ERROR: out of memory"); // THIS returns !!
    }

    memcpy(hdata, src, varsize);

    //----------------------------------------------------------------------
    // save original screen content and set backbuffer as main drawing plane
    //----------------------------------------------------------------------
    LCD_save(scrsave);
    PortSet(backb, 239, 127);

    //-----------------
    // MAIN LOOP ...
    //-----------------
    while (!done) {
        //-------------------------------------------
        // if necessary -> update the complete screen
        //-------------------------------------------
        if (updatescreen) {
            ClearScreen();
            //-----------------
            // for all rows ...
            //-----------------
            for (i=0;i<NR_ROWS;i++) {
                //draw where we are in the file (hex output)

                if (varplace+i*NR_COLUMNS<varsize) {
                    PrintAddress(0,i*6+1,varplace+i*NR_COLUMNS);
                }

                //---------------------------------------
                // draw all bytes of a row ...
                //---------------------------------------
                for (j=0;j<NR_COLUMNS;j++) {
                    if (varplace+j+i*NR_COLUMNS<varsize) {
                        PrintLeftSideNumber(20+10*j,i*6+1,hdata[varplace+i*NR_COLUMNS+j]);
                        PrintRightSideChar(C89_92(105,145)+j*6,i*6+1,hdata[varplace+i*NR_COLUMNS+j]);
                    }
                }
            }
            //-----------------------------------------------------
            //draw info bar in one fell swoop and reset update flag
            //-----------------------------------------------------
            DrawInfoBar(locked_flag,varsize,save_flag);
            updatescreen = 0;
        }

        cursorx = 20+(curx<<3)+(curx<<1)+(curab<<2);
        cursory = (cury<<2)+(cury<<1);
        XOR_Cursor(cursorx,cursory);
        LCD_restore(backb);

        //keypress phase
        key = GetInputKey();

        if (key==264) {// ESC
            done = 1;
        }
        else if (key=='X') {
            LCD_BUFFER save;
            LCD_save(save);
            ClearScreen();
            //draw_infobar(locked_flag,varsize,save_flag);
            DrawHelpPage();
            LCD_restore(backb);
            GetInputKey();
            memcpy(backb,save,LCD_SIZE);
            LCD_restore(save);
        }

        XOR_Cursor(cursorx,cursory);

        //---------------------------------
        // treatment key for PAGE DOWN (F1)
        //---------------------------------
        if (key==KEY_F1 && varplace+NR_BYTES<varsize) {
            curx=0,cury=0,varplace+=NR_BYTES,updatescreen=1;
        }
        //---------------------------------
        // treatment key for PAGE UP (F2)
        //---------------------------------
        else if (key==KEY_F2) {
            updatescreen=1;
            if(varplace >= (unsigned short)NR_BYTES) varplace-=NR_BYTES;
            else varplace=0;
        }
        //----------------------------------
        // treatment key for FIRST PAGE (F4)
        //----------------------------------
        else if (key==KEY_F4 && varplace!=0) {
            varplace=0,updatescreen=1,curx=0,cury=0,curab=0;
        }
        //----------------------------------
        // treatment key for LAST PAGE (F3)
        //----------------------------------
        else if (key==KEY_F3 && varplace!=varsize-(varsize%NR_BYTES)) {
            varplace=varsize-(varsize%NR_BYTES),updatescreen=1,curx=0,cury=0,curab=0;
        }
        //----------------------------------
        // treatment key for QUICK EXIT (F5)
        //----------------------------------
        else if (key==KEY_F5) {
            done=1,save_flag=0;
        }

        //-------------------------------------
        // keys 0-9 and a-f and A-F (edit keys)
        //-------------------------------------
        else if (((key>='0' && key<='9')  ||
                  (key>='A' && key<='F')) && !locked_flag && !archived_flag)
        {
            unsigned char value;

            if (key >='0' && key<='9') {
                value = key - '0';
            }
            else {
                value = key - 'A' + 10;
            }

            if (curab==0) {
                hdata[varplace+cury*NR_COLUMNS+curx] &=0x0F;
                hdata[varplace+cury*NR_COLUMNS+curx] |= (value <<4);
            }
            else {
                hdata[varplace+cury*NR_COLUMNS+curx] &=0xF0;
                hdata[varplace+cury*NR_COLUMNS+curx] |= (value & 0x0F);
            }
            save_flag=1;
            if (save_flag && !changeflag) changeflag=1,updatescreen=1;

            PrintLeftSideNumber(20+10*curx,cury*6+1,hdata[varplace+cury*NR_COLUMNS+curx]);

            for (i=0;i<NR_COLUMNS && varplace+cury*NR_COLUMNS+i<varsize;i++) {
                PrintRightSideChar(C89_92(105,145)+i*6,cury*6+1,hdata[varplace+cury*NR_COLUMNS+i]);
            }

            //-------------------------------
            // TRICK:
            // simulate a cursor right action
            //-------------------------------
            key = KEY_RIGHT;
        }

        //------------------------------
        // treatment key RIGHT
        //------------------------------
        if (key==KEY_RIGHT) {
            if (curab==0) curab++;
            else if (curx < NR_COLUMNS-1 && varplace+curx+cury*NR_COLUMNS+1<varsize) curx++,curab--;

            //-------------------------------------------------------------
            // TRICK:
            // simulate a cursor DOWN action + set cursor to first position
            //-------------------------------------------------------------
            else if (curx == NR_COLUMNS-1 && varplace+(cury+1)*NR_COLUMNS<varsize) {
                key = KEY_DOWN;
                curx = 0,curab--;
            }
        }
        //------------------------------
        // treatment key LEFT
        //------------------------------
        else if (key==KEY_LEFT) {
            if(curab==1)    curab--;
            else if(curx>0) curx--,curab++;
            else if (curx == 0 && (cury>0 || varplace>0)) {
                key = KEY_UP;
                curx = NR_COLUMNS-1,curab++;
            }
        }

        //------------------------------
        // treatment key UP
        //------------------------------
        if (key==KEY_UP && (cury>0 || varplace>0)) {
            cury--;
            if(cury<0) {
                cury=NR_ROWS-1,updatescreen=1;
                if(varplace>=NR_BYTES) varplace-=NR_BYTES;
                else varplace=0;
            }
        }
        //------------------------------
        // treatment key DOWN
        //------------------------------
        else if(key==KEY_DOWN && varplace+(cury+1)*NR_COLUMNS<varsize) {
            if (varplace+curx+(cury+1)*NR_COLUMNS<varsize) cury++;
            if (cury>NR_ROWS-1) {
                cury=0;
                curx=0;
                varplace+=NR_BYTES;
                updatescreen=1;
            }
        }

    } //end main while loop

    PortRestore();

    //---------------------------------------------------------------
    // if anything changed query user if he wants to save the changes
    //---------------------------------------------------------------
    if (save_flag) {
        if (DlgMessage("Variable Data Changed!", "Save Changes?", BT_CANCEL, BT_OK)==13)
            memcpy(src, hdata, varsize);
    }

    //-------------------------------------------------------------------
    // restore original screen, free allocated memory and unlock variable
    //-------------------------------------------------------------------
    LCD_restore(scrsave);
    free(hdata);
    free(charset);
    HeapUnlock(symptr_handle);
}

//#############################################################################
//###################### NO MORE FAKES BEYOND THIS LINE #######################
//#############################################################################
//
//=============================================================================
// Revision History
//=============================================================================
//
// $Log: tthedit.c,v $
// Revision 1.13  2002/04/17 10:33:58  tnussb
// (1) some comments removed
// (2) key values replaced by TIGCCLIB defines
//
// Revision 1.12  2002/03/13 15:46:02  tnussb
// typos fixed and version number raised
//
// Revision 1.11  2002/03/13 15:13:00  tnussb
// uses now own function PrintAddress() instead of redefining macro printf_xy.
// PrintAddress() should be much faster as the previous use of sprintf().
//
// Revision 1.10  2002/02/28 15:33:03  tnussb
// (1) bug in drawing routine fixed (missing mask-out of old data)
// (2) XOR_Cursor() completely rewritten
// (3) major speedup of page drawing by re-implementing
//     PrintLeftSideNumber and PrintRightSideChar() (not using
//     printf_xy() anymore).
// (4) Many minor codesize optimizations
//
// Revision 1.9  2002/02/18 09:25:52  tnussb
// (1) made sprite data global, it's smaller [xdanger]
// (2) PrintRightSideChar() and PrintLeftSideNumber() replaced by macros [tnussb]
// (3) some of the exit points in main fixed (missing cleanup added) [tnussb]
//
// Revision 1.8  2002/02/13 19:47:34  tnussb
// (1) Small bugfix (bug occured only on 92+) - Lionel Debroux
// (2) Size and speed optimizations (thanks to PsiGoo for giving me the idea of
//     using Sprite8_XOR and not ScrRectFill) - Lionel Debroux
// (3) Speed optimizations by inline sprite drawing into routines (no separate
//     sprite drawing routines anymore) - Thomas Nussbaumer
//
// Revision 1.7  2002/02/11 12:57:36  tnussb
// bugfixes by Lionel Debroux
//
// Revision 1.6  2002/02/07 10:04:13  tnussb
// many changes done by XDanger
//
// Revision 1.5  2001/07/31 22:56:59  Thomas Nussbaumer
// generic commit for Tools Suite version 0.99.10
//
// Revision 1.4  2001/01/06 10:32:11  Thomas Nussbaumer
// (1) changes necessary for new TIGCC Library 2.31 (int<->short)
// (2) problems stated by -Wall fixed
//
// Revision 1.3  2000/11/28 00:14:03  Thomas Nussbaumer
// (1) bug in edit treatment fixed
// (2) cursor wrap around added
//
// Revision 1.2  2000/10/01 14:55:05  Thomas Nussbaumer
// version number fixed
//
// Revision 1.1  2000/10/01 14:51:46  Thomas Nussbaumer
// initial version
//
//
