/*
 * imgmover.c
 *
 * Simple C application using raylib to move images between directories.
 *
 * History:
 *      20250119 Creation. Menu bar.
 *      20250123 Load font.
 *      20250213 Support sticky drop-down menus.
 *
 * 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 "raylib.h"
#include "roboto_regular.c"

#define DEFAULTWIDTH 1280
#define DEFAULTHEIGHT 768

#ifndef FILLXY
#define FILLXY(xywh,valx,valy) (xywh).x=(valx),(xywh).y=(valy)
#endif

#ifndef FILLWH
#define FILLWH(xywh,valw,valh) (xywh).w=(valw),(xywh).h=(valh)
#endif

#ifndef FILLXYWH
#define FILLXYWH(xywh,valx,valy,valw,valh) (xywh).x=(valx),(xywh).y=(valy),(xywh).w=(valw),(xywh).h=(valh)
#endif

#ifndef UNROLLXYWH
#define UNROLLXYWH(xywh) (xywh).x,(xywh).y,(xywh).w,(xywh).h
#endif

typedef struct xywh_t {
        int x;
        int y;
        int w;
        int h;
} xywh_t;

typedef struct menudata_t {
        char *title;
        xywh_t xywh;
        int sizeoptions;
        char **options;
        xywh_t optionsxywh;
        int flag_open;
        int flag_stickyopen;
        int currentoption;
} menudata_t;

typedef struct dirdata_t {
        int height;
        char *dirname;
} dirdata_t;

typedef struct im_t {
        int w;
        int h;
        int sizemenudata;
        menudata_t **menudata;
        int sizedirdata;
        dirdata_t **dirdata;
        int currentdirdata;
        int leftscrollpos;
        int rightscrollpos;
        Font font;
        int font_height;
} im_t;

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

int imdraw_menus(im_t *im);

int imutil_menu_count(char *menus);
char *imutil_menu_get(char *menus, int targetn, int *len);
int imutil_submenu_count(char *menus);
char *imutil_submenu_get(char *menus, int targetn, int *len);
char *imutil_strduplen(char *str, int len);
int is_imutil_insidexywh(Vector2 pos, xywh_t *xywh);
int menudata_pos2option(menudata_t *menudata, Vector2 pos);
int *getcodepoints(int *sizecodepoints);

int
main(int argc, char *argv[])
{
        im_t *im;
        Vector2 mousepos;
        int flag_ignorelmb;
        int i,j;
        int lmbpressed,lmbreleased,lmbdown;
        int flag_outsideall;
        if((im=im_init("Fichero\nAjustes\nSalir\n\nEditar\nNuevo directorio\n\nAyuda\nInformación sobre el programa\n\n"))==NULL) {
                return(1);
        }
        flag_ignorelmb=0;
        while(!WindowShouldClose()) {
                mousepos=GetMousePosition();
                lmbpressed=IsMouseButtonPressed(0);
                lmbreleased=IsMouseButtonReleased(0);
                lmbdown=IsMouseButtonDown(0);
                /* check if we have to open a menu */
                flag_outsideall=1;
                for(i=0;i<im->sizemenudata;i++) {
                        int insidetitle,currentoption;
                        insidetitle=is_imutil_insidexywh(mousepos,&(im->menudata[i]->xywh));
                        currentoption=menudata_pos2option(im->menudata[i],mousepos);
                        flag_outsideall=(currentoption!=-1 || insidetitle)?0:flag_outsideall;
                        if(lmbreleased && insidetitle) {
                                for(j=0;j<im->sizemenudata;j++) {
                                        im->menudata[j]->flag_stickyopen=(j==i)?1:0;
                                        im->menudata[j]->flag_open=0;
                                        im->menudata[j]->currentoption=-1;
                                }
                        } else if((lmbpressed || lmbdown) && insidetitle) {
                                for(j=0;j<im->sizemenudata;j++) {
                                        im->menudata[j]->flag_open=(j==i)?1:0;
                                        im->menudata[j]->flag_stickyopen=0;
                                        im->menudata[j]->currentoption=-1;
                                }
                        } else if((lmbdown || im->menudata[i]->flag_stickyopen) && currentoption!=-1) {
                                for(j=0;j<im->sizemenudata;j++) {
                                        if(lmbreleased==0 || j!=i || im->menudata[i]->flag_stickyopen==0) {
                                                im->menudata[j]->flag_open=(j==i)?im->menudata[i]->flag_open:0;
                                                im->menudata[j]->flag_stickyopen=(j==i)?im->menudata[i]->flag_stickyopen:0;
                                                im->menudata[j]->currentoption=(j==i)?currentoption:-1;
                                        } else {
                                                im->menudata[j]->flag_open=0;
                                                im->menudata[j]->flag_stickyopen=0;
                                                im->menudata[j]->currentoption=-1;
#if 1
fprintf(stderr,"SELECTED: \"%s\"->\"%s\"\n",im->menudata[i]->title,im->menudata[i]->options[currentoption]);
#endif
                                        }
                                }
                        } else if(im->menudata[i]->flag_stickyopen && currentoption==-1) {
                                if(lmbreleased) {
                                        im->menudata[i]->flag_open=0;
                                        im->menudata[i]->flag_stickyopen=0;
                                }
                                im->menudata[i]->currentoption=-1;
                        } else if(lmbreleased && currentoption!=-1) {
                                for(j=0;j<im->sizemenudata;j++) {
                                        im->menudata[j]->flag_open=0;
                                        im->menudata[j]->flag_stickyopen=0;
                                        im->menudata[j]->currentoption=-1;
                                }
#if 1
fprintf(stderr,"SELECTED: \"%s\"->\"%s\"\n",im->menudata[i]->title,im->menudata[i]->options[currentoption]);
#endif
                        } else if(lmbdown==0) {
                                im->menudata[i]->flag_open=0;
                                im->menudata[i]->flag_stickyopen=0;
                                im->menudata[i]->currentoption=-1;
                        }
                }
                if(flag_outsideall) {
                        for(j=0;j<im->sizemenudata;j++) {
                                im->menudata[j]->currentoption=-1;
                        }
                }
                BeginDrawing();
                ClearBackground(RAYWHITE);
                imdraw_menus(im);
                EndDrawing();
        }
        CloseWindow();
        im_free(im),im=NULL;
        return(0);
}

im_t *
im_init(char *menus)
{
        im_t *im;
        int i,j;
        char *str,*substr;
        int len,sublen;
        menudata_t *menudata;
        if(menus==NULL)
                return(NULL); /* sanity check failed */
        if((im=calloc(1,sizeof(im_t)))==NULL
          || (im->sizemenudata=imutil_menu_count(menus))<=0
          || (im->menudata=calloc(im->sizemenudata,sizeof(menudata_t)))==NULL
        ) {
                im_free(im),im=NULL;
                return(NULL); /* insuf. mem. */
        }
        /* init menus */
        for(i=0;i<im->sizemenudata;i++) {
                if((menudata=im->menudata[i]=calloc(1,sizeof(menudata_t)))==NULL
                  || (str=imutil_menu_get(menus,i,&len))==NULL
                  || (menudata->title=imutil_strduplen(str,len))==NULL
                  || (menudata->sizeoptions=imutil_submenu_count(str))<=0
                  || (menudata->options=calloc(menudata->sizeoptions,sizeof(char *)))==NULL
                ) {
                        im_free(im),im=NULL;
                        return(NULL); /* insuf. mem. */
                }
                for(j=0;j<menudata->sizeoptions;j++) {
                        if((substr=imutil_submenu_get(str,j,&sublen))==NULL
                          || (menudata->options[j]=imutil_strduplen(substr,sublen))==NULL
                        ) {
                                im_free(im),im=NULL;
                                return(NULL); /* insuf. mem. */
                        }
                }
        }
        /* init window */
        SetTraceLogLevel(LOG_ERROR);
        InitWindow((im->w=DEFAULTWIDTH),(im->h=DEFAULTHEIGHT),"Image Mover");
        SetTargetFPS(30);
        /* init font */
        {
                int sizecodepoints;
                int *codepoints;
                im->font_height=18;
                codepoints=getcodepoints(&sizecodepoints);
                im->font=LoadFontFromMemory(".ttf",(const unsigned char *)roboto_regular,sizeof(roboto_regular)-1,im->font_height,codepoints,sizecodepoints);
        }
#if 0
        /* test imutil_menu_xxx */
        int n,m,l,ml,len,mlen;
        char *ptr,*mptr;
        for(n=0,l=imutil_menu_count(menus);n<l;n++) {
                ptr=imutil_menu_get(menus,n,&len);
                fprintf(stderr,"menu[%i]:\"",n);
                fwrite(ptr,1,len,stderr);
                fprintf(stderr,"\"->");
                for(m=0,ml=imutil_submenu_count(ptr);m<ml;m++) {
                        mptr=imutil_submenu_get(ptr,m,&mlen);
                        fprintf(stderr,"|\"");
                        fwrite(mptr,1,mlen,stderr);
                        fprintf(stderr,"\"");
                }
                fprintf(stderr,"\n");
        }
#endif
#if 0
        /* test menudata */
        for(i=0;i<im->sizemenudata;i++) {
                fprintf(stderr,"menu[%i]:\"%s\"->",i,im->menudata[i]->title);
                for(j=0;j<im->menudata[i]->sizeoptions;j++)
                        fprintf(stderr,"|\"%s\"",im->menudata[i]->options[j]);
                fprintf(stderr,"\n");
        }
#endif
        return(im);
}

void
im_free(im_t *im)
{
        int i,j;
        if(im==NULL)
                return;
        if(im->menudata!=NULL) {
                menudata_t *menudata;
                for(i=0;i<im->sizemenudata;i++) {
                        if((menudata=im->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(im->menudata[i]),im->menudata[i]=NULL,menudata=NULL;
                }
                free(im->menudata),im->menudata=NULL,im->sizemenudata=0;
        }
        if(im->dirdata!=NULL) {
                dirdata_t *dirdata;
                for(i=0;i<im->sizedirdata;i++) {
                        if((dirdata=im->dirdata[i])==NULL)
                                continue;
                        if(dirdata->dirname!=NULL)
                                free(dirdata->dirname),dirdata->dirname=NULL;
                }
                free(im->dirdata),im->dirdata=NULL,im->sizedirdata=0;
        }
        free(im),im=NULL;
        return;
}

int
imdraw_menus(im_t *im)
{
        int i,j,k,x;
        menudata_t *menudata;
        DrawRectangle(0,0,im->w, im->font_height+im->font_height/2, (Color){ 235, 235, 235, 235 } );
        for(i=0,x=0;i<im->sizemenudata;i++) {
                Vector2 v2;
                menudata=im->menudata[i];
                v2=MeasureTextEx(im->font,menudata->title,im->font_height,0);
                FILLXYWH(menudata->xywh,x,0,((int)v2.x)+im->font_height,im->font_height+im->font_height/2);
                v2.x=(float) (menudata->xywh.x+im->font_height/2);
                v2.y=(float) (menudata->xywh.y+im->font_height/4);
                DrawTextEx(im->font
                  ,menudata->title
                  ,v2
                  ,im->font_height
                  ,0
                  ,(Color){ 45, 45, 45, 255 }
                );
                if(menudata->flag_open || menudata->flag_stickyopen) {
                        int underline_height=3;
                        int maxw;
                        DrawRectangle(menudata->xywh.x,menudata->xywh.y+menudata->xywh.h-underline_height,menudata->xywh.w,underline_height, (Color){ 53,132,228,255 } );
                        for(j=0,maxw=0;j<menudata->sizeoptions;j++) {
                                v2=MeasureTextEx(im->font,menudata->options[j],im->font_height,0);
                                maxw=(((int)(v2.x))>maxw)?((int)(v2.x)):maxw;
                        }
                        maxw=(maxw<(menudata->xywh.w+im->font_height))?(menudata->xywh.w+im->font_height):maxw;
                        maxw+=im->font_height;
                        FILLXYWH(menudata->optionsxywh,menudata->xywh.x+1,menudata->xywh.y+menudata->xywh.h+2,maxw,(im->font_height+im->font_height/2)*menudata->sizeoptions);
                        DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y-2,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y-2,(Color){ 255,255,255,255 } );
                        DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y,menudata->optionsxywh.x-1,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 255,255,255,255 } );
                        DrawLine(menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 192,192,192,255 } );
                        DrawLine(menudata->optionsxywh.x-1,menudata->optionsxywh.y+menudata->optionsxywh.h+1,menudata->optionsxywh.x+menudata->optionsxywh.w+2,menudata->optionsxywh.y+menudata->optionsxywh.h+1,(Color){ 192,192,192,255 } );
                        DrawRectangle(menudata->optionsxywh.x,menudata->optionsxywh.y,menudata->optionsxywh.w,menudata->optionsxywh.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->optionsxywh.x+1,menudata->optionsxywh.y+(im->font_height+(im->font_height/2))*k,menudata->optionsxywh.w-2,im->font_height+im->font_height/2,(Color){ 53,132,228,255 });
                                v2.x=(float) (menudata->optionsxywh.x+im->font_height/2);
                                v2.y=(float) (menudata->optionsxywh.y+(im->font_height/4)+(im->font_height+(im->font_height/2))*k);
                                DrawTextEx(im->font
                                  ,menudata->options[k]
                                  ,v2
                                  ,im->font_height
                                  ,0
                                  ,c
                                );
                        }
                } else {
                        FILLXYWH(menudata->optionsxywh,0,0,0,0);
                }
                x=menudata->xywh.x+menudata->xywh.w;
        }
#warning TODO
        return(0);
}

int
imutil_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 *
imutil_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
imutil_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 *
imutil_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 *
imutil_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
is_imutil_insidexywh(Vector2 pos, xywh_t *xywh)
{
        if(xywh==NULL)
                return(0); /* sanity check error */
        if(pos.x>=(float)(xywh->x)
          && pos.x<=(float)(xywh->x+xywh->w)
          && pos.y>=(float)(xywh->y)
          && pos.y<=(float)(xywh->y+xywh->h)
        ) {
                return(1);
        }
        return(0);
}

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

int *
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,
};
        if(sizecodepoints!=NULL)
                *sizecodepoints=(int) (sizeof(codepoints)/sizeof(codepoints[0]));
        return(codepoints);
}