/***************************************************************************
 *
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 *
 * Copyright (C) 2006 Frederik M.J. Vestre
 * Copyright (C) 2006 Adam Gashlin
 *
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"
#include <ctype.h>
#include "../lib/playback_control.h"
#include <inttypes.h>
#include "mallocer.h" /*mallocer resetable memory allocator*/
#include "mwdb.h"
#if LCD_DEPTH > 1
//#define COLORLINKS
#endif

PLUGIN_HEADER

struct plugin_api *rb;

/* change on any change to file format, histstate */
#define SAVE_FILE_VERSION_MAGIC 0x12340004

static bool wrquit=false;
static char *filename;      /* name of database, without extension */

#define MAINMEMBUF 0
//#define INFLATEBUF 1
//#define ARTICLEBUF 2
/*static void *mainmembuf;*/

void *inflatebuf;           /* heap for gunzip */
char *articlebuf;           /* destination for uncompressed article */
uint32_t articlebuflen=0;   /* size of the article buffer */
uint32_t articlelen=0;      /* used size of the article buffer */

int lastlinkcount=0;        /* set by render() to the number of links found */

/* provides everything render() needs to know to render a screen */
struct linestate {
    int32_t renderoff;
    int renderlen;
    int32_t linknameoff;
    int inlink,inlinkname;
#ifdef COLORLINKS
    uint8_t inem;
#endif
    int eof; /* should be interpreted as disallowing further scrolling down */
};

#define ARTICLENAMEBUF_LENGTH 255

/* where we'll be scrolling next */
struct linestate curlinestate,nextlinestate;

/* scrollback, to allow scrolling backwards fairly quickly (without some memory entire article
   might need to be reparsed to find context of previous line) */
/* stack, up to SCROLLBACK_LENGTH, past that the oldest entry is discarded */
#define SCROLLBACK_LENGTH 50
int curlinehist,linehistdepth;
int curline;
struct linestate linehist[SCROLLBACK_LENGTH];

/* history, for going back to a previous article */

struct histstate {
    char name[ARTICLENAMEBUF_LENGTH];
    int32_t curoff;
    uint32_t res_lo;   /* avoid searching again */
    uint32_t res_hi;
};

/*
  It should be possible to reconstruct a linestate from a histstate as follows:
  1) load the article given by res_lo, res_hi
  2) linestate=0
  3) while linestate.renderoff<histstate.curoff && !eof, advance
*/  

/* stack, up to HISTORY_LENGTH deep, past that oldest entry is discarded */
/* the current article occupies hist[curhist] */
#define HISTORY_LENGTH 20
int curhist,histdepth;
struct histstate hist[HISTORY_LENGTH];

struct menu_item mww_menu[] = {
    {"Save History",NULL},       /* 0 */
    {"Navigate",NULL},          /* 1 */
    {"Find Similar Article",NULL}, /* 2 */
    {"Find Article",NULL},    /* 3 */
    {"Clear History",NULL},     /* 4 */
    {"View History",NULL},      /* 5 */
    {"Show Playback Menu", NULL },/* 6 */
    {"Exit",NULL}               /* 7 */
};

int nprintf(const char *fmt, ...);
static void viewer_exit(void *parameter);
static struct linestate render(struct linestate cur, int norender,int linktonote);
static void readlink(struct linestate cur, int link, char * namebuf, int buflen);
static void viewer_exit(void *parameter)
{
    (void)parameter;
/*    Z_Close();
    rb->close(fd);*/
}

static void set_article_offset(int32_t off);
static void reset_scrollback(void);
static void advance_scrollback(int a, int dorender);
static void reset_history(void);
static void advance_history(int d);

void save_status(const char * filename) {
    char fnbuf[MAX_PATH+1];
    int fd;
    int i,histi;
    uint32_t magic;
    
    rb->snprintf(fnbuf,MAX_PATH,"%s.wws",filename);
    
    fd = rb->open(fnbuf, O_WRONLY|O_CREAT);
    if (fd<0) return;
    
    magic=SAVE_FILE_VERSION_MAGIC;
    
    rb->write(fd, &magic, sizeof(magic));
    rb->write(fd, &histdepth, sizeof(histdepth));
    
    histi=curhist-(histdepth-1);
    if (histi<0) histi+=HISTORY_LENGTH;
        
    for (i=0;i<HISTORY_LENGTH;i++) {
        rb->write(fd, hist+histi, sizeof(struct histstate));
        histi=(histi+1)%HISTORY_LENGTH;
        
    }
    rb->close(fd);
}

int load_status(const char * filename) {
    char fnbuf[MAX_PATH+1];
    int fd;
    int i,histi;
    uint32_t magic=0;
    
    rb->snprintf(fnbuf,MAX_PATH,"%s.wws",filename);
    fd = rb->open(fnbuf, O_RDONLY);
    if (fd<0) return 0;
    
    if (rb->read(fd,&magic,sizeof(magic))<=0) {rb->close(fd); return 0;}
    if (magic!=SAVE_FILE_VERSION_MAGIC) {rb->close(fd); return 0;}
    
    if (rb->read(fd,&histi,sizeof(histi))<=0) {rb->close(fd); return 0;}
    if (histi<=0) {rb->close(fd); return 0;}
                
    //reset_history();
        
    for (i=0;i<histi;i++) {
        if (rb->read(fd,hist+i,sizeof(struct histstate))<=0) {rb->close(fd); return 0;}
    }
    histdepth=histi;
    curhist=i-1;
    
    rb->close(fd);
    
    return 1;
}

static void set_article_offset(int32_t off) {
    /* go to offset off in current article */
    printsim("set_article_offset(%d)\n",off);
    reset_scrollback();
    curline=0;
    //hist[curhist].curoff=0;
    rb->memset(&curlinestate,0,sizeof(curlinestate));
    rb->memset(&nextlinestate,0,sizeof(nextlinestate));
    
    hist[curhist].curoff=curlinestate.renderoff=10+articlebuf[8];
    curlinestate.renderlen=articlelen-10-articlebuf[8];
    
    nextlinestate=render(curlinestate,1,0);
    // render is done here to ensure that we correctly check eof
    //nextlinestate=render(curlinestate,0,0);
    //rb->lcd_clear_display();
        
    while (!nextlinestate.eof && hist[curhist].curoff<off) {
        advance_scrollback(1,0);
    } 
}

static void reset_scrollback(void) {
    curlinehist=0;
    linehistdepth=0;
}

/* all scrolling done through this */
static void advance_scrollback(int a, int dorender) {
    int i;
    printsim("advance_scrollback(%d,%d)\n",a,dorender);
    if (a<-1) {
        for (i=0;i<-a;i++) {
            advance_scrollback(-1,(dorender && i==-a-1));
        }
    } else if (a>1) {
        for (i=0;i<a;i++) {
            advance_scrollback(1,(dorender && i==-a-1));
        }
    } else if (a==0) return;
    else if (a<0) {
        /* scroll backwards one line */
        if (curline==0) return;
        
        curline--;
    
        if (linehistdepth>0) {
            /* if we have records to fall back on */
            linehistdepth--;
            curlinehist--;
            printsim("use linehist[%d]\n",curlinehist);
            if (curlinehist<0) curlinehist=SCROLLBACK_LENGTH-1;
            curlinestate=linehist[curlinehist];
            nextlinestate=render(curlinestate,!dorender,0);
            hist[curhist].curoff=curlinestate.renderoff;
        } else {
            int amounttoscroll=curline;
            /* redo from start */
            
            reset_scrollback(); /* clear scrollback */
            set_article_offset(0);    /* go to top */
            advance_scrollback(amounttoscroll,dorender); /* scoll down */
        }
       
    } else {
        /* scroll forwards one line */
        if (!nextlinestate.eof) {
            printsim("save to linehist[%d]\n",curlinehist);
            linehist[curlinehist]=curlinestate;
            curlinehist=(curlinehist+1)%SCROLLBACK_LENGTH;
            linehistdepth++;
            if (linehistdepth>SCROLLBACK_LENGTH) linehistdepth=SCROLLBACK_LENGTH;

            curlinestate=nextlinestate;
            curline++;
            nextlinestate=render(curlinestate,!dorender,0);
            hist[curhist].curoff=curlinestate.renderoff;
        }
    }
}

static void reset_history(void) {
    curhist=0;
    histdepth=1;
}


static void advance_history(int d) {
    if (d==1) {
        curhist=(curhist+1)%HISTORY_LENGTH;
        histdepth++;
        if (histdepth>HISTORY_LENGTH) histdepth=HISTORY_LENGTH;
    } else if (d==-1) {
        if (histdepth>1) {
            curhist--;
            if (curhist<0) curhist=HISTORY_LENGTH-1;
            histdepth--;
        }
    }
}

/*
uint16_t decode_utf8_char(const char *src,uint16_t *olen)
{
	uint16_t ucs;
	const char *retchar=rb->utf8decode(src,&ucs);
	*olen=retchar-src;
	return ucs;
}
*/
int iswhitespace(uint16_t c) {
    return (c==' ' || c=='\n');
}
int islinebreak(uint16_t c) {
    return (c=='\n');
}

#define LINEBUFLEN 255
#define MARKUP_STARTULINE	1
#define MARKUP_ENDULINE		2
#define MARKUP_STARTBOLD	3
#define MARKUP_ENDBOLD		4
#define MARKUP_STARTITALIC	5
#define MARKUP_ENDITALIC	6
#define MARKUP_STARTLINK	7
#define MARKUP_ENDLINK		8
#define MARKUP_LINEFEED		10
#define MARKUP_MODE		15
//#define MARKUP_MODE		15

/* return next place to scroll to */
static struct linestate render(struct linestate cur, int norender,int linktonote) {
    int i;                  /* offset in buf */
    int x,y;                /* current render location */
    const char * nextchar;  /* character after the current UTF-8 character */
    int charsize;           /* size of the current UTF-8 character */
    uint16_t ucs;           /* current UTF-8 character */
    char buf[LINEBUFLEN+1]; /* the current line of text to be rendered */
    uint8_t underline[LCD_WIDTH]; /* should there be an underline at this pixel? */
    int fontheight;
#ifdef COLORLINKS
    uint8_t em[LCD_WIDTH];  /* type of emphasis for this pixel */
    unsigned lastcol=0;
    bool colorem=true;
    rb->memset(em,0,sizeof(em));
#endif
    struct linestate nextline;  /* state to return */

    /* state to back up to when doing line break */
    int lastspace_i=0;
    int lastspace_linkcount=0;
    struct linestate lastspace;
    
    int linkcount;
    int linedone=0;
    int lastwaswhitespace;  /* if the last character was whitespace we'll ignore subsequent whitespace */
    
    printsim("render(*,%d,%d) off=%d\n",norender,linktonote,cur.renderoff);

    fontheight = rb->font_get(FONT_UI)->height;
    y=1;
    
    linkcount=1;
    lastlinkcount=0;
    
    nextline.renderoff=-1;
    /* it's not going to be any less the end... */
    nextline.eof=cur.eof;
    
    if (!norender) rb->memset(underline,0,sizeof(underline));

    while (((y+fontheight) < LCD_HEIGHT) && cur.renderlen>0) {
        lastspace.renderoff=-1;
        ucs=0;
        lastwaswhitespace=1; /* prevent whitespace from appearing at the beginning of a line */
        /* scroll halfway down the screen */
        //if (y==(LCD_HEIGHT/fontheight/2)*fontheight+1) halfway=article;
        /* see how much we can fit on a line */
        for (x=1,i=0,linedone=0;!linedone && i<LINEBUFLEN && cur.renderlen>0;) {
            /*handle markup*/
            switch (articlebuf[cur.renderoff]) {
                case MARKUP_STARTULINE:
                case MARKUP_STARTBOLD:
                case MARKUP_STARTITALIC:
#ifdef COLORLINKS
                    switch (articlebuf[cur.renderoff]) {
                        case MARKUP_STARTULINE:
                            cur.inem=1;
                            break;
                        case MARKUP_STARTBOLD:
                            cur.inem=2;
                            break;
                        case MARKUP_STARTITALIC:
                            cur.inem=3;
                            break;
                    }
#endif
                    cur.renderlen--;
                    cur.renderoff++;
                    break;
                case MARKUP_ENDULINE:
                case MARKUP_ENDBOLD:
                case MARKUP_ENDITALIC:
#ifdef COLORLINKS
                    if (cur.inem) {
                      cur.inem=0;
                    }
#endif
                    cur.renderlen--;
                    cur.renderoff++;
                    break;                    
                case MARKUP_STARTLINK:
                    //printf("startlink\n");
                    cur.inlink=1;
                    cur.linknameoff=cur.renderoff;
                    cur.renderlen--;
                    cur.renderoff++;
                    break;
                case MARKUP_ENDLINK:
                    //printf("endlink\n");
                    if (cur.inlinkname) {
                        cur.inlinkname=0;
                        linkcount++;
                    }
                    if (cur.inlink) {
                        /* start outputting link text */
                        cur.renderlen+=cur.renderoff-cur.linknameoff;
                        cur.renderoff-=cur.renderoff-cur.linknameoff;
                        cur.linknameoff=0;
                        cur.inlink=0;
                        cur.inlinkname=1;
                    }
                    cur.renderlen--;
                    cur.renderoff++;
                    break;
                case MARKUP_MODE:
                    if (cur.inlink) cur.linknameoff=cur.renderoff;
                    cur.renderlen--;
                    cur.renderoff++;
                    break;
                default:
                    nextchar = rb->utf8decode(articlebuf+cur.renderoff,&ucs);
                    charsize = nextchar-cur.renderoff-articlebuf;
                    /* multiple newlines work */
                    if (islinebreak(ucs)) {linedone=1;/* break;*/}
                    /* but multiple other whitespace will be ignored */
                    if (!linedone && !cur.inlink && !(iswhitespace(ucs) && lastwaswhitespace)) {
                        /* display */
                        int charwidth=rb->font_get_width(rb->font_get(FONT_UI),ucs);
                        
                        if (iswhitespace(ucs)) {
                            lastspace=cur;
                            lastspace_i=i;
                            lastspace_linkcount=linkcount;
                            lastwaswhitespace=1;
                        } else lastwaswhitespace=0;
                        
                        if ((x+=charwidth) > LCD_WIDTH) {linedone=1; break;}

                        if (!norender) {
                            rb->memcpy(buf+i,articlebuf+cur.renderoff,charsize);
                            if (cur.inlinkname) rb->memset(underline+(x-charwidth),linkcount,charwidth);
#ifdef COLORLINKS
                            if (cur.inem) rb->memset(em+(x-charwidth),cur.inem,charwidth);
#endif
                        }
                        i+=charsize;

                    } else {
                        /* hidden */
                        
                    }
                    cur.renderlen-=charsize;
                    cur.renderoff+=charsize;
            } /* end markup switch */
        } /* end for characters in a line */
        if (x>=LCD_WIDTH) {
            /* the next character would be offscreen, terminate at previous space */
            if (lastspace.renderoff==-1) {
                /* if we have a long word, break it here */
            } else {
                cur=lastspace;
                linkcount=lastspace_linkcount;
                i=lastspace_i;
            }
                            
        }
            
        if (!norender) {
            int uc,ucm,t;
            //printsim("doing render\n");
            buf[(i<=LINEBUFLEN)?i:LINEBUFLEN]='\0';
        
            rb->lcd_putsxy(1,y, (unsigned char *)buf);
            rb->lcd_getstringsize(buf,&ucm,&t);

            int oldmode=rb->lcd_get_drawmode();
            
            /* mark links */
            for (uc=0;uc<LCD_WIDTH;uc++) {
#ifdef COLORLINKS
                /* mark emphasis */
                if (em[uc]) {
                        lastcol=rb->lcd_get_foreground();
                        rb->lcd_set_drawmode(DRMODE_SOLID);
                        switch (em[uc]) {
                            case 1:
                                rb->lcd_set_foreground(LCD_RGBPACK(255,0,0));
                                break;
                            case 2:
                                rb->lcd_set_foreground(LCD_RGBPACK(0,255,0));
                                break;
                            case 3:
                                rb->lcd_set_foreground(LCD_RGBPACK(0,0,255));
                                break;
                        }
                        rb->lcd_drawpixel(uc,y+fontheight-1);
                        rb->lcd_set_foreground(lastcol);

                }
                em[uc]=0;
#endif
                if (underline[uc] && uc > 1 && uc < (ucm+1)) {
                    if (underline[uc]==linktonote) {
                        rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
                        rb->lcd_vline(uc,y,y+fontheight-1);
                    } else {
                        rb->lcd_set_drawmode(DRMODE_SOLID);
                        rb->lcd_drawpixel(uc,y+fontheight-1);
                    }
                    lastlinkcount=underline[uc];
                }
                underline[uc]=0;
                /* clear rest of line */
                if (uc >= ucm+1) {
                    rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
                    rb->lcd_vline(uc,y,y+fontheight-1);
                }
            }
            
            rb->lcd_set_drawmode(oldmode);
        }

        y+=fontheight;
        /*scroll one line*/
        if (nextline.renderoff==-1) {
            nextline=cur;
            if (norender) return nextline;
        }
    }
    if (cur.renderlen<=0) {cur.eof=1; nextline.eof=1; printsim("eof\n");}
    
    if (!norender) rb->lcd_update();
    return ((nextline.renderoff==-1)?cur:nextline);
}

static void readlink(struct linestate cur, int link, char * namebuf, int buflen) {
    int linkcount=0;
    
    rb->memset(namebuf,0,buflen);
    
    if (!link) return;
        
    /*
      screen starts partway through a link
    */
    if (cur.inlink || cur.inlinkname) {
        printsim("starting readlink() within a link\n");
        /* seek back to the start of the link */
        while (articlebuf[cur.renderoff] != MARKUP_STARTLINK) {
            cur.renderoff--;
            cur.renderlen++;
        }
    }
    
    while (cur.renderlen) {
        if (articlebuf[cur.renderoff] == MARKUP_STARTLINK) {
            linkcount++;
            if (linkcount==link) {
                cur.renderoff++;
                cur.renderlen--;
                break;
            }
        }
        cur.renderoff++;
        cur.renderlen--;
    }
    if (linkcount==link) {
        int i;
        for (i=0;i<buflen-1 && cur.renderlen>0;) {
            int charsize;
            if (articlebuf[cur.renderoff]==MARKUP_ENDLINK || articlebuf[cur.renderoff]==MARKUP_MODE) break;

            charsize = rb->utf8seek(articlebuf+cur.renderoff,1);
            rb->memcpy(namebuf+i,articlebuf+cur.renderoff,charsize);
            cur.renderlen-=charsize;
            i+=charsize;
            cur.renderoff+=charsize;
        }
    }
}

/* locate headings, indicated by a line beginning with = */
/* this is unfortunately quite stupid about markup in headings */

static int render_outline(int inoff, int norender) {
    int renderoff;
    int renderlen;
    int nextlineoff;
    int linecount;
    int intdepth=0,extdepth=0,linestart;
    int charsize;
    int fontheight;
    int y;

    char preserv;
    enum {
        STATE_START, /* start of line */
        STATE_DD,    /* determining depth */
        STATE_DD2,   /* check closing =s */
        STATE_HTEXT, /* heading text */
        STATE_OTHERTEXT /* other text */
    } state;
    
    if (inoff<10) inoff=10;
    renderoff=inoff;
    renderlen=articlelen-renderoff;
    linecount=0;
    
    y=1;
    fontheight = rb->font_get(FONT_UI)->height;
    
    if (!norender) rb->lcd_clear_display();
    
    if (renderoff==10) { /* we will include the article title */
        if (!norender) {
            //rb->lcd_clear_display();
            preserv=articlebuf[articlebuf[8]+10];
            articlebuf[articlebuf[8]+10]='\0';
            rb->lcd_putsxy(1,y,articlebuf+10);
            //printsim("%s\n",articlebuf+10);
            articlebuf[articlebuf[8]+10]=preserv;
        }
        y+=fontheight;
        linecount++;
        
        renderlen-=articlebuf[8];
        renderoff+=articlebuf[8];
    }
    nextlineoff=renderoff;
        
    linestart=renderoff;
    state=STATE_START;
    
    while (renderlen>0 && y+fontheight < LCD_HEIGHT) {
        switch (state) {
            case STATE_START:
                linestart=renderoff;
                switch (articlebuf[renderoff]) {
                    case '=':
                        if (linecount==1) {
                            nextlineoff=renderoff; /* beginning of second line */
                            if (norender) return nextlineoff;
                        }
                        intdepth=1;
                        state=STATE_DD;
                        break;
                    case MARKUP_LINEFEED:
                        break;
                    default:
                        state=STATE_OTHERTEXT;
                        break;
                }
                break;
            case STATE_DD:
                switch (articlebuf[renderoff]) {
                    case '=':
                        intdepth++;
                        break;
                    case MARKUP_LINEFEED:
                        state=STATE_START; /* give up */
                        break;
                    default:
                        state=STATE_HTEXT;
                        break;
                }
                break;
            case STATE_DD2:
                switch (articlebuf[renderoff]) {
                    case '=':
                        extdepth++;
                        break;
                    case MARKUP_LINEFEED:
                        /* final acceptance here */
                        if (!norender) {
                            if (linecount==0) rb->lcd_clear_display();
                            int depth=(extdepth<intdepth)?extdepth:intdepth;
                            int whitey=0;
                            preserv=articlebuf[renderoff-depth];
                            articlebuf[renderoff-depth]='\0';
                                                        
                            /* trim leading whitespace */
                            
                            while (iswhitespace(articlebuf[linestart+depth+whitey])) {
                                charsize = rb->utf8seek(articlebuf+linestart+depth+whitey,1);
                                whitey+=charsize;
                            }
                            
                            rb->lcd_putsxy(1+depth*rb->font_get_width(rb->font_get(FONT_UI),'_'),y, articlebuf+linestart+depth+whitey);
                                                        
                            articlebuf[renderoff-depth]=preserv;
                        }
                        linecount++;
                        y+=fontheight;
                        state=STATE_START;
                        break;
                    default:
                        state=STATE_HTEXT; /* return to HTEXT, might be an = within a heading */
                        break;
                }
                break;
            case STATE_HTEXT:
                switch (articlebuf[renderoff]) {
                    case '=':
                        extdepth=1;
                        state=STATE_DD2;
                        break;
                    case MARKUP_LINEFEED:
                        state=STATE_START; /* give up */
                        break;
                    default:
                        /* continue to accept text within the heading */
                        break;
                }
                break;
            case STATE_OTHERTEXT:
                switch (articlebuf[renderoff]) {
                    case MARKUP_LINEFEED:
                        state=STATE_START;
                        break;
                    default:
                        break;
                }
                break;
            default:
                printsim("state error\n");
        } /* end switch (state) */
        charsize = rb->utf8seek(articlebuf+renderoff,1);
        renderlen-=charsize;
        renderoff+=charsize;
    }
    
    if (!norender) rb->lcd_update();
    
    return nextlineoff;
}

void render_history(int off) {
    int i=curhist,j,d;
    int x,y,fontheight,t;
    char buf[10];
    
    rb->lcd_clear_display();
    
    y=1;
    fontheight = rb->font_get(FONT_UI)->height;
    
    for (j=1,d=histdepth;d>0 && y+fontheight<LCD_HEIGHT;d--,j++) {
        if (j>=(off+1)) {
            //printsim("%2d. %s\n",j,hist[i].name);
            rb->snprintf(buf,10,"%2d. ",j);
            rb->lcd_putsxy(1,y,buf);
            rb->lcd_getstringsize(buf,&x,&t);
            rb->lcd_putsxy(1+x,y,hist[i].name);
            y+=fontheight;
        }
        i--;
        if (i<0) i=HISTORY_LENGTH-1;
    }
    
    rb->lcd_update();
}

/* read 2 character ascii hex */
static int readhex(unsigned char * buf) {
    int t=0;
    if (buf[0]>='0'&&buf[0]<='9') t=(buf[0]-'0')*16;
    else if (buf[0]>='A'&&buf[0]<='F') t=(buf[0]-'A'+10)*16;
    else return -1;
    
    if (buf[1]>='0'&&buf[1]<='9') t+=(buf[1]-'0');
    else if (buf[1]>='A'&&buf[1]<='F') t+=(buf[1]-'A'+10);
    else return -1;
    
    return t;
} 

static bool viewer_init(void)
{
    enum {
        MODE_LINK,
        MODE_NAVVIEW,
        MODE_HISTVIEW,
        MODE_NORMAL
    } mode;

    int linkno;         /* currently selected link */
    int navcur;         /* current offset for navigation */
    int navnext;        /* next offset for navigation mode */
    int navline;        /* current line for navigation (important for scrolling up) */
    int histviewoff;        /* how far back */
    
    char * target;      /* an anchor to seek out */
    
    bool prompt=1;      /* prompt for article name? */
    bool dofind=1;      /* run mwdb_findarticle? */
    curline=0;          /* current line */
	
    reset_history();
    hist[curhist].curoff=0; /* first article starts at beginning */
   
    /* allocate memory */
    wpw_init_mempool(rb,MAINMEMBUF);
    inflatebuf=wpw_malloc(MAINMEMBUF,0x13500);
    articlebuflen=wpw_available(MAINMEMBUF)>0x32000?0x32000:wpw_available(MAINMEMBUF);
    articlebuf=wpw_malloc(MAINMEMBUF,articlebuflen);
   
    /* initially no name in prompt, subsequently default to previous name */
    rb->memset(hist[curhist].name,0,ARTICLENAMEBUF_LENGTH);
    
    if (load_status(filename)) {
        prompt=0;
        dofind=0;
    }
    
    target=0;

loadnewarticle:
    if (prompt || dofind) {
    if (!mwdb_findarticle(filename,hist[curhist].name,ARTICLENAMEBUF_LENGTH,&hist[curhist].res_lo,&hist[curhist].res_hi,true,prompt,1)) {
            /* try case-insensitive match if case-sensitive match fails */
            if (!rb->strlen(hist[curhist].name)) {
                /* bailed out */
                
                /* automatically go back */
                if (histdepth>1) {
                    prompt=0;
                    dofind=0;
                    advance_history(-1);
                    
                    goto loadnewarticle;
                }
                
                return true; 
            }
            if (!mwdb_findarticle(filename,hist[curhist].name,ARTICLENAMEBUF_LENGTH,&hist[curhist].res_lo,&hist[curhist].res_hi,true,0,0)) {
                rb->splash(HZ*2, 1, "didn't find \"%s\"",hist[curhist].name);
        
                /* automatically go back */
                if (histdepth>1) {
                    prompt=0;
                    dofind=0;
                    advance_history(-1);
                    
                    goto loadnewarticle;
                }
                return true; /* No reason to make the plugin fail */
            }
        }
    }
    
    prompt=0;
    
    if (!mwdb_loadarticle(filename,inflatebuf,articlebuf,articlebuflen,&articlelen,hist[curhist].res_lo,hist[curhist].res_hi)) {
        return false; /* load error is a problem */
    }

    rb->lcd_clear_display();
    
    /* if a target has been specified */
    if (target) {
        int i,j;
        
        /* find headings */
        navcur=-1;
        navnext=0;
        while (navcur!=navnext) {
            navcur=navnext;
            navnext=render_outline(navcur,1);
            /* navnext will be pointing at the start of the line with the target in it */
            
            /*
            * Attempt match, this is a bit tricky (and it is probably equally tricky to get
            * an anchor pointing to another document in wikicode in the first place).
            */
            printsim("attempt to find target: *%s*\n",target);
            for (i=0,j=navnext;target[i] && articlebuf[j];) {
                printsim("compare %c and %c\n",target[i],articlebuf[j]);
                if (target[i]==articlebuf[j]) {
                    i++; j++;
                } else {
                    printsim("fail %d!=%d\n",target[i],articlebuf[j]);
                    if (i==0 && (articlebuf[j]=='=' || articlebuf[j]==' ')) j++; /* ignore starting =s, spaces */
                    else if (target[i]=='_' && articlebuf[j]==' ') {i++; j++;} /* spaces converted to underscores */
                    else if (target[i]=='.') { /* check for escape sequences */
                        if (articlebuf[j]==readhex(target+i+1)) {i+=3; j++;}
                        else break;
                    }
                    else break;
                }
            }
            if (target[i]) continue;
            while (articlebuf[j]==' ') j++; /* sometimes we see spaces before the closing = */
            if (articlebuf[j]!='=') continue; /* we want the exact heading */
            
            /* we have a winner! */
            hist[curhist].curoff=navnext;
        }
        target=0;
    }

    set_article_offset(hist[curhist].curoff);
    nextlinestate=render(curlinestate,0,0);
    /* get authoritative title name */
    rb->memcpy(hist[curhist].name,articlebuf+10,articlebuf[8]);
    hist[curhist].name[(int)(articlebuf[8])]='\0';
    
    mode=MODE_NORMAL;
    linkno=0;
    navcur=0;
    navnext=0;
    navline=0;
    histviewoff=0;
	
	 /*{
        int i;
        for (i=0;i<0x1000;i++)
        printf("%c",articlebuf[i]);
    }*/
        
    int button;
    while (!wrquit) {
		//button = rb->button_get_w_tmo(HZ/250);
        button = rb->button_get(true);
		//printf("Button:%d\n",button);
		//(1000/HZ) * ticks
        if (rb->default_event_handler_ex(button, viewer_exit, NULL)== SYS_USB_CONNECTED)
            return PLUGIN_USB_CONNECTED;
        switch(button){
            case BUTTON_MENU:
                switch (mode) {
                case MODE_HISTVIEW:
                case MODE_LINK:
                case MODE_NAVVIEW:
                    rb->lcd_clear_display();
                    mode=MODE_NORMAL;
                    nextlinestate=render(curlinestate,0,0);
                    break;
                case MODE_NORMAL:
                {
                    //wrquit=true;
                    int menuno = rb->menu_init(mww_menu,sizeof(mww_menu)/sizeof(*mww_menu),NULL,NULL,NULL,NULL);
                    int selection;
                    int lasthist;
                    if (menuno<0) return false;
                    selection=rb->menu_show(menuno);
                    rb->menu_exit(menuno);
                    switch (selection) {
                        case MENU_ATTACHED_USB:
                            return PLUGIN_USB_CONNECTED;
                        case MENU_SELECTED_EXIT:
                            break;
                        case 0: /* save status */
                            save_status(filename);
                            break;
                        case 1: /* navigate */
                            mode=MODE_NAVVIEW;
                            navcur=0;
                            navnext=render_outline(navcur,0);
                            navline=0;
                            break;
                        case 2: /* find similar article */
                            lasthist=curhist;
                            prompt=1;
                            dofind=1;

                            advance_history(1);
                            rb->strcpy(hist[curhist].name,hist[lasthist].name);
                            hist[curhist].curoff=0;

                            goto loadnewarticle;
                            break;
                        case 3: /* find article (blank name) */
                            lasthist=curhist;
                            prompt=1;
                            dofind=1;

                            advance_history(1);
                            rb->memset(hist[curhist].name,0,ARTICLENAMEBUF_LENGTH);
                            hist[curhist].curoff=0;

                            goto loadnewarticle;
                            break;
                        case 4: /* clear hist */
                            reset_history();
                            rb->memset(hist[curhist].name,0,ARTICLENAMEBUF_LENGTH);
                            hist[curhist].curoff=0;
                            prompt=1;
                            dofind=1;
                            goto loadnewarticle;
                            break;
                        case 5: /* view hist */
                            mode=MODE_HISTVIEW;
                            histviewoff=0;
                            render_history(histviewoff);
                            break;
                       case 6: /* playback control */
                           playback_control(rb);
                           break;
                        case 7: /* exit */
                            wrquit=true;
                            break;
                    } /* end menu switch */
                    if ((mode==MODE_NORMAL) && !wrquit) {
                        rb->lcd_clear_display();
                        nextlinestate=render(curlinestate,0,0);
                    }
                } /* end menu block */
                } /* end mode switch */
                break;
            case BUTTON_SELECT:
                switch (mode) {
                case MODE_NAVVIEW:
                    set_article_offset(navcur);
                    nextlinestate=render(curlinestate,0,0);
                    mode=MODE_NORMAL;
                    break;
                case MODE_NORMAL:
                    /* enter link select mode */
                    mode=MODE_LINK;
                    linkno=1;
                    nextlinestate=render(curlinestate,0,linkno);
                    if (lastlinkcount==0) mode=MODE_NORMAL;
                    break;
                case MODE_LINK:
                    /* load a new article */
                    advance_history(1);
                    readlink(curlinestate,linkno,hist[curhist].name,ARTICLENAMEBUF_LENGTH);
                    if ((target=rb->strrchr(hist[curhist].name,'#'))) {
                        /* cut the target name off the end of the string */
                        target[0]='\0';
                        target++; /* to point at the name of the target */
                    }
                    hist[curhist].curoff=0;
                    dofind=1;
                    goto loadnewarticle;
                case MODE_HISTVIEW:
                    /* jump back */
                    if (histviewoff>0) {
                        int i;
                        for (i=0;i<histviewoff;i++)
                            advance_history(-1);
                        dofind=0;
                        mode=MODE_NORMAL;
                        goto loadnewarticle;
                    }
                    nextlinestate=render(curlinestate,0,0);
                    mode=MODE_NORMAL;
                    break;
                default:
                    break;
                } /* end mode switch */
                break;
#ifdef BUTTON_SCROLL_FWD
            case BUTTON_SCROLL_FWD:
            case BUTTON_SCROLL_FWD|BUTTON_REPEAT:
#else
            case BUTTON_DOWN:
            case BUTTON_DOWN|BUTTON_REPEAT:
#endif
                switch (mode) {
                case MODE_NAVVIEW:
                    if (navnext!=navcur) {
                        navline++;
                    }
                    navcur=navnext;
                    navnext=render_outline(navcur,0);
                    break;
                case MODE_LINK:
                    /* select next link */
                    if (linkno<lastlinkcount) {
                        linkno++;
                        nextlinestate=render(curlinestate,0,linkno);
                    }
                    break;
                case MODE_NORMAL:
                    /* scroll down */
                    advance_scrollback(1,1);
                    break;
                case MODE_HISTVIEW:
                    if (histviewoff<histdepth-1) {
                        histviewoff++;
                        render_history(histviewoff);
                    }
                    break;
                } /* end mode switch */
                break;
                
#ifdef BUTTON_SCROLL_BACK
            case BUTTON_SCROLL_BACK:
            case BUTTON_SCROLL_BACK|BUTTON_REPEAT:
#else
            case BUTTON_UP:
            case BUTTON_UP|BUTTON_REPEAT:
#endif
                switch (mode) {
                case MODE_NAVVIEW:
                    {
                        int i;
                        if (navline>0) {
                            navline--;
                            navnext=0;
                            for (i=0;i<navline;i++) {
                                navcur=navnext;
                                navnext=render_outline(navcur,1);
                            }
                            navcur=navnext;
                            navnext=render_outline(navcur,0);
                        }
                    }
                    break;
                case MODE_LINK:
                    /* select previous link */
                    if (linkno>1) {
                        linkno--;
                        nextlinestate=render(curlinestate,0,linkno);
                    }
                    break;
                case MODE_NORMAL:
                    /* scroll up */
                    advance_scrollback(-1,1);
                    break;
                case MODE_HISTVIEW:
                    if (histviewoff>0) {
                        histviewoff--;
                        render_history(histviewoff);
                    }
                    break;
                } /* end mode switch */
                break;
            case BUTTON_LEFT: /* go back */
                switch (mode) {
                case MODE_NORMAL:
                    if (histdepth>1) {
                        advance_history(-1);
                        dofind=0;
                        goto loadnewarticle;
                    }
                    break;
                default:
                    break;
                } /* end mode switch */
        } /* end button switch */
    } /* end main loop */
    return true;
}

enum plugin_status plugin_start(struct plugin_api* api, void* file)
{
    rb = api;

    if (!file)
    	return PLUGIN_ERROR;
    filename = file;
    filename[rb->strlen(filename)-4]='\0';
    
    rb->lcd_set_drawmode(DRMODE_SOLID);

    if (!viewer_init())
	    return PLUGIN_ERROR;
    
    viewer_exit(NULL);
    return PLUGIN_OK;
}
// kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle;
