/******************************************************************************
*
* project name:    TIGCC Tools Suite
* file name:       ebook.c
* initial date:    19/08/2000
* author:          thomas.nussbaumer@gmx.net
* description:     a generic ebook reader which uses TTArchives with special
*                  extension "ebk" as ebooks
*
* NOTE: the first entry of an ebook must be a 2-plane 160x100 pixels title
*       image with name "title" in compressed state. Otherwise the ebook will
*       be rejected.
*       The rest of the ebook content must be compressed textfiles which were
*       previously processed with ttdos2ebk or any similar tool to remove the
*       line breaks within paragraphs.
*
*
* $Id: ebook.c,v 1.92 2002/05/23 06:29:46 tnussb Exp $
*
******************************************************************************/

#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 NO_EXIT_SUPPORT      /* doesn't need exit support */
#define OPTIMIZE_ROM_CALLS
#include <tigcclib.h>



//#define TIME_MEASUREMENT

//-----------------------------------------------------------------------------
// necessary stuff for unpacking
//-----------------------------------------------------------------------------
#include "ttunpack.h"
#include "ttarchive.h"


//-----------------------------------------------------------------------------
// title of reader and date of build
//-----------------------------------------------------------------------------
#define READER_TITLE   "TICT eBook-Reader v1.92"
#define READER_DATE    "22/05/2002"


//-----------------------------------------------------------------------------
// top and bottom line of menu
//-----------------------------------------------------------------------------
#define MENU_TOP       READER_TITLE" "READER_DATE
#define MENU_BOTTOM    "\x15&\x16=select  enter=choose  esc=exit"


//-----------------------------------------------------------------------------
// folder definitions
//-----------------------------------------------------------------------------
#define BOOKS_FOLDER   ($(tictebks))
#define MARKS_FOLDER   ($(tictmark))
#define MARKS_FOLDER2  "tictmark"


//-----------------------------------------------------------------------------
// how much pages are allowed per part
//-----------------------------------------------------------------------------
#define MAX_PAGES   1000


//-----------------------------------------------------------------------------
// HERE COMES THE CALCULATOR DEPENDED DEFINES
//-----------------------------------------------------------------------------
#define WIDTH_IN_PIXELS   C89_92(160,240)
#define HEIGHT_IN_PIXELS  C89_92(100,128)
#define K_LEFT            C89_92(338,337)
#define K_RIGHT           C89_92(344,340)
#define K_UP              C89_92(337,338)
#define K_DOWN            C89_92(340,344)
#define K_OFF2            C89_92(16651,8459)


//-----------------------------------------------------------------------------
// some useful key definitions
//-----------------------------------------------------------------------------
#define K_ESCAPE   264
#define K_ENTER     13
#define K_F1     0x10c
#define K_F5     0x110
#define K_OFF     4363


//-----------------------------------------------------------------------------
// precalculated extension of a page
//-----------------------------------------------------------------------------
#define LINES_PER_PAGE  ((HEIGHT_IN_PIXELS-6)/7)


//-----------------------------------------------------------------------------
// type definition of the bookmarks structure
//
// NOTE: the part of the latest saved bookmark gets the high bit set
//-----------------------------------------------------------------------------
#define NR_BOOKMARKS 10

typedef struct {
    unsigned short  part[NR_BOOKMARKS];   // parts are numbered starting at 1
    unsigned short  page[NR_BOOKMARKS];   // pages are numbered starting at 1
} BookMarks;


#define BOOKMARKS_MASK_LATEST   0x8000
#define IS_LATEST_MARK(m)       ((m) & BOOKMARKS_MASK_LATEST)
#define REAL_MARK_VALUE(m)      ((m) & ~(BOOKMARKS_MASK_LATEST))


//-----------------------------------------------------------------------------
// globals
//-----------------------------------------------------------------------------
short          exit_completely = 0;  // flag which indicates a F5 press
unsigned char  active_book[9]  = {}; // name of active book
BookMarks*     active_marks    = 0;  // pointer to active bookmarks structure
short*         charwidth       = 0;  // will be used to speedup length calculation
unsigned char* screenbuffer    = 0;  // should be set to the screenbuffer to use
unsigned char* doublebuffer    = 0;  // allocated memory for double buffering
short          nr_parts        = 0;  // number of parts of a book
short          act_part        = 0;  // actual part of a book
short          load_part       = -1; // info about which part and
short          load_page       = -1; // which page we are going to load
unsigned char* charset         = NULL;  // buffer for "grabbed" 4x6 font
INT_HANDLER    old_inthandler  = NULL;
volatile short restore_status  = 0;



//=============================================================================
// interrupt handler which restores statusline if wanted
//=============================================================================
DEFINE_INT_HANDLER (RestoreStatusHandler) {
    if (restore_status) {
        memcpy(LCD_MEM+(HEIGHT_IN_PIXELS-7)*30,doublebuffer+(HEIGHT_IN_PIXELS-7)*30,7*30);
    }
    ExecuteHandler (old_inthandler);
}



//=============================================================================
// 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
    for (i=0;i<256;i++) {
        charwidth[i] = FontCharWidth(i);
        DrawChar(0,i*5,i,A_REPLACE);
    }
    PortRestore();
    FontSetSys(F_6x8); // used in the rest of the program
}



//=============================================================================
// fills a specified number of lines with given byte value (NOTE: on the TI89
// it will fill more than what is visible on the screen, but we don't care)
//=============================================================================
#define FillFullLines(y,nr,c)    memset(screenbuffer+(y)*30,c,30*(nr))



//=============================================================================
// draws string using own sprites (about 4 times faster than DrawStrXY)
//=============================================================================
void FastStringXY(short x,short y,const unsigned char* s) {
    unsigned char* sprite;
    long           addr;
    unsigned short cnt;
    short          ytemp;

    while (*s) {
        sprite = &charset[(short)(*s)*5];
        ytemp  = y;
        if ((*s) == 0x67) ytemp++;  // move letter 'g' one pixel down

        addr = ((long)screenbuffer)+(ytemp<<5)-(ytemp<<1)+((x>>3)&0x1e);
        cnt  = 24-(x&15);

        // unrolled loop for more speed ....
        // NOTE: We are using XOR here, so this function can also be used
        // for the "white on black" strings
        *(long*)addr^=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr^=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr^=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr^=(long)(*sprite++)<<cnt;addr+=30;
        *(long*)addr^=(long)(*sprite)<<cnt;

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



//=============================================================================
// calculates x position that given string is centered
//=============================================================================
short CalcCenterPosition(const char* s,short f) {
    return (WIDTH_IN_PIXELS-DrawStrWidth(s,f))>>1;
}



//=============================================================================
// draws the titlebar containing the given String
//=============================================================================
void DrawTitleBar(const char* s) {
    FillFullLines(0,7,0xff);
    FastStringXY(CalcCenterPosition(s,F_4x6),1,s);
}



//=============================================================================
// returns decompressed entry from TTArchive
//
// returns 0 or pointer to allocated memory
// the allocated size is stored in parameter *size
//
// No checking if archive is valid or index exists is done within this function
//=============================================================================
short out_of_mem = 0;
unsigned char* GetDecompressedEntry(unsigned char*  archive,
                                    unsigned short  index,
                                    unsigned short* size)
{
    unsigned char* start;
    unsigned char* buf;

    out_of_mem = 0;
    //----------------------------------------------------------
    // suppose that we will come here with only valid indices !!
    // so no checks anymore
    //----------------------------------------------------------
    start = ttarchive_data(archive,index);

    if (!ttunpack_valid(start)) return 0;

    *size = ttunpack_size(start);

    if (!(buf = malloc(*size))) {
        out_of_mem = 1;
        return 0;
    }

    if (ttunpack_decompress(start,buf)) {
        free(buf);
        return 0;
    }

    return buf;
}



//=============================================================================
// try to find corresponding bookmark file in bookmarks folder
//=============================================================================
SYM_ENTRY* FindBookMarks(unsigned char* bookname) {
    char       buf[100];
    char*      bp = buf;

    *bp++ = 0;
    sprintf(bp,"%s\\%s",MARKS_FOLDER2,bookname);

    return SymFindPtr(bp+strlen(bp),0);
}



//=============================================================================
// load bookmarks
//
// if the part of one bookmark has its hibit set it will
// be copied to variables load_part/load_page
//
// otherwise load_part/load_page are set to 1
// (load automatically part1/page1)
//=============================================================================
static inline void LoadBookMarks(void) {
    short          i;
    unsigned char* src;
    SYM_ENTRY*     entry = FindBookMarks(active_book);

    load_part = 1;
    load_page = 1;

    memset(active_marks,0,sizeof(BookMarks));

    if (!entry) return;
    if (!(src = HLock(entry->handle))) return;
    src+=2;
    memcpy(active_marks,src,sizeof(BookMarks));
    HeapUnlock(entry->handle);

    for (i=0;i<NR_BOOKMARKS;i++) {
        if (IS_LATEST_MARK(active_marks->part[i])) {
            load_part = REAL_MARK_VALUE(active_marks->part[i]);
            load_page = active_marks->page[i];
            if (load_part > nr_parts || !load_page) {
                load_part = 1;
                load_page = 1;
            }
            break;
        }
    }
}



//=============================================================================
// save bookmarks (the return value is not queried yet)
//=============================================================================
short SaveBookMarks(void) {
    SYM_ENTRY*     symptr = FindBookMarks(active_book);
    unsigned char* src;
    unsigned char  buffer[100];
    unsigned char* bp = buffer;
    HANDLE         h;

    // if there is already a bookmark file re-use it
    if (symptr) {
        if ( symptr->flags.bits.archived ||
             symptr->flags.bits.locked   ||
             !(src = HLock(symptr->handle)) ) return 0;
        src+=2;
        memcpy(src,active_marks,sizeof(BookMarks));
        HeapUnlock(symptr->handle);
        return 1;
    }

    *bp++ = 0;
    sprintf(bp,"%s\\%s",MARKS_FOLDER2,active_book);

    FolderAdd(MARKS_FOLDER);  // always try to generate the folder so that
                              // there will no dialog pop up

    if (!(h = HeapAlloc(sizeof(BookMarks)+8))) {
        SymDel(bp+strlen(bp));
        return 0;
    }

    if (!(symptr = DerefSym(SymAdd(bp+strlen(bp))))) {
        HeapFree(h);
        return 0;
    }

    symptr->handle = h;

    // get bookmarks file
    if (!(src = HLock(h))) {
        SymDel(bp+strlen(bp));
        return 0;
    }

    // calculations evaluated during compile time.
    // No optimizations necessary!!
    *src++ = (sizeof(BookMarks)+6) / 256;
    *src++ = (sizeof(BookMarks)+6) % 256;

    memcpy(src,active_marks,sizeof(BookMarks));
    src+=sizeof(BookMarks);
    *src++ = 0;
    *src++ = 'c';
    *src++ = 'f';
    *src++ = 'g';
    *src++ = 0;
    *src++ = OTH_TAG;
    HeapUnlock(h);
    return 1;
}



//=============================================================================
// get input key pressed by user
//
// replacement for ngetchx() which handle APD and OFF key combinations
//=============================================================================
short GetUserInput(void) {
    void *kbq = kbd_queue();
    unsigned short key;

    while (1) {
        OSTimerRestart(APD_TIMER);
        //---------------------------------------
        // wait until we get a key
        // if the APD timer expires -> turn
        // calculator off
        //---------------------------------------
        while (OSdequeue(&key, kbq)) {
            if (!IsGrayMode()) idle();  // only idle when not in grayscale mode (title pic)

            if (OSTimerExpired(APD_TIMER)) {
                off();
                OSTimerRestart(APD_TIMER);
            }
        }

        //---------------------------------------
        // handle calculator-off key combinations
        // if the APD timer expires -> turn
        // calculator off
        //---------------------------------------
        if (key == K_OFF || key == K_OFF2) {
            off();
        }
        //-----------------------------------
        // special treatment of F5
        //-----------------------------------
        else if (key == K_F5) {
            exit_completely = 1;
            return K_ESCAPE;
        }
        return key & 0xf7ff;
    }
}



//=============================================================================
// handles the book mark dialog
//=============================================================================
static inline short BookMarkDialog(void) {
    short      sy     = HEIGHT_IN_PIXELS/2 - 2;
    SCR_RECT   rect   = {{4,sy-10,WIDTH_IN_PIXELS-5,sy+10}};
    short      read;
    LCD_BUFFER b;

    LCD_save(b);

    ScrRectFill(&rect,ScrRect,A_REVERSE);

    //---------------------------------
    // the following rect.l operations
    // perform the following separate
    // tasks, but be shorter in length
    //---------------------------------
    //rect.xy.x0--;
    //rect.xy.y0--;
    //rect.xy.x1++;
    //rect.xy.y1++;
    rect.l -= 0x01010000;
    rect.l += 0x00000101;
    DrawClipRect(ScrToWin(&rect),ScrRect,A_NORMAL);

    DrawStrXY(C89_92(5,45),sy-3,"Press 0-9 to set Bookmark",A_NORMAL);

    do {
        read = GetUserInput();
        if (read == 'X' || read == 'x') read = '0';
        if ((read >= '0' && read <= '9') || read == K_ESCAPE) break;
    }
    while (1);

    LCD_restore(b);
    if (read == K_ESCAPE) return -1;
    return (read - '0');
}



//=============================================================================
// Displays title of eBook
//
// !! index 0 of archive must be a 2-planes 160x100 image and name title
//
// returns 0 in case of error or keycode
//=============================================================================
static inline short ShowTitle(unsigned char* archive) {
    TTARCHIVE_ENTRY* tmp = ttarchive_desc(archive,0);
    unsigned char*   buf;
    unsigned short   size;
    short            ret;
    short            i;
    unsigned char*   dest1;
    unsigned char*   dest2;
    unsigned char*   src1;
    unsigned char*   src2;

    if (tmp == 0) return 0;

    //----------------------------
    // simple check for name title
    //----------------------------
    if (strncmp("title",tmp->name,8)) return 0;

    if (!(buf = GetDecompressedEntry(archive,0,&size))) return 0;
    if (size != 4000) {
        free(buf);
        return 0;
    }


    if (!GrayOn()) {
        free(buf);
        out_of_mem = 1;
        return 0;
    }

    dest1 = GetPlane(0);
    dest2 = GetPlane(1);
    src1  = buf;
    src2  = buf+2000;

    memset(dest1,0,LCD_SIZE);
    memset(dest2,0,LCD_SIZE);

#if defined(USE_TI92P)
    dest1 += 30*14 + 5;
    dest2 += 30*14 + 5;
#endif

    for (i=0;i<100;i++) {
        memcpy(dest1,src1,20);
        memcpy(dest2,src2,20);
        dest1 += 30;
        dest2 += 30;
        src1  += 20;
        src2  += 20;
    }

    ret = GetUserInput();

    free(buf);
    GrayOff();

    return ret;
}



//=============================================================================
// fetches next line from inputbuffer into outputbuffer with respect to the
// maximum available bytes of the inputbuffer (max_avail)
//
// returns number of bytes processed from inputbuffer
//
// NOTE: (1) spaces at the end of a line are skipped
//       (2) if a word doesn't end at the end of a line and there was
//           a space previously detected, this not fitting word will be
//           left for the next line. This will make reading the document
//           much easier, because words are not splitted between lines !!!
//       (3) due to the fact that not all characters have the same
//           size this routine may return more than WIDTH_IN_PIXELS/4 characters,
//           because it tries to pack as much characters as possible into
//           one line. So no space is wasted.
//=============================================================================
unsigned short NextLine(unsigned char* inputbuffer,
                        unsigned char* outputbuffer,
                        unsigned short max_avail)
{
    unsigned short processed             = 0;
    unsigned char* input_orig            = inputbuffer;
    short          line_length           = 0;
    unsigned short last_space_processed  = 0;
    unsigned char* last_space_pos        = 0; // just to make compiler happy
    unsigned char  actchar;

    do {
        actchar = *inputbuffer++;

        if (actchar == 0x0a) {   // linebreak found - close output string
            *outputbuffer++ = 0; // and "break" out of looping ...
            processed++;
            break;
        }

        // if we'll find a space remember its position and how
        // much bytes we have processed so far ...
        if (actchar == ' ') {
            last_space_pos       = outputbuffer;
            last_space_processed = processed;
        }

        *outputbuffer++ = actchar;
        processed++;

        line_length += charwidth[*(outputbuffer-1)];
    }
    while (processed != max_avail && line_length < (WIDTH_IN_PIXELS-5));

    //-----------------------------------------------------------------
    // if we have the end of the part reached do no further processing,
    // but return immediately
    //-----------------------------------------------------------------
    if (processed != max_avail) {
        //----------------------------------------------------------------
        // if we are located within a word, go back to last known space if
        // there is one ...
        //----------------------------------------------------------------
        if (last_space_processed) {
            if (actchar      != ' '  && // last processed byte was no space
                actchar      != 0x0a && // last processed byte was no linebreak
                *inputbuffer != ' ')    // NEXT byte is NO space
            {
                processed    = last_space_processed;
                outputbuffer = last_space_pos;
            }
        }

        //------------------------------------------
        // skip trailing spaces if there are any ...
        //------------------------------------------
        while (processed != max_avail && *(input_orig+processed) == ' ') processed++;
    }

    *outputbuffer = 0; // "close" string
    return processed;
}



//=============================================================================
// Displays help page
//=============================================================================
static inline void ShowHelp(void) {
    LCD_BUFFER         b;
    short              i;
    static const char* help[9] = {
        "[DOWN],[+]   next page",
        "[UP],[-]         previous page",
        "[LEFT]           first page",
        "[RIGHT]         last page",
        "[ESC]             back to selection menu",
        "[F1]               this page",
        "[F5]               boss key (quick exit)",
        "[0]-[9]          goto bookmark",
        "[x]                 set bookmark"};

    LCD_save(b);
    ClearScreen();
    DrawTitleBar("TICT eBook Reader - Control Keys");

    for (i=0;i<9;i++) FastStringXY(0,10+(i<<3),help[i]);

    GetUserInput();
    LCD_restore(b);
}



//=============================================================================
// handles display of a complete part
//=============================================================================
void DisplayPart(unsigned char* data, unsigned short length) {
    unsigned short offset = 0;
    unsigned short pages  = 0;
    short          line;
    char           tmp_str[100];
    short          input;
    unsigned short actpage;
    unsigned short pageoffset[MAX_PAGES]; // dirty hack - static offset table
    short          validinput;


#if defined(TIME_MEASUREMENT)
    //-----------------------------------------------
    // start timer for clocks and enter the main loop
    //-----------------------------------------------
    OSFreeTimer(USER_TIMER);
    OSRegisterTimer(USER_TIMER,1000);
    OSTimerRestart(USER_TIMER);
#endif

    //-------------------------------------------------------
    // first parse number of pages and setup pageoffset table
    // so we can use page-up ...
    //-------------------------------------------------------
    while (offset < length) {
        pageoffset[pages++] = offset;
        for (line=0;line<LINES_PER_PAGE;line++) {
             offset += NextLine(data+offset,tmp_str,length-offset);
             if (offset >= length) break;
        }
        if (pages >= MAX_PAGES) return;
    }

#if defined(TIME_MEASUREMENT)
    {
       unsigned long measure_val = OSTimerCurVal(USER_TIMER);
       //-----------------------------------------------
       // stop timer again, turn grayscales off, restore
       // old interrupt handlers again and free memory
       //-----------------------------------------------
       OSFreeTimer(USER_TIMER);
       sprintf(tmp_str,"%lu tics",(1000-measure_val)*50);
       DrawStr(10,10,tmp_str,A_REPLACE);
       ngetchx();
    }
#endif

    if (load_page != -1) {
        if (!load_page) actpage  = pages;
        else            actpage  = load_page;

        if (actpage > pages) actpage = pages;

        offset    = pageoffset[actpage-1];
        load_part = -1;
        load_page = -1;
    }
    else {
        actpage = 1;
        offset  = 0;
    }

    do {
        if (offset < length) {
            restore_status = 0;
            screenbuffer = doublebuffer;
            memset(doublebuffer,0,LCD_SIZE);

            FillFullLines(0,7,0xff);
            sprintf(tmp_str,"Part %u: page %u of %u",act_part,actpage,pages);

            FastStringXY(0,1,tmp_str);
            FastStringXY(WIDTH_IN_PIXELS-45,1,"[F1] for help");

            //-------------------------------------------
            // draw complete page
            //-------------------------------------------
            for (line=1;line<LINES_PER_PAGE+1;line++) {
                offset += NextLine(data+offset,tmp_str,length-offset);
                FastStringXY(0,2+line*7,tmp_str);
                if (offset >= length) break;
            }
            screenbuffer = LCD_MEM;
            memcpy(screenbuffer,doublebuffer,LCD_SIZE);
            restore_status = 1;
        }

        validinput = 0;

        do {
            input = GetUserInput();

            //--------------------------------------------------
            // NOTE: the if/else chaining takes less space
            //       than the corresponding switch statement !!!
            //--------------------------------------------------
            if (input == K_UP || input == '-') {
                if (actpage > 1) {
                    offset = pageoffset[actpage-2];
                    actpage--;
                    validinput = 1;
                }
                else {
                    if (act_part > 1) {
                        load_part = act_part - 1;
                        load_page = 0;  // indicates last page !!!!
                        input = K_ESCAPE;
                    }
                }
            }
            else if (input == K_DOWN || input == '+') {
                if (actpage < pages) {
                    actpage++;
                    validinput = 1;
                }
                else {
                    if (act_part < nr_parts) {
                        load_part = act_part + 1;
                        load_page = 1;
                        input     = K_ESCAPE;
                    }
                }
            }
            else if (input == K_LEFT) {
                if (actpage != 1) {
                    offset     = 0;
                    actpage    = 1;
                    validinput = 1;
                }
            }
            else if (input == K_RIGHT) {
                if (actpage != pages) {
                    offset     = pageoffset[pages-1];
                    actpage    = pages;
                    validinput = 1;
                }
            }
            else if (input == K_F1) {
                restore_status = 0;
                ShowHelp();
                restore_status = 1;
            }
            else if (input == 'x' || input == 'X') {
                // for the BookmarkDialog it is not necessary to
                // turn off status line restoring, because the
                // bookmark dialog will not "overdraw" the status
                // line area
                short ret = BookMarkDialog();
                short i;

                if (ret != -1) {
                    for (i=0;i<NR_BOOKMARKS;i++) active_marks->part[i] = REAL_MARK_VALUE(active_marks->part[i]);
                    active_marks->page[ret] = actpage;
                    active_marks->part[ret] = act_part | BOOKMARKS_MASK_LATEST;
                    SaveBookMarks();
                }
            }
            else if (isdigit(input)) {
                short num      = input - '0';
                short tmp_part = REAL_MARK_VALUE(active_marks->part[num]);

                // just do it when mark is okay ....
                if (active_marks->page[num]) {
                    if (tmp_part == act_part) {
                        actpage = active_marks->page[num];
                        if (actpage > pages) actpage = pages;
                        offset = pageoffset[actpage-1];
                        validinput = 1;
                    }
                    else {
                        if (tmp_part && tmp_part <= nr_parts) {
                            load_part = tmp_part;
                            load_page = active_marks->page[num];
                        }
                        input = K_ESCAPE;
                    }
                }
            }
        }
        while (!validinput && input != K_ESCAPE);
    }
    while (input != K_ESCAPE);
    restore_status = 0;
}



//=============================================================================
// handles part selection menu
//
// returns part index to view or -1 for escape
//=============================================================================
static inline short HandlePartSelection(void) {
    char  s[100];
    short input;

    ClearScreen();
    DrawTitleBar(MENU_TOP);
    FillFullLines(HEIGHT_IN_PIXELS - 7,7,0xff);
    FastStringXY(C89_92(19,59),HEIGHT_IN_PIXELS-6,MENU_BOTTOM);

    memcpy(doublebuffer,LCD_MEM,LCD_SIZE);
    restore_status = 1;
    do {
        if (load_part != -1) {
            act_part = load_part;
            break;
        }
        sprintf(s,"Part %d of %d",act_part,nr_parts);
        FillFullLines(HEIGHT_IN_PIXELS/2-10,20,0);
        DrawStrXY(CalcCenterPosition(s,F_6x8),HEIGHT_IN_PIXELS/2-4,s,A_REPLACE);
        input = GetUserInput();
        if (input == K_LEFT || input == K_UP) {
            act_part--;
            act_part=(act_part+nr_parts-1)%nr_parts+1;  // warp around treatment
        }
        else if (input == K_RIGHT || input == K_DOWN) {
            act_part++;
            act_part=(act_part+nr_parts-1)%nr_parts+1;  // warp around treatment
        }
        else if (input == K_ESCAPE) {
            act_part = -1;
            break;
        }
        else if (isdigit(input)) {
            short num = input - '0';
            //---------------------------------------------------------------
            // just do bookmark loading when mark is okay (page must be != 0)
            //---------------------------------------------------------------
            if (active_marks->page[num]) {
                load_part = REAL_MARK_VALUE(active_marks->part[num]);
                load_page = active_marks->page[num];
            }
        }
    }
    while (input != K_ENTER);

    if (act_part != -1) {
        sprintf(s,"Please wait while unpacking Part %d ...",act_part);
        FillFullLines(HEIGHT_IN_PIXELS/2-10,20,0);
        FastStringXY(CalcCenterPosition(s,F_4x6),HEIGHT_IN_PIXELS/2-4,s);
    }

    restore_status = 0;
    return act_part;
}



//=============================================================================
// check if given symentry is a TTArchive and if yes, try to extract its
// description, otherwise use symentry name as description
//
// returns 1 if okay,    0 otherwise
//=============================================================================
static inline short CheckArchiveAndGetDesc(SYM_ENTRY* symptr,unsigned char* buffer) {
    unsigned char* src;
    unsigned short size;
    unsigned char* comment;
    short          was_locked = HeapGetLock(symptr->handle);

    src = HLock(symptr->handle); // lock in any case even if it was already locked

    if (!src) return 0;

    size = (((unsigned short)*src) << 8) + (unsigned short)*(src+1);

    src+=2;  // skip "length of variable" bytes

    if (!ttarchive_valid(src)) {
        if (!was_locked) HeapUnlock(symptr->handle);
        return 0;
    }

    comment = ttarchive_info(src);

    if (comment) strncpy(buffer,comment,TTARCHIVE_INFOLENGTH);
    else         strncpy(buffer,symptr->name,8);

    if (!was_locked) HeapUnlock(symptr->handle);
    return 1;
}



//=============================================================================
// book selection menu
//
// credits for this routine goes to Zeljko Juric !!
// (I have adapted this code from his ScottFree driver)
//=============================================================================
static inline HANDLE HandleBookSelection(short* lastbook) {
    short             select  = 0;
    short             index   = 1;
    short             found   = 0;
    short             i;
    short             handle  = PopupNew((char*)"SELECT AN eBOOK",C89_92(53,69));
    SYM_ENTRY*        symptr  = SymFindFirst(BOOKS_FOLDER,1);
    char              desc[TTARCHIVE_INFOLENGTH+1];

    ClearScreen();
    DrawClipRect(ScrToWin(ScrRect),ScrRect,A_NORMAL);
    FastStringXY(C89_92(11,51),C89_92(85,110),"(c) TI-Chess Team   http://tict.ticalc.org");
    DrawStrXY(C89_92(11,51),3,READER_TITLE,A_NORMAL);

    while (symptr) {
        if (CheckArchiveAndGetDesc(symptr,desc)) {
            found++;
            PopupAddText(handle,-1,desc,index);
        }
        symptr=SymFindNext();
        index++;
    }

    if (found) select = PopupDo(handle,CENTER,CENTER,*lastbook);
    else {
        DrawStr(C89_92(26,66),30,"No eBooks found !!!",A_REPLACE);
        select = 0;
        GetUserInput();
    }
    ClearScreen();
    HeapFree(handle);

    if (!select) return 0;

    *lastbook = select;
    symptr=SymFindFirst(BOOKS_FOLDER,1);

    for(i=1;i<select;i++) symptr=SymFindNext();

    active_book[8] = 0;
    strncpy(active_book,symptr->name,8);

    return symptr->handle;
}



//=============================================================================
// clears screen and outputs error message
//=============================================================================
void OutError(const char* s) {
    ClearScreen();
    DrawStrXY(0,0,s,A_REPLACE);
    GetUserInput();
}



//=============================================================================
// handles display of book
//
// when this function is called we can be sure that its an TTArchive !!
//=============================================================================
void HandleBookReading(HANDLE h) {
    unsigned char* src;
    unsigned short length;
    unsigned char* data;
    short          i;
    short          was_locked = HeapGetLock(h);

    src = HLock(h); // lock in any case! even if it was locked previously

    if (!src) {
        OutError("ERROR: cannot lock book");
        return;
    }

    src+=2;  // skip "length of variable" bytes

    i = ShowTitle(src);

    if (!i || i == K_ESCAPE) {
        if (!was_locked) HeapUnlock(h);
        if (!i) {
            if (out_of_mem) OutError("ERROR: out of memory");
            else            OutError("ERROR: title not found");
        }
        return;
    }

    nr_parts = ttarchive_entries(src) - 1;  // -1 is necessary due to the title picture

    //--------------------------------------------------------
    // load bookmarks. nr_parts must be set previously.
    // will set load_part/load_page
    //--------------------------------------------------------
    LoadBookMarks();
    act_part = 1;

    do {
        if ((HandlePartSelection()) == -1) break;

        if (!(data = GetDecompressedEntry(src,act_part,&length))) {
            if (out_of_mem) OutError("ERROR: out of memory");
            else            OutError("ERROR: part not found");
            break;
        }
        DisplayPart(data,length);
        free(data);
        if (exit_completely) break;
    }
    while (1);

    if (!was_locked) HeapUnlock(h);
}



//=============================================================================
// nomen omen est
//=============================================================================
void _main(void) {
    SYM_ENTRY* symptr;
    BookMarks  tmpmarks;
    short      lastbook = 1;
    ESI        argptr;
    void*      orig_screen;
    HANDLE     h;
    short      previous_font = FontGetSys();

    //------------------------------------------------------------------
    // allocate space for the charset, width table and screenbuffers ...
    //------------------------------------------------------------------
    if (!(charset = malloc(5*256+2*256+LCD_SIZE*2))) {
        ST_showHelp("ERROR: out of memory");
        return;
    }
    charwidth       = (short*)(charset + 5*256);
    doublebuffer    = charset + 5*256 + 2*256;
    orig_screen     = charset + 5*256 + 2*256+LCD_SIZE;

    //-----------------------------------------------
    // install statusline restoring interrupt handler
    //-----------------------------------------------
    old_inthandler = GetIntVec(AUTO_INT_5);
    restore_status = 0;
    SetIntVec(AUTO_INT_5,RestoreStatusHandler);

    //-------------------------
    // setup globals
    //-------------------------
    exit_completely  = 0;
    active_marks     = &tmpmarks;
    out_of_mem       = 0;

    SetupCharSet();
    screenbuffer = LCD_MEM;

    LCD_save(orig_screen);

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

    if (GetArgType(argptr) == STR_TAG) {
        char *fname = GetStrnArg(argptr);
        char tmpstr[40];

        tmpstr[0] = 0;

        if ((symptr = DerefSym(SymFind(strcpy(tmpstr + 1, fname) + strlen(fname))))) {
            active_book[8] = 0;
            strncpy(active_book,symptr->name,8);
            HandleBookReading(symptr->handle);
        }
    }
    else {
        do {
            //-------------------------
            // open book selection menu
            //-------------------------
            h = HandleBookSelection(&lastbook);
            if (!h) break;
            HandleBookReading(h);
            if (exit_completely) break;
        }
        while (1);
    }

    SetIntVec(AUTO_INT_5,old_inthandler);
    LCD_restore(orig_screen);
    free(charset);
    FontSetSys(previous_font);
}


//#############################################################################
//###################### NO MORE FAKES BEYOND THIS LINE #######################
//#############################################################################
//
//=============================================================================
// Revision History
//=============================================================================
//
// $Log: ebook.c,v $
// Revision 1.92  2002/05/23 06:29:46  tnussb
// version raised to reflect the use of the old unpacking version
//
// Revision 1.91  2002/03/28 21:14:32  tnussb
// again size optimizations by Francesco Orabona (bremen79@infinito.it)
//
// Revision 1.90  2002/03/13 15:07:35  tnussb
// no changes - just the version number raised, because a new exepack decompression
// function is used
//
// Revision 1.89  2002/03/06 16:13:29  tnussb
// minor size optimizations by Francesco Orabona (bremen79@infinito.it)
//
// Revision 1.88  2002/03/04 08:58:05  tnussb
// don't disable statusline restoring during bookmark dialog is shown (not necessary)
//
// Revision 1.87  2002/03/01 17:14:52  tnussb
// Restores now statusline area by using an own INT5 handler. This wastes
// again 400 bytes, but I think thats okay.
//
// Revision 1.86  2002/03/01 16:32:46  tnussb
// (1) Thanx to Austin Chu (qarv@yahoo.com) for pointing me out how stupid
//     and therefore slow NextLine() was implemented previously
// (2) many parts rewritten to optimize the size (now the size is 7317 Bytes
//     on the TI89 - great, much space to waste in further versions ;-)
// (3) "white on black" text is now drawn with FastStringXY, too
// (4) uses now FillFullLines macro instead of RectFill()
// (5) previously set font will be restored now before exit
// (6) FontSetSys() only used in CharSet grabbing, because all 4x6 strings
//     are now drawn by FastStringXY
// (7) I have moved the bottom line to the top, because I think this way
//     reading the text is a little bit easier. If the user presses now any
//     key which turns on a status flag, he has to refresh the screen by
//     his own by pressing once pageup+pagedown. This problem should be fixed
//     in one of the next versions.
//
// Revision 1.85  2002/02/27 11:16:44  tnussb
// 328 bytes saved by declaring some functions "static inline" and
// by using "static const" for some character arrays
//
// Revision 1.84  2002/02/13 18:13:02  tnussb
// (1) previously broken sprite drawing fixed again
// (2) FastDrawStringXY() heavily optimized
// (3) allocating now also memory for doublebuffer
//
// Revision 1.83  2002/02/11 09:43:24  tnussb
// (1) the buffer used to save screen and the charwidth are no
//     more on the stack, to avoid stack overflows (Lionel Debroux)
// (2) minor size optimizations
//
// Revision 1.82  2002/02/11 08:35:47  tnussb
// all warnings reported by compiler option -Wwrite-strings fixed
//
// Revision 1.81  2002/02/07 10:00:15  tnussb
// (1) uses now NO_EXIT_SUPPORT to reduce code size
// (2) minor fix to suppress a compiler warning of TIGCC 0.93
//
// Revision 1.80  2001/08/01 21:18:14  Thomas Nussbaumer
// (1) serious bug in bookmark dialog handling fixed (previously
//     not wanted keys had escaped from the routine, too)
// (2) [+] and [+] key can be used for paging
//
// Revision 1.77  2001/06/20 13:00:39  Thomas Nussbaumer
// now you can press 'x' twice instead of 'x' + '0'
//
// Revision 1.76  2001/04/08 21:04:05  Thomas Nussbaumer
// (1) using now idle() in GetUserInput()
// (2) letter 'g' displayed now one pixel lower than the others
//
// Revision 1.75  2001/03/21 21:41:03  Thomas Nussbaumer
// changed version date and version number forced to 1.75
//
// Revision 1.71  2001/02/04 16:45:13  Thomas Nussbaumer
// (1) using 7 pixels per line for better readability
// (2) using new header files for decompression and ttarchive access
//
// Revision 1.70  2001/01/22 23:09:33  Thomas Nussbaumer
// (1) using now again complete character set (0..255), but grabbed from a
// backbuffer during startup
// (2) stating now out-of-memory instead of part-not-found or title-not-found
// (3) broken bookmark feature fixed
// (4) using now HANDLEs wherever possible and applicable instead of SYM_ENTRYs
//
// Revision 1.63  2001/01/16 02:11:47  Thomas Nussbaumer
// fixed problem when reader is in tictebks directory again
//
// Revision 1.62  2001/01/16 01:12:48  Thomas Nussbaumer
// now it should be save to install the reader in the tictebks folder, too
//
// Revision 1.61  2001/01/10 22:15:05  Thomas Nussbaumer
// (1) own font 4x6 drawing routine to speed up page drawing added
// (2) some size optimizations performed to keep size under 8kB
//
// Revision 1.60  2001/01/06 10:47:07  Thomas Nussbaumer
// (1) problems reported by compiling with -Wall fixed
// (2) necessary changes for TIGCC Library 2.31 (int<->short)
// (3) can handle now commandline argument
//
// Revision 1.57  2000/10/01 14:55:57  Thomas Nussbaumer
// problem with 'w' & 'm' characters fixed
//
// Revision 1.56  2000/08/30 20:01:55  Thomas Nussbaumer
// using now assembler optimized version of unpack routine
//
// Revision 1.55  2000/08/30 19:41:19  Thomas Nussbaumer
// demonstration of nostub library usage (#define LIBRARY_VERSION) added
//
// Revision 1.54  2000/08/29 12:52:05  Thomas Nussbaumer
// saved 80 bytes more. Due to the optimized decompression algorithm
// we have about 800 bytes free for further improvements.
// I wonder if the upcoming metatag handling can fit into 800 bytes.
// The rendering engine will undergo heavy reconstructions for
// supporting the variable alignment of the text.
//
// Revision 1.53  2000/08/27 23:43:07  Thomas Nussbaumer
// (1) if there is no latest saved bookmark automatically load page1 of part1
// (2) try to load previous/next part only if not on first or last part already
//
// Revision 1.52  2000/08/27 20:55:08  Thomas Nussbaumer
// (1) reloads now at latest saved bookmark
// (2) on the end of a part you can now page further down to go to the next
// part
//
// Revision 1.51  2000/08/27 19:06:49  Thomas Nussbaumer
// (1) size reduced about more than 600 bytes by using now
//     separate builds instead of runtime calculator queries
// (2) speedup of line length calculation by doing it by hand
//     instead using function CharWidth()
//
// Revision 1.50  2000/08/23 20:35:48  Thomas Nussbaumer
// forcing version 1.50 (official released)
//
// Revision 1.4  2000/08/23 01:20:56  Thomas Nussbaumer
// (1) handling of 20 characters eBook description added
// (2) show only valid ttarchives in booklist
// (3) bug corrected in FindBookMarks(). this bug has
//     crashed my poor TI92p !!! that was the first crash
//     since about 6 month. normally my software will not
//     do such things ;-)
// (4) heavily reduced again to fit in 8kB. now we have
//     still 181 bytes left for further extensions
//
// Revision 1.3  2000/08/21 23:50:11  Thomas Nussbaumer
// (1) handling now APD (auto-power-down)
// (2) handling of calculator OFF key combinations
// (3) books must be stored now in folder tictebks (for autofind)
// (4) using now book selection box instead of commandline argument
// (5) up to 10 bookmarks can be set for each book
// (6) bookmarks are stored in folder tictmark
// (7) jumping to a bookmark is done by pressing one of the
//     numerical key (0-9)
// (8) book mark setting is done by pressing 'x' and a numerical key
// (9) fits exactly in 8kB (AMS 2.03) - just 7 bytes left ;-)
//
// Revision 1.2  2000/08/20 23:01:57  Thomas Nussbaumer
// missing free() added in ShowTitle()
//
// Revision 1.1  2000/08/20 16:51:19  Thomas Nussbaumer
// initial version
//
