/*
 * This file is distributed under the terms of the
 * GNU General Public License. See the file `License'
 * in the root directory of the present distribution,
 * or http://www.gnu.org/copyleft/gpl.txt .
 *
 * generic QM/MM support library
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "libqmmm.h"

#define BUF_SIZE 1024

/* global variable for global QM/MM configuration */
qmmm_config_t qmmmcfg;

/* local helper function: advance char pointer beyond leading whitespace */
static char *skip_whitespace(char *ptr)
{
    while ((*ptr == ' ') || (*ptr == '\t')
           || (*ptr == '\r') || (*ptr == '\n')) ++ptr;
    return ptr;
}

/* local helper function: trim string to remove trailing whitespace */
static void trim_whitespace(char *ptr)
{
    while ((*ptr != ' ') && (*ptr != '\t')
           && (*ptr != '\r') && (*ptr != '\n')) ++ptr;
    *ptr = '\0';
}


/* read and parse global QM/MM configuration file and
   store the result in a qmmm_config_t struct */
int read_qmmm_config(const char *file, qmmm_config_t *cfg)
{
    FILE *fp;
    char *ptr;
    int i, lineno, len;
    char buf[BUF_SIZE];

    /* need to have config file and storage for config provided */
    if ((file == NULL) || (cfg == NULL))
        return QMMM_ERROR;

    /* clear config */
    memset(cfg, 0, sizeof(qmmm_config_t));

    fp = fopen(file,"r");
    if (fp == NULL) return QMMM_ERROR;

    lineno = 0;
    while (fgets(buf,BUF_SIZE,fp)) {
        ++lineno;

        /* remove comments */
        ptr = strchr(buf,'#');
        if (ptr != NULL) *ptr = '\0';

        /* skip over leading whitespace */
        ptr = skip_whitespace(buf);
        len = strlen(ptr);
        if (len == 0) continue;

        /* convert keyword to lower case */
        for (i=0; i < len; ++i) {
            if ((ptr[i]=='\0') || (ptr[i]==' ') || (ptr[i]=='\t'))
                break;
            ptr[i] = tolower(ptr[i]);
        }

        /* handle keywords */
        if (strncmp(ptr,"mode",4) == 0) {
            ptr = skip_whitespace(ptr+4);
            if ((*ptr=='m') || (*ptr=='M'))
                cfg->qmmm_mode = QMMM_MODE_MECH;
            else if ((*ptr=='e') || (*ptr=='E'))
                cfg->qmmm_mode = QMMM_MODE_ELEC;
            else if ((*ptr=='o') || (*ptr=='O'))
                cfg->qmmm_mode = QMMM_MODE_OFF;
            else cfg->qmmm_mode = QMMM_ERROR;

        } else if (strncmp(ptr,"comm",4) == 0) {
            ptr = skip_whitespace(ptr+4);
            if ((*ptr=='m') || (*ptr=='M'))
                cfg->comm_mode = QMMM_COMM_MPI;
            else if ((*ptr=='s') || (*ptr=='S'))
                cfg->comm_mode = QMMM_COMM_SHM;
            else cfg->comm_mode = QMMM_ERROR;

        } else if (strncmp(ptr,"steps",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            cfg->steps = atoi(ptr);

        } else if (strncmp(ptr,"verbose",7) == 0) {
            ptr = skip_whitespace(ptr+7);
            trim_whitespace(ptr);
            cfg->verbose = atoi(ptr);

        } else if (strncmp(ptr,"handle",6) == 0) {
            ptr = skip_whitespace(ptr+6);
            trim_whitespace(ptr);
            if (cfg->handle) free(cfg->handle);
            cfg->handle = strdup(ptr);

        } else if (strncmp(ptr,"restart",7) == 0) {
            ptr = skip_whitespace(ptr+7);
            trim_whitespace(ptr);
            if (cfg->restart) free(cfg->restart);
            cfg->restart = strdup(ptr);

        } else if (strncmp(ptr,"qmdir",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->qmdir) free(cfg->qmdir);
            cfg->qmdir = strdup(ptr);

        } else if (strncmp(ptr,"madir",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->madir) free(cfg->madir);
            cfg->madir = strdup(ptr);

        } else if (strncmp(ptr,"sldir",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->sldir) free(cfg->sldir);
            cfg->sldir = strdup(ptr);

        } else if (strncmp(ptr,"qminp",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->qminp) free(cfg->qminp);
            cfg->qminp = strdup(ptr);

        } else if (strncmp(ptr,"mainp",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->mainp) free(cfg->mainp);
            cfg->mainp = strdup(ptr);

        } else if (strncmp(ptr,"slinp",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->slinp) free(cfg->slinp);
            cfg->slinp = strdup(ptr);

        } else if (strncmp(ptr,"qmout",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->qmout) free(cfg->qmout);
            cfg->qmout = strdup(ptr);

        } else if (strncmp(ptr,"maout",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->maout) free(cfg->maout);
            cfg->maout = strdup(ptr);

        } else if (strncmp(ptr,"slout",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            trim_whitespace(ptr);
            if (cfg->slout) free(cfg->slout);
            cfg->slout = strdup(ptr);

        } else if (strncmp(ptr,"qmcmd",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->qmcmd) free(cfg->qmcmd);
            cfg->qmcmd = strdup(ptr);

        } else if (strncmp(ptr,"macmd",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->macmd) free(cfg->macmd);
            cfg->macmd = strdup(ptr);

        } else if (strncmp(ptr,"slcmd",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->slcmd) free(cfg->slcmd);
            cfg->slcmd = strdup(ptr);

        } else if (strncmp(ptr,"qmarg",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->qmarg) free(cfg->qmarg);
            cfg->qmarg = strdup(ptr);

        } else if (strncmp(ptr,"maarg",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->maarg) free(cfg->maarg);
            cfg->maarg = strdup(ptr);

        } else if (strncmp(ptr,"slarg",5) == 0) {
            ptr = skip_whitespace(ptr+5);
            if (cfg->slarg) free(cfg->slarg);
            cfg->slarg = strdup(ptr);

        } else {
            fprintf(stderr,"\nParse error in line %d of file %s\n>>%s<<\n\n",
                    lineno, file, buf);
            return QMMM_ERROR;
        }

        if (feof(fp)) break;
    }

    fclose(fp);
    return QMMM_OK;
}


/* perform consistency checks on a qmmm_config_t struct */
const char *check_qmmm_config(qmmm_config_t *cfg) {
    const char *msg = NULL;

    if (cfg->qmmm_mode == QMMM_MODE_NONE) {
        msg = "QM/MM coupling mode not set";
    } else if (cfg->steps < 1) {
        msg = "Number of QM/MM steps must be > 0";
    } else if (cfg->qmdir == NULL) {
        msg = "QM directory not set";
    } else if (cfg->madir == NULL) {
        msg = "MM master directory not set";
    } else if (cfg->sldir == NULL) {
        msg = "MM slave directory not set";
    } else if (cfg->qminp == NULL) {
        msg = "QM input file not set";
    } else if (cfg->mainp == NULL) {
        msg = "MM master input file not set";
    } else if (cfg->slinp == NULL) {
        msg = "MM slave input file not set";
    } else if (cfg->qmout == NULL) {
        msg = "QM output file not set";
    } else if (cfg->maout == NULL) {
        msg = "MM master output file not set";
    } else if (cfg->slout == NULL) {
        msg = "MM slave output file not set";
    }
    return msg;
}

/* take the existing global QM/MM configuration and write it to a file */
int write_qmmm_config(const char *file, qmmm_config_t *cfg)
{
    FILE *fp;
    time_t now;

    if (cfg == NULL)
        return QMMM_ERROR;

    if (file == NULL)
        fp = stderr;
    else {
        fp = fopen(file,"w");
        if (fp == NULL) return QMMM_ERROR;
    }

    now = time(NULL);
    fprintf(fp,"# QM/MM global configuration. %s\n",ctime(&now));
    fprintf(fp,"# comm_mode: %d\n", cfg->comm_mode);
    if (cfg->qmmm_mode == QMMM_MODE_OFF) {
        fputs("mode off\n",fp);
    } else if (cfg->qmmm_mode == QMMM_MODE_NONE) {
        fputs("mode none\n",fp);
    } else if (cfg->qmmm_mode == QMMM_MODE_MECH) {
        fputs("mode mechanical\n",fp);
    } else if (cfg->qmmm_mode == QMMM_MODE_ELEC) {
        fputs("mode electrostatic\n",fp);
    } else fputs("mode unknown\n",fp);

    if (cfg->restart) fprintf(fp,"restart %s\n",cfg->restart);
    if (cfg->handle)  fprintf(fp,"handle %s\n", cfg->handle);
    fprintf(fp,"steps %d\n",cfg->steps);
    fprintf(fp,"verbose %d\n",cfg->verbose);

    fputs("\n# QM system configuration:\n",fp);
    if (cfg->qmdir) fprintf(fp,"qmdir %s\n", cfg->qmdir);
    else fputs("qmdir (required input not set)\n",fp);

    if (cfg->qminp) fprintf(fp,"qminp %s\n", cfg->qminp);
    else fputs("qminp (required input not set)\n",fp);

    if (cfg->qmout) fprintf(fp,"qmout %s\n", cfg->qmout);
    else fputs("qmout NULL\n",fp);

    if (cfg->qmcmd) fprintf(fp,"qmcmd %s\n", cfg->qmcmd);
    if (cfg->qmarg) fprintf(fp,"qmarg %s\n", cfg->qmarg);

    fputs("\n# MM master system configuration:\n",fp);
    if (cfg->madir) fprintf(fp,"madir %s\n", cfg->madir);
    else fputs("madir (required input not set)\n",fp);

    if (cfg->mainp) fprintf(fp,"mainp %s\n", cfg->mainp);
    else fputs("mainp (required input not set)\n",fp);

    if (cfg->maout) fprintf(fp,"maout %s\n", cfg->maout);
    else fputs("maout NULL\n",fp);

    if (cfg->macmd) fprintf(fp,"macmd %s\n", cfg->macmd);
    if (cfg->maarg) fprintf(fp,"maarg %s\n", cfg->maarg);

    fputs("\n# MM slave system configuration:\n",fp);
    if (cfg->sldir) fprintf(fp,"sldir %s\n", cfg->sldir);
    else fputs("sldir (required input not set)\n",fp);

    if (cfg->slinp) fprintf(fp,"slinp %s\n", cfg->slinp);
    else fputs("slinp (required input not set)\n",fp);

    if (cfg->slout) fprintf(fp,"slout %s\n", cfg->slout);
    else fputs("slout NULL\n",fp);

    if (cfg->slcmd) fprintf(fp,"slcmd %s\n", cfg->slcmd);
    if (cfg->slarg) fprintf(fp,"slarg %s\n", cfg->slarg);

    if (file != NULL) fclose(fp);
    return QMMM_OK;
}

/* free storage associated with qmmm_config_t struct */
void free_qmmm_config(qmmm_config_t *cfg) {

    if (cfg->qmdir != NULL) free(cfg->qmdir);
    if (cfg->madir != NULL) free(cfg->madir);
    if (cfg->sldir != NULL) free(cfg->sldir);

    if (cfg->qminp != NULL) free(cfg->qminp);
    if (cfg->mainp != NULL) free(cfg->mainp);
    if (cfg->slinp != NULL) free(cfg->slinp);

    if (cfg->qmout != NULL) free(cfg->qmout);
    if (cfg->maout != NULL) free(cfg->maout);
    if (cfg->slout != NULL) free(cfg->slout);

    if (cfg->qmcmd != NULL) free(cfg->qmcmd);
    if (cfg->macmd != NULL) free(cfg->macmd);
    if (cfg->slcmd != NULL) free(cfg->slcmd);

    if (cfg->qmarg != NULL) free(cfg->qmarg);
    if (cfg->maarg != NULL) free(cfg->maarg);
    if (cfg->slarg != NULL) free(cfg->slarg);
}
