/*
 * boxui.c
 *
 * Multiplatform UI library for SDL3.
 *
 * (c) 2023-2024 Dario Rodriguez <antartica@box-ui.org>
 * Licensed under the terms of the MIT/X license.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <stdarg.h>

#include "SDL3/SDL.h"
#include "SDL3/SDL_iostream.h"
#include "SDL3_ttf/SDL_ttf.h"

#include "boxui.h"

#include "notosans_regular_ttf.c"

#define DEFAULTFONTSIZE 16

#define MAXPENDINGSIGNAL 64
#define BLOCKSIGNALACTION 16
#define BLOCKWINDOW 8

#ifdef DEBUG_BOXUI
#define DEBUGFPRINTF(a) fprintf a
#else
#define DEBUGFPRINTF(a)
#endif

typedef enum datatype_t {
        datatype_literal=0, /* inline string */
        datatype_escapedquotedliteral, /* (only used when parsing) inline quoted string */
        datatype_quotedliteral, /* quoted string (%s or unescaping escapedquotedliteral) */
        datatype_integer, /* (only used when parsing) %i */
        datatype_textvariable, /* %s%i */
        datatype_asset, /* %p%i%i */
        datatype_callback, /* %p%p */
} datatype_t;

typedef struct signalaction_t {
        int nsignal;
        char *actionstr;
} signalaction_t;

typedef struct signaldata_t {
        int readpos;
        int writepos;
        int buf[MAXPENDINGSIGNAL];
        int sizeactions;
        int usedactions;
        signalaction_t *actions;
} signaldata_t;

typedef struct intboxui_t {
        SDL_Window *window;
        SDL_Renderer *renderer;
        int width;
        int height;
        signaldata_t signaldata;
        SDL_IOStream *defaultfontdata;
        TTF_Font *defaultfont;
        int defaultfontwidth;
        int defaultfontheight;
        void *userptr;
        int isdirty;
        int haschanged;
        SDL_Texture *texture;
        /* temporary members (to be able to test) */
        char currenttext[1024];
        char currentclickevent[1024];
        char currentactionstr[1024];
        int flag_currentactionstrused;
} intboxui_t;

static void intboxui_signalhandler_gen(int nsignal, int is_set, intboxui_t *boxuiset);
static int intboxui_render(intboxui_t *boxui);
static char *intboxuiutil_getnext(char *data, int *posdata, va_list valist, int *resl, datatype_t *datatype, char **ptr1, char **ptr2, int *int1, int *int2, int *in_error);
static char *datatypedup_literalquotedliteral(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2);
static int datatypedup_int(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int defaultint, int *in_error);
static int datatypedup_istrue(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int *in_error);
static int memstrcmp(char *ptr, int l, char *str);

boxui_t *
boxui_init(SDL_Window *window, SDL_Renderer *renderer, int width, int height)
{
        intboxui_t *boxui;
        if((boxui=malloc(sizeof(intboxui_t)))==NULL) {
                SDL_Log("boxui: insufficient memory for main structure\n");
                return(NULL); /* insuf. mem for boxui struct */
        }
        memset(boxui,0,sizeof(intboxui_t));
        if(!TTF_Init()
          || (boxui->defaultfontdata=SDL_IOFromConstMem(notosans_regular_ttf,notosans_regular_ttf_len))==NULL
          || (boxui->defaultfont=TTF_OpenFontIO(boxui->defaultfontdata,0,DEFAULTFONTSIZE))==NULL
        ) {
                boxui_free(boxui),boxui=NULL;
                SDL_Log("boxui: Couldn't initialize TTF: %s\n",SDL_GetError());
                return(NULL);
        }
        boxui->window=window;
        boxui->renderer=renderer;
        boxui->width=width;
        boxui->height=height;
        boxui->isdirty=1;
        /* font width and height */
        {
                int w,h;
                /* width */
                TTF_GetStringSize(boxui->defaultfont,"m",0,&w,&h);
                boxui->defaultfontwidth=w;
                /* height */
                boxui->defaultfontheight=TTF_GetFontHeight(boxui->defaultfont);
        }
        return((boxui_t *)boxui);
}

void
boxui_free(boxui_t *paramboxui)
{
        int i;
        intboxui_t *boxui;
        if((boxui=(intboxui_t *)paramboxui)==NULL)
                return; /* nothing to do */
        intboxui_signalhandler_gen(0,1,NULL);
        if(boxui->signaldata.actions!=NULL) {
                signalaction_t *action;
                for(i=0,action=boxui->signaldata.actions
                  ;i<boxui->signaldata.usedactions
                  ;i++,action++
                ) {
                        if(action->actionstr!=NULL)
                                free(action->actionstr),action->actionstr=NULL;
                }
                free(boxui->signaldata.actions),boxui->signaldata.actions=NULL,boxui->signaldata.sizeactions=boxui->signaldata.usedactions=0;
        }
        if(boxui->defaultfont!=NULL)
                TTF_CloseFont(boxui->defaultfont),boxui->defaultfont=NULL;
        if(boxui->defaultfontdata!=NULL)
                SDL_CloseIO(boxui->defaultfontdata),boxui->defaultfontdata=NULL;
        if(boxui->texture!=NULL)
                SDL_DestroyTexture(boxui->texture),boxui->texture=NULL;
        free(boxui),boxui=NULL;
        return;
}

int
boxui_resize(boxui_t *paramboxui, int width, int height)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL) {
                /* XXX (OLD): DEBUGFPRINTF((stderr,"WARNING: %s%s%i: boxui_resize: called with NULL boxui", (filedubug!=NULL)?filedebug:"",(filedubug!=NULL)?":":"",linedebug)); */
                return(-1);
        }
        boxui->width=width;
        boxui->height=height;
        boxui->isdirty=1;
        return(0);
}

int
boxui_setuserptr(boxui_t *boxui, void *userptr)
{
        if(boxui==NULL)
                return(-1);
        ((intboxui_t *)boxui)->userptr=userptr;
        return(0);
}

void *
boxui_getuserptr(boxui_t *boxui)
{
        if(boxui==NULL)
                return(NULL);
        return(((intboxui_t *)boxui)->userptr);
}

static void
intboxui_signalhandler_gen(int nsignal, int is_set, intboxui_t *boxuiset)
{
        static signaldata_t *signaldata;
        int pos;
        if(is_set) {
                signaldata=(boxuiset!=NULL)?&(boxuiset->signaldata):NULL;
                return;
        }
        if(signaldata==NULL)
                return; /* no handler */
        pos=signaldata->writepos;
        pos%=MAXPENDINGSIGNAL;
        signaldata->buf[pos]=nsignal;
        pos++;
        pos%=MAXPENDINGSIGNAL;
        signaldata->writepos=pos;
        return;
}

void
intboxui_signalhandler(int nsignal)
{
        intboxui_signalhandler_gen(nsignal, 0, NULL);
        return;
}

int
boxui_setsignal(boxui_t *paramboxui, int nsignal, char *actionstr)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        int i;
        signaldata_t *signaldata;
        char *newstr;
        if(boxui==NULL || actionstr==NULL)
                return(-1); /* sanity check failed */
        intboxui_signalhandler_gen(0,1,boxui);
        signaldata=&(((intboxui_t*)boxui)->signaldata);
        /* check if signal already registered */
        for(i=0;i<signaldata->usedactions;i++) {
                if(signaldata->actions[i].nsignal==nsignal) {
                        if(strcmp(actionstr,signaldata->actions[i].actionstr)==0)
                                return(0); /* already configured */
                        if((newstr=strdup(actionstr))==NULL)
                                return(-1); /* mem. insuf. */
                        free(signaldata->actions[i].actionstr),signaldata->actions[i].actionstr=NULL;
                        signaldata->actions[i].actionstr=newstr;
                        return(0); /* all done */
                }
        }
        /* enlarge array if necessary */
        if(signaldata->usedactions==signaldata->sizeactions) {
                signalaction_t *newactions;
                int reqsize=sizeof(signalaction_t)*(signaldata->sizeactions+BLOCKSIGNALACTION);
                if((newactions=realloc(signaldata->actions,reqsize))==NULL)
                        return(-1); /* mem. insuf. */
                signaldata->actions=newactions,newactions=NULL;
                memset(signaldata->actions+signaldata->sizeactions,0,sizeof(signalaction_t)*BLOCKSIGNALACTION);
                signaldata->sizeactions+=BLOCKSIGNALACTION;
        }
        /* add element */
        if((signaldata->actions[signaldata->usedactions].actionstr=strdup(actionstr))==NULL)
                return(-1); /* mem. insuf. */
        signaldata->actions[signaldata->usedactions].nsignal=nsignal;
        signaldata->usedactions++;
        /* install signal handler */
        return(0);
}

int
boxui_event(boxui_t *paramboxui, SDL_Event *event)
{
#warning TODO: we do not have the position of the texture in the window
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL || event==NULL)
                return(-1); /* sanity check failed */
        if(event->type==SDL_EVENT_WINDOW_SHOWN
          || event->type==SDL_EVENT_WINDOW_EXPOSED
          || event->type==SDL_EVENT_WINDOW_RESIZED
          || event->type==SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED
          || event->type==SDL_EVENT_WINDOW_MINIMIZED
          || event->type==SDL_EVENT_WINDOW_MAXIMIZED
          || event->type==SDL_EVENT_WINDOW_RESTORED
          || event->type==SDL_EVENT_WINDOW_OCCLUDED
          || event->type==SDL_EVENT_WINDOW_ENTER_FULLSCREEN
          || event->type==SDL_EVENT_WINDOW_LEAVE_FULLSCREEN
        ) {
                boxui->isdirty=1;
        } else
#if 1
        /* test */
               if(event->type==SDL_EVENT_MOUSE_BUTTON_UP) {
                if(boxui->currentclickevent[0]!='\0') {
                        boxui_putaction((boxui_t *)boxui,boxui->currentclickevent,NULL);
                }
        }
        /* end of test */
#endif
        return(0);
}

int
boxui_tick(boxui_t *paramboxui)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL)
                return(-1); /* sanity check failed */
        if(boxui->isdirty)
                intboxui_render(boxui);
        return(0);
}

int
boxui_haschanged(boxui_t *paramboxui)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL)
                return(0); /* sanity check failed */
        return(boxui->isdirty || boxui->haschanged);
}

SDL_Texture *
boxui_gettexture(boxui_t *paramboxui)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL)
                return(NULL);
#if 0
fprintf(stderr,"GETTEXTURE\n");
#endif
        if(boxui->texture==NULL || boxui->isdirty) {
                boxui->isdirty=1;
                boxui_tick((boxui_t *) boxui);
                if(boxui->texture==NULL) {
                        SDL_Log("boxui: Couldn't generate texture\n");
                        return(NULL);
                }
        }
        boxui->haschanged=0;
        return(boxui->texture);
}



char *
boxui_getaction(boxui_t *paramboxui, char **actiondata)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL)
                return(NULL); /* sanity check failed */
        /* test */
        if(boxui->flag_currentactionstrused || boxui->currentactionstr[0]=='\0')
                return(NULL);
        boxui->flag_currentactionstrused=1;
        return(boxui->currentactionstr);
        /* end of test */
#warning TODO
        return(NULL);
}

int
boxui_putaction(boxui_t *paramboxui, char *firstelem, ...)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL || firstelem==NULL)
                return(-1); /* sanity check failed */
        /* test */
        strncpy(boxui->currentactionstr,firstelem,sizeof(boxui->currentactionstr));
        boxui->currentactionstr[sizeof(boxui->currentactionstr)-1]='\0';
        boxui->flag_currentactionstrused=0;
        /* end of test */
#warning TODO
        return(-1);
}

char *
boxui_add(boxui_t *paramboxui, char *filedebug, int linedebug, char *classwiname, char *format, ...)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL || classwiname==NULL || format==NULL)
                return(NULL); /* sanity check failed */
        /* test */
        if(strchr(format,'%')==NULL)
                boxui_config(boxui,NULL,-1,classwiname,format);
        return(classwiname);
        /* end of test */
#warning TODO
}

char *
boxui_config(boxui_t *paramboxui, char *filedebug, int linedebug, char *winame, char *format, ...)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL || winame==NULL || format==NULL)
                return(NULL); /* sanity check failed */
        /* test */
        {
                char *start,*end;
                int l;
                /* text */
                if((start=strchr(format,'\"'))!=NULL
                  && (start=start+1)!=NULL
                  && (end=strchr(start,'\"'))!=NULL) {
                        l=end-start;
                        l=(l>sizeof(boxui->currenttext))?sizeof(boxui->currenttext)-1:l;
                        memcpy(boxui->currenttext,start,l);
                        boxui->currenttext[l]='\0';
                        boxui->isdirty=1;
                }
                /* click */
                for(start=strchr(format,'-');start!=NULL;start=strchr(start+1,'-')) {
                        if(memcmp(start,"-click ",7)==0) {
                                start+=7;
                                end=strchr(start,' ');
                                end=(end==NULL)?start+strlen(start):end;
                                l=end-start;
                                l=(l>sizeof(boxui->currentclickevent))?sizeof(boxui->currentclickevent)-1:l;
                                memcpy(boxui->currentclickevent,start,l);
                                boxui->currentclickevent[l]='\0';
                        }

                }
        }
        return(winame);
        /* end of test */
#warning TODO
}

int
boxuiutil_datacmp(char *datastr,char *key,char *value)
{
#warning TODO
        return(-1);
}

int
boxui_pack(boxui_t *boxui, char *filedebug, int linedebug, char *winame, char *format, ...)
{
#warning TODO
        return(0);
}

static int
intboxui_render(intboxui_t *paramboxui)
{
        intboxui_t *boxui=(intboxui_t *)paramboxui;
        if(boxui==NULL)
                return(-1); /* sanity check failed */
        /* test */
#if 0
fprintf(stderr,"RENDER: START\n");
#endif
        if(boxui->texture==NULL) {
                if((boxui->texture=SDL_CreateTexture(boxui->renderer,SDL_PIXELFORMAT_RGBA8888,SDL_TEXTUREACCESS_TARGET,boxui->width,boxui->height))==NULL) {
                        SDL_Log("boxui: couldn't create output texture\n");
                        return(-1);
                }
        }
        SDL_SetRenderTarget(boxui->renderer,boxui->texture);
        SDL_SetRenderDrawColor(boxui->renderer,0xff,0xff,0xff,0xff);
        SDL_RenderClear(boxui->renderer);
        if(boxui->currenttext[0]!='\0') {
                SDL_Surface *fgsurface=NULL;
                SDL_Texture *fgtexture=NULL;
                SDL_FRect dstrect;
                SDL_Color bg={0x00,0x00,0x00,0x00};
                SDL_Color fg={0x00,0x00,0x00,0xff};
                if((fgsurface=TTF_RenderText_Shaded(boxui->defaultfont,boxui->currenttext,0,fg,bg))!=NULL
                  && (fgtexture=SDL_CreateTextureFromSurface(boxui->renderer,fgsurface))!=NULL) {
#if 0
fprintf(stderr,"RENDER: text (w:%i h:%i)\n",fgsurface->w,fgsurface->h);
#endif
                        dstrect.x=(boxui->width-fgsurface->w)/2;
                        dstrect.y=(boxui->height-fgsurface->h)/2;
                        dstrect.w=fgsurface->w,dstrect.h=fgsurface->h;
#if 0
fprintf(stderr,"RENDER: text (w:%i h:%i) dstrect:%ix%i+%i+%i\n",fgsurface->w,fgsurface->h,dstrect.w,dstrect.h,dstrect.x,dstrect.y);
#endif
                        SDL_RenderTexture(boxui->renderer,fgtexture,NULL,&dstrect);
                }
                if(fgsurface!=NULL)
                        SDL_DestroySurface(fgsurface),fgsurface=NULL;
                if(fgtexture!=NULL)
                        SDL_DestroyTexture(fgtexture),fgtexture=NULL;
        }
        SDL_SetRenderTarget(boxui->renderer,NULL);
        boxui->isdirty=0;
        boxui->haschanged=1;
#if 0
fprintf(stderr,"RENDER: START\n");
#endif
        /* end of test */
#warning TODO
        return(0);
}


static char *
intboxuiutil_getnext(char *data, int *posdata, va_list valist, int *resl, datatype_t *datatype, char **ptr1, char **ptr2, int *int1, int *int2, int *in_error)
{
        char *ptr;
        char *res;
        ptr=data+*posdata;
        /* skip trailing spaces */
        while(*ptr==' ')
                ptr++;
        if(*ptr!='-') {
                *in_error=1;
                DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", '-' not found at \"%s\"\n",data,data+*posdata,ptr));
                return(NULL);
        }
        /* get keyword (including the '-') */
        res=ptr;
        while(*ptr!=' ' && *ptr!='\0')
                ptr++;
        *resl=ptr-res;
        /* skip the space */
        ptr++;
        /* get value */
        if(*ptr!='\"' && *ptr!='%') {
                *datatype=datatype_literal;
                *ptr1=ptr;
                while(*ptr!=' ' && *ptr!='\0')
                        ptr++;

                *int1=ptr-*ptr1;
        } else if(*ptr=='\"') {
                int flag_esc;
                *datatype=datatype_escapedquotedliteral;
                ptr++;
                *ptr1=ptr;
                for(flag_esc=0;!(flag_esc==0 && *ptr=='\"') && *ptr!='\0';ptr++) {
                        if(flag_esc)
                                flag_esc=0;
                        else if(*ptr=='\\')
                                flag_esc=1;
                }
                if(*ptr!='\"') {
                        *in_error=1;
                        DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", unterminated string at \"%s\"\n",data,data+*posdata,*ptr1));
                        return(NULL);
                }
                *int1=ptr-*ptr1;
                ptr++;
        } else if(ptr[0]=='%' && ptr[1]=='s' && (ptr[2]=='\0' || ptr[2]==' ')) {
                *datatype=datatype_quotedliteral;
                *ptr1=va_arg(valist, char *);
                *int1=strlen(*ptr1);
                ptr+=2;
        } else if(ptr[0]=='%' && ptr[1]=='i' && (ptr[2]=='\0' || ptr[2]==' ')) {
                *datatype=datatype_integer;
                *int1=va_arg(valist, int);
                ptr+=2;
        } else if(ptr[0]=='%' && ptr[1]=='s' && ptr[2]=='%' && ptr[3]=='i' && (ptr[4]=='\0' || ptr[4]==' ')) {
                *datatype=datatype_textvariable;
                *ptr1=va_arg(valist, char *);
                *int1=va_arg(valist, int);
                ptr+=4;
        } else if(ptr[0]=='%' && ptr[1]=='p' && ptr[2]=='%' && ptr[3]=='i' && ptr[4]=='%' && ptr[5]=='i' && (ptr[6]=='\0' || ptr[6]==' ')) {
                *datatype=datatype_asset;
                *ptr1=va_arg(valist, char *);
                *int1=va_arg(valist, int);
                *int2=va_arg(valist, int);
                ptr+=6;
        } else if(ptr[0]=='%' && ptr[1]=='p' && ptr[2]=='%' && ptr[3]=='p' && (ptr[4]=='\0' || ptr[4]==' ')) {
                *datatype=datatype_callback;
                *ptr1=va_arg(valist, char *);
                *ptr2=va_arg(valist, char *);
                ptr+=4;
        } else {
                *in_error=1;
                DEBUGFPRINTF((stderr,"WARNING: intboxuiutil_getnext(): wrong data in \"%s\", analyzing \"%s\", unrecognized value type at \"%s\"\n",data,data+*posdata,ptr));
                return(NULL);
        }
        /* save the current position and return the key */
        *posdata=ptr-data;
        return(res);
}

static char *
datatypedup_literalquotedliteral(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2)
{
        char *res,*aux;
        int t,i;
        int flag_esc;
        res=NULL;
        int req;
        /* two passes; first calculates needed storage space, second copies the data */
        for(t=0,req=0;t<2;t++) {
                if(datatype==datatype_literal || datatype==datatype_quotedliteral) {
                        req=int1+1;
                        if(t) {
                                memcpy(res,ptr1,req);
                                ptr1[int1]='\0';
                        }
                } else if(datatype==datatype_escapedquotedliteral) {
                        for(req=0,aux=ptr1,flag_esc=0,i=0;!(flag_esc==0 && *aux=='\"') && *aux!='\0' && i<int1;aux++) {
                                if(flag_esc) {
                                        if(t)
                                                res[req]=*aux;
                                        req++;
                                        flag_esc=0;
                                } else if(*aux=='\\') {
                                        flag_esc=1;
                                } else {
                                        if(t)
                                                res[req]=*aux;
                                        req++;
                                }
                        }
                        if(t)
                                res[req]='\0';
                        req++;
                } else if(datatype==datatype_integer) {
                        int l=req;
                        for(i=int1,req=0;i>0 || req==0;) {
                                if(t)
                                        res[(l-2)-req]=(i%10)+'0';
                                req++;
                                i/=10;
                        }
                        if(t)
                                res[l-1]='\0';
                        req++;
                } else {
                        return(NULL); /* type is not amenable to strdup */
                }
                if(t==0) {
                        if((res=malloc(req))==NULL)
                                return(NULL); /* insuf. mem. */
                }
        }
        return(res);
}

static int
datatypedup_int(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int defaultint, int *in_error)
{
        if(in_error!=NULL)
                *in_error=0;
        if(datatype==datatype_integer)
                return(int1);
        else if(datatype==datatype_literal || datatype==datatype_escapedquotedliteral || datatype==datatype_quotedliteral) {
                int i,res;
                if(int1==0)
                        return(defaultint); /* no data */
                for(i=0,res=0;i<int1;i++) {
                        if(!(ptr1[i]>='0' && ptr1[i]<='9')) {
                                if(in_error!=NULL)
                                        *in_error=1;
                                return(defaultint);
                        }
                        res=(res*10)+(ptr1[i]-'0');
                }
                return(res);
        }
        if(in_error!=NULL)
                *in_error=1;
        return(defaultint);
}

static int
datatypedup_istrue(datatype_t datatype, char *ptr1, char *ptr2, int int1, int int2, int *in_error)
{
        if(in_error!=NULL)
                *in_error=0;
        if(datatype==datatype_literal || datatype==datatype_escapedquotedliteral || datatype==datatype_quotedliteral) {
                if(memstrcmp(ptr1,int1,"true")==0)
                        return(1);
                return(0);
        }
        if(in_error!=NULL)
                *in_error=1;
        return(0);
}

static int
memstrcmp(char *ptr, int l, char *str)
{
        int res;
        res=memcmp(ptr,str,l);
        if(res==0 && str[l]=='\0')
                return(0);
        return((res==0)?1:res);
}