/*************************************************************************\
* Copyright (c) 2009 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
 *      Authors:        Benjamin Franksen (BESY) and Marty Kraimer
 *      Date:           06-01-91
 *      major Revision: 07JuL97
 */

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>

#include "dbDefs.h"
#include "ellLib.h"
#include "epicsMutex.h"
#include "epicsThread.h"
#include "cantProceed.h"

#include "initHooks.h"

typedef struct initHookLink {
    ELLNODE          node;
    initHookFunction func;
} initHookLink;

static ELLLIST functionList = ELLLIST_INIT;
static epicsMutexId listLock;

/*
 * Lazy initialization functions
 */
static void initHookOnce(void *arg)
{
    listLock = epicsMutexMustCreate();
}

static void initHookInit(void)
{
    static epicsThreadOnceId onceFlag = EPICS_THREAD_ONCE_INIT;
    epicsThreadOnce(&onceFlag, initHookOnce, NULL);
}

/*
 * To be called before iocInit reaches state desired.
 */
int initHookRegister(initHookFunction func)
{
    initHookLink *newHook;
    ELLNODE *cur;

    if (!func) return 0;

    initHookInit();

    epicsMutexMustLock(listLock);

    for(cur = ellFirst(&functionList); cur; cur = ellNext(cur)) {
        const initHookLink *fn = CONTAINER(cur, initHookLink, node);
        if(fn->func==func) {
            /* silently ignore duplicate */
            epicsMutexUnlock(listLock);
            return 0;
        }
    }

    newHook = (initHookLink *)mallocMustSucceed(sizeof(initHookLink), "initHookRegister");
    newHook->func = func;

    ellAdd(&functionList, &newHook->node);
    epicsMutexUnlock(listLock);
    return 0;
}

/*
 * Called by iocInit at various points during initialization.
 * This function must only be called by iocInit and relatives.
 */
void initHookAnnounce(initHookState state)
{
    initHookLink *hook;

    initHookInit();

    epicsMutexMustLock(listLock);
    hook = (initHookLink *)ellFirst(&functionList);
    epicsMutexUnlock(listLock);

    while (hook != NULL) {
        hook->func(state);

        epicsMutexMustLock(listLock);
        hook = (initHookLink *)ellNext(&hook->node);
        epicsMutexUnlock(listLock);
    }
}

void initHookFree(void)
{
    initHookInit();
    epicsMutexMustLock(listLock);
    ellFree(&functionList);
    epicsMutexUnlock(listLock);
}

/*
 * Call any time you want to print out a state name.
 */
const char *initHookName(int state)
{
    const char *stateName[] = {
        "initHookAtIocBuild",
        "initHookAtBeginning",
        "initHookAfterCallbackInit",
        "initHookAfterCaLinkInit",
        "initHookAfterInitDrvSup",
        "initHookAfterInitRecSup",
        "initHookAfterInitDevSup",
        "initHookAfterInitDatabase",
        "initHookAfterFinishDevSup",
        "initHookAfterScanInit",
        "initHookAfterInitialProcess",
        "initHookAfterCaServerInit",
        "initHookAfterIocBuilt",

        "initHookAtIocRun",
        "initHookAfterDatabaseRunning",
        "initHookAfterCaServerRunning",
        "initHookAfterIocRunning",

        "initHookAtIocPause",
        "initHookAfterCaServerPaused",
        "initHookAfterDatabasePaused",
        "initHookAfterIocPaused",

        "initHookAtShutdown",
        "initHookAfterCloseLinks",
        "initHookAfterStopScan",
        "initHookAfterStopCallback",
        "initHookAfterStopLinks",
        "initHookBeforeFree",
        "initHookAfterShutdown",

        "initHookAfterPrepareDatabase",
        "initHookBeforeCleanupDatabase",

        "initHookAfterInterruptAccept",
        "initHookAtEnd"
    };
    STATIC_ASSERT(NELEMENTS(stateName)==initHookAtEnd+1);
    if (state < 0 || state >= NELEMENTS(stateName)) {
        return "Not an initHookState";
    }
    return stateName[state];
}
