/*
 * 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>
#if !defined(__linux__) && !defined(ANDROID)
#include "win32_pipe.h"
#endif

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

#define UNLOADTIMEOUTSECONDS 10

static int mypipe(int fds[2]);
static int mypiperead(int fd, char *buf, int count);
static int mypipewrite(int fd, char *buf, int count);

void
bg_staticinit(void)
{
#if !defined(__linux__) && !defined(ANDROID)
        win32pipe_init();
#endif
}

void
bg_staticfini(void)
{
#if !defined(__linux__) && !defined(ANDROID)
        win32pipe_fini();
#endif
}

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="Error init pipes (please check program is not blocked in firewall)")==NULL
          || (bg->pipe[0]=bg->pipe[1]=-1)!=-1
          || mypipe(bg->pipe)!=0
          || (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) {
                char dummy=1;
#if 1
fprintf(stderr,"bg_free: notifying thread to exit\n");
#endif
                mypipewrite(bg->pipe[WR],&dummy,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;
        }
        if(bg->pipe[0]!=-1)
                close(bg->pipe[0]),bg->pipe[0]=-1;
        if(bg->pipe[1]!=-1)
                close(bg->pipe[1]),bg->pipe[1]=-1;
        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 i;
        bgload_t *bgload;
        char dummy;
        if(bg==NULL)
                return(-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
                        dummy=0;
                        mypipewrite(bg->pipe[WR],&dummy,1);
                        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
                        dummy=0;
                        bgload->lended_to_thread=1;
                        mypipewrite(bg->pipe[WR],&dummy,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))) {
#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;
        char dummy;
        bg=(bg_t *)parambg;
        int i;
        bgload_t *bgload,*candidate;
        while(1) {
                mypiperead(bg->pipe[RD],&dummy,1);
#if 1
fprintf(stderr,"Thread received byte: %i\n",(int)((unsigned char)dummy));
#endif
                if(dummy!=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;
                }
        }
        pthread_exit(NULL);
        return(NULL);
}



#if !defined(__linux__) && !defined(ANDROID)
static int
mypipe(int fds[2])
{
        return(win32pipe_pipe(fds));
}

static int
mypiperead(int fd, char *buf, int count)
{
        return(win32pipe_read(fd,buf,count));
}

static int
mypipewrite(int fd, char *buf, int count)
{
        return(win32pipe_write(fd,buf,count));
}
#else
static int
mypipe(int fds[2])
{
        return(pipe(fds));
}

static int
mypiperead(int fd, char *buf, int count)
{
        return(read(fd,buf,count));
}

static int
mypipewrite(int fd, char *buf, int count)
{
        return(write(fd,buf,count));
}
#endif