/*
 * bg.c
 *
 * Wrapper over pthread with the addition of some interthread comms.
 *
 * History:
 *      20250904 Creation from imgmover prototype.
 *
 * 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 <pthread.h>
#include <sys/time.h>
#if !defined(__linux__) && !defined(ANDROID)
#include "win32_pipe.h"
#endif

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

#define UNLOADTIMEOUTSECONDS 10
#define THREADNUMBER 8

static int mysleep(struct timeval *tv);

void
bg_staticinit(void)
{
        /* not needed now, was for win32pipe_init() */
}

void
bg_staticfini(void)
{
        /* not needed now, was for win32pipe_fini() */
}

bg_t *
bg_init(int sizebgload)
{
        bg_t *bg;
        char *errstr;
        bg=NULL;
        if((errstr="Insuf. mem. for bg")==NULL
          || (bg=calloc(1,sizeof(bg_t)))==NULL
          || (errstr="Insuf mem bgload")==NULL
          || (bg->bgload=calloc(sizebgload,sizeof(bgload_t)))==NULL
          || (bg->sizebgload=sizebgload)!=sizebgload
          || (errstr="pthread attr init error")==NULL
          || pthread_attr_init(&(bg->tattr))!=0
          || (errstr="pthread create error")==NULL
          || pthread_create(&(bg->thread),&(bg->tattr),bg_thread,(void *)bg)!=0
          || (bg->flag_threadstarted=1)!=1
        ) {
                global_messagebox("%s",errstr);
                bg_free(bg);
                return(NULL);
        }
        return(bg);
}


void
bg_free(bg_t *bg)
{
        int i;
        if(bg==NULL)
                return; /* nothing to do */
        if(bg->flag_threadstarted) {
                bg->do_exit=1;
#if 1
fprintf(stderr,"bg_free: joining thread\n");
#endif
                pthread_join(bg->thread,NULL);
#if 1
fprintf(stderr,"bg_free: thread joined OK\n");
#endif
                bg->flag_threadstarted=0;
                bg->do_exit=0;
        }
        if(bg->bgload!=NULL) {
                bgload_t *bgload;
                for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                        if(bgload->has_data) {
                                UnloadImage(bgload->image);
                                bgload->has_data=0;
                        }
                }
                free(bg->bgload),bg->bgload=NULL,bg->sizebgload=0;
        }
        return;
}

int
bg_resetmarks(bg_t *bg)
{
        int i;
        bgload_t *bgload;
        if(bg==NULL)
                return(-1);
        for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++)
                bgload->has_mark=0;
        return(0);
}

bgload_t *
bg_get(bg_t *bg, char *path)
{
        int i;
        bgload_t *bgload;
        if(bg==NULL)
                return(NULL);
        for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                if(bgload->thread_finished && bgload->has_data && strcmp(path,bgload->path)==0) {
                        bgload->has_mark=1;
#if 1
fprintf(stderr,"bg_get: \"%s\"\n",bgload->path);
#endif
                        return(bgload);
                }
        }
        return(NULL);
}

int
bg_add(bg_t *bg, char *path, int flag_pinned)
{
        int i;
        bgload_t *bgload;
        if(bg==NULL)
                return(-1);
        if(path!=NULL && flag_pinned) {
                memset(bg->pinnedpath,0,sizeof(bg->pinnedpath));
                strncpy(bg->pinnedpath,path,sizeof(bg->pinnedpath)-1);
        }
        for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                if(bgload->lended_to_thread && strcmp(path,bgload->path)==0) {
                        bgload->is_todo=1;
                        bgload->has_mark=1;
                        gettimeofday(&(bgload->tv_lastadded),NULL); // Breaks premise of "don't touch until thread finished", but this is rather innocuous
                        return(0); /* already on list */
                }
        }
        for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                if(bgload->lended_to_thread==0) {
                        memset(bgload,0,sizeof(bgload_t));
                        strncpy(bgload->path,path,sizeof(bgload->path));
                        bgload->path[sizeof(bgload->path)-1]='\0';
                        bgload->is_todo=1;
                        bgload->has_mark=1;
                        gettimeofday(&(bgload->tv_lastadded),NULL); // Breaks premise of "don't touch until thread finished", but this is rather innocuous
                        bgload->lended_to_thread=1;
                        return(0); /* added to list */
                }
        }
        return(-1); /* couldn't add */
}

int
bg_freeunmarked(bg_t *bg)
{
        int i;
        bgload_t *bgload;
        time_t now;
        if(bg==NULL)
                return(-1);
        now=time(NULL);
        for(i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                if(bgload->lended_to_thread && bgload->thread_finished && bgload->has_mark==0) {
                        if(bgload->has_data
                          && (bgload->lastused<(now-UNLOADTIMEOUTSECONDS)
                            || bgload->lastused>(now+UNLOADTIMEOUTSECONDS))
                          && strcmp(bgload->path,bg->pinnedpath)!=0
                        ) {
#if 1
fprintf(stderr,"bg: Unloading: \"%s\"\n",bgload->path);
#endif
                                UnloadImage(bgload->image);
                                bgload->has_data=0;
                        }
#if 1
else {
fprintf(stderr,"bg: Cancelling: \"%s\"\n",bgload->path);
}
#endif
                        memset(bgload,0,sizeof(bgload_t));
                } else if(bgload->lended_to_thread && bgload->thread_finished && bgload->has_mark!=0 && bgload->has_data) {
                        bgload->lastused=now;
                }
        }
        return(0);
}

void *
bg_thread(void *parambg)
{
        bg_t *bg;
        bg=(bg_t *)parambg;
        int i;
        bgload_t *bgload,*candidate;
        while(1) {
                if(bg->do_exit!=0)
                        break; /* was told to exit */
                for(candidate=NULL,i=0,bgload=bg->bgload;i<bg->sizebgload;i++,bgload++) {
                        if(bgload->lended_to_thread==0)
                                continue;
                        if(bgload->is_todo==0) {
                                bgload->thread_finished=1;
                                continue;
                        }
                        if(bgload->has_data==0 && bgload->has_failedload==0) {
                                if(candidate==NULL
                                  || bgload->tv_lastadded.tv_sec>candidate->tv_lastadded.tv_sec
                                  || (bgload->tv_lastadded.tv_sec==candidate->tv_lastadded.tv_sec && bgload->tv_lastadded.tv_usec>candidate->tv_lastadded.tv_usec)
                                ) {
                                        candidate=bgload;
                                }
                        }
                }
                if(candidate!=NULL) {
                        bgload=candidate;
                        bgload->image=global_loadimage(bgload->path);
                        if(IsImageValid(bgload->image))
                                bgload->has_data=1;
                        else
                                bgload->has_failedload=1;
                        bgload->lastused=time(NULL);
                        bgload->thread_finished=1;
                } else {
                        struct timeval tv;
                        tv.tv_sec=0,tv.tv_usec=50*1000; /* sleep 50ms if nothing to do */
                        mysleep(&tv);
                }
#if 1
fprintf(stderr,"."),fflush(stderr);
#endif
        }
        pthread_exit(NULL);
        return(NULL);
}

#if !defined(__linux__) && !defined(ANDROID)
static int
mysleep(struct timeval *tv)
{
        win32pipe_sleep(tv->tv_sec*1000+tv->tv_usec/1000);
        return(0);
}
#else
static int
mysleep(struct timeval *tv)
{
        struct timespec ts;
        if(tv==NULL)
                return(-1);
        ts.tv_sec=tv->tv_sec,ts.tv_nsec=tv->tv_usec*1000;
        nanosleep(&ts,NULL);
        return(0);
}
#endif