/** @file  pmConfig.h
 *
 *  @author PAP, IfA
 *
 *  @version $Revision: 1.7.4.2 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-02-24 04:02:19 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 *
 */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "pslib.h"
#include "pmConfig.h"

#define PS_SITE "PS_SITE"               // Name of the environment variable containing the site config file
#define PS_DEFAULT_SITE "ipprc.config"  // Default site config file

/** readConfig
 *
 * This function attempts to open the file specified in the parameter list
 * parse it into metadata, then return it as a psMetaData table.
 *
 */
static bool readConfig(
    psMetadata **config,                // Config to output
    const char *name,                   // Name of file
    const char *description)            // Description of file
{
    unsigned int numBadLines = 0;

    psLogMsg(__func__, PS_LOG_INFO, "Loading %s configuration from file %s\n",
             description, name);
    *config = psMetadataConfigParse(NULL, &numBadLines, name, true);
    if (numBadLines > 0) {
        psLogMsg(__func__, PS_LOG_WARN, "%d bad lines in %s configuration file (%s)\n",
                 description, name);
    }
    if (!*config) {
        psError(PS_ERR_IO, false, "Unable to read %s configuration from %s\n",
                description, name);
        return false;
    }

    return true;
}


/******************************************************************************
pmConfigRead(**site, **camera, **recipe, *argc, **argv, char *recipeName)
 
XXX: The log/trace command line options (as processed by psArgumentVerbosity)
must take precedence override the values set here.  This must be, somehow,
coded.
 
XXX: Must load camera and recipe configuration if specified in the command line.
 *****************************************************************************/
bool pmConfigRead(
    psMetadata **site,
    psMetadata **camera,
    psMetadata **recipe,
    int *argc,
    char **argv,
    const char *recipeName)
{
    PS_ASSERT_PTR_NON_NULL(site, false);
    // PS_ASSERT_PTR_NON_NULL(*site, false);
    PS_ASSERT_PTR_NON_NULL(camera, false);
    // PS_ASSERT_PTR_NON_NULL(*camera, false);
    PS_ASSERT_PTR_NON_NULL(recipe, false);
    // PS_ASSERT_PTR_NON_NULL(*recipe, false);
    PS_ASSERT_INT_POSITIVE(*argc, false);
    PS_ASSERT_PTR_NON_NULL(argv, false);

    //
    // The following section of code attempts to determine which file is
    // the configuration file.  At the end of this code block, the siteName
    // variable will contain the name of the configuration file.
    //
    char *siteName = NULL;
    //
    // First, try command line
    //
    psS32 argNum = psArgumentGet(*argc, argv, "-site");
    if (argNum != 0) {
        //
        // We remove the "-site" argument from argv.  Then
        // we look for the next argument, which should be the filename, and
        // remove it as well.
        //
        psArgumentRemove(argNum, argc, argv);
        if (argNum >= *argc) {
            psLogMsg(__func__, PS_LOG_WARN,
                     "-site command-line switch provided without the required filename --- ignored.\n");
        } else {
            siteName = argv[argNum];
            psArgumentRemove(argNum, argc, argv);
        }
    }
    //
    // Next, try environment variable
    //
    if (!siteName) {
        siteName = getenv(PS_SITE);
    }

    //
    // Last chance is ~/.ipprc
    //
    bool cleanupSiteName = false; // Do I have to psFree siteName?
    if (!siteName) {
        siteName = psStringCopy(PS_DEFAULT_SITE);
        cleanupSiteName = true;
    }

    //
    // We have the connfiguration filename; now we read and parse the config
    // file and store in psMetadata struct site.
    //

    if (!readConfig(site, siteName, "site")) {
        return false;
    }
    if (cleanupSiteName) {
        psFree(siteName);
    }


    //
    // Next, we do a similar thing for the recipe configuration file.  The
    // file is read and parsed into psMetadata struct "recipe".
    //
    //
    argNum = psArgumentGet(*argc, argv, "-recipe");
    if (argNum > 0) {
        psArgumentRemove(argNum, argc, argv);
        if (argNum >= *argc) {
            psLogMsg(__func__, PS_LOG_WARN,
                     "-recipe command-line switch provided without the required filename --- ignored.\n");
        } else {
            psArgumentRemove(argNum, argc, argv);
            readConfig(recipe, argv[argNum], "recipe");
        }
    }
    // Or, load the recipe from the camera file, if appropriate
    if (! *recipe && *camera && recipeName) {
        *recipe = pmConfigRecipeFromCamera(*camera, recipeName);
    }


    //
    // Next, we do a similar thing for the camera configuration file.  The
    // file is read and parsed into psMetadata struct "camera".
    //
    argNum = psArgumentGet(*argc, argv, "-camera");
    if (argNum > 0) {
        psArgumentRemove(argNum, argc, argv);
        if (argNum >= *argc) {
            psLogMsg(__func__, PS_LOG_WARN,
                     "-camera command-line switch provided without the required filename --- ignored.\n");
        } else {
            psArgumentRemove(argNum, argc, argv);
            readConfig(camera, argv[argNum], "camera");
        }
    } else {
        // XXX: Not sure is this is correct.
        *camera = NULL;
    }


    //
    // We now have the config, camera, and recipe files parsed and stored in
    // metadata.  Now, we can look into the site configuration and do
    // the required stuff.
    //
    bool mdok = true;   // Status of MD lookup result

    //
    // If TIME is specified in the configuration file, then we must initialize
    // with a call to psTimeInitialize.
    //
    psString timeName = psMetadataLookupStr(&mdok, *site, "TIME");
    if (mdok && timeName) {
        psTrace(__func__, 7, "Initialising psTime with file %s\n", timeName);
        // XXX: PAP had a call to psLibInit is PRODUCTION not set.  Why?
        psTimeInitialize(timeName);
    }


    //
    // If LOGLEVEL is specified in the configuration file, then we must initialize
    // with a call to psLogSetLevel().
    //
    int logLevel = psMetadataLookupS32(&mdok, *site, "LOGLEVEL");
    if (mdok && logLevel >= 0) {
        psTrace(__func__, 7, "Setting log level to %d\n", logLevel);
        psLogSetLevel(logLevel);
    }


    //
    // If LOGFORMAT is specified in the configuration file, then we must initialize
    // with a call to psLogSetFormat().
    //
    psString logFormat = psMetadataLookupStr(&mdok, *site, "LOGFORMAT");
    if (mdok && logFormat) {
        psTrace(__func__, 7, "Setting log format to %s\n", logFormat);
        psLogSetFormat(logFormat);
    }


    //
    // If LOGDEST is specified in the configuration file, then we must initialize
    // with a call to psLogSetDestination().
    // XXX: This is not spec'ed in the SDRS.
    //
    psString logDest = psMetadataLookupStr(&mdok, *site, "LOGDEST");
    if (mdok && logDest) {
        // XXX: Only stdout and stderr are provided for now; this section should be
        // expanded in the future to do files, and perhaps even sockets.
        int logFD = STDIN_FILENO; // a known invalid value
        if (!strcasecmp(logDest, "STDOUT")) {
            logFD = STDOUT_FILENO;
        }
        if (!strcasecmp(logDest, "STDERR")) {
            logFD = STDERR_FILENO;
        }
        if (logFD == STDIN_FILENO) {
            psLogMsg(__func__, PS_LOG_WARN, "Only STDERR and STDOUT currently supported as a log destination.\n");
            logFD = STDERR_FILENO;
        }
        psTrace(__func__, 7, "Setting log destination to STDOUT.\n");
        psLogSetDestination(logFD);
    }

    //
    // If TRACE is specified in the configuration file, then we must initialize
    // with a call to psTraceSetLevel().
    // XXX: This is not spec'ed in the SDRS.
    //
    psMetadata *trace = psMetadataLookupMD(&mdok, *site, "TRACE");
    if (mdok && trace) {
        psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
        psMetadataItem *traceItem = NULL; // Item from MD iteration
        while ((traceItem = psMetadataGetAndIncrement(traceIter))) {
            if (traceItem->type != PS_DATA_S32) {
                psLogMsg(__func__, PS_LOG_WARN, "The level for trace component %s is not of type S32 (%x)\n",
                         traceItem->name, traceItem->type);
                continue;
            }
            psTrace(__func__, 7, "Setting trace level for %s to %d\n", traceItem->name, traceItem->data.S32);
            psTraceSetLevel(traceItem->name, traceItem->data.S32);
        }
        psFree(traceIter);
    }

    //
    // Allow command line options to override defaults for logging.
    // XXX: Is it appropriate to use the ArgVerbosity function for this?
    //   A: it removes the options from the command line.
    //   B: will the pmConfigRead function always be called on initialization.
    //
    psS32 saveLogLevel = psLogGetLevel();
    psArgumentVerbosity(argc, argv);
    // XXX: substitute the string for the default log level for "2".
    if (2 == psLogGetLevel()) {
        psLogSetLevel(saveLogLevel);
    }
    return(true);
}


// XXX EAM : was trying headerItem when it was NULL
// XXX EAM : should just free & return on first failure
bool pmConfigValidateCamera(
    const psMetadata *camera,
    const psMetadata *header)
{
    // Read the rule for that camera
    bool mdStatus = true;
    psMetadata *rule = psMetadataLookupMD(&mdStatus, camera, "RULE");
    if (! mdStatus || ! rule) {
        psLogMsg(__func__, PS_LOG_WARN, "Unable to read rule for camera.\n");
        return false;
    }

    // Apply the rules
    psMetadataIterator *ruleIter = psMetadataIteratorAlloc(rule, PS_LIST_HEAD, NULL); // Rule iterator
    psMetadataItem *ruleItem = NULL;    // Item from the metadata
    bool match = true;                  // Does it match?
    while ((ruleItem = psMetadataGetAndIncrement(ruleIter)) && match) {
        // Check for the existence of the rule
        psMetadataItem *headerItem = psMetadataLookup((psMetadata*)header, ruleItem->name);
        if (! headerItem || headerItem->type != ruleItem->type) {
            psFree(ruleIter);
            return false;
        }

        // Check to see if the rule works
        switch (ruleItem->type) {
        case PS_DATA_STRING:
            psTrace(__func__, 8, "Matching %s: '%s' vs '%s'\n", ruleItem->name,
                    ruleItem->data.V, headerItem->data.V);
            if (strncmp(ruleItem->data.V, headerItem->data.V,
                        strlen(ruleItem->data.V)) != 0) {
                psFree(ruleIter);
                return false;
            }
            break;
        case PS_DATA_S32:
        case PS_DATA_BOOL:
            psTrace(__func__, 8, "Matching %s: %d vs %d\n", ruleItem->name,
                    ruleItem->data.S32, headerItem->data.S32);
            if (ruleItem->data.S32 != headerItem->data.S32) {
                psFree(ruleIter);
                return false;
            }
            break;
        case PS_DATA_F32:
            psTrace(__func__, 8, "Matching %s: %f vs %f\n", ruleItem->name,
                    ruleItem->data.F32, headerItem->data.F32);
            if (ruleItem->data.F32 != headerItem->data.F32) {
                psFree(ruleIter);
                return false;
            }
            break;
        case PS_DATA_F64:
            psTrace(__func__, 8, "Matching %s: %g vs %g\n", ruleItem->name,
                    ruleItem->data.F64, headerItem->data.F64);
            if (ruleItem->data.F64 != headerItem->data.F64) {
                psFree(ruleIter);
                return false;
            }
            break;
        default:
            psLogMsg(__func__, PS_LOG_WARN, "Ignoring invalid type in metadata: %x\n",
                     ruleItem->type);
        }
    } // Iterating through the RULEs

    psFree(ruleIter);
    return match;
}



// Work out what camera we have, based on the FITS header and a set of
// rules specified in the IPP configuration; return the camera configuration
psMetadata *pmConfigCameraFromHeader(
    const psMetadata *ipprc,            // The IPP configuration
    const psMetadata *header)           // The FITS header
{
    bool mdStatus = false;  // Metadata lookup status
    psMetadata *cameras = psMetadataLookupMD(&mdStatus, ipprc, "CAMERAS");
    if (! mdStatus) {
        psError(PS_ERR_IO, false, "Unable to find CAMERAS in the configuration.\n");
        return NULL;
    }
    psMetadata *winner = NULL;       // The camera configuration whose rule first matches
    //  the supplied header
    // Iterate over the cameras
    psMetadataIterator *iterator = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
    psMetadataItem *cameraItem = NULL; // Item from the metadata

    while ((cameraItem = psMetadataGetAndIncrement(iterator))) {
        // Open the camera information
        psTrace(__func__, 3, "Inspecting camera %s (%s)\n", cameraItem->name,
                cameraItem->comment);
        psMetadata *camera = NULL; // The camera metadata
        if (cameraItem->type == PS_DATA_METADATA) {
            camera = psMemIncrRefCounter(cameraItem->data.md);
        } else if (cameraItem->type == PS_DATA_STRING) {
            psTrace(__func__, 5, "Reading camera configuration for %s...\n", cameraItem->name);
            unsigned int badLines = 0;  // Number of bad lines in reading camera configuration
            camera = psMetadataConfigParse(NULL, &badLines, cameraItem->data.V, true);
            if (badLines > 0) {
                psLogMsg(__func__, PS_LOG_WARN, "%d bad lines encountered while reading camera"
                         "configuration %s\n", badLines, cameraItem->name);
            }
        }

        if (! camera) {
            psLogMsg(__func__, PS_LOG_WARN, "Unable to interpret camera configuration for %s (%s)\n",
                     cameraItem->name, cameraItem->comment);
            continue;
        }

        if (pmConfigValidateCamera(camera, header)) {
            if (! winner) {
                // This is the first match
                winner = psMemIncrRefCounter(camera);
                psLogMsg(__func__, PS_LOG_INFO, "FITS header matches camera %s\n",
                         cameraItem->name);
            } else {
                // We have a duplicate match
                psLogMsg(__func__, PS_LOG_WARN, "Additional camera found that matches the rules: %s\n",
                         cameraItem->name);
            }
        } // Done inspecting the camera

        psFree(camera);

    } // Done looking at all cameras
    if (! winner) {
        psError(PS_ERR_IO, true, "Unable to find an camera that matches input FITS header!\n");
    }

    psFree(iterator);
    return winner;
}

psMetadata *pmConfigRecipeFromCamera(
    const psMetadata *camera,
    const char *recipeName)
{
    PS_ASSERT_PTR_NON_NULL(camera, false);
    PS_ASSERT_PTR_NON_NULL(recipeName, false);

    psMetadata *recipe = NULL;          // Recipe to read
    bool mdok = true;                   // Status of MD lookup
    psMetadata *recipes = psMetadataLookupMD(&mdok, camera, "RECIPES"); // The list of recipes
    if (! mdok || ! recipes) {
        psLogMsg(__func__, PS_LOG_WARN, "RECIPES in the camera configuration file is not of type METADATA\n");
    } else {
        psString recipeFileName = psMetadataLookupStr(&mdok, recipes, recipeName);
        (void)readConfig(&recipe, recipeFileName, "recipe");
    }

    return recipe;
}

/******************************************************************************
pmConfigDB(*site)
 
XXX: this should allow the option of having NO database server, if chosen by config
XXX: What should we use for the Database namespace in the call to psDBInit()?
This is currently NULL.
 *****************************************************************************/

psDB *pmConfigDB(
    psMetadata *site)
{
    PS_ASSERT_PTR_NON_NULL(site, NULL);
    psBool mdStatus01 = false;
    psBool mdStatus02 = false;
    psBool mdStatus03 = false;

    // XXX leaky strings
    psString dbServer = psMetadataLookupStr(&mdStatus01, site, "DBSERVER");
    psString dbUsername = psMetadataLookupStr(&mdStatus02, site, "DBUSER");
    psString dbPassword = psMetadataLookupStr(&mdStatus03, site, "DBPASSWORD");
    psString dbName = psMetadataLookupStr(&mdStatus01, site, "DBNAME");
    if (!(mdStatus01 & mdStatus02 & mdStatus03)) {
        psLogMsg(__func__, PS_LOG_WARN, "Could not determine database server name, userID, and password from site metadata.\n");
        return(NULL);
    }

    return(psDBInit(dbServer, dbUsername, dbPassword, dbName));
}
