/*
 * This file is part of the GIRAFFE Pipeline
 * Copyright (C) 2002-2019 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>
#if defined HAVE_SYS_TYPES_H
#  include <sys/types.h>
#endif

#include <cxmemory.h>
#include <cxstring.h>
#include <cxstrutils.h>

#include <cpl_propertylist.h>

#include "giutils.h"
#include "gipaf.h"


#define PAF_HDR_START      "PAF.HDR.START"
#define PAF_TYPE           "PAF.TYPE"
#define PAF_ID             "PAF.ID"
#define PAF_NAME           "PAF.NAME"
#define PAF_DESC           "PAF.DESC"
#define PAF_CRTE_NAME      "PAF.CRTE.NAME"
#define PAF_CRTE_TIME      "PAF.CRTE.DAYTIM"
#define PAF_LCHG_NAME      "PAF.LCHG.NAME"
#define PAF_LCHG_TIME      "PAF.LCHG.DAYTIM"
#define PAF_CHCK_NAME      "PAF.CHCK.NAME"
#define PAF_CHCK_TIME      "PAF.CHCK.DAYTIM"
#define PAF_CHCK_CHECKSUM  "PAF.CHCK.CHECKSUM"
#define PAF_HDR_END        "PAF.HDR.END"


/*
 * Value and comment field start position
 */

static const cxsize PAF_FIELD_OFFSET_VALUE = 20;
static const cxsize PAF_FIELD_OFFSET_COMMENT = 45;


/*
 * Maximum length of a parameter file record, i.e. maximum number of
 * characters per line of a parameter file on disk. This does not include
 * a trailing 0.
 */

static const cxsize PAF_RECORD_MAX = 256;


/**
 * @defgroup gipaf VLT Parameter File Support
 *
 * TBD
 */

/**@{*/

struct GiPafHdr {
    cxchar *name;
    cxchar *type;
    cxchar *id;
    cxchar *description;
};

typedef struct GiPafHdr GiPafHdr;


struct GiPaf {
    GiPafHdr *header;
    cpl_propertylist *records;
};


inline static cxint
_giraffe_paf_format_line(cx_string *line, const cxchar *name,
                           const cxchar *value, const cxchar *comment,
                           cxbool append)
{

    cxchar buffer[PAF_RECORD_MAX + 2];

    cxsize sz = 0;
    cxsize cpos = 0;


    cx_assert(line != NULL);

    if (name == NULL) {
        return -1;
    }


    /*
     * Verify that the record name fits into the buffer. The extra
     * character is for the semicolon which has to be present.
     */

    sz = strlen(name);

    if (sz + 1 > PAF_RECORD_MAX) {
        return 1;
    }

    memset(buffer, ' ', PAF_RECORD_MAX + 1);
    memcpy(buffer, name, sz);

    cpos = sz;

    if (value != NULL) {

        if (cpos < PAF_FIELD_OFFSET_VALUE) {
            cpos = PAF_FIELD_OFFSET_VALUE;
        }
        else {
            ++cpos;
        }

        sz = strlen(value);


        /*
         * Verify that writing the value string does not overflow
         * the buffer.
         */

        if (sz > PAF_RECORD_MAX - cpos + 1) {
            return 2;
        }

        memcpy(&buffer[cpos], value, sz);
        cpos += sz;

    }

    buffer[cpos++] = ';';


    /*
     * Comments are only printed if there is room in the buffer for
     * at least 3 characters, so that not only the hash and the
     * following blank appear in the output because of the finite
     * record size.
     */

    if (comment != NULL && comment[0] != '\0' &&
        (PAF_RECORD_MAX - cpos + 1) > 2) {

        if (cpos < PAF_FIELD_OFFSET_COMMENT) {
            cpos = PAF_FIELD_OFFSET_COMMENT;
        }
        else {
            ++cpos;
        }

        memcpy(&buffer[cpos], "# ", 2);
        cpos += 2;

        sz = strlen(comment);
        sz = sz < PAF_RECORD_MAX - cpos + 1 ? sz : PAF_RECORD_MAX - cpos + 1;

        memcpy(&buffer[cpos], comment, sz);
        cpos += sz;
    }

    buffer[cpos] = '\n';
    buffer[++cpos] = '\0';


    if (append == TRUE) {
        cx_string_append(line, buffer);
    }
    else {
        cx_string_set(line, buffer);
    }

    return 0;

}


inline static cxint
_giraffe_paf_convert_property(cx_string *line, cpl_property *property,
                              cxbool append)
{

    cxint status = 0;

    const cxchar *name;
    const cxchar *comment;

    cx_string *value = NULL;


    cx_assert(line != NULL);

    if (property == NULL) {
        return -1;
    }

    value = cx_string_new();

    switch (cpl_property_get_type(property)) {
        case CPL_TYPE_CHAR:
        {
            cxchar c =  cpl_property_get_char(property);

            cx_string_sprintf(value, "%c", c);
        }
        break;

        case CPL_TYPE_BOOL:
        {
            cxint b = cpl_property_get_bool(property);

            if (b != 0) {
                cx_string_set(value, "T");
            }
            else {
                cx_string_set(value, "F");
            }
        }
        break;

        case CPL_TYPE_INT:
        {
            cxint i =  cpl_property_get_int(property);

            cx_string_sprintf(value, "%d", i);
        }
        break;

        case CPL_TYPE_LONG:
        {
            cxlong l =  cpl_property_get_long(property);

            cx_string_sprintf(value, "%ld", l);
        }
        break;

        case CPL_TYPE_FLOAT:
        {
            cxfloat f =  cpl_property_get_float(property);

            cx_string_sprintf(value, "%.15G", f);

            if (!strchr(cx_string_get(value), '.')) {

                if (strchr(cx_string_get(value), 'E')) {
                    cx_string_sprintf(value, "%.1E", f);
                }
                else {
                    cx_string_append(value, ".");
                }
            }
        }
        break;

        case CPL_TYPE_DOUBLE:
        {
            cxdouble d =  cpl_property_get_double(property);

            cx_string_sprintf(value, "%.15G", d);

            if (!strchr(cx_string_get(value), '.')) {

                if (strchr(cx_string_get(value), 'E')) {
                    cx_string_sprintf(value, "%.1E", d);
                }
                else {
                    cx_string_append(value, ".");
                }
            }
        }
        break;

        case CPL_TYPE_STRING:
        {
            const cxchar *s = cpl_property_get_string(property);

            cx_string_sprintf(value, "\"%s\"", s);
        }
        break;

        default:

            /*
             * Unsupported property type. This point should never be
             * reached!
             */

            cx_string_delete(value);

            return 1;
            break;
    }

    name = cpl_property_get_name(property);
    comment = cpl_property_get_comment(property);

    status = _giraffe_paf_format_line(line, name, cx_string_get(value),
                                      comment, append);

    if (status != 0) {
        cx_string_delete(value);
        return 2;
    }

    cx_string_delete(value);

    return 0;

}


inline static GiPafHdr *
_giraffe_pafhdr_create(const cxchar *name, const cxchar *type,
                       const cxchar *id, const cxchar *description)
{

    GiPafHdr *self = cx_calloc(1, sizeof *self);


    self->name = cx_strdup(name);
    self->type = cx_strdup(type);

    if (id != NULL) {
        self->id = cx_strdup(id);
    }

    if (description != NULL) {
        self->description = cx_strdup(description);
    }

    return self;

}


inline static void
_giraffe_pafhdr_destroy(GiPafHdr *self)
{

    if (self != NULL) {
        if (self->name != NULL) {
            cx_free(self->name);
            self->name = NULL;
        }

        if (self->type != NULL) {
            cx_free(self->type);
            self->type = NULL;
        }

        if (self->id != NULL) {
            cx_free(self->id);
            self->id = NULL;
        }

        if (self->description != NULL) {
            cx_free(self->description);
            self->description = NULL;
        }

        cx_free(self);
    }

    return;

}


inline static cxint
_giraffe_pafhdr_write(GiPafHdr *self, FILE *stream)
{

    if (stream == NULL) {
        return -1;
    }

    if (self != NULL) {

        cxchar *user;
        cxchar *timestamp;

        cx_string *header = cx_string_new();


#if defined HAVE_GETUID && defined HAVE_GETPWUID

        struct passwd *pw;

        pw = getpwuid(getuid());

        if (pw == NULL) {
            cx_string_delete(header);
            return 1;
        }

        user = pw->pw_name;

#else
        user = getenv("USER");
        user = user == NULL ? getenv("LOGNAME") : user;

        if (user == NULL) {
            cx_string_delete(header);
            return 1;
        }

#endif

        timestamp = giraffe_localtime_iso8601();

        if (timestamp == NULL) {
            cx_string_delete(header);
            return 2;
        }


        /*
         * Write formatted header records to a string buffer
         */

        _giraffe_paf_format_line(header, PAF_HDR_START, NULL, NULL, TRUE);
        _giraffe_paf_format_line(header, PAF_TYPE, self->type, "Type of "
                                 "parameter file", TRUE);

        if (self->id != NULL) {
            _giraffe_paf_format_line(header, PAF_ID, self->id, NULL, TRUE);
        }
        else {
            _giraffe_paf_format_line(header, PAF_ID, "", NULL, TRUE);
        }

        _giraffe_paf_format_line(header, PAF_NAME, self->name, "Name of "
                                 "PAF", TRUE);

        if (self->description != NULL) {
            _giraffe_paf_format_line(header, PAF_DESC, self->description,
                                     "Short description of PAF", TRUE);
        }
        else {
            _giraffe_paf_format_line(header, PAF_DESC, "", "Short "
                                     "description of PAF", TRUE);
        }

        _giraffe_paf_format_line(header, PAF_CRTE_NAME, user, "Name of "
                                 "creator", TRUE);
        _giraffe_paf_format_line(header, PAF_CRTE_TIME, timestamp,
                                 "Civil time for creation", TRUE);

        _giraffe_paf_format_line(header, PAF_LCHG_NAME, "", "Author of "
                                 "par. file", TRUE);
        _giraffe_paf_format_line(header, PAF_LCHG_TIME, "", "Timestamp for "
                                 "last change", TRUE);

        _giraffe_paf_format_line(header, PAF_CHCK_NAME, "", "Name of appl. "
                                 "checking", TRUE);
        _giraffe_paf_format_line(header, PAF_CHCK_TIME, "", "Time for "
                                 "checking", TRUE);
        _giraffe_paf_format_line(header, PAF_CHCK_CHECKSUM, "", "Checksum "
                                 "for the PAF", TRUE);

        _giraffe_paf_format_line(header, PAF_HDR_END, NULL, NULL, TRUE);


        /*
         * Write string buffer contents to the output stream
         */

        fprintf(stream, "%s", cx_string_get(header));

        if (ferror(stream) != 0) {
            cx_free(timestamp);
            cx_string_delete(header);

            return 3;
        }

        cx_free(timestamp);
        cx_string_delete(header);
    }

    return 0;

}


GiPaf *
giraffe_paf_new(const cxchar *name, const cxchar *type, const cxchar *id,
                const cxchar *description)
{

    GiPaf *self = NULL;


    if (name == NULL || type == NULL) {
        return NULL;
    }

    self = cx_malloc(sizeof *self);

    self->header = _giraffe_pafhdr_create(name, type, id, description);
    self->records = cpl_propertylist_new();

    cx_assert(self->header != NULL);
    cx_assert(self->records != NULL);

    return self;

}


void
giraffe_paf_delete(GiPaf *self)
{

    if (self != NULL) {
        if (self->records != NULL) {
            cpl_propertylist_delete(self->records);
            self->records = NULL;
        }

        if (self->header != NULL) {
            _giraffe_pafhdr_destroy(self->header);
            self->header = NULL;
        }

        cx_free(self);
    }

    return;

}


cxchar *
giraffe_paf_get_name(const GiPaf *self)
{

    if (self == NULL) {
        return NULL;
    }

    cx_assert(self->header != NULL);
    cx_assert(self->header->name != NULL);

    return self->header->name;

}


cxint
giraffe_paf_set_name(GiPaf *self, const cxchar *name)
{

    cx_assert(self != NULL);

    if (name == NULL) {
        return -1;
    }

    if (self->header->name != NULL) {
        self->header->name = cx_realloc(self->header->name,
                                        (strlen(name) + 1) * sizeof(cxchar));
        strcpy(self->header->name, name);
    }
    else {
        self->header->name = cx_strdup(name);
    }

    return 0;

}


cxchar *
giraffe_paf_get_type(const GiPaf *self)
{

    if (self == NULL) {
        return NULL;
    }

    cx_assert(self->header != NULL);
    cx_assert(self->header->type != NULL);

    return self->header->type;

}


cxint
giraffe_paf_set_type(GiPaf *self, const cxchar *type)
{

    cx_assert(self != NULL);

    if (type == NULL) {
        return -1;
    }

    if (self->header->type != NULL) {
        self->header->type = cx_realloc(self->header->type,
                                        (strlen(type) + 1) * sizeof(cxchar));
        strcpy(self->header->type, type);
    }
    else {
        self->header->type = cx_strdup(type);
    }

    return 0;

}


cxchar *
giraffe_paf_get_id(const GiPaf *self)
{

    if (self == NULL) {
        return NULL;
    }

    cx_assert(self->header != NULL);

    return self->header->id;

}


cxint
giraffe_paf_set_id(GiPaf *self, const cxchar *id)
{

    cx_assert(self != NULL);

    if (id == NULL) {
        return -1;
    }

    if (self->header->id != NULL) {
        self->header->id = cx_realloc(self->header->id,
                                      (strlen(id) + 1) * sizeof(cxchar));
        strcpy(self->header->id, id);
    }
    else {
        self->header->id = cx_strdup(id);
    }

    return 0;

}


cxchar *
giraffe_paf_get_description(const GiPaf *self)
{

    if (self == NULL) {
        return NULL;
    }

    cx_assert(self->header != NULL);

    return self->header->description;

}


cxint
giraffe_paf_set_description(GiPaf *self, const cxchar *description)
{

    cx_assert(self != NULL);

    if (description == NULL) {
        return -1;
    }

    if (self->header->description != NULL) {
        self->header->description = cx_realloc(self->header->description,
                                               (strlen(description) + 1) *
                                               sizeof(cxchar));
        strcpy(self->header->description, description);
    }
    else {
        self->header->description = cx_strdup(description);
    }

    return 0;

}


cpl_propertylist *
giraffe_paf_get_properties(const GiPaf *self)
{

    if (self == NULL) {
        return NULL;
    }

    cx_assert(self->records != NULL);

    return self->records;

}


cxint
giraffe_paf_set_properties(GiPaf *self, const cpl_propertylist *properties)
{

    cx_assert(self != NULL);

    if (properties == NULL) {
        return -1;
    }

    if (self->records != NULL) {
        cpl_propertylist_delete(self->records);
    }

    self->records = cpl_propertylist_duplicate(properties);

    return 0;

}


cxint
giraffe_paf_write(const GiPaf *self)
{

    cxint status = 0;

    FILE *stream = NULL;


    if (self == NULL) {
        return -1;
    }

    cx_assert(self->header != NULL);

    stream = fopen(giraffe_paf_get_name(self), "wb");

    if (stream == NULL) {
        return 1;
    }


    /*
     * Write PAF header
     */

    status = _giraffe_pafhdr_write(self->header, stream);

    if (status != 0) {
        fclose(stream);
        return 2;
    }

    fflush(stream);


    /*
     * Write PAF records
     */

    if (self->records != NULL && !cpl_propertylist_is_empty(self->records)) {

        cxchar buffer[PAF_RECORD_MAX];

        register cxlong i;

        cx_string *line = NULL;


        buffer[0] = '#';
        memset(&buffer[1], '-', 78);
        buffer[79] = '\0';
        fprintf(stream, "%s\n", buffer);

        if (ferror(stream) != 0) {
            fclose(stream);
            return 3;
        }

        line = cx_string_new();

        for (i = 0; i < cpl_propertylist_get_size(self->records); i++) {

            cpl_property *p = cpl_propertylist_get(self->records, i);


            status = _giraffe_paf_convert_property(line, p, FALSE);

            if (status != 0) {
                cx_string_delete(line);
                fclose(stream);

                return 4;
            }

            fprintf(stream, "%s", cx_string_get(line));

            if (ferror(stream) != 0) {
                cx_string_delete(line);
                fclose(stream);

                return 5;
            }
        }

        cx_string_delete(line);
        fflush(stream);

    }

    fclose(stream);

    return 0;

}
/**@}*/
