// SPDX-License-Identifier: GPL-2.0-only
/* eurephia_values.c  --  Generic interface for processing key->value pairs
 *
 * This version is modified to work outside the eurephia project.
 *
 *  GPLv2 only - Copyright (C) 2008
 *               David Sommerseth <dazo@users.sourceforge.net>
 *
 */

/**
 * @file   eurephia_values.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-08-06
 *
 * @brief  Generic interface for handling key->value pairs
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <eurephia_nullsafe.h>
#include <eurephia_values_struct.h>



/**
 * Function for freeing up an eurephiaVALUES stack.  This function is normally not called
 * directly, but usually via the eFree_values(...) macro.
 *
 * @param vls  Pointer to a eurephiaVALUES stack to be freed.
 */
void eFree_values_func(eurephiaVALUES *vls) {
	eurephiaVALUES *ptr = NULL, *ptr_next = NULL;

	ptr = vls;
	while( ptr ) {
		free_nullsafe(ptr->key);
		free_nullsafe(ptr->val);

		ptr_next = ptr->next;
		free_nullsafe(ptr);
		ptr = ptr_next;
	}
}


/**
 * Retrieve an eurephiaVALUES element for a given value key
 *
 * @param vls  Pointer to the eurephiaVALUES stack where to search for the element
 * @param key  String containing the key name of the value requested.
 *
 * @return Returns an eurephiaVALUES element on success, otherwise NULL.
 */
eurephiaVALUES *eGet_valuestruct(eurephiaVALUES *vls, const char *key)
{
        eurephiaVALUES *ptr = NULL;

        if( (vls == NULL) || (key == NULL) ) {
                return NULL;
        }

        ptr = vls;
        while( ptr != NULL ) {
                if( (ptr->key != NULL) && (strcmp(key, ptr->key) == 0) ) {
                        return ptr;
                }
                ptr = ptr->next;
        }
        return NULL;
}


/**
 * Retrieves the value of a given key from an eurephiaVALUES stack.
 *
 * @param vls  Pointer to an eurephiaVALUES stack where to search for the value
 * @param key  String containing the key name of the value requested
 *
 * @return Returns a string (char *) with the requested value if found, otherwise NULL.
 */
char *eGet_value(eurephiaVALUES *vls, const char *key)
{
        eurephiaVALUES *ptr = NULL;

        ptr = eGet_valuestruct(vls, key);
        return (ptr != NULL ? ptr->val : NULL);
}


/**
 * Creates a new eurephiaVALUES stack
 *
 * @param log   Log context
 * @param evgid  int value, giving the stack an ID number.  Useful when looking through log files later on.
 *
 * @return Returns an empty eurephiaVALUES struct on success, otherwise NULL.
 */
eurephiaVALUES *eCreate_value_space(LogContext *log, int evgid)
{
        eurephiaVALUES *ptr = NULL;

        ptr = (eurephiaVALUES *) malloc_nullsafe(log, sizeof(eurephiaVALUES) + 2);
	ptr->log = log;
        ptr->evgid = evgid;
        return ptr;
}


/**
 * Adds a new eurephiaVALUES stack to another eurephiaVALUES stack.  If the evgid value differs, it will
 * be overwritten with the value of the destination stack.
 *
 * @param vls    Destination eurephiaVALUES stack
 * @param newval Source eurephiaVALUES stack
 */
void eAdd_valuestruct(eurephiaVALUES *vls, eurephiaVALUES *newval) {
        eurephiaVALUES *ptr = NULL;
        int vid = 0;

        assert(vls != NULL);

        if( (vls->key == NULL) && (vls->val == NULL) && (vls->next == NULL) && (vls->evid == 0)) {
                // Update header record if it is empty, by copying newval record.  Free newval afterwards
                vls->key  = strdup(newval->key);
                vls->val  = strdup(newval->val);
                vls->evid = 0;
                vls->next = NULL;
                eFree_values_func(newval);
        } else {
                // Add values to the value chain, loop to the end and append it
                ptr = vls;
                while( ptr->next != NULL ) {
                        ptr = ptr->next;
                        vid = (vid > ptr->evid ? vid : ptr->evid);
                }
                newval->evid = vid+1;     // Increase the value counter
                newval->evgid = ptr->evgid;
                ptr->next = newval;
        }
}


/**
 * Adds a new key/value pair to an eurephiaVALUES stack
 *
 * @param vls  Destination eurephiaVALUES stack
 * @param key  Key name for the value being stored
 * @param val  Value to be stored
 */
void eAdd_value(eurephiaVALUES *vls, const char *key, const char *val)
{
        eurephiaVALUES *ptr = NULL;

        assert(vls != NULL);

        // Allocate buffer and save values
        ptr = eCreate_value_space(vls->log, vls->evid);
        if( ptr == NULL ) {
		writelog(vls->log, LOG_EMERG, "Failed to add value to the value chain");
		exit(9);
        }
        ptr->key = strdup_nullsafe(key);
        ptr->val = strdup_nullsafe(val);
        ptr->evgid = vls->evgid;

        // Add value struct to the chain
        eAdd_valuestruct(vls, ptr);
}


/**
 * Updates the value of a key in a values stack
 *
 * @param vls      eurephiaVALUES key/value stack to update
 * @param key      String with key name to update
 * @param newval   String with the new value
 * @param addunkn  Add unknown keys.  If set to 1, if the key is not found it will add a new key
 */
void eUpdate_value(eurephiaVALUES *vls, const char *key, const char *newval, const int addunkn) {
	eurephiaVALUES *ptr = NULL;

	assert( (vls != NULL) && (key != NULL) );

	ptr = eGet_valuestruct(vls, key);
	if( ptr ) {
		free_nullsafe(ptr->val);
		ptr->val = strdup_nullsafe(newval);
	} else if( addunkn == 1 ) {
		eAdd_value(vls, key, newval);
	}
}


/**
 * Updates a value struct element based on another value struct element contents (key/value)
 *
 * @param vls      eurephiaVALUES key/value stack to update
 * @param newval   eurephiaVALUES element with the new value
 * @param addunkn  Add unknown keys.  If set to 1, if the key is not found it will add a new key
 *
 * @return Returns a pointer to the first element in the chain.  If the element being updated
 *         was the first element in the old chain, the first element will be a new element with a
 *         new address.
 */
eurephiaVALUES *eUpdate_valuestruct(eurephiaVALUES *vls, eurephiaVALUES *newval, const int addunkn) {
	eurephiaVALUES *ptr = NULL, *prevptr = NULL;

	assert( (vls != NULL) && (newval != NULL) && (newval->key != NULL) );

	prevptr = vls;
	for( ptr = vls; ptr != NULL; ptr = ptr->next ) {
                if( (ptr->key != NULL) && (strcmp(newval->key, ptr->key) == 0) ) {
			newval->evgid = ptr->evgid;
			newval->evid = ptr->evid;
			newval->next = ptr->next;
			ptr->next = NULL;
			if( ptr == vls ) {
				// If the element found is the first one, do special treatment
				eFree_values_func(ptr);
				return newval;
			} else {
				prevptr->next = newval;
				eFree_values_func(ptr);
				return vls;
			}
                }
		prevptr = ptr;
	}

	if( addunkn == 1 ) {
		eAdd_valuestruct(vls, newval);
	}
	return vls;
}


/**
 * Removes the key/value pair identified by evgid and evid from the given eurephiaVALUES chain
 *
 * @param vls    Pointer to an eurephiaVALUES chain with the data
 * @param evgid  Group ID of the chain
 * @param evid   Element ID of the chain element to be removed
 *
 * @return Returns a pointer to the chain.  The pointer is only changed if the first element in the
 *         chain is deleted
 */
eurephiaVALUES *eRemove_value(eurephiaVALUES *vls, unsigned int evgid, unsigned int evid) {
        eurephiaVALUES *ptr = NULL, *prev_ptr = NULL;
        int found = 0;

        // Find the value element
        for( ptr = vls; ptr != NULL; ptr = ptr->next ) {
                if( (ptr->evgid == evgid) && (ptr->evid == evid) ) {
                        found = 1;
                        break;
                }
                prev_ptr = ptr;
        }

        if( !found ) {
                return vls;
        }

        if( ptr != vls ) {
                prev_ptr->next = ptr->next;
                ptr->next = NULL;
                eFree_values_func(ptr);
                return vls;
        } else {
                prev_ptr = ptr->next;
                ptr->next = NULL;
                eFree_values_func(ptr);
                return prev_ptr;
        }
}


/**
 * Counts number of elements in an eurephiaVALUES chain.
 *
 * @param vls eurephiaVALUES pointer to be counted
 *
 * @return Returns number of elements found.
 */
unsigned int eCount(eurephiaVALUES *vls) {
	eurephiaVALUES *ptr = NULL;
	unsigned int c = 0;

	if( vls == NULL ) {
		return 0;
	}
	for(ptr = vls; ptr != NULL; ptr = ptr->next ) {
		c++;
	}
	return c;
}
