/*
 * rayui.c
 *
 * Collections of functions to aid creating GUIs using raylib
 *
 * History:
 *      20250902 Creation from imgmover 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 <stdarg.h>
#include <sys/time.h>
#include "raylib.h"
#include "rayui.h"
#include "roboto_regular.c"

#define DEFAULTTARGETFPS 30
#if defined(ANDROID) || defined(SIMANDROID)
#define FONTSIZE 64
#define FONTBIGSIZE 96
#define FONTHUGESIZE 128
#else
#define FONTSIZE 18
#define FONTBIGSIZE 32
#define FONTHUGESIZE 48
#endif

#if !defined(__linux__) && !defined(ANDROID) && RAYLIB_VERSION_MAJOR==5 && RAYLIB_VERSION_MINOR==0
/* the old raylib used in the windows build lacks this function */
bool IsImageValid(Image image)
{
    bool result = false;

    if ((image.data != NULL) &&     // Validate pixel data available
        (image.width > 0) &&        // Validate image width
        (image.height > 0) &&       // Validate image height
        (image.format > 0) &&       // Validate image format
        (image.mipmaps > 0)) result = true; // Validate image mipmaps (at least 1 for basic mipmap level)

    return result;
}
#endif

menubar_t *rayuimenubar_init(char *menus, font_t *font);
void rayuimenubar_free(menubar_t *menubar);

int rayuimenubar_mouse(menubar_t *menubar, Vector2 mousepos, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail, char **sel_menu, char **sel_submenu);
int rayuimenubar_draw(menubar_t *menubar, int windowwidth, int windowheight, int *needs_nextredraw);

static int *intglobal_getcodepoints(int *sizecodepoints);

int intglobal_menu_count(char *menus);
char *intglobal_menu_get(char *menus, int targetn, int *len);
int intglobal_submenu_count(char *menus);
char *intglobal_submenu_get(char *menus, int targetsubn, int *len);

char *intglobal_strduplen(char *str, int len);

int intglobal_menudata_pos2option(menudata_t *menudata, Vector2 pos);

static char *intglobal_messageboxcaption(char *newcaption);

rayui_t *
rayui_init(int w, int h, char *title, char *menus)
{
        rayui_t *rayui;
        char *errstr;
        if((rayui=calloc(1,sizeof(rayui_t)))==NULL) {
                rayui_free(rayui),rayui=NULL;
                return(NULL); /* insuf. mem. */
        }
        rayui->defaultfontdata=(const unsigned char *)roboto_regular;
        rayui->sizedefaultfontdata=sizeof(roboto_regular)-1;
        /* init window */
        SetTraceLogLevel(LOG_ERROR);
        InitWindow((rayui->w=w),(rayui->h=h),(title==NULL)?"rayui":title);
        rayui->windowinit=1;
        SetTargetFPS(DEFAULTTARGETFPS);
        rayui->targetfps=DEFAULTTARGETFPS;
        /* init fonts and contents */
        if(title!=NULL)
                intglobal_messageboxcaption(title);
        if((errstr="Couldn't init font")==NULL
          || (rayui->font=rayuifont_init(FONTSIZE))==NULL
          || (rayui->fontbig=rayuifont_init(FONTBIGSIZE))==NULL
          || (rayui->fonthuge=rayuifont_init(FONTHUGESIZE))==NULL
          || (errstr="Couldn't init menus")==NULL
          || (menus!=NULL && (rayui->menubar=rayuimenubar_init(menus,rayui->font))==NULL)
        ) {
                global_messagebox("%s",errstr);
                rayui_free(rayui),rayui=NULL;
                return(NULL); /* insuf. mem. */
        }
        return(rayui);
}

void
rayui_free(rayui_t *rayui)
{
        if(rayui==NULL)
                return;
        if(rayui->menubar!=NULL)
                rayuimenubar_free(rayui->menubar),rayui->menubar=NULL;
        if(rayui->font!=NULL)
                rayuifont_free(rayui->font),rayui->font=NULL;
        if(rayui->fontbig!=NULL)
                rayuifont_free(rayui->fontbig),rayui->fontbig=NULL;
        if(rayui->fonthuge!=NULL)
                rayuifont_free(rayui->fonthuge),rayui->fonthuge=NULL;
        if(rayui->windowinit)
                CloseWindow(),rayui->windowinit=0;
        free(rayui),rayui=NULL;
        return;
}


int
rayui_scrollablerectreset(rayui_t *rayui, mousedata_t *mousedata)
{
        if(rayui==NULL || mousedata==NULL)
                return(-1);
        mousedata->usedscrollablerect=0;
        return(0);
}

int
rayui_scrollablerectadd(rayui_t *rayui, mousedata_t *mousedata,whxy_t *rect, char *id, int scrollpos, int (*tryselect)(void *userptr, Vector2 mousepos),void *userptr, int scrollthreshold)
{
        scrollrect_t *scrollrect;
        if(rayui==NULL || mousedata==NULL || rect==NULL || id==NULL)
                return(-1);
        if(mousedata->usedscrollablerect==MAXSCROLLABLERECT)
                return(-1);
        scrollrect=mousedata->scrollablerect+mousedata->usedscrollablerect;
        memcpy(&(scrollrect->whxy),rect,sizeof(whxy_t));
        strncpy(scrollrect->id,id,SIZESCROLLABLERECTID);
        scrollrect->id[sizeof(scrollrect->id)-1]='\0';
        scrollrect->scrollpos=scrollpos;
        scrollrect->tryselect=tryselect;
        scrollrect->userptr=userptr;
        scrollrect->scrollthreshold=scrollthreshold;
        mousedata->usedscrollablerect++;
        return(0);
}

int
rayui_getmousedata(rayui_t *rayui, mousedata_t *mousedata)
{
        int i;
        if(rayui==NULL || mousedata==NULL)
                return(-1);
        mousedata->oldmousepos=mousedata->mousepos;
        mousedata->mousepos=GetMousePosition();
        mousedata->wheel=GetMouseWheelMoveV();
        mousedata->lmbpressed=IsMouseButtonPressed(0);
        mousedata->lmbreleased=IsMouseButtonReleased(0);
        mousedata->oldlmbdown=mousedata->lmbdown;
        mousedata->lmbdown=IsMouseButtonDown(0);
        mousedata->oldrmbdown=mousedata->rmbdown;
        mousedata->rmbdown=IsMouseButtonDown(1);
        mousedata->click_avail=1;
        mousedata->has_mousechanges=(mousedata->lmbdown!=mousedata->oldlmbdown
            || mousedata->rmbdown!=mousedata->oldrmbdown
            || mousedata->mousepos.x!=mousedata->oldmousepos.x
            || mousedata->mousepos.y!=mousedata->oldmousepos.y
            || mousedata->wheel.x!=0
            || mousedata->wheel.y!=0
          )?1:0;
        mousedata->needs_nextredraw=0;
        /* mouse process scrolling */
#if 1
if(mousedata->lmbdown!=mousedata->oldlmbdown)
 fprintf(stderr,"[%s:+%i+%i] ===\n",(mousedata->lmbdown==1)?"DOWN":".UP.",(int)mousedata->mousepos.x,(int)mousedata->mousepos.y);
#endif
        if(mousedata->lmbdown==1 && mousedata->oldlmbdown==0 && mousedata->scrollstart==0) {
                for(i=0;i<mousedata->usedscrollablerect;i++) {
                        if(is_global_insidewhxy(mousedata->mousepos,&(mousedata->scrollablerect[i].whxy),0)) {
                                strncpy(mousedata->scrollingid,mousedata->scrollablerect[i].id,sizeof(mousedata->scrollingid));
                                mousedata->scrollingid[sizeof(mousedata->scrollingid)-1]='\0';
                                mousedata->scrollstart=global_currentmilliseconds();
                                mousedata->scrollstartpos=mousedata->mousepos;
                                mousedata->scrollposstart=mousedata->scrollablerect[i].scrollpos;
                                break;
                        }
                }
#if 1
if(i>=mousedata->usedscrollablerect) {
fprintf(stderr,"Couldn't start scrolling because not in scrollable area\n");
}
#endif
        }
#if 1
else if(mousedata->lmbdown==1 && mousedata->oldlmbdown==0 && mousedata->scrollstart!=0) {
fprintf(stderr,"Couldn't start scrolling because scrollstart!=0\n");
}
#endif
        if(mousedata->scrollstart!=0 && mousedata->lmbdown==0) {
                mousedata->scrollstart=0;
                if(mousedata->is_scrolling)
                        mousedata->click_avail=0; /* this click is the mouseup of the scroll */
        }
        mousedata->is_scrolling=(mousedata->scrollstart==0)?0:mousedata->is_scrolling;
        if(mousedata->is_scrolling==0 && mousedata->scrollstart!=0) {
                for(i=0;i<mousedata->usedscrollablerect;i++) {
                        if(is_global_insidewhxy(mousedata->mousepos,&(mousedata->scrollablerect[i].whxy),0) && mousedata->scrollablerect[i].tryselect!=NULL) {
                                float t;
                                t=mousedata->scrollstartpos.y-mousedata->mousepos.y;
                                t=(t<0)?-t:t;
                                if(t>mousedata->scrollablerect[i].scrollthreshold) {
                                        mousedata->is_scrolling=1;
                                        mousedata->scrolllast=0;
                                }
                                t=mousedata->scrollstartpos.x-mousedata->mousepos.x;
                                t=(t<0)?-t:t;
                                if(t>mousedata->scrollablerect[i].scrollthreshold) {
                                        mousedata->is_scrolling=0,mousedata->scrollstart=0;
                                        if(mousedata->scrollablerect[i].tryselect(mousedata->scrollablerect[i].userptr,mousedata->mousepos)!=-1) {
                                                mousedata->click_avail=0;
                                                break;
                                        }
                                }
                        }
                }
        }
        if(mousedata->is_scrolling) {
                long long tcur,tdif;
                long long ycur;
                tcur=global_currentmilliseconds();
                tdif=tcur-mousedata->scrolllast;
                ycur=mousedata->scrollstartpos.y-mousedata->mousepos.y;
                mousedata->scrollspeed=(tdif>0)?(mousedata->oldmousepos.y-mousedata->mousepos.y)*100000/tdif:0;
                mousedata->scrollpos=mousedata->scrollposstart+ycur;
                mousedata->scrolllast=tcur;
        }
        if(mousedata->is_scrolling==0 && mousedata->scrollspeed!=0) {
                mousedata->scrollspeed=mousedata->scrollspeed*4/5;
                mousedata->scrollpos+=mousedata->scrollspeed;
        }
        return(0);
}

int
is_rayui_scrolling(rayui_t *rayui, mousedata_t *mousedata,char *id, int *newscrollpos)
{
        if(rayui==NULL || mousedata==NULL || id==NULL || newscrollpos==NULL)
                return(0);
        if((mousedata->is_scrolling || mousedata->scrollspeed!=0) && strcmp(mousedata->scrollingid,id)==0) {
                *newscrollpos=mousedata->scrollpos;
                return(1);
        }
        return(0);
}

void
rayui_settargetfps(rayui_t *rayui, int newfps)
{
        if(rayui==NULL)
                return;
        newfps=(newfps<=0)?DEFAULTTARGETFPS:newfps;
        rayui->targetfps=newfps;
        SetTargetFPS(newfps);
        return;
}

int
has_rayui_timeleft(rayui_t *rayui)
{
        struct timeval *deadline,now;
        if(rayui==NULL)
                return(1);
        deadline=&(rayui->deadline);
        gettimeofday(&now,NULL);
        if(deadline->tv_sec<now.tv_sec || (deadline->tv_sec==now.tv_sec && deadline->tv_usec<now.tv_usec))
                return(0);
        return(1);
}

void
rayui_timereset(rayui_t *rayui)
{
        struct timeval *deadline;
        long deadlineincr;
        if(rayui==NULL)
                return;
        deadline=&(rayui->deadline);
        gettimeofday(deadline,NULL);
        deadlineincr=1000000L/((rayui->targetfps!=0)?rayui->targetfps:DEFAULTTARGETFPS);
        deadline->tv_usec+=deadlineincr;
        deadline->tv_sec+=(deadline->tv_usec)/1000000L;
        deadline->tv_usec%=1000000L;
}


long long
global_currentmilliseconds(void)
{
        long long res;
        struct timeval now;
        gettimeofday(&now,NULL);
        res=((long long) (now.tv_sec))*1000000L+((long long) (now.tv_usec));
        return(res);
}

font_t *
rayuifont_init(int size)
{
        font_t *font;
        int sizecodepoints;
        int *codepoints;
        if((font=calloc(1,sizeof(font_t)))==NULL)
                return(NULL); /* insuf. mem. */
        font->height=size;
        codepoints=intglobal_getcodepoints(&sizecodepoints);
        font->font=LoadFontFromMemory(".ttf",(const unsigned char *)roboto_regular,sizeof(roboto_regular)-1,font->height,codepoints,sizecodepoints);
        return(font);
}

void
rayuifont_free(font_t *font)
{
        if(font==NULL)
                return;
        /* NOTE: Cannot call UnloadFont(font->font) as the data was not malloc'd; see https://github.com/raysan5/raylib/blob/master/examples/others/embedded_files_loading.c */
        free(font),font=NULL;
        return;
}

menubar_t *
rayuimenubar_init(char *menus, font_t *font)
{
        int i,j;
        char *str,*substr;
        int len,sublen;
        menubar_t *menubar;
        menudata_t *menudata;
        if(menus==NULL || font==NULL)
                return(NULL); /* sanity check failed */
        if((menubar=calloc(1,sizeof(menubar_t)))==NULL
          || (menubar->sizemenudata=intglobal_menu_count(menus))<=0
          || (menubar->menudata=calloc(menubar->sizemenudata,sizeof(menudata_t)))==NULL
        ) {
                rayuimenubar_free(menubar),menubar=NULL;
                return(NULL); /* insuf. mem. */
        }
        menubar->ptrfont=font;
        menubar->height=font->height+font->height/2;
        /* init menus */
        for(i=0;i<menubar->sizemenudata;i++) {
                if((menudata=menubar->menudata[i]=calloc(1,sizeof(menudata_t)))==NULL
                  || (str=intglobal_menu_get(menus,i,&len))==NULL
                  || (menudata->title=intglobal_strduplen(str,len))==NULL
                  || (menudata->sizeoptions=intglobal_submenu_count(str))<=0
                  || (menudata->options=calloc(menudata->sizeoptions,sizeof(char *)))==NULL
                ) {
                        rayuimenubar_free(menubar),menubar=NULL;
                        return(NULL); /* insuf. mem. */
                }
                for(j=0;j<menudata->sizeoptions;j++) {
                        if((substr=intglobal_submenu_get(str,j,&sublen))==NULL
                          || (menudata->options[j]=intglobal_strduplen(substr,sublen))==NULL
                        ) {
                                rayuimenubar_free(menubar),menubar=NULL;
                                return(NULL); /* insuf. mem. */
                        }
                }
        }
        return(menubar);
}

void
rayuimenubar_free(menubar_t *menubar)
{
        int i,j;
        menudata_t *menudata;
        if(menubar==NULL)
                return;
        if(menubar->menudata!=NULL) {
                for(i=0;i<menubar->sizemenudata;i++) {
                        if((menudata=menubar->menudata[i])==NULL)
                                continue;
                        if(menudata->title!=NULL)
                                free(menudata->title),menudata->title=NULL;
                        if(menudata->options!=NULL) {
                                for(j=0;j<menudata->sizeoptions;j++) {
                                        if(menudata->options[j]!=NULL)
                                                free(menudata->options[j]),menudata->options[j]=NULL;
                                }
                                free(menudata->options),menudata->options=NULL,menudata->sizeoptions=0;
                        }
                        free(menubar->menudata[i]),menubar->menudata[i]=NULL,menudata=NULL;
                }
                free(menubar->menudata),menubar->menudata=NULL,menubar->sizemenudata=0;
        }
        free(menubar),menubar=NULL;
        return;
}

int
rayuimenubar_mouse(menubar_t *menubar, Vector2 mousepos, int lmbpressed, int lmbreleased, int lmbdown, int *click_avail, char **sel_menu, char **sel_submenu)
{
        int flag_outsideall;
        int i,j;
        if(menubar==NULL || click_avail==NULL || sel_menu==NULL || sel_submenu==NULL)
                return(-1);
        *click_avail=1;
        *sel_menu=NULL;
        *sel_submenu=NULL;
        flag_outsideall=1;
#if 0
if(lmbpressed || lmbdown)
 fprintf(stderr,"in_menubar_mouse: lmbpressed:%i lmbdown:%i\n",lmbpressed,lmbdown);
#endif
        for(i=0;i<menubar->sizemenudata;i++) {
                int insidetitle,currentoption;
                insidetitle=is_global_insidewhxy(mousepos,&(menubar->menudata[i]->whxy),0);
                currentoption=intglobal_menudata_pos2option(menubar->menudata[i],mousepos);
                flag_outsideall=(currentoption!=-1 || insidetitle)?0:flag_outsideall;
                if(lmbreleased && insidetitle) {
                        for(j=0;j<menubar->sizemenudata;j++) {
                                menubar->menudata[j]->flag_stickyopen=(j==i)?1:0;
                                menubar->menudata[j]->flag_open=0;
                                menubar->menudata[j]->currentoption=-1;
                        }
                } else if((lmbpressed || lmbdown) && insidetitle) {
                        for(j=0;j<menubar->sizemenudata;j++) {
                                menubar->menudata[j]->flag_open=(j==i)?1:0;
                                menubar->menudata[j]->flag_stickyopen=0;
                                menubar->menudata[j]->currentoption=-1;
                        }
                } else if((lmbdown || menubar->menudata[i]->flag_stickyopen) && currentoption!=-1) {
                        for(j=0;j<menubar->sizemenudata;j++) {
                                if(lmbreleased==0 || j!=i || menubar->menudata[i]->flag_stickyopen==0) {
                                        menubar->menudata[j]->flag_open=(j==i)?menubar->menudata[i]->flag_open:0;
                                        menubar->menudata[j]->flag_stickyopen=(j==i)?menubar->menudata[i]->flag_stickyopen:0;
                                        menubar->menudata[j]->currentoption=(j==i)?currentoption:-1;
                                } else {
                                        menubar->menudata[j]->flag_open=0;
                                        menubar->menudata[j]->flag_stickyopen=0;
                                        menubar->menudata[j]->currentoption=-1;
                                        /* has selected this submenu */
                                        *click_avail=0;
                                        *sel_menu=menubar->menudata[j]->title;
                                        *sel_submenu=menubar->menudata[j]->options[currentoption];
                                }
                        }
                } else if(menubar->menudata[i]->flag_stickyopen && currentoption==-1) {
                        if(lmbreleased) {
                                menubar->menudata[i]->flag_open=0;
                                menubar->menudata[i]->flag_stickyopen=0;
                        }
                        menubar->menudata[i]->currentoption=-1;
                } else if(lmbreleased && currentoption!=-1) {
                        for(j=0;j<menubar->sizemenudata;j++) {
                                menubar->menudata[j]->flag_open=0;
                                menubar->menudata[j]->flag_stickyopen=0;
                                menubar->menudata[j]->currentoption=-1;
                        }
                        /* has selected this submenu */
                        *click_avail=0;
                        *sel_menu=menubar->menudata[i]->title;
                        *sel_submenu=menubar->menudata[i]->options[currentoption];
                } else if(lmbdown==0) {
                        menubar->menudata[i]->flag_open=0;
                        menubar->menudata[i]->flag_stickyopen=0;
                        menubar->menudata[i]->currentoption=-1;
                }
        }
        if(flag_outsideall) {
                for(j=0;j<menubar->sizemenudata;j++) {
                        menubar->menudata[j]->currentoption=-1;
                }
        }
        /* update click_avail */
        for(j=0;j<menubar->sizemenudata;j++) {
                if(menubar->menudata[j]->flag_open || menubar->menudata[j]->flag_stickyopen) {
                        *click_avail=0;
                        break;
                }
        }
        return(0);
}

int
rayuimenubar_draw(menubar_t *menubar, int windowwidth, int windowheight, int *needs_nextredraw)
{
        int i,j,k,x;
        menudata_t *menudata;
        font_t *font;
        if(menubar==NULL)
                return(-1); /* sanity check failed */
        font=menubar->ptrfont;
        DrawRectangle(0,0,windowwidth, font->height+font->height/2, (Color){ 235, 235, 235, 235 } );
        for(i=0,x=0;i<menubar->sizemenudata;i++) {
                Vector2 v2;
                menudata=menubar->menudata[i];
                v2=MeasureTextEx(font->font,menudata->title,font->height,0);
                FILLWHXY(menudata->whxy,((int)v2.x)+font->height,font->height+font->height/2,x,0);
                v2.x=(float) (menudata->whxy.x+font->height/2);
                v2.y=(float) (menudata->whxy.y+font->height/4);
                DrawTextEx(font->font
                  ,menudata->title
                  ,v2
                  ,font->height
                  ,0
                  ,(Color){ 45, 45, 45, 255 }
                );
                if(menudata->flag_open || menudata->flag_stickyopen) {
                        int underline_height=3;
                        int maxw;
                        DrawRectangle(menudata->whxy.x,menudata->whxy.y+menudata->whxy.h-underline_height,menudata->whxy.w,underline_height, (Color){ 53,132,228,255 } );
                        for(j=0,maxw=0;j<menudata->sizeoptions;j++) {
                                v2=MeasureTextEx(font->font,menudata->options[j],font->height,0);
                                maxw=(((int)(v2.x))>maxw)?((int)(v2.x)):maxw;
                        }
                        maxw=(maxw<(menudata->whxy.w+font->height))?(menudata->whxy.w+font->height):maxw;
                        maxw+=font->height;
                        FILLWHXY(menudata->optionswhxy,maxw,(font->height+font->height/2)*menudata->sizeoptions,menudata->whxy.x+1,menudata->whxy.y+menudata->whxy.h+2);
                        DrawLine(menudata->optionswhxy.x-1,menudata->optionswhxy.y-2,menudata->optionswhxy.x+menudata->optionswhxy.w+2,menudata->optionswhxy.y-2,(Color){ 255,255,255,255 } );
                        DrawLine(menudata->optionswhxy.x-1,menudata->optionswhxy.y,menudata->optionswhxy.x-1,menudata->optionswhxy.y+menudata->optionswhxy.h+1,(Color){ 255,255,255,255 } );
                        DrawLine(menudata->optionswhxy.x+menudata->optionswhxy.w+2,menudata->optionswhxy.y,menudata->optionswhxy.x+menudata->optionswhxy.w+2,menudata->optionswhxy.y+menudata->optionswhxy.h+1,(Color){ 192,192,192,255 } );
                        DrawLine(menudata->optionswhxy.x-1,menudata->optionswhxy.y+menudata->optionswhxy.h+1,menudata->optionswhxy.x+menudata->optionswhxy.w+2,menudata->optionswhxy.y+menudata->optionswhxy.h+1,(Color){ 192,192,192,255 } );
                        DrawRectangle(menudata->optionswhxy.x,menudata->optionswhxy.y,menudata->optionswhxy.w,menudata->optionswhxy.h,(Color){ 235, 235, 235, 235 });
                        for(k=0;k<menudata->sizeoptions;k++) {
                                Color c;
                                c=(k==menudata->currentoption)?((Color){ 255,255,255,255 }):((Color){ 45, 45, 45, 255 });
                                if(k==menudata->currentoption)
                                        DrawRectangle(menudata->optionswhxy.x+1,menudata->optionswhxy.y+(font->height+(font->height/2))*k,menudata->optionswhxy.w-2,font->height+font->height/2,(Color){ 53,132,228,255 });
                                v2.x=(float) (menudata->optionswhxy.x+font->height/2);
                                v2.y=(float) (menudata->optionswhxy.y+(font->height/4)+(font->height+(font->height/2))*k);
                                DrawTextEx(font->font
                                  ,menudata->options[k]
                                  ,v2
                                  ,font->height
                                  ,0
                                  ,c
                                );
                        }
                } else {
                        FILLWHXY(menudata->optionswhxy,0,0,0,0);
                }
                x=menudata->whxy.x+menudata->whxy.w;
        }
        return(0);
}

static int *
intglobal_getcodepoints(int *sizecodepoints)
{
        static int codepoints[]={
/* Basic Latin */
32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,
/* Latin-1 Supplement */
160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,
/* down arrow (U+2186 -- this font doesn't have arrows, this is old roman numeral 50 */
(0x21<<8)|0x86,
};
        if(sizecodepoints!=NULL)
                *sizecodepoints=(int) (sizeof(codepoints)/sizeof(codepoints[0]));
        return(codepoints);
}

int
intglobal_menu_count(char *menus)
{
        int n;
        char *ptr,*next;
        if(menus==NULL)
                return(0); /* sanity check error */
        for(ptr=menus,n=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) {
                if(next[1]=='\n') {
                        next++;
                        n++;
                }
        }
        return(n);
}

char *
intglobal_menu_get(char *menus, int targetn, int *len)
{
        int n;
        char *ptr,*next,*end;
        int is_title;
        if(len!=NULL)
                *len=0;
        if(menus==NULL || targetn<0)
                return(NULL); /* sanity check error */
        end=menus+strlen(menus);
        for(ptr=menus,is_title=1,n=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) {
                if(targetn==n && is_title==1) {
                        if(len!=NULL)
                                *len=next-ptr;
                        return(ptr); /* found, return start of menu string */
                }
                is_title=0;
                if(next[1]=='\n') {
                        n++;
                        next++;
                        is_title=1;
                }
        }
        return(end); /* not found, return end of menus */
}

int
intglobal_submenu_count(char *menus)
{
        int subn;
        char *ptr,*next;
        if(menus==NULL)
                return(0); /* sanity check error */
        next=strchr(menus,'\n');
        if(next==NULL)
                return(0); /* no title */
        for(subn=0,ptr=next+1;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1) {
                subn++;
                if(next[1]=='\n')
                        break;
        }
        return(subn);
}

char *
intglobal_submenu_get(char *menus, int targetsubn, int *len)
{
        char *ptr,*next,*end;
        int subn;
        if(len!=NULL)
                *len=0;
        if(menus==NULL || targetsubn<0)
                return(NULL); /* sanity check error */
        end=menus+strlen(menus);
        next=strchr(menus,'\n');
        if(next==NULL)
                return(end); /* no title */
        for(ptr=next+1,subn=0;(next=strchr(ptr,'\n'))!=NULL;ptr=next+1,subn++) {
                if(targetsubn==subn) {
                        if(len!=NULL)
                                *len=next-ptr;
                        return(ptr);
                }
                if(next[1]=='\n')
                        break; /* "\n\n" marks the end of submenus */
        }
        return(end);
}

char *
intglobal_strduplen(char *str, int len)
{
        char *res;
        if(len<0 || (str==NULL && len!=0))
                return(NULL);
        if((res=malloc(len+1))==NULL)
                return(NULL);
        memcpy(res,str,len);
        res[len]='\0';
        return(res);
}

int
intglobal_menudata_pos2option(menudata_t *menudata, Vector2 pos)
{
        int n,h;
        if(menudata==NULL
          || (menudata->flag_open==0 && menudata->flag_stickyopen==0)
          || is_global_insidewhxy(pos, &(menudata->optionswhxy),0)==0
        ) {
                return(-1);
        }
        h=(menudata->sizeoptions==0)?0:menudata->optionswhxy.h/menudata->sizeoptions;
        n=(((int)pos.y)-menudata->optionswhxy.y)/h;
        return(n);
}


static char *
intglobal_messageboxcaption(char *newcaption)
{
        static char caption[1024]={"Message"};
        if(newcaption!=NULL) {
                strncpy(caption,newcaption,sizeof(caption));
                caption[sizeof(caption)-1]='\0';
        }
        return(caption);
}


#if !defined(__linux__) && !defined(ANDROID)
int MessageBoxA(void *hWnd,void *lpText,void *lpCaption,unsigned int uType);
void
global_messagebox(char *format, ...)
{
        char msgbuf[4096];
        va_list va;
        va_start(va,format);
        vsnprintf(msgbuf,sizeof(msgbuf),format,va);
        msgbuf[sizeof(msgbuf)-1]='\0';
        va_end(va);
        MessageBoxA(NULL,msgbuf,intglobal_messageboxcaption(NULL),0);
        return;
}
#else
void
global_messagebox(char *format, ...)
{
        va_list va;
        va_start(va,format);
        vfprintf(stderr,format,va);
        fprintf(stderr,"\n");
        va_end(va);
        return;
}
#endif

#ifdef ANDROID
Image
global_loadimage(const char *filename)
{
        unsigned char *filedata=NULL;
        FILE *f=NULL;
        struct stat st;
        Image img;
        char *ext;
        if((f=fopen(filename,"r"))==NULL
          || fstat(fileno(f),&st)!=0
          || st.st_size<=0
          || (filedata=(unsigned char *)malloc(st.st_size))==NULL
          || fread(filedata,1,st.st_size,f)!=st.st_size
        ) {
                if(f!=NULL)
                        fclose(f),f=NULL;
                if(filedata!=NULL)
                        free(filedata),filedata=NULL;
                return((Image){0});
        }
        fclose(f);
        ext=strchr(filename,'.');
        ext=(ext==NULL)?filename+strlen(filename):ext;
        img=LoadImageFromMemory(ext,filedata,st.st_size);
        free(filedata),filedata=NULL;
        return(img);
}
#else
Image
global_loadimage(const char *filename)
{
        return(LoadImage(filename));
}
#endif

int
is_global_insidewhxy(Vector2 pos, whxy_t *whxy, int margin)
{
        if(whxy==NULL)
                return(0); /* sanity check error */
        if(pos.x>=(float)(whxy->x-margin)
          && pos.x<=(float)(whxy->x+whxy->w+margin)
          && pos.y>=(float)(whxy->y-margin)
          && pos.y<=(float)(whxy->y+whxy->h+margin)
        ) {
                return(1);
        }
        return(0);
}