/*
 * imgmover.c
 *
 * Simple C application using raylib to cselect/rotate/classify/arrange photos.
 *
 * History:
 *      20250902 Rewrite using parts of old prototype.
 *      20250920 Reimplemented all scroll functionality.
 *
 * Author: Dario Rodriguez dario@darionomono.com
 * (c) Dario Rodriguez 2025
 * This program is licensed under the terms of GNU GPL v2.1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <pthread.h>
#if !defined(__linux__) && !defined(ANDROID)
#include "win32_pipe.h"
#endif

#include "rayui.h"
#include "bg.h"

#define DEFAULTWIDTH 1280
#define DEFAULTHEIGHT 768
//#define LEFTIMAGESIDELEN 326
#define LEFTIMAGESIDELEN 64
#define SCROLLTHRESHOLD (LEFTIMAGESIDELEN/3)
#define WHEELSTEP LEFTIMAGESIDELEN
#define SEP '/'
#define SIZEBGLOAD (3*((DEFAULTWIDTH+(LEFTIMAGESIDELEN-1))/LEFTIMAGESIDELEN)*((DEFAULTHEIGHT+(LEFTIMAGESIDELEN-1))/LEFTIMAGESIDELEN))
#define DEFAULTDIRDATAHEIGHT 492
#define ADDREMOVEDIRDATAHEIGHT 128
#define UTF8DOWNARROW "\xe2\x86\x86" /* U+2186 in UTF-8 */
#define DEFAULTDIRDATATRIANGLEW 35
#define BLOCKLISTINGBUF 2048
#define BLOCKLISTINGELEMS 1024
#define BLOCKDIRDATA 16

#define MYFUNC "UNKNOWN"
#if 0
#define IMDEBUG(a) fprintf(stderr,"%s:%i: %s: %s\n",__FILE__,__LINE__,MYFUNC,a)
#define IMDEBUG2(a) fprintf(stderr,"%s:%i: %s: ",__FILE__,__LINE__,MYFUNC),fprintf a
#else
#define IMDEBUG(a)
#define IMDEBUG2(a)
#endif

// RIGHTSIDEMARGIN=FONTBIGSIZE/2

typedef struct thumb_t {
        whxy_t whxy;
        whxy_t screenwhxy;
        Texture2D texture;
        int has_texture;
        int has_failedload;
} thumb_t;

typedef struct listingdata_t {
        thumb_t left;
        thumb_t right;
        char *name; /* first byte in the name is the type, next is the name, i.e. a directory is "dhome" and a file is "f.bashrc" (this is done for easier sorting) */
} listingdata_t;

typedef struct listing_t {
        int sizeelems;
        int usedelems;
        listingdata_t *elems;
        int sizebuf;
        int usedbuf;
        char *buf;
        int has_leftwhxy;
        int has_rightwhxy;
        whxy_t lastleftwhxy;
} listing_t;

typedef struct dirdata_t {
        int height;
        char *dirname;
        listing_t listing;
        int leftscrollpos;
} dirdata_t;

typedef struct texture_t {
        char currentpath[2048];
        Texture2D texture;
        int texturew;
        int textureh;
        int has_texture;
        int has_failedload;
        whxy_t source; /* be able to detect a "double click" */
} texture_t;

typedef struct body_t {
        rayui_t *rayui;
        char *rootdir;
        whxy_t whxy;
        whxy_t backwhxy;
        int leftsize;
        int sizedirdata;
        dirdata_t **dirdata;
        int currentdirdata;
        int rightscrollpos;
        font_t *ptrfont;
        font_t *ptrfontbig;
        font_t *ptrfonthuge;
        texture_t texture;
        int is_displayingtexture;
        texture_t bigtexture;
        int flag_drawbigtexture;
        whxy_t dirdataadd;
        Font roundedbox;
        bg_t *bg;
        char selectedpath[2048];
} body_t;


typedef struct im_t {
        rayui_t *rayui;
        mousedata_t mousedata;
        body_t *body;
} im_t;

im_t *im_init(char *rootdir);
void im_free(im_t *im);

body_t *im_body_init(rayui_t *rayui, int w, int h, int x, int y, int leftsidesize, char *rootdir);
void im_body_free(body_t *body);

int im_body_add(body_t *body,char *dir);
int im_body_tryselect(body_t *body, Vector2 mousepos);

int im_body_mouse(body_t *body, mousedata_t *mousedata);
int im_body_draw(body_t *body, mousedata_t *mousedata, int windowwidth, int windowheight, int *needs_nextredraw);

static int strptrcmp(void *a,void *b);
int listing_get(listing_t *listing, char *pathprefix, char *path, int flag_sort);
void listing_freedata(listing_t *listing);
int listing_fillwhxy(listing_t *listing, font_t *font, int w, int sidelen, int is_left, int rightsidemargin);

int is_imagefilename(char *filename);

int texture_load(texture_t *texture, char *fullpath, int maxw, int maxh, bg_t *bg);
int texture_draw(texture_t *texture, int x0, int y0, int maxw, int maxh);
int texture_freedata(texture_t *texture);

int imutil_aspectmaximize(int w, int h, int maxw, int maxh, int *neww, int *newh);


int
main(int argc, char *argv[])
{
        im_t *im;
        char *rootdir;
        int needs_nextredraw;
        if(argc<2 || strcmp(argv[argc-1],"--help")==0) {
                global_messagebox("imgmover\n\nSyntax: %s <rootdirectory>\n",(argc>0)?argv[0]:"imgmover");
                return(1);
        }
        rootdir=argv[1];
        bg_staticinit();
        if((im=im_init(rootdir))==NULL) {
                global_messagebox("%s","Couldn't init");
                return(1);
        }
        while(!WindowShouldClose()) {
                rayui_timereset(im->rayui);
                needs_nextredraw=0;
                rayui_getmousedata(im->rayui,&(im->mousedata));
                /* process mouse actions */
                if(im->mousedata.click_avail)
                        im_body_mouse(im->body, &(im->mousedata));
                /* draw screen contents */
                BeginDrawing();
                ClearBackground(RAYWHITE);
                rayui_scrollablerectreset(im->rayui,&(im->mousedata));
                im_body_draw(im->body,&(im->mousedata),im->rayui->w,im->rayui->h,&needs_nextredraw);
                if(needs_nextredraw==0 && im->mousedata.has_mousechanges==0 && im->mousedata.scrollspeed==0) {
                        /* Wait for new events when calling EndDrawing() */
                        EnableEventWaiting();
                } else {
                        /* EndDrawing() doesn't wait for new events, returns immediately */
                        DisableEventWaiting();
                }
                EndDrawing();
        }
        im_free(im),im=NULL;
        bg_staticfini();
        return(0);
}


im_t *
im_init(char *rootdir)
{
        im_t *im;
        char *errstr;
        if((im=calloc(1,sizeof(im_t)))==NULL) {
                im_free(im),im=NULL;
                return(NULL); /* insuf. mem. */
        }
        if((errstr="init interface")==NULL
          || (im->rayui=rayui_init(DEFAULTWIDTH,DEFAULTHEIGHT,"Image Mover",NULL))==NULL
          || (errstr="Couldn't init interface data")==NULL
          || (im->body=im_body_init(im->rayui,im->rayui->w,im->rayui->h, 0, 0, im->rayui->w*2/3, rootdir))==NULL
        ) {
                global_messagebox("Couldn't %s",errstr);
                im_free(im),im=NULL;
                return(NULL); /* insuf. mem. */
        }
        /* add the starting directory */
        im_body_add(im->body,"");
        return(im);
}

void
im_free(im_t *im)
{
        if(im==NULL)
                return;
        if(im->body!=NULL)
                im_body_free(im->body),im->body=NULL;
        if(im->rayui!=NULL)
                rayui_free(im->rayui),im->rayui=NULL;
        free(im),im=NULL;
}

body_t *
im_body_init(rayui_t *rayui, int w, int h, int x, int y, int leftsidesize, char *rootdir)
{
        body_t *body;
        char *errstr;
        static char sep[]={SEP,'\0'};
        if(rayui->font==NULL || rayui->fontbig==NULL || rayui->fonthuge==NULL || rootdir==NULL) {
                global_messagebox("im_body_init sanity check failed");
                return(NULL); /* sanity check failed */
        }
        if((errstr="Insuf. mem. for body")==NULL
          || (body=calloc(1,sizeof(body_t)))==NULL
          || (errstr="Insuf. mem. for rootdir str")==NULL
          || (body->rootdir=strdup(rootdir))==NULL
          || (errstr="Error init bg")==NULL
          || (body->bg=bg_init(SIZEBGLOAD))==NULL
        ) {
                global_messagebox("%s",errstr);
                im_body_free(body),body=NULL;
                return(NULL);
        }
        body->rayui=rayui;
        FILLWHXY(body->whxy,w,h,x,y);
#if 1
fprintf(stderr,"im_body_init: %ix%i+%i+%i\n",UNROLLWHXY(body->whxy));
#endif
        body->leftsize=leftsidesize;
        body->ptrfont=rayui->font;
        body->ptrfontbig=rayui->fontbig;
        body->ptrfonthuge=rayui->fonthuge;
        if(body->rootdir[0]!='\0' && strcmp(body->rootdir,sep)!=0 && body->rootdir[strlen(body->rootdir)-1]==sep[0])
                body->rootdir[strlen(body->rootdir)-1]='\0'; /* rootdir doesn't need the final '/' */
        /* init rounded box glyph (a really big zero) */
        {
                int codepoints[]={'O','+','-'};
                body->roundedbox=LoadFontFromMemory(".ttf",rayui->defaultfontdata,rayui->sizedefaultfontdata,ADDREMOVEDIRDATAHEIGHT*4,codepoints,sizeof(codepoints)/sizeof(codepoints[0]));
        }
        return(body);
}

void
im_body_free(body_t *body)
{
        int i;
        dirdata_t *dirdata;
        if(body==NULL)
                return; /* nothing to do */
        if(body->dirdata!=NULL) {
                for(i=0;i<body->sizedirdata;i++) {
                        if((dirdata=body->dirdata[i])==NULL)
                                continue;
                        if(dirdata->dirname!=NULL)
                                free(dirdata->dirname),dirdata->dirname=NULL;
                        listing_freedata(&(dirdata->listing));
                        free(dirdata),dirdata=body->dirdata[i]=NULL;
                }
                free(body->dirdata),body->dirdata=NULL,body->sizedirdata=0;
        }
        texture_freedata(&(body->texture));
        texture_freedata(&(body->bigtexture));
        if(body->rootdir!=NULL)
                free(body->rootdir),body->rootdir=NULL;
        /* NOTE: Cannot call UnloadFont(body->roundedbox) as the data was not malloc'd; see https://github.com/raysan5/raylib/blob/master/examples/others/embedded_files_loading.c */
        if(body->bg!=NULL)
                bg_free(body->bg),body->bg=NULL;
        free(body),body=NULL;
        return;
}

int
im_body_add(body_t *body,char *dir)
{
        int i;
        dirdata_t *dirdata;
        if(body==NULL || dir==NULL)
                return(-1); /* sanity check failed */
        for(i=0;i<body->sizedirdata;i++) {
                if(body->dirdata[i]==NULL)
                        break;
        }
        if(i==body->sizedirdata) {
                /* dirdata container full, add more slots */
                dirdata_t **newdirdata;
                if((newdirdata=realloc(body->dirdata,sizeof(dirdata_t *)*(body->sizedirdata+BLOCKDIRDATA)))==NULL)
                        return(-1); /* insuf. mem. */
                body->dirdata=newdirdata;
                memset(body->dirdata+body->sizedirdata,0,sizeof(dirdata_t *)*BLOCKDIRDATA);
                body->sizedirdata+=BLOCKDIRDATA;
        }
        if((dirdata=body->dirdata[i]=calloc(1,sizeof(dirdata_t)))==NULL
          || (dirdata->dirname=strdup(dir))==NULL
        ) {
                if(dirdata!=NULL)
                        free(dirdata),dirdata=body->dirdata[i]=NULL;
                return(-1); /* insuf. mem. */
        }
        dirdata->height=DEFAULTDIRDATAHEIGHT;
        dirdata->dirname=strdup(dir);
        listing_get(&(dirdata->listing),body->rootdir,dir,1);
        return(0);
}

int
im_body_tryselect(body_t *body, Vector2 mousepos)
{
        int i;
        listing_t *listing;
        listingdata_t *elem;
        if(body==NULL || body->currentdirdata<0 || body->currentdirdata>=body->sizedirdata || body->dirdata[body->currentdirdata]==NULL || body->currentdirdata>=body->sizedirdata)
                return(-1); /* sanity check failed */
        body->selectedpath[0]='\0';
        if(!is_global_insidewhxy(mousepos,&(body->whxy),0) || mousepos.x>body->leftsize)
                return(-1); /* outside leftside */
        listing=&(body->dirdata[body->currentdirdata]->listing);
        for(i=0;i<listing->usedelems;i++) {
                elem=listing->elems+i;
                if(is_global_insidewhxy(mousepos,&(elem->left.screenwhxy),0)) {
                        strncpy(body->selectedpath,elem->name,sizeof(body->selectedpath));
                        body->selectedpath[sizeof(body->selectedpath)-1]='\0';
                        return(0); /* found */
                }
        }
        return(-1); /* no thumbnail found for pos */
}

int
im_body_mouse(body_t *body, mousedata_t *mousedata)
{
        int i;
        char *ptr;
        dirdata_t *dirdata;
        listingdata_t *ld;
        int margin;
        if(body==NULL || mousedata==NULL || body->currentdirdata<0 || body->currentdirdata>=body->sizedirdata || body->dirdata[body->currentdirdata]==NULL)
                return(-1); /* sanity check error */
        dirdata=body->dirdata[body->currentdirdata];
        /* wheel */
        body->dirdata[body->currentdirdata]->leftscrollpos-=(int)mousedata->wheel.y*WHEELSTEP;
        if(is_rayui_scrolling(body->rayui,mousedata,"body.leftside",&(body->dirdata[body->currentdirdata]->leftscrollpos)))
                fprintf(stderr,"SCROLLING bosy.leftside newscrollpos:%i\n",body->dirdata[body->currentdirdata]->leftscrollpos); /* nothing to do, will fix coors for both wheel and scrolling in the next lines */
        body->dirdata[body->currentdirdata]->leftscrollpos=(body->dirdata[body->currentdirdata]->leftscrollpos<0)?0:body->dirdata[body->currentdirdata]->leftscrollpos;
        if(body->dirdata[body->currentdirdata]->leftscrollpos>dirdata->listing.lastleftwhxy.y)
                body->dirdata[body->currentdirdata]->leftscrollpos=dirdata->listing.lastleftwhxy.y;
        /* check if we have to process a click */
        if(mousedata->click_avail==0 || mousedata->lmbreleased==0)
                return(0); /* nothing else to do */
        /* show image in full screen */
        if(body->flag_drawbigtexture) {
                if(mousedata->lmbreleased)
                        body->flag_drawbigtexture=0;
                return(0); /* nothing else to do */
        }
        margin=body->ptrfont->height/4;
        if(body->is_displayingtexture && mousedata->lmbreleased && is_global_insidewhxy(mousedata->mousepos,&(body->texture.source),margin) && body->selectedpath[0]=='\0') {
                body->flag_drawbigtexture=1;
                return(0); /* nothing else to do */
        }
        /* leftside backbutton */
        if(is_global_insidewhxy(mousedata->mousepos,&(body->backwhxy),0)) {
                static char sep[]={SEP};
                if((ptr=strrchr(dirdata->dirname,sep[0]))!=NULL) {
                        /* previous dir */
                        *ptr='\0';
                } else {
                        /* root dir */
                        dirdata->dirname[0]='\0';
                }
                listing_get(&(dirdata->listing),body->rootdir,dirdata->dirname,1);
                body->dirdata[body->currentdirdata]->leftscrollpos=0;
                mousedata->click_avail=0;
                return(0);
        }
        /* leftside directories */
        for(i=0;i<dirdata->listing.usedelems;i++) {
                ld=dirdata->listing.elems+i;
                if(ld->name[0]!='d')
                        continue;
                if(is_global_insidewhxy(mousedata->mousepos,&(ld->left.screenwhxy),0)) {
                        char *newname,*oldprefix;
                        int l,l0,l1;
                        static char sep[]={SEP};
                        oldprefix=(dirdata->dirname[0]==SEP && dirdata->dirname[1]=='\0')?"":dirdata->dirname;
                        l0=strlen(oldprefix);
                        l1=strlen(ld->name+1);
                        l=l0+((l0>0)?1:0)+l1+1;
                        if((newname=malloc(l))==NULL)
                                return(-1); /* insuf. mem. */
                        if(l0>0) {
                                memcpy(newname,oldprefix,l0);
                                newname[l0]=sep[0];
                        }
                        memcpy(newname+l0+((l0>0)?1:0),ld->name+1,l1);
                        newname[l0+((l0>0)?1:0)+l1]='\0';
                        free(dirdata->dirname),dirdata->dirname=NULL;
                        dirdata->dirname=newname;
                        listing_get(&(dirdata->listing),body->rootdir,dirdata->dirname,1);
                        body->dirdata[body->currentdirdata]->leftscrollpos=0;
                        mousedata->click_avail=0;
                        return(0);
                }
        }
        /* dirdata processing */
        if(body->selectedpath[0]=='\0') {
                /* dirdata select */
                if(mousedata->mousepos.x>body->leftsize && mousedata->mousepos.y>body->whxy.y) {
                        int righty;
                        for(righty=body->whxy.y,i=0;i<body->sizedirdata;i++) {
                                if(body->dirdata[i]==NULL)
                                        continue;
                                if(mousedata->mousepos.y>=righty && mousedata->mousepos.y<(righty+body->dirdata[i]->height) && i!=body->currentdirdata) {
                                        body->currentdirdata=i;
                                        mousedata->click_avail=0;
                                        return(0);
                                }
                                righty+=body->dirdata[i]->height;
                        }
                }
                /* detect click on "add dirdata" button */
                if(is_global_insidewhxy(mousedata->mousepos,&(body->dirdataadd),0)) {
                        im_body_add(body,body->dirdata[body->currentdirdata]->dirname);
                        mousedata->click_avail=0;
                        return(0);
                }
                /* dirdata remove */
#warning TODO
        }
        /* unselect selectedpath */
        if(mousedata->lmbpressed==0 && body->selectedpath[0]!='\0') {
                if(mousedata->mousepos.x>body->leftsize && mousedata->mousepos.y>body->whxy.y) {
                        int righty;
                        for(righty=body->whxy.y,i=0;i<body->sizedirdata;i++) {
                                if(body->dirdata[i]==NULL)
                                        continue;
                                if(mousedata->mousepos.y>=righty && mousedata->mousepos.y<(righty+body->dirdata[i]->height) && i!=body->currentdirdata && strcmp(body->dirdata[i]->dirname,body->dirdata[body->currentdirdata]->dirname)!=0) {
#warning TODO
#if 1
fprintf(stderr,"WOULD MOVE \"%s/%s\" to \"%s\"\n",body->dirdata[body->currentdirdata]->dirname,body->selectedpath+1,body->dirdata[i]->dirname);
#endif
                                        mousedata->click_avail=0;
                                        break;
                                }
                                righty+=body->dirdata[i]->height;
                        }
                }
                body->selectedpath[0]='\0';
        }
        return(0);
}

#ifdef MYFUNC
#undef MYFUNC
#define MYFUNC "im_body_draw"
#endif

int
im_body_draw(body_t *body, mousedata_t *mousedata, int windowwidth, int windowheight, int *needs_nextredraw)
{
        int i,k,margin,righty;
        int lastx,lasty;
        dirdata_t *dirdata;
        listingdata_t *elem;
        Vector2 v2,m2;
        font_t *font,*fontbig,*fonthuge;
        int is_leftside;
        int flag_skiprightside;
        int flag_skipall;
        int xoff,yoff;
        thumb_t *thumb;
        char statustooltip[1024];
        char imageundermouse[1024];
        thumb_t *selectedthumb;
        IMDEBUG("Pre-sanitycheck");
        if(body==NULL)
                return(-1);
        font=body->ptrfont;
        fontbig=body->ptrfontbig;
        fonthuge=body->ptrfonthuge;
        FILLWHXY(body->backwhxy,0,0,0,0);
        margin=font->height/4;
        body->is_displayingtexture=0;
        selectedthumb=NULL;
        /* if we are displaying a full screenimage... */
        if(body->flag_drawbigtexture && body->texture.has_texture) {
                /* draw image in full screen */
                int maxw,maxh;
                IMDEBUG("begin draw");
                maxw=windowwidth;
                maxh=windowheight-body->whxy.y;
                if(body->bigtexture.has_texture==0 || strcmp(body->bigtexture.currentpath,body->texture.currentpath)!=0) {
#if 1
fprintf(stderr,"~~~ TEXTURE_LOAD FOR BIGTEXTURE \"%s\"\n",body->texture.currentpath);
#endif
                        bg_add(body->bg,body->texture.currentpath,1);
                        texture_load(&(body->bigtexture),body->texture.currentpath,maxw,maxh,body->bg);
                }
                if(body->bigtexture.has_texture && strcmp(body->bigtexture.currentpath,body->texture.currentpath)==0) {
                        int x0,y0;
                        x0=0;
                        y0=body->whxy.y;
                        texture_draw(&(body->bigtexture),x0,y0,maxw,maxh);
                        return(0); /* all done */
                } else {
#if 0
#warning TEMP
                        body->flag_drawbigtexture=0; /* error loading big texture, draw screen normally */
#endif
                }
        }
        /* calculate positions */
        IMDEBUG("calculate positions");
        for(i=0;i<body->sizedirdata;i++) {
                if(body->dirdata[i]==NULL)
                        continue;
                if(body->dirdata[i]->listing.has_rightwhxy==0) {
                        int sidelen;
                        sidelen=body->dirdata[i]->height-(fontbig->height+fontbig->height/4+font->height+margin*4+fontbig->height/4);
                        listing_fillwhxy(&(body->dirdata[i]->listing),body->ptrfont,body->whxy.w-(body->whxy.x+body->leftsize),sidelen,0,0);
                        body->dirdata[i]->listing.has_rightwhxy=1;
                }
                if(i==body->currentdirdata && body->dirdata[i]->listing.has_leftwhxy==0) {
                        listing_fillwhxy(&(body->dirdata[i]->listing),body->ptrfont,body->leftsize,LEFTIMAGESIDELEN,1,0);
                        body->dirdata[i]->listing.has_leftwhxy=1;
                }
        }
        /* draw left side background */
        IMDEBUG("draw left bg");
        {
                whxy_t whxy;
                FILLWHXY(whxy,body->leftsize, body->whxy.h,body->whxy.x,body->whxy.y);
                rayui_scrollablerectadd(body->rayui,mousedata,&whxy,"body.leftside",body->dirdata[body->currentdirdata]->leftscrollpos,(int (*)(void *, Vector2))im_body_tryselect,body,SCROLLTHRESHOLD);
        }
        DrawRectangle(body->whxy.x,body->whxy.y,body->leftsize, body->whxy.h, (Color){ 215, 215, 215, 255 } );
        IMDEBUG2((stderr,"%ix%i+%i+%i\n",body->leftsize, body->whxy.h,body->whxy.x,body->whxy.y));
        /* draw right side background */
        IMDEBUG2((stderr,"body->whxy:%ix%i+%i+%i\n",UNROLLWHXY(body->whxy)));
        IMDEBUG2((stderr,"body->leftsize:%i\n",body->leftsize));
        IMDEBUG("draw right bg");
        DrawRectangle(body->whxy.x+body->leftsize,body->whxy.y,body->whxy.w-body->leftsize, body->whxy.h, (Color){ 227, 227, 227, 255 } );
        IMDEBUG2((stderr,"DrawRectangle(body->whxy.x(%i)+body->leftsize(%i),body->whxy.y(%i),body->whxy.w(%i)-body->leftsize(%i), body->whxy.h(%i), (Color){ 227, 227, 227, 255 } );\n"
        ,body->whxy.x,body->leftsize,body->whxy.y,body->whxy.w,body->leftsize, body->whxy.h));
        IMDEBUG2((stderr,"%ix%i+%i+%i\n",body->whxy.w-body->leftsize, body->whxy.h,body->whxy.x+body->leftsize,body->whxy.y));
        /* reset lazy load marks */
        bg_resetmarks(body->bg);
        /* first pass, draw leftside, second pass, draw all of rightside */
        statustooltip[0]='\0';
        for(is_leftside=1,flag_skiprightside=flag_skipall=0;is_leftside>=0 && flag_skiprightside==0 && flag_skipall==0;is_leftside--) {
                for(i=(is_leftside)?body->currentdirdata:0,righty=body->whxy.y;flag_skipall==0 && ((is_leftside && i==body->currentdirdata) || (!is_leftside && i<body->sizedirdata));i++) {
                        int sidelen;
                        if((dirdata=body->dirdata[i])==NULL)
                                continue;
                        if(is_leftside && !(i==body->currentdirdata))
                                continue; /* this element is not in leftside */
                        sidelen=(is_leftside)?LEFTIMAGESIDELEN:dirdata->height-(fontbig->height+fontbig->height/4+font->height+margin*4+fontbig->height/4);
                        /* draw left side back arrow if is_leftside and not in root dir */
                        if(is_leftside && !(dirdata->dirname[0]=='\0' || (dirdata->dirname[0]==SEP && dirdata->dirname[1]=='\0'))) {
                                m2=MeasureTextEx(fontbig->font,UTF8DOWNARROW,fontbig->height,0);
                                v2.x=(float) (body->whxy.x+fontbig->height/2);
                                v2.y=(float) (body->whxy.y+fontbig->height/4+(fontbig->height-v2.x)/2)-body->dirdata[body->currentdirdata]->leftscrollpos;
                                FILLWHXY(body->backwhxy,m2.x+fontbig->height/4,m2.x+fontbig->height/4,v2.x-fontbig->height/4,v2.y-fontbig->height/8);
#if 0
DrawTexture(fontbig->font.texture, 0, 0, WHITE); /* font glyphs -- see https://github.com/raysan5/raylib/issues/2022 */
DrawRectangle(UNROLLXYWH(body->backwhxy),((Color){ 0,255,0,255 })); /* hit zone */
#endif
                                if((v2.y+fontbig->height)>=0)
                                        DrawTextPro(fontbig->font,UTF8DOWNARROW,(Vector2){v2.x+m2.x,v2.y},(Vector2){0,0},90.0,fontbig->height,0,(Color){65,65,65,255});
                        }
                        if(is_leftside) {
                                /* ...dirname */
                                v2.y=(float) (body->whxy.y+fontbig->height/4-body->dirdata[body->currentdirdata]->leftscrollpos);
                                if((v2.y+fontbig->height)>=0) {
                                        m2=MeasureTextEx(fontbig->font,dirdata->dirname,fontbig->height,0);
                                        v2.x=(float) (body->whxy.x+fontbig->height/2)+(body->leftsize-(body->whxy.x+fontbig->height)-m2.x)/2;
                                        DrawTextEx(fontbig->font
                                          ,dirdata->dirname
                                          ,v2
                                          ,fontbig->height
                                          ,0
                                          ,(Color){ 65, 65, 65, 255 }
                                        );
                                }
                        } else { /* rightside */
                                /* ...bg */
                                DrawRectangle(body->whxy.x+body->leftsize,righty,body->whxy.w-body->leftsize, dirdata->height,(i==body->currentdirdata)?((Color){ 168, 168, 168, 255 }):((Color){ 227, 227, 227, 255 }));
                                /* ...bottom separator */
                                DrawRectangle(body->whxy.x+body->leftsize,righty+dirdata->height-1,body->whxy.w-body->leftsize, 1, (Color){ 221, 221, 221, 255 } );
                                /* ...dirname */
                                DrawTextEx(fontbig->font
                                  ,dirdata->dirname
                                  ,(Vector2) {body->whxy.x+body->leftsize+fontbig->height/2,righty+fontbig->height/4}
                                  ,fontbig->height
                                  ,0
                                  ,(i==body->currentdirdata)?((Color){ 240, 240, 240, 255 }):((Color){ 65, 65, 65, 255 })
                                );
                        }
                        /* directories */
                        xoff=((is_leftside)?0:body->leftsize);
                        yoff=((is_leftside)?body->whxy.y:righty)+fontbig->height/4+fontbig->height+font->height/4-(is_leftside?body->dirdata[body->currentdirdata]->leftscrollpos:0);
                        if(is_leftside && dirdata->dirname[0]=='\0')
                                yoff-=fontbig->height/4+fontbig->height;
                        for(k=0,lastx=lasty=0;k<dirdata->listing.usedelems;k++) {
                                elem=dirdata->listing.elems+k;
                                thumb=(is_leftside)?&(elem->left):&(elem->right);
                                if(elem->name[0]!='d' || strcmp(elem->name,"d.")==0 || strcmp(elem->name,"d..")==0) {
                                        if(elem->name[0]=='d')
                                                FILLWHXY(thumb->screenwhxy,0,0,0,0);
                                        continue;
                                }
                                if((thumb->whxy.y+yoff)>(body->whxy.y+body->whxy.h) || thumb->whxy.w==0 || thumb->whxy.h==0) {
#warning TODO: if !is_leftside, draw "..." in huge font using lastx,lasty as reference
                                        break;
                                }
                                if((thumb->whxy.y+yoff+thumb->whxy.h)<0) {
                                        FILLWHXY(thumb->screenwhxy,0,0,0,0);
                                        continue;
                                }
                                FILLWHXY(thumb->screenwhxy,thumb->whxy.w,thumb->whxy.h,thumb->whxy.x+xoff,thumb->whxy.y+yoff);
                                DrawRectangleLines(thumb->screenwhxy.x,thumb->screenwhxy.y,thumb->screenwhxy.w,thumb->screenwhxy.h,((Color){ 65, 65, 65, 255 }));
                                DrawTextEx(font->font,elem->name+1,(Vector2){thumb->screenwhxy.x+margin,thumb->screenwhxy.y+margin},font->height,0,((Color){ 65, 65, 65, 255 }));
                                lastx=thumb->screenwhxy.x+thumb->screenwhxy.w,lasty=thumb->screenwhxy.y+thumb->screenwhxy.h;
                        }
                        for(;k<dirdata->listing.usedelems;k++) {
                                elem=dirdata->listing.elems+k;
                                if(elem->name[0]!='d')
                                        continue;
                                thumb=(is_leftside)?&(elem->left):&(elem->right);
                                FILLWHXY(thumb->screenwhxy,0,0,0,0);
                        }
                        /* files */
                        imageundermouse[0]='\0';
                        for(k=0,lastx=lasty=0;k<dirdata->listing.usedelems;k++) {
                                Texture2D *te;
                                whxy_t *whxy;
                                elem=dirdata->listing.elems+k;
                                thumb=(is_leftside)?&(elem->left):&(elem->right);
                                whxy=&(thumb->whxy);
                                te=&(thumb->texture);
                                int has_imagedrawn;
                                if(elem->name[0]!='f' && elem->name[0]!='l')
                                        continue;
                                if((whxy->y+yoff)>(body->whxy.y+body->whxy.h) || whxy->w==0 || whxy->h==0) {
#warning TODO: if !is_leftside, draw "..." in huge font using lastx,lasty as reference
                                        break;
                                }
                                if(is_leftside && is_global_insidewhxy(mousedata->mousepos,&(thumb->screenwhxy),margin)) {
                                        strncpy(statustooltip,elem->name+1,sizeof(statustooltip));
                                        statustooltip[sizeof(statustooltip)-1]='\0';
                                }
                                /* show image */
                                has_imagedrawn=0;
                                if(is_imagefilename(elem->name+1) && !(thumb->screenwhxy.y>(body->whxy.y+body->whxy.h) || (thumb->screenwhxy.y+thumb->screenwhxy.h)<body->whxy.y)) {
                                        if(is_global_insidewhxy(mousedata->mousepos,&(thumb->screenwhxy),0)) {
                                                snprintf(imageundermouse,sizeof(imageundermouse),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1);
                                                imageundermouse[sizeof(imageundermouse)-1]='\0';
                                        }
                                        if(thumb->has_texture==0 && thumb->has_failedload==0) {
                                                bgload_t *bgload;
                                                char fullpath[2048];
                                                *needs_nextredraw=1;
                                                snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1);
                                                fullpath[sizeof(fullpath)-1]='\0';
                                                bgload=bg_get(body->bg,fullpath);
                                                if(bgload!=NULL && bgload->has_data!=0) {
                                                        Image im;
                                                        im=ImageCopy(bgload->image);
                                                        if(IsImageValid(im)) {
                                                                int neww,newh;
                                                                Image im2,impixel;
#if 1
fprintf(stderr,"Loaded %s\n",fullpath);
{
int oldw=im.width,oldh=im.height;
#endif
                                                                im2=GenImageColor(sidelen,sidelen,(Color){0,0,0,255});
                                                                imutil_aspectmaximize(im.width,im.height,sidelen,sidelen,&neww,&newh);
                                                                ImageResize(&im,neww,newh);
                                                                impixel=ImageCopy(im);
                                                                ImageResize(&impixel,1,1);
#if 1
if(neww>sidelen || newh>sidelen)
fprintf(stderr,"elem:\"%s\" sidelen:%i old:%ix%i new:%ix%i\n",elem->name+1,sidelen,oldw,oldh,neww,newh);
}
#endif
                                                                ImageDraw(&im2,impixel,(Rectangle) {0,0,1,1},(Rectangle) {0,0,sidelen,sidelen},(Color){255,255,255,255});
                                                                ImageDraw(&im2,im,(Rectangle) {0,0,neww,newh},(Rectangle) {(sidelen-neww)/2,(sidelen-newh)/2,neww,newh},(Color){255,255,255,255});
                                                                *te=LoadTextureFromImage(im2);
                                                                UnloadImage(im);
                                                                UnloadImage(im2);
                                                                thumb->has_texture=1;
                                                        } else {
                                                                thumb->has_failedload=1;
                                                        }
                                                } else if(bgload==NULL) {
                                                        bg_add(body->bg,fullpath,0);
                                                }
                                        }
                                        if(thumb->has_texture!=0) {
                                                FILLWHXY(thumb->screenwhxy,whxy->w,whxy->h,whxy->x+xoff,whxy->y+yoff);
                                                DrawTexture(*te,thumb->screenwhxy.x,thumb->screenwhxy.y,WHITE);
                                                has_imagedrawn=1;
                                                lastx=whxy->x+whxy->w,lasty=whxy->y+whxy->h+yoff;
                                                if(is_leftside && mousedata->rmbdown==0 && is_global_insidewhxy(mousedata->mousepos,&(thumb->screenwhxy),margin) && mousedata->scrollspeed==0) {
                                                        /* draw image in rightside */
                                                        char fullpath[2048];
                                                        int maxw,maxh;
                                                        maxw=windowwidth-(body->leftsize-DEFAULTDIRDATATRIANGLEW);
                                                        maxh=windowheight-body->whxy.y;
                                                        snprintf(fullpath,sizeof(fullpath),"%s/%s/%s",body->rootdir,dirdata->dirname,elem->name+1);
                                                        fullpath[sizeof(fullpath)-1]='\0';
                                                        if((body->texture.has_texture==0 && !(body->texture.has_failedload && strcmp(body->texture.currentpath,fullpath)==0))
                                                          || strcmp(body->texture.currentpath,fullpath)!=0
                                                        ) {
                                                                texture_load(&(body->texture),fullpath,maxw,maxh,body->bg);
                                                        }
                                                        if(body->texture.has_texture && strcmp(body->texture.currentpath,fullpath)==0) {
                                                                int x0,y0;
                                                                x0=body->leftsize-DEFAULTDIRDATATRIANGLEW;
                                                                y0=body->whxy.y;
                                                                texture_draw(&(body->texture),x0,y0,maxw,maxh);
                                                                body->is_displayingtexture=1;
                                                                memcpy(&(body->texture.source),&(thumb->screenwhxy),sizeof(body->texture.source));
                                                                flag_skiprightside=1;
                                                        }
                                                }
                                        }
                                }
                                if(has_imagedrawn==0) {
                                        char *ptr;
                                        char shortname[1024];
                                        whxy_t *pos;
                                        int l;
                                        font_t *myfont=(is_leftside)?fonthuge:font;
                                        pos=is_leftside?&(elem->left.whxy):&(elem->right.whxy);
                                        FILLWHXY(thumb->screenwhxy,whxy->w,whxy->h,whxy->x+xoff,whxy->y+yoff);
                                        DrawRectangle(thumb->screenwhxy.x,thumb->screenwhxy.y,thumb->screenwhxy.w,thumb->screenwhxy.h,((Color){0,0,0,64}));
                                        if((ptr=strchr(elem->name+1,'.'))!=NULL) {
                                                m2=MeasureTextEx(myfont->font,ptr,myfont->height,0);
                                                DrawTextEx(myfont->font,ptr,(Vector2){whxy->x+xoff+(sidelen-m2.x)/2,whxy->y+yoff+(font->height)/2+(whxy->w-myfont->height)/2},myfont->height,0,(Color){ 0,0,0,96 });
                                        }
                                        ptr=(ptr==NULL)?elem->name+1:ptr;
                                        l=(ptr-(elem->name+1));
                                        l=(l>=sizeof(shortname))?sizeof(shortname)-1:l;
                                        memcpy(shortname,elem->name+1,l);
                                        shortname[l]='\0';
                                        DrawRectangle(pos->x+xoff,pos->y+yoff,pos->w,font->height+font->height/2,((Color){0,0,0,64}));
                                        DrawTextEx(font->font,shortname,(Vector2){pos->x+xoff+font->height/4,pos->y+yoff+font->height/4},font->height,0,(Color){ 192,192,192,255 });
                                        lastx=whxy->x+xoff+whxy->w,lasty=whxy->y+yoff+whxy->h;
                                }
                                /* show selected rectangle */
                                if(is_leftside && strcmp(body->selectedpath,elem->name)==0) {
                                        Color c=((Color){0,0,255,64});
                                        DrawRectangle(thumb->screenwhxy.x,thumb->screenwhxy.y,thumb->screenwhxy.w,thumb->screenwhxy.h,c);
                                        c=((Color){0,0,255,255});
                                        DrawRectangle(thumb->screenwhxy.x,thumb->screenwhxy.y,thumb->screenwhxy.w,2,c);
                                        DrawRectangle(thumb->screenwhxy.x,thumb->screenwhxy.y+2,2,thumb->screenwhxy.h-4,c);
                                        DrawRectangle(thumb->screenwhxy.x+thumb->screenwhxy.w-2,thumb->screenwhxy.y+2,2,thumb->screenwhxy.h-4,c);
                                        DrawRectangle(thumb->screenwhxy.x,thumb->screenwhxy.y+thumb->screenwhxy.h-2,thumb->screenwhxy.w,2,c);
                                        selectedthumb=thumb;
                                }
                        }
                        if(imageundermouse[0]!='\0')
                                bg_add(body->bg,imageundermouse,0); /* to prioritise loading this image */
                        for(;k<dirdata->listing.usedelems;k++) {
                                elem=dirdata->listing.elems+k;
                                if(elem->name[0]!='f' && elem->name[0]!='l')
                                        continue;
                                thumb=(is_leftside)?&(elem->left):&(elem->right);
                                FILLWHXY(thumb->screenwhxy,0,0,0,0);
                        }
                        /* ...finishing touchs */
                        if(is_leftside) {
                                ;
                        } else {
                                if(i==body->currentdirdata) {
                                        /* draw right side "current" marker inside left side area */
                                        DrawTriangle((Vector2){((float)body->whxy.x)+body->leftsize,((float)righty)}, (Vector2){((float)body->whxy.x)+body->leftsize-DEFAULTDIRDATATRIANGLEW,((float)righty)+dirdata->height/2}, (Vector2){((float)body->whxy.x)+body->leftsize,((float)righty)+dirdata->height-1}, (Color){ 168, 168, 168, 255 } );
                                } else if(body->selectedpath[0]!='\0' && mousedata->mousepos.x>body->leftsize && mousedata->mousepos.y>=righty && mousedata->mousepos.y<(righty+dirdata->height) && strcmp(body->dirdata[i]->dirname,body->dirdata[body->currentdirdata]->dirname)!=0) {
                                        whxy_t whxy;
                                        Color c=((Color){0,0,255,64});
                                        FILLWHXY(whxy,body->whxy.w-body->leftsize,dirdata->height,body->leftsize,righty);
                                        DrawRectangle(whxy.x,whxy.y,whxy.w,whxy.h,c);
                                        c=((Color){0,0,255,255});
                                        DrawRectangle(whxy.x,whxy.y,whxy.w,2,c);
                                        DrawRectangle(whxy.x,whxy.y+2,2,whxy.h-4,c);
                                        DrawRectangle(whxy.x+whxy.w-2,whxy.y+2,2,whxy.h-4,c);
                                        DrawRectangle(whxy.x,whxy.y+whxy.h-2,whxy.w,2,c);
                                }
                                /* advance to next element */
                                righty+=dirdata->height;
                        }
                }
        }
        if(flag_skiprightside==0 && (righty+DEFAULTDIRDATAHEIGHT)<=(body->whxy.y+body->whxy.h)) {
                GlyphInfo gi;
                int xoff;
                int margin;
                margin=20;
                FILLWHXY(body->dirdataadd,(body->whxy.x+body->whxy.w-ADDREMOVEDIRDATAHEIGHT-margin),righty+margin,ADDREMOVEDIRDATAHEIGHT,ADDREMOVEDIRDATAHEIGHT);
                gi=GetGlyphInfo(body->roundedbox,'O');
                xoff=0;
                DrawTexturePro(body->roundedbox.texture
                  ,(Rectangle){0.0+xoff,0.0,(float)gi.image.width+5,(float)gi.image.height+2}
                  ,(Rectangle) { (float) body->dirdataadd.x, (float) body->dirdataadd.y, (float) body->dirdataadd.w, (float)body->dirdataadd.h}
                  ,(Vector2){0.0,0.0}
                  ,0.0
                  ,(Color){ 168, 168, 168, 255 }
                );
                DrawRectangle(body->dirdataadd.x+13+12,body->dirdataadd.y+13,body->dirdataadd.w-26-24, (float)body->dirdataadd.h-26,(Color){ 168, 168, 168, 255 });
                DrawRectangle(body->dirdataadd.x+13,body->dirdataadd.y+13+12,body->dirdataadd.w-26, (float)body->dirdataadd.h-26-24,(Color){ 168, 168, 168, 255 });
                xoff+=gi.image.width+4;
                gi=GetGlyphInfo(body->roundedbox,'+');
                DrawTexturePro(body->roundedbox.texture
                  ,(Rectangle){xoff,0.0,(float)gi.image.width,(float)gi.image.height}
                  ,(Rectangle) { (float) body->dirdataadd.x+30-2, (float) body->dirdataadd.y+30, (float) body->dirdataadd.w-60, (float)body->dirdataadd.h-60}
                  ,(Vector2){0.0,0.0}
                  ,0.0
                  ,(Color){ 227, 227, 227, 255 }
                );
        } else {
                memset(&(body->dirdataadd),0,sizeof(body->dirdataadd));
        }
        /* if moving elem, show line from orig to mousedata->mousepos */
        if(body->selectedpath[0]!='\0' && selectedthumb!=NULL) {
                Color c=((Color){0,0,255,255});
                DrawLineEx((Vector2){(float)(selectedthumb->screenwhxy.x+selectedthumb->screenwhxy.w/2),(float)(selectedthumb->screenwhxy.y+selectedthumb->screenwhxy.h/2)},mousedata->mousepos,2.0,c);
        }
        /* show tooltip */
        if(statustooltip[0]!='\0') {
                m2=MeasureTextEx(font->font,statustooltip,font->height,0);
                DrawRectangle(0,windowheight-1-margin*2-font->height,m2.x+margin*2,font->height+margin*2,((Color){0,0,0,96}));
                DrawTextEx(font->font,statustooltip,(Vector2){margin,windowheight-1-margin-font->height},font->height,0,(Color){ 255,255,255,128 });
        }
        /* free not used bg load items (only if idle, as it takes time) */
        if(mousedata->scrollspeed==0 && *needs_nextredraw==0)
                bg_freeunmarked(body->bg);
        return(0);
}

static int
strptrcmp(void *a,void *b)
{
        return(strcmp(((listingdata_t *)a)->name,((listingdata_t *)b)->name));
}

#ifdef MYFUNC
#undef MYFUNC
#define MYFUNC "listing_get"
#endif
int
listing_get(listing_t *listing, char *pathprefix, char *parampath, int flag_sort)
{
        int i,l;
        DIR *d;
        struct dirent *de;
        unsigned char dtype;
        char path[1024];
        listingdata_t *ld;
        if(listing==NULL)
                return(-1); /* sanity check failed */
        /* free old textures */
        for(i=0;i<listing->usedelems;i++) {
                ld=listing->elems+i;
                if(ld->left.has_texture) {
                        UnloadTexture(ld->left.texture);
                        ld->left.has_texture=0;
                        ld->left.has_failedload=0;
                }
                if(ld->right.has_texture) {
                        UnloadTexture(ld->right.texture);
                        ld->right.has_texture=0;
                        ld->right.has_failedload=0;
                }
        }
        /* reset struct */
        listing->usedelems=listing->usedbuf=0;
        memset(listing->elems,0,sizeof(listingdata_t)*listing->sizeelems);
        listing->has_leftwhxy=listing->has_rightwhxy=0;
        /* fill listing */
        if(pathprefix==NULL && parampath==NULL)
                return(-1); /* nothing to fill */
        snprintf(path,sizeof(path),"%s%c%s",(pathprefix!=NULL)?pathprefix:"",SEP,(parampath!=NULL)?parampath:"");
        path[sizeof(path)-1]='\0';
        if((d=opendir(path))==NULL)
                return(-1); /* dir not found */
        while((de=readdir(d))!=NULL) {
                l=strlen(de->d_name);
                dtype='u';
#ifdef _DIRENT_HAVE_D_TYPE
                dtype=(de->d_type==DT_BLK)?'b'
                      :(de->d_type==DT_CHR)?'c'
                      :(de->d_type==DT_DIR)?'d'
                      :(de->d_type==DT_FIFO)?'p'
                      :(de->d_type==DT_LNK)?'l'
                      :(de->d_type==DT_REG)?'f'
                      :(de->d_type==DT_SOCK)?'s'
                      :'u';
#endif
                if(dtype=='u') {
                        char stpath[2048];
                        struct stat st;
                        mode_t m;
                        snprintf(stpath,sizeof(stpath),"%s%c%s",path,SEP,de->d_name);
                        stpath[sizeof(stpath)-1]='\0';
                        if(stat(stpath,&st)==0 && (m=(st.st_mode&S_IFMT))!=0) {
                                dtype=(m==S_IFBLK)?'b'
                                      :(m==S_IFCHR)?'c'
                                      :(m==S_IFDIR)?'d'
                                      :(m==S_IFIFO)?'p'
#ifdef S_IFLNK
                                      :(m==S_IFLNK)?'l'
#endif
                                      :(m==S_IFREG)?'f'
#ifdef S_IFSOCK
                                      :(m==S_IFSOCK)?'s'
#endif
                                      :'u';
                        }
                }
                /* check for space for this elem (and get space if necessary) */
                if(listing->usedelems==listing->sizeelems) {
                        listingdata_t *newelems;
                        if((newelems=realloc(listing->elems,sizeof(listingdata_t)*(listing->sizeelems+BLOCKLISTINGELEMS)))==NULL) {
                                closedir(d),d=NULL;
                                return(-1); /* insuf. mem. */
                        }
                        listing->elems=newelems;
                        memset(listing->elems+listing->sizeelems,0,sizeof(listingdata_t)*BLOCKLISTINGELEMS);
                        listing->sizeelems+=BLOCKLISTINGELEMS;
                }
                /* check for space for this elem data (and get space if necessary) */
                if((listing->sizebuf+l+2)>listing->sizebuf) {
                        int i;
                        char *newbuf;
                        int newsize;
                        int off;
                        newsize=listing->sizebuf+l+2+BLOCKLISTINGBUF-1;
                        newsize/=BLOCKLISTINGBUF;
                        newsize*=BLOCKLISTINGBUF;
                        if((newbuf=realloc(listing->buf,newsize))==NULL) {
                                closedir(d),d=NULL;
                                return(-1); /* insuf. mem. */
                        }
                        /* the elem data buffer has a new pointer, fix previous entries */
                        for(i=0;i<listing->usedelems;i++) {
                                off=(listing->elems[i].name)-listing->buf;
                                listing->elems[i].name=newbuf+off;
                        }
                        listing->buf=newbuf;
                        listing->sizebuf+=BLOCKLISTINGELEMS;
                }
                /* store the data */
                listing->elems[listing->usedelems++].name=listing->buf+listing->usedbuf;
                listing->buf[listing->usedbuf++]=dtype;
                memcpy(listing->buf+listing->usedbuf,de->d_name,l+1);
IMDEBUG(listing->buf+listing->usedbuf);
                listing->usedbuf+=l+1;
        }
        closedir(d),d=NULL;
        if(flag_sort)
                qsort(listing->elems,listing->usedelems,sizeof(listingdata_t ),(int (*)(const void *, const void *)) strptrcmp);
        return(0);
}

void
listing_freedata(listing_t *listing)
{
        if(listing==NULL)
                return; /* nothing to do */
        if(listing->elems!=NULL) {
                int i;
                listingdata_t *ld;
                for(i=0;i<listing->usedelems;i++) {
                        ld=listing->elems+i;
                        if(ld->left.has_texture) {
                                UnloadTexture(ld->left.texture);
                                ld->left.has_texture=0;
                                ld->left.has_failedload=0;
                        }
                        if(ld->right.has_texture) {
                                UnloadTexture(ld->right.texture);
                                ld->right.has_texture=0;
                                ld->right.has_failedload=0;
                        }
                }
                free(listing->elems),listing->elems=NULL,listing->sizeelems=listing->usedelems=0;
        }
        if(listing->buf!=NULL)
                free(listing->buf),listing->buf=NULL,listing->sizebuf=listing->usedbuf=0;
        return;
}

int
listing_fillwhxy(listing_t *listing, font_t *font, int w, int sidelen, int is_leftside, int rightsidemargin)
{
        int x0,y0,x1;
        int k,x,y;
        Vector2 m2;
        int margin;
        listingdata_t *elem;
        thumb_t *thumb;
        if(listing==NULL || font==NULL)
                return(-1); /* sanity check failed */
        margin=font->height/4;
        if(is_leftside)
                memset(&(listing->lastleftwhxy),0,sizeof(listing->lastleftwhxy));
        /* directories */
        if(is_leftside) {
                x0=font->height/2;
                x1=w-DEFAULTDIRDATATRIANGLEW-font->height/2;
                y0=0;
        } else {
                x0=rightsidemargin;
                x1=w-font->height/2;
                y0=0;
        }
        for(k=0,x=x0,y=y0;k<listing->usedelems;k++) {
                elem=listing->elems+k;
                thumb=(is_leftside)?&(elem->left):&(elem->right);
                if(elem->name[0]!='d' || strcmp(elem->name,"d.")==0 || strcmp(elem->name,"d..")==0)
                        continue;
                m2=MeasureTextEx(font->font,elem->name+1,font->height,0);
                if((x+margin*2+m2.x)>x1 && !(is_leftside==0 && y>y0))
                        x=x0,y+=font->height+margin*2+font->height/4;
                if(is_leftside==0 && y>y0) {
                        FILLWHXY(thumb->whxy,0,0,0,0);
                        continue;
                }
                FILLWHXY(thumb->whxy,margin*2+m2.x,margin*2+font->height,x,y);
                if(is_leftside && (thumb->whxy.y+thumb->whxy.h)>(listing->lastleftwhxy.y+listing->lastleftwhxy.h))
                        memcpy(&(listing->lastleftwhxy),&(thumb->whxy),sizeof(whxy_t));
                x+=margin*2+m2.x+font->height/4;
        }
        y+=((x==x0)?0:font->height+margin*2+font->height/4);
        /* files */
        if(is_leftside)
                y0=y;
        else
                y0+=margin*2+font->height+margin;
        for(k=0,x=x0,y=y0;k<listing->usedelems;k++) {
                elem=listing->elems+k;
                thumb=(is_leftside)?&(elem->left):&(elem->right);
                if(elem->name[0]!='f' && elem->name[0]!='l')
                        continue;
                if(!is_leftside && y>y0) {
                        FILLWHXY(thumb->whxy,0,0,0,0);
                        continue;
                }
                if((x+margin*2+sidelen)>x1 && !(is_leftside==0 && y>y0))
                        x=x0,y+=sidelen+margin*2;
                if(is_leftside==0 && y>y0) {
                        FILLWHXY(thumb->whxy,0,0,0,0);
                        continue;
                }
                FILLWHXY(thumb->whxy,sidelen,sidelen,x,y);
                if(is_leftside && (thumb->whxy.y+thumb->whxy.h)>(listing->lastleftwhxy.y+listing->lastleftwhxy.h))
                        memcpy(&(listing->lastleftwhxy),&(thumb->whxy),sizeof(whxy_t));
                x+=margin*2+sidelen;
        }
        return(0);
}

int
is_imagefilename(char *filename)
{
        char *ptr;
        int i;
        char *knownext[]={".jpg",".jpeg",".JPG",".JPEG",".Jpg",".Jpeg"
                         ,".png",".PNG",".Png"
                         ,".tga",".TGA",".Tga"
                         ,".bmp",".BMP",".Bmp"
                         ,".psd",".PSD",".Psd"
                         ,".gif",".GIF",".Gif"
                         ,".hdr",".HDR",".Hdr"
                         ,".pic",".PIC",".Pic"
                         ,".pnm",".PNM",".Pnm"
        };
        if(filename==NULL)
                return(0); /* sanity check failed */
        if((ptr=strrchr(filename,'.'))==NULL)
                return(0); /* no extension found */
        for(i=0;i<(sizeof(knownext)/sizeof(knownext[0]));i++) {
                if(strcmp(ptr,knownext[i])==0)
                        return(1); /* it has a known ext */
        }
        return(0); /* not in the knownext list */
}

int
texture_load(texture_t *texture, char *fullpath, int maxw, int maxh, bg_t *bg)
{
        Image im;
        int neww,newh;
        bgload_t *bgload;
        if(texture==NULL || fullpath==NULL)
                return(-1); /* sanity check failed */
        if(texture->has_texture) {
                UnloadTexture(texture->texture);
                texture->currentpath[0]='\0';
                texture->has_texture=0;
                texture->has_failedload=0;
        }
        if(bg!=NULL && (bgload=bg_get(bg,texture->currentpath))!=NULL && bgload->has_data!=0) {
                im=ImageCopy(bgload->image);
        } else {
#if 0
#warning TEST CODE
                return(-1); /* if not loaded in background, return -1 to indicate "not ready" */
#else
                im=global_loadimage(fullpath);
#endif
        }
        if(IsImageValid(im)) {
                imutil_aspectmaximize(im.width,im.height,maxw,maxh,&neww,&newh);
                ImageResize(&im,neww,newh);
                texture->texture=LoadTextureFromImage(im);
                UnloadImage(im);
                strncpy(texture->currentpath,fullpath,sizeof(texture->currentpath));
                texture->currentpath[sizeof(texture->currentpath)-1]='\0';
                texture->has_texture=1;
                texture->has_failedload=0;
                texture->texturew=neww;
                texture->textureh=newh;
        } else {
                strncpy(texture->currentpath,fullpath,sizeof(texture->currentpath));
                texture->currentpath[sizeof(texture->currentpath)-1]='\0';
                texture->has_texture=0;
                texture->has_failedload=1;
        }
        return(0);
}

int
texture_draw(texture_t *texture, int x0, int y0, int maxw, int maxh)
{
        if(texture==NULL || texture->has_texture==0)
                return(-1); /* sanity check failed */
        DrawRectangle(x0,y0,maxw,maxh,(Color){ 215, 215, 215, 255 } );
        DrawTexture(texture->texture,x0+(maxw-texture->texturew)/2,y0+(maxh-texture->textureh)/2,WHITE);
        return(0);
}

int
texture_freedata(texture_t *texture)
{
        if(texture==NULL)
                return(-1); /* sanity check failed */
        if(texture->has_texture) {
                UnloadTexture(texture->texture);
                texture->currentpath[0]='\0';
                texture->has_texture=0;
                texture->has_failedload=0;
        }
        return(0);
}

int
imutil_aspectmaximize(int w, int h, int maxw, int maxh, int *neww, int *newh)
{
        if(neww==NULL || newh==NULL || w==0 || h==0 || maxw==0 || maxh==0)
                return(-1);
        if((w/h)>(maxw/maxh) || (maxw==maxh && w>h)) {
                *neww=maxw;
                *newh=h*maxw/w;
        } else { /* (w/h)<=(maxw/maxh) */
                *newh=maxh;
                *neww=w*maxh/h;
        }
        return(0);
}