/* dumpvalue() service, which prints to Standard Output the value of
   an xmlrpc_value.

   We've put this in a separate module in hopes that it eventually can be
   used for debugging purposes in other places.
*/

//#define _GNU_SOURCE

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

#include "xmlrpc_config.h"  /* information about this build environment */
#include "int.h"
#include "casprintf.h"
#include "mallocvar.h"

#include "xmlrpc-c/base.h"
#include "xmlrpc-c/string_int.h"

#include "dumpvalue.h"



static void
dumpInt(const char *   const prefix,
        xmlrpc_value * const valueP) {

    xmlrpc_env env;
    xmlrpc_int value;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_int(&env, valueP, &value);
    
    if (env.fault_occurred)
        printf("Internal error: unable to extract value of "
               "integer xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%sInteger: %d\n", prefix, value);

    xmlrpc_env_clean(&env);
}



static void
dumpBool(const char *   const prefix,
         xmlrpc_value * const valueP) {

    xmlrpc_env env;
    xmlrpc_bool value;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_bool(&env, valueP, &value);
    
    if (env.fault_occurred)
        printf("Internal error: Unable to extract value of "
               "boolean xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%sBoolean: %s\n", prefix, value ? "TRUE" : "FALSE");

    xmlrpc_env_clean(&env);
}



static void
dumpDouble(const char *   const prefix,
           xmlrpc_value * const valueP) {

    xmlrpc_env env;
    xmlrpc_double value;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_double(&env, valueP, &value);
    
    if (env.fault_occurred)
        printf("Internal error: Unable to extract value from "
               "floating point number xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%sFloating Point: %f\n", prefix, value);

    xmlrpc_env_clean(&env);
}



static void
dumpDatetime(const char *   const prefix,
             xmlrpc_value * const valueP) {

    printf("%sDon't know how to print datetime value %lx.\n", 
           prefix, (unsigned long) valueP);
}



static size_t
nextLineSize(const char * const string,
             size_t       const startPos,
             size_t       const stringSize) {
/*----------------------------------------------------------------------------
   Return the length of the line that starts at offset 'startPos' in the
   string 'string', which is 'stringSize' characters long.

   'string' in not NUL-terminated.
   
   A line begins at beginning of string or after a newline character and
   runs through the next newline character or end of string.  The line
   includes the newline character at the end, if any.
-----------------------------------------------------------------------------*/
    size_t i;

    for (i = startPos; i < stringSize && string[i] != '\n'; ++i);

    if (i < stringSize)
        ++i;  /* Include the newline */

    return i - startPos;
}



static void
dumpMultilineString(const char * const prefix,
                    const char * const value,
                    size_t       const length) {

    size_t cursor;  /* Index into value[] */

    for (cursor = 0; cursor < length; ) {
        /* Print one line of buffer */

        size_t const lineSize = nextLineSize(value, cursor, length);
        const char * const printableLine =
            xmlrpc_makePrintable_lp(&value[cursor], lineSize);
        
        printf("%s%s\n", prefix, printableLine);

        cursor += lineSize;

        strfree(printableLine);
    }
}



static void
dumpSimpleString(const char * const value,
                 size_t       const length) {

    const char * const printable = xmlrpc_makePrintable_lp(value, length);

    printf("'%s'\n", printable);

    strfree(printable);
}



static void
dumpString(const char *   const prefix,
           xmlrpc_value * const valueP) {

    xmlrpc_env env;
    size_t length;
    const char * value;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_string_lp(&env, valueP, &length, &value);
    
    if (env.fault_occurred)
        printf("Internal error: Unable to extract value from "
               "string xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else {
        printf("%sString: ", prefix);
        if (strlen(value) == length && strchr(value, '\n')) {
            const char * prefix2;
            casprintf(&prefix2, "%s  ", prefix);
            printf("\n");
            dumpMultilineString(prefix2, value, length);
            strfree(prefix2);
        } else
            dumpSimpleString(value, length);

        strfree(value);
    }
    xmlrpc_env_clean(&env);
}



static void
dumpBase64(const char *   const prefix,
           xmlrpc_value * const valueP) {

    xmlrpc_env env;
    const unsigned char * value;
    size_t length;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_base64(&env, valueP, &length, &value);
    
    if (env.fault_occurred)
        printf("Unable to parse base64 bit string xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else {
        unsigned int i;

        printf("%sBit string: ", prefix);
        for (i = 0; i < length; ++i)
            printf("%02x", value[i]);

        free((void*)value);
    }
    xmlrpc_env_clean(&env);
}



static void
dumpArray(const char *   const prefix,
          xmlrpc_value * const arrayP) {

    xmlrpc_env env;
    unsigned int arraySize;

    xmlrpc_env_init(&env);

    XMLRPC_ASSERT_ARRAY_OK(arrayP);

    arraySize = xmlrpc_array_size(&env, arrayP);
    if (env.fault_occurred)
        printf("Unable to get array size.  %s\n", env.fault_string);
    else {
        int const spaceCount = strlen(prefix);

        unsigned int i;
        const char * blankPrefix;

        printf("%sArray of %u items:\n", prefix, arraySize);

        casprintf(&blankPrefix, "%*s", spaceCount, "");

        for (i = 0; i < arraySize; ++i) {
            xmlrpc_value * valueP;

            xmlrpc_array_read_item(&env, arrayP, i, &valueP);

            if (env.fault_occurred)
                printf("Unable to get array item %u\n", i);
            else {
                const char * prefix2;

                casprintf(&prefix2, "%s  Index %2u ", blankPrefix, i);
                dumpValue(prefix2, valueP);
                strfree(prefix2);

                xmlrpc_DECREF(valueP);
            }
        }
        strfree(blankPrefix);
    }
    xmlrpc_env_clean(&env);
}



static void
dumpStructMember(const char *   const prefix,
                 xmlrpc_value * const structP,
                 unsigned int   const index) {

    xmlrpc_env env;

    xmlrpc_value * keyP;
    xmlrpc_value * valueP;
    
    xmlrpc_env_init(&env);

    xmlrpc_struct_read_member(&env, structP, index, &keyP, &valueP);

    if (env.fault_occurred)
        printf("Unable to get struct member %u\n", index);
    else {
        int const blankCount = strlen(prefix);
        const char * prefix2;
        const char * blankPrefix;

        casprintf(&prefix2, "%s  Key:   ", prefix);
        dumpValue(prefix2, keyP);
        strfree(prefix2);

        casprintf(&blankPrefix, "%*s", blankCount, "");
        
        casprintf(&prefix2, "%s  Value: ", blankPrefix);
        dumpValue(prefix2, valueP);
        strfree(prefix2);

        strfree(blankPrefix);

        xmlrpc_DECREF(keyP);
        xmlrpc_DECREF(valueP);
    }
    xmlrpc_env_clean(&env);
}



static void
dumpStruct(const char *   const prefix,
           xmlrpc_value * const structP) {

    xmlrpc_env env;
    unsigned int structSize;

    xmlrpc_env_init(&env);

    structSize = xmlrpc_struct_size(&env, structP);
    if (env.fault_occurred)
        printf("Unable to get struct size.  %s\n", env.fault_string);
    else {
        unsigned int i;

        printf("%sStruct of %u members:\n", prefix, structSize);

        for (i = 0; i < structSize; ++i) {
            const char * prefix1;

            if (i == 0)
                prefix1 = strdup(prefix);
            else {
                int const blankCount = strlen(prefix);
                casprintf(&prefix1, "%*s", blankCount, "");
            }            
            dumpStructMember(prefix1, structP, i);

            strfree(prefix1);
        }
    }
    xmlrpc_env_clean(&env);
}



static void
dumpCPtr(const char *   const prefix,
         xmlrpc_value * const valueP) {

    xmlrpc_env env;
    void * value;

    xmlrpc_env_init(&env);

    xmlrpc_read_cptr(&env, valueP, &value);
        
    if (env.fault_occurred)
        printf("Unable to parse C pointer xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%sC pointer: '%lux'\n", prefix, (unsigned long)value);

    xmlrpc_env_clean(&env);
}



static void
dumpNil(const char *   const prefix,
        xmlrpc_value * const valueP) {

    xmlrpc_env env;

    xmlrpc_env_init(&env);

    xmlrpc_read_nil(&env, valueP);
        
    if (env.fault_occurred)
        printf("Internal error: nil value xmlrpc_value %lx "
               "is not valid.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%sNil\n", prefix);

    xmlrpc_env_clean(&env);
}



static void
dumpI8(const char *   const prefix,
       xmlrpc_value * const valueP) {

    xmlrpc_env env;
    xmlrpc_int64 value;
    
    xmlrpc_env_init(&env);

    xmlrpc_read_i8(&env, valueP, &value);
    
    if (env.fault_occurred)
        printf("Internal error: unable to extract value of "
               "64-bit integer xmlrpc_value %lx.  %s\n",
               (unsigned long)valueP, env.fault_string);
    else
        printf("%s64-bit integer: %" PRId64 "\n", prefix, value);

    xmlrpc_env_clean(&env);
}



static void
dumpUnknown(const char *   const prefix,
            xmlrpc_value * const valueP) {

    printf("%sDon't recognize value type %u of xmlrpc_value %lx.\n", 
           prefix, xmlrpc_value_type(valueP), (unsigned long)valueP);
    printf("%sCan't print it.\n", prefix);
}



void
dumpValue(const char *   const prefix,
          xmlrpc_value * const valueP) {

    switch (xmlrpc_value_type(valueP)) {
    case XMLRPC_TYPE_INT:
        dumpInt(prefix, valueP);
        break;
    case XMLRPC_TYPE_BOOL: 
        dumpBool(prefix, valueP);
        break;
    case XMLRPC_TYPE_DOUBLE: 
        dumpDouble(prefix, valueP);
        break;
    case XMLRPC_TYPE_DATETIME:
        dumpDatetime(prefix, valueP);
        break;
    case XMLRPC_TYPE_STRING: 
        dumpString(prefix, valueP);
        break;
    case XMLRPC_TYPE_BASE64:
        dumpBase64(prefix, valueP);
        break;
    case XMLRPC_TYPE_ARRAY: 
        dumpArray(prefix, valueP);
        break;
    case XMLRPC_TYPE_STRUCT:
        dumpStruct(prefix, valueP);
        break;
    case XMLRPC_TYPE_C_PTR:
        dumpCPtr(prefix, valueP);
        break;
    case XMLRPC_TYPE_NIL:
        dumpNil(prefix, valueP);
        break;
    case XMLRPC_TYPE_I8:
        dumpI8(prefix, valueP);
        break;
    default:
        dumpUnknown(prefix, valueP);
    }
}
