/********************************************************************
 * sixtp-utils.c                                                    *
 * Copyright (c) 2001 Gnumatic, Inc.                                *
 *                                                                  *
 * 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, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
 *                                                                  *
 ********************************************************************/

#include <guid.hpp>
#include <config.h>

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

#include <time.h>
#include <errno.h>

#ifndef HAVE_STRPTIME
#include "strptime.h"
#endif
#include <gnc-date.h>

#include "sixtp.h"
#include "sixtp-utils.h"

#include <charconv>
#include <cctype>

static QofLogModule log_module = GNC_MOD_IO;

gboolean
isspace_str (const gchar* str, int nomorethan)
{
    const gchar* cursor = str;
    while (*cursor && (nomorethan != 0))
    {
        if (!isspace (*cursor))
        {
            return (FALSE);
        }
        cursor++;
        nomorethan--;
    }
    return (TRUE);
}

gboolean
allow_and_ignore_only_whitespace (GSList* sibling_data,
                                  gpointer parent_data,
                                  gpointer global_data,
                                  gpointer* result,
                                  const char* text,
                                  int length)
{
    return (isspace_str (text, length));
}

gboolean
generic_accumulate_chars (GSList* sibling_data,
                          gpointer parent_data,
                          gpointer global_data,
                          gpointer* result,

                          const char* text,
                          int length)
{
    gchar* copytxt = g_strndup (text, length);
    g_return_val_if_fail (result, FALSE);

    *result = copytxt;
    return (TRUE);
}


void
generic_free_data_for_children (gpointer data_for_children,
                                GSList* data_from_children,
                                GSList* sibling_data,
                                gpointer parent_data,
                                gpointer global_data,
                                gpointer* result,
                                const gchar* tag)
{
    if (data_for_children) g_free (data_for_children);
}

gchar*
concatenate_child_result_chars (GSList* data_from_children)
{
    GSList* lp;
    gchar* name = g_strdup ("");

    g_return_val_if_fail (name, NULL);

    /* child data lists are in reverse chron order */
    data_from_children = g_slist_reverse (g_slist_copy (data_from_children));

    for (lp = data_from_children; lp; lp = lp->next)
    {
        sixtp_child_result* cr = (sixtp_child_result*) lp->data;
        if (cr->type != SIXTP_CHILD_RESULT_CHARS)
        {
            PERR ("result type is not chars");
            g_slist_free (data_from_children);
            g_free (name);
            return (NULL);
        }
        else
        {
            char* temp;
            temp = g_strconcat (name, (gchar*) cr->data, nullptr);
            g_free (name);
            name = temp;
        }
    }
    g_slist_free (data_from_children);
    return (name);
}

/****************************************************************************/
/* string to data converters...
 */


template <typename T>
static bool parse_chars_into_num (const char* str, T* num_ptr)
{
    if (!str || !num_ptr)
        return false;

    while (std::isspace (*str))
        ++str;

    const char* end_ptr = str + std::strlen (str);

    auto res = std::from_chars (str, end_ptr, *num_ptr);
    if (res.ec != std::errc{})
        return false;

    while (std::isspace (*res.ptr))
        ++res.ptr;

    return (res.ptr == end_ptr);
}

/*********/
/* double
 */

gboolean
string_to_double (const char* str, double* result)
{
#if __cpp_lib_to_chars >= 201611L
    return parse_chars_into_num<double>(str, result);
#else
    // because from_chars in cpp < 201611L cannot parse floats
    char* endptr = nullptr;
    g_return_val_if_fail (str && result, false);
    *result = std::strtod (str, &endptr);
    return (endptr != str);
#endif
}

/*********/
/* gint64
 */
gboolean
string_to_gint64 (const gchar* str, gint64* v)
{
    return parse_chars_into_num<gint64>(str, v);
}

/*********/
/* guint16
 */
gboolean
string_to_guint16 (const gchar* str, guint16* v)
{
    return parse_chars_into_num<guint16>(str, v);
}

/*********/
/* guint
 */
gboolean
string_to_guint (const gchar* str, guint* v)
{
    return parse_chars_into_num<guint>(str, v);
}

/************/
/* hex string
 */

gboolean
hex_string_to_binary (const gchar* str,  void** v, guint64* data_len)
{
    /* Convert a hex string to binary.  No whitespace allowed. */
    const gchar* cursor = str;
    guint64 str_len;
    gboolean error = FALSE;

    g_return_val_if_fail (str, FALSE);
    g_return_val_if_fail (v, FALSE);
    g_return_val_if_fail (data_len, FALSE);

    str_len = strlen (str);
    /* Since no whitespace is allowed and hex encoding is 2 text chars
       per binary char, the result must be half the input size and the
       input size must be even. */
    if ((str_len % 2) != 0) return (FALSE);
    *data_len = 0;
    *v = g_new0 (char, str_len / 2);

    g_return_val_if_fail (*v, FALSE);

    while (*cursor && * (cursor + 1))
    {
        gchar tmpstr[2];
        int tmpint;

        if (isspace (*cursor) || isspace (* (cursor + 1)))
        {
            error = TRUE;
        }
        else
        {
            int num_read;
            tmpstr[0] = *cursor;
            tmpstr[0] = * (cursor + 1);

            if ((sscanf (tmpstr, "%x%n", &tmpint, &num_read) < 1)
                || (num_read != 2))
            {
                error = TRUE;
            }
            else
            {
                * ((gchar*) (v + *data_len)) = tmpint;
                *data_len += 1;
                cursor += 2;
            }
        }
    }

    if (error || (*data_len != (str_len / 2)))
    {
        g_free (*v);
        *v = NULL;
        *data_len = 0;
        return (FALSE);
    }

    return (TRUE);
}

/***************************************************************************/
/* simple chars only parser - just grabs all it's contained chars and
   does what you specify in the end handler - if you pass NULL as the
   end handler to simple_chars_only_parser_new, the characters are just
   passed to the parent as a new string.

   input: NA
   returns: gchar array allocated via g_new, etc.

   start: NA
   chars: generic_accumulate_chars.
   end: varies - default is to concatenate all accumulated chars and return.

   cleanup-result: g_free (for chars)
   cleanup-chars: g_free (for chars)
   fail: NA
   result-fail: g_free (for chars)
   chars-fail: g_free (for chars)

 */

gboolean
generic_return_chars_end_handler (gpointer data_for_children,
                                  GSList* data_from_children,
                                  GSList* sibling_data,
                                  gpointer parent_data,
                                  gpointer global_data,
                                  gpointer* result,
                                  const gchar* tag)
{
    gchar* txt = NULL;

    txt = concatenate_child_result_chars (data_from_children);
    g_return_val_if_fail (txt, FALSE);
    *result = txt;
    return (TRUE);
}


sixtp*
simple_chars_only_parser_new (sixtp_end_handler end_handler)
{
    return sixtp_set_any (
               sixtp_new (), FALSE,
               SIXTP_END_HANDLER_ID, (end_handler
                                      ? end_handler
                                      : generic_return_chars_end_handler),
               SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
               SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
               SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
               SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
               SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
               SIXTP_NO_MORE_HANDLERS);
}

/****************************************************************************/
/* generic timespec handler for XML Version 1 files.

   A collection of node functions intended to parse a sub-node set
   that looks like this:

     <date-posted>
       <s>Mon, 05 Jun 2000 23:16:19 -0500</s>
       <ns>658864000</ns>
     </date-posted>

   and produce a time64.  The start handler for the top allocates
   the time64 and passes it to the children.  The <s> block sets
   the seconds and the <ns> block is ignored.  If
   all goes well, returns the time64 as the result.
*/

/* Top level timespec node:

   input: user end handler *
   returns: time64

   start: Allocates Time64ParseInfo* for data_for_children.
   characters: none (whitespace only).
   end: g_free Time64ParseInfo + any other actions

   cleanup-result: NA
   cleanup-chars: NA
   fail: g_free data_for_children.
   result-fail: g_free data_for_children.
   chars-fail: NA

 */

gboolean
generic_timespec_start_handler (GSList* sibling_data, gpointer parent_data,
                                gpointer global_data,
                                gpointer* data_for_children, gpointer* result,
                                const gchar* tag, gchar** attrs)
{
    Time64ParseInfo* tsp = g_new0 (Time64ParseInfo, 1);
    g_return_val_if_fail (tsp, FALSE);
    *data_for_children = tsp;
    return (TRUE);
}

/* You can't use this function directly.  You have to call it from
   your own end handler.  If it returns TRUE, *result will contain the
   new timespec.  Otherwise, you can presume that everything's been
   cleaned up properly and return FALSE.  */
gboolean
timespec_parse_ok (Time64ParseInfo* info)
{
    return info->s_block_count != 1;
}

/* generic_timespec_end_handler - must be customized and provided by
   the user. */

/* <s> (parent timespec-node)

   input: Time64ParseInfo *
   returns: NA

   start: NA
   characters: accumulate.
   end: convert characters to secs part of input time64 and inc s_block_count.

   cleanup-result: NA
   cleanup-chars: g_free data.
   fail: NA
   result-fail: NA
   chars-fail: g_free data.

 */

gboolean
generic_timespec_secs_end_handler (gpointer data_for_children,
                                   GSList*  data_from_children, GSList* sibling_data,
                                   gpointer parent_data, gpointer global_data,
                                   gpointer* result, const gchar* tag)
{
    gchar* txt = NULL;
    Time64ParseInfo* info = (Time64ParseInfo*) parent_data;

    g_return_val_if_fail (parent_data, FALSE);

    txt = concatenate_child_result_chars (data_from_children);
    g_return_val_if_fail (txt, FALSE);

    info->time = gnc_iso8601_to_time64_gmt (txt);
    g_free (txt);

// gnc_iso8601_to_time64_gmt returns INT64_MAX on failure.
    g_return_val_if_fail (info->time < INT64_MAX, FALSE);

    info->s_block_count++;
    return (TRUE);
}

/* <s> (parent timespec-node)

   input: Time64ParseInfo *
   returns: NA

   start: NA
   characters: accumulate.
   end: NA

   cleanup-result: NA
   cleanup-chars: NA.
   fail: NA
   result-fail: NA
   chars-fail: g_free data.

 */

gboolean
generic_timespec_nsecs_end_handler (gpointer data_for_children,
                                    GSList*  data_from_children, GSList* sibling_data,
                                    gpointer parent_data, gpointer global_data,
                                    gpointer* result, const gchar* tag)
{
    return TRUE;
}

static sixtp*
timespec_sixtp_new (sixtp_end_handler ender)
{
    return sixtp_set_any (
               sixtp_new (), FALSE,
               SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
               SIXTP_END_HANDLER_ID, ender,
               SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
               SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
               SIXTP_NO_MORE_HANDLERS);
}

sixtp*
generic_timespec_parser_new (sixtp_end_handler end_handler)
{
    sixtp* top_level =
        sixtp_set_any (sixtp_new (), FALSE,
                       SIXTP_START_HANDLER_ID, generic_timespec_start_handler,
                       SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace,
                       SIXTP_END_HANDLER_ID, end_handler,
                       SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
                       SIXTP_FAIL_HANDLER_ID, generic_free_data_for_children,
                       SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
                       SIXTP_NO_MORE_HANDLERS);
    g_return_val_if_fail (top_level, NULL);

    if (!sixtp_add_some_sub_parsers (
            top_level, TRUE,
            "s", timespec_sixtp_new (generic_timespec_secs_end_handler),
            "ns", timespec_sixtp_new (generic_timespec_nsecs_end_handler),
            NULL, NULL))
    {
        return NULL;
    }

    return (top_level);
}

/****************************************************************************/
/* <?> generic guid handler...

   Attempts to parse the current accumulated characters data as a guid
   and return it.

   input: NA
   returns: GncGUID*

   start: NA
   characters: return string copy for accumulation in end handler.
   end: concatenate all chars and create and return GncGUID*, if possible.

   cleanup-result: g_free the GncGUID*
   cleanup-chars: g_free the result string.
   fail: NA
   result-fail: g_free the GncGUID*
   chars-fail: g_free the result string.

 */

gboolean
generic_guid_end_handler (gpointer data_for_children,
                          GSList*  data_from_children, GSList* sibling_data,
                          gpointer parent_data, gpointer global_data,
                          gpointer* result, const gchar* tag)
{
    gchar* txt = NULL;
    GncGUID* gid;
    gboolean ok;

    txt = concatenate_child_result_chars (data_from_children);
    g_return_val_if_fail (txt, FALSE);

    gid = g_new (GncGUID, 1);
    if (!gid)
    {
        g_free (txt);
        return (FALSE);
    }

    ok = string_to_guid (txt, gid);
    g_free (txt);

    if (!ok)
    {
        PERR ("couldn't parse GncGUID");
        g_free (gid);
        return (FALSE);
    }

    *result = gid;
    return (TRUE);
}

sixtp*
generic_guid_parser_new (void)
{
    return sixtp_set_any (
               sixtp_new (), FALSE,
               SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
               SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
               SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
               SIXTP_END_HANDLER_ID, generic_guid_end_handler,
               SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
               SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
               SIXTP_NO_MORE_HANDLERS);
}

/****************************************************************************/
/* <?> generic gnc_numeric handler...

   Attempts to parse the current accumulated characters data as a
   gnc_numeric and return it.

   input: NA
   returns: gnc_numeric*

   start: NA
   characters: return string copy for accumulation in end handler.
   end: concatenate all chars and create and return gnc_numeric*, if possible.

   cleanup-result: g_free the gnc_numeric*
   cleanup-chars: g_free the result string.
   fail: NA
   result-fail: g_free the gnc_numeric*
   chars-fail: g_free the result string.

 */

gboolean
generic_gnc_numeric_end_handler (gpointer data_for_children,
                                 GSList*  data_from_children, GSList* sibling_data,
                                 gpointer parent_data, gpointer global_data,
                                 gpointer* result, const gchar* tag)
{
    gnc_numeric* num = NULL;
    gchar* txt = NULL;
    gboolean ok = FALSE;

    txt = concatenate_child_result_chars (data_from_children);

    if (txt)
    {
        num = g_new (gnc_numeric, 1);
        if (num)
        {
            *num = gnc_numeric_from_string (txt);
            if (!gnc_numeric_check (*num))
            {
                ok = TRUE;
                *result = num;
            }
        }
    }

    g_free (txt);
    if (!ok)
    {
        PERR ("couldn't parse numeric quantity");
        g_free (num);
    }

    return (ok);
}

sixtp*
generic_gnc_numeric_parser_new (void)
{
    return sixtp_set_any (
               sixtp_new (), FALSE,
               SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
               SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
               SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
               SIXTP_END_HANDLER_ID, generic_gnc_numeric_end_handler,
               SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
               SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
               SIXTP_NO_MORE_HANDLERS);
}

/***************************************************************************/

sixtp*
restore_char_generator (sixtp_end_handler ender)
{
    return sixtp_set_any (
               sixtp_new (), FALSE,
               SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
               SIXTP_END_HANDLER_ID, ender,
               SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
               SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
               SIXTP_NO_MORE_HANDLERS);
}

/***************************** END OF FILE *********************************/
