
#include "psm.h"

UBYTE *sem_name = TITLENAME " V" FULL_VERSION " tracking semaphore";

struct track_sem {
    struct SignalSemaphore sem;
    struct List track_list;
};

struct track_entry {
    struct Node node;
    struct Screen *scrn;
    struct List mem_list;
};

struct track_mem {
    struct MinNode node;
};

static struct track_sem *get_track_sem(void)
{
    struct track_sem *track;
    
    Forbid();
    if( !(track = (struct track_sem *)FindSemaphore(sem_name)) ) {
        track = (struct track_sem *)
            AllocVec(sizeof(struct track_sem),MEMF_PUBLIC|MEMF_CLEAR);
        if(!track) {
            Permit();
            return NULL;
        }
        if( !(track->sem.ss_Link.ln_Name
                = AllocVec(strlen(sem_name)+2,MEMF_PUBLIC)) ) {
            Permit();
            FreeVec(track);
            return NULL;
        }

        strcpy(track->sem.ss_Link.ln_Name,sem_name);
        track->sem.ss_Link.ln_Pri = 0;
        InitSemaphore(&track->sem);
        NewList(&track->track_list);
        AddSemaphore(&track->sem);
    }
    ObtainSemaphore(&track->sem);

    Permit();
    return track;
}

// -----------------------------------------------
// Free all memory attached to a screen tracker.
// -----------------------------------------------

void FreeScrnTrack(void *scrn_track)
{
    struct Node *ent;
    
    if(!scrn_track) return;

    while( (ent=RemTail(&((struct track_entry *)scrn_track)->mem_list)) ) {
        FreeVec(ent);
    }
    
    FreeVec(scrn_track);
}

// -----------------------------------------------
// Allocate a new screen tracker.
// -----------------------------------------------

void *AllocScrnTrack(void)
{
    struct track_entry *track;
    
    if(!(track=AllocVec(sizeof(struct track_entry),MEMF_PUBLIC|MEMF_CLEAR))) {
        return NULL;
    }
    
    NewList(&track->mem_list);
    track->scrn = NULL;
    return track;
}

// -----------------------------------------------
// Allocate a block of memory and attach it to the screen tracker.
// -----------------------------------------------

void *AllocScrnTrackMem(void *scrn_track,ULONG size)
{
    void *mem;

    size += sizeof(struct track_mem);

    mem = AllocVec(size,MEMF_PUBLIC|MEMF_CLEAR);
    
    if(mem) {
        AddTail( &((struct track_entry *)scrn_track)->mem_list, mem );
        return (void *)( ((ULONG)mem) + sizeof(struct track_mem) );
    }
    return NULL;
}

// -----------------------------------------------
// Add a completed screen tracker to the public list.
// -----------------------------------------------

void AddScrnTrack(void *scrn_track,struct Screen *scrn)
{
    struct track_sem *sem;

    if(!scrn_track) return;

    if(scrn) {
        ((struct track_entry *)scrn_track)->scrn = scrn;
    } else if(!((struct track_entry *)scrn_track)->scrn) {
        FreeScrnTrack(scrn_track);
        return;
    }

    if( (sem = get_track_sem()) ) {
        AddTail(&sem->track_list,scrn_track);
        ReleaseSemaphore(&sem->sem);
    }
}

// -----------------------------------------------
// Find and remove the tracker attached to the given screen.
// -----------------------------------------------

void *GetScrnTrack(struct Screen *scrn)
{
    struct track_sem *sem;
    struct track_entry *ent;

    if(!scrn) return NULL;

    if( (sem = get_track_sem()) ) {
        for(ent = head_node(&sem->track_list);
            ent != NULL;
            ent = next_node(ent)) {
            
            if(ent->scrn == scrn) {
                Remove(&ent->node);
                ReleaseSemaphore(&sem->sem);
                return ent;
            }
        }
        ReleaseSemaphore(&sem->sem);
    }
    
    return NULL;
}

// -----------------------------------------------
// Check if the given screen is being tracked.
// -----------------------------------------------

void *CheckScrnTrack(struct Screen *scrn)
{
    struct track_sem *sem;
    struct track_entry *ent;

    if(!scrn) return NULL;

    if( (sem = get_track_sem()) ) {
        for(ent = head_node(&sem->track_list);
            ent != NULL;
            ent = next_node(ent)) {
            
            if(ent->scrn == scrn) {
                ReleaseSemaphore(&sem->sem);
                return ent;
            }
        }
        ReleaseSemaphore(&sem->sem);
    }

    return NULL;
}

// -----------------------------------------------
// Close the screen and any memory we may be tracking.
// -----------------------------------------------

BOOL CloseScrnTrack(struct Screen *scrn)
{
    struct track_entry *ent;
    
    if(!scrn) return TRUE;

    ent = GetScrnTrack(scrn);
    
    if( CloseScreen(scrn) ) {
        if(ent) FreeScrnTrack(ent);
        return TRUE;
    }

    AddScrnTrack(ent,NULL);
    return FALSE;
}
