/********************************************************************
 * gnc-freqspec-xml-v2.c -- xml routines for FreqSpecs              *
 * Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org>          *
 * Copyright (C) 2001 Ben Stanley <bds02@uow.edu.au>                *
 *                                                                  *
 * 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                   *
 *                                                                  *
 *******************************************************************/

extern "C"
{
#include <config.h>

#include <glib.h>
#include <string.h>
#include "qof.h"
#include "SchedXaction.h"
#include "FreqSpec.h"
}

#include "gnc-xml-helper.h"
#include "sixtp.h"
#include "sixtp-utils.h"
#include "sixtp-parsers.h"
#include "sixtp-utils.h"
#include "sixtp-dom-parsers.h"
#include "sixtp-dom-generators.h"

#include "gnc-xml.h"

#include "io-gncxml-v2.h"

#include "sixtp-dom-parsers.h"

const gchar* freqspec_version_string = "1.0.0";

struct freqTypeTuple
{
    const char*         str;
    FreqType        ft;
};

struct freqTypeTuple freqTypeStrs[] =
{
    { "none",             INVALID },
    { "once",             ONCE },
    { "daily",            DAILY },
    { "weekly",           WEEKLY },
    { "monthly",          MONTHLY },
    { "month_relative",   MONTH_RELATIVE },
    { "composite",        COMPOSITE },
    { NULL,               static_cast<FreqType> (-1) },
};

struct uiFreqTypeTuple
{
    const char*        str;
    UIFreqType        uift;
};

struct uiFreqTypeTuple uiFreqTypeStrs[] =
{
    { "none",         UIFREQ_NONE },
    { "once",         UIFREQ_ONCE },
    { "daily",        UIFREQ_DAILY },
    { "daily_mf",     UIFREQ_DAILY_MF },
    { "weekly",       UIFREQ_WEEKLY },
    { "bi_weekly",    UIFREQ_BI_WEEKLY },
    { "semi_monthly", UIFREQ_SEMI_MONTHLY },
    { "monthly",      UIFREQ_MONTHLY },
    { "quarterly",    UIFREQ_QUARTERLY },
    { "tri_anually",  UIFREQ_TRI_ANUALLY },
    { "semi_yearly",  UIFREQ_SEMI_YEARLY },
    { "yearly",       UIFREQ_YEARLY },
    { NULL,           static_cast<UIFreqType> (-1) }
};

/**
 * Struct passed around as user-data when parsing the FreqSpec.
 **/
typedef struct
{
    QofBook*         book;            /* Book we're loading into. */

    Recurrence* recurrence;
    GList* recurrence_list;

    /* fields used in the union of unions... :) */
    GDate                 once_day;     /* once */
    gint64                interval;     /* all [except once] */
    gint64                offset;       /* all [except once] */
    gint64                day;          /* monthly or month-relative */
    gint64                occurrence;   /* month-relative */
    gint64                weekend_adj;  /* monthly/yearly */
    GList*                list;         /* composite */
    UIFreqType            uift;
} fsParseData;

static void
fspd_init (fsParseData* fspd)
{
    fspd->list    = NULL;
    fspd->book    = NULL;
    fspd->recurrence = g_new0 (Recurrence, 1);
    fspd->recurrence_list = NULL;
    fspd->uift = UIFREQ_NONE;
    fspd->interval
        = fspd->offset
          = fspd->day
            = fspd->occurrence
              = 0;
    fspd->weekend_adj = WEEKEND_ADJ_NONE;
    g_date_clear (&fspd->once_day, 1);
}

static
gboolean
gnc_fs_handler (xmlNodePtr node, gpointer d);

static
gboolean
fs_uift_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    int            i;
    char*        nodeTxt;

    nodeTxt = dom_tree_to_text (node);

    g_return_val_if_fail (nodeTxt, FALSE);
    for (i = 0; uiFreqTypeStrs[i].str != NULL; i++)
    {
        if (g_strcmp0 (nodeTxt, uiFreqTypeStrs[i].str) == 0)
        {
            fspd->uift = uiFreqTypeStrs[i].uift;
            g_free (nodeTxt);
            return TRUE;
        }
    }
    g_free (nodeTxt);
    return FALSE;
}

static
gboolean
fs_date_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    GDate*                foo;
    foo = dom_tree_to_gdate (node);
    if (foo == NULL)
        return FALSE;
    fspd->once_day = *foo;
    g_date_free (foo);
    return TRUE;
}

static
gboolean
fs_interval_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;

    ret = dom_tree_to_integer (node, &foo);
    if (! ret)
    {
        return ret;
    }
    fspd->interval = foo;
    return TRUE;
}

static
gboolean
fs_offset_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;

    ret = dom_tree_to_integer (node, &foo);
    if (! ret)
        return ret;
    fspd->offset = foo;
    return TRUE;
}

static
gboolean
fs_day_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;

    ret = dom_tree_to_integer (node, &foo);
    if (! ret)
        return ret;
    fspd->day = foo;
    return TRUE;
}

static
gboolean
fs_weekday_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;
    ret = dom_tree_to_integer (node, &foo);
    if (!ret)
        return ret;
    fspd->day = foo;
    return TRUE;
}

static
gboolean
fs_occurrence_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;
    ret = dom_tree_to_integer (node, &foo);
    if (!ret)
        return ret;
    fspd->occurrence = foo;
    return TRUE;
}

static
gboolean
fs_weekend_adj_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        ret;
    gint64          foo;
    ret = dom_tree_to_integer (node, &foo);
    if (!ret)
        return ret;
    fspd->weekend_adj = foo;
    return TRUE;
}

static
gboolean
fs_subelement_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    GList* recurrences;

    recurrences = dom_tree_freqSpec_to_recurrences (node, fspd->book);
    if (recurrences == NULL)
        return FALSE;

    {
        GList* r_iter;
        for (r_iter = recurrences; r_iter != NULL; r_iter = r_iter->next)
        {
            Recurrence* r = (Recurrence*)r_iter->data;
            GDate recurrence_date;
            if (fspd->uift == UIFREQ_SEMI_MONTHLY)
            {
                // complementry hack around 'once' freqspects not being valid. :/
                recurrence_date = recurrenceGetDate (r);
                recurrenceSet (r, recurrenceGetMultiplier (r), PERIOD_MONTH, &recurrence_date,
                               recurrenceGetWeekendAdjust (r));
            }
            fspd->recurrence_list = g_list_append (fspd->recurrence_list, r);
        }
    }
    return TRUE;
}

struct dom_tree_handler fs_union_dom_handlers[] =
{
    { "fs:date",        fs_date_handler,        0, 0 },
    { "fs:interval",    fs_interval_handler,    0, 0 },
    { "fs:offset",      fs_offset_handler,      0, 0 },
    { "fs:day",         fs_day_handler,         0, 0 },
    { "fs:weekday",     fs_weekday_handler,     0, 0 },
    { "fs:occurrence",  fs_occurrence_handler,  0, 0 },
    { "fs:weekend_adj", fs_weekend_adj_handler, 0, 0 },
    { "gnc:freqspec",   fs_subelement_handler,  0, 0 },
    { NULL, NULL, 0, 0 },
};

static gboolean
fs_none_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean    successful;
    successful = dom_tree_generic_parse (node,
                                         fs_union_dom_handlers,
                                         fspd);
    return successful;
}

static
gboolean
fs_once_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        successful;

    successful = dom_tree_generic_parse (node,
                                         fs_union_dom_handlers,
                                         fspd);
    if (!successful)
        return FALSE;
    recurrenceSet (fspd->recurrence, 0, PERIOD_ONCE, &fspd->once_day,
                   WEEKEND_ADJ_NONE);

    return TRUE;
}

static gboolean
fs_daily_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    GDate offset_date;
    gboolean        successful;
    successful = dom_tree_generic_parse (node, fs_union_dom_handlers, fspd);
    if (!successful)
        return FALSE;

    g_date_clear (&offset_date, 1);
    g_date_set_julian (&offset_date, fspd->offset == 0 ? 7 : fspd->offset);
    recurrenceSet (fspd->recurrence, fspd->interval, PERIOD_DAY, &offset_date,
                   WEEKEND_ADJ_NONE);

    return TRUE;
}

static
gboolean
fs_weekly_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    GDate offset_date;
    gboolean        successful;
    successful = dom_tree_generic_parse (node,
                                         fs_union_dom_handlers,
                                         fspd);
    if (!successful)
        return FALSE;

    g_date_clear (&offset_date, 1);
    g_date_set_julian (&offset_date, fspd->offset == 0 ? 7 : fspd->offset);
    recurrenceSet (fspd->recurrence, fspd->interval, PERIOD_WEEK, &offset_date,
                   WEEKEND_ADJ_NONE);

    return TRUE;
}

static
gboolean
fs_monthly_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    GDate offset_date;
    gboolean        successful;
    successful = dom_tree_generic_parse (node,
                                         fs_union_dom_handlers,
                                         fspd);
    if (!successful)
        return FALSE;


    g_date_clear (&offset_date, 1);
    g_date_set_julian (&offset_date, 1);
    g_date_add_months (&offset_date, fspd->offset);
    g_date_set_day (&offset_date, fspd->day);
    if (fspd->uift == UIFREQ_ONCE)
    {
        // hack...
        recurrenceSet (fspd->recurrence, fspd->interval, PERIOD_ONCE, &offset_date,
                       WEEKEND_ADJ_NONE);
    }
    else
    {
        recurrenceSet (fspd->recurrence, fspd->interval,
                       PERIOD_MONTH, &offset_date,
                       static_cast<WeekendAdjust> (fspd->weekend_adj));
    }

    return successful;
}

static
gboolean
fs_month_relative_handler (xmlNodePtr node, gpointer data)
{
    g_critical ("this was never supported, how is it in the datafile?");
    return FALSE;
}

static
gboolean
fs_guid_handler (xmlNodePtr node, gpointer data)
{
    return TRUE;
}

static
gboolean
fs_composite_handler (xmlNodePtr node, gpointer data)
{
    fsParseData* fspd = static_cast<decltype (fspd)> (data);
    gboolean        successful;
    successful = dom_tree_generic_parse (node,
                                         fs_union_dom_handlers,
                                         fspd);
    return successful;
}

static struct dom_tree_handler fs_dom_handlers[] =
{
    { "gnc:freqspec",      gnc_fs_handler,            0, 0 },
    { "fs:ui_type",        fs_uift_handler,           1, 0 },
    { "fs:id",             fs_guid_handler,           1, 0 },
    { "fs:none",           fs_none_handler,           0, 0 },
    { "fs:once",           fs_once_handler,           0, 0 },
    { "fs:daily",          fs_daily_handler,          0, 0 },
    { "fs:weekly",         fs_weekly_handler,         0, 0 },
    { "fs:monthly",        fs_monthly_handler,        0, 0 },
    { "fs:month_relative", fs_month_relative_handler, 0, 0 },
    { "fs:composite",      fs_composite_handler,      0, 0 },
    { NULL, NULL, 0, 0 }
};

static
gboolean
gnc_fs_handler (xmlNodePtr node, gpointer d)
{
    return dom_tree_generic_parse (node, fs_dom_handlers, d);
}

static
gboolean
gnc_freqSpec_end_handler (gpointer data_for_children,
                          GSList* data_from_children, GSList* sibling_data,
                          gpointer parent_data, gpointer global_data,
                          gpointer* result, const gchar* tag)
{
    fsParseData                fspd;
    gboolean                successful = FALSE;
    xmlNodePtr                tree = (xmlNodePtr)data_for_children;
    sixtp_gdv2*                globaldata = (sixtp_gdv2*)global_data;

    fspd_init (&fspd);
    fspd.book = globaldata->book;

    /* this won't actually get invoked [FreqSpecs aren't top-level
       elements]; see dom_tree_to_freqSpec(), below. */
    if (parent_data)
        return TRUE;

    if (!tag)
        return TRUE;

    g_return_val_if_fail (tree, FALSE);

    successful = dom_tree_generic_parse (tree, fs_dom_handlers, &fspd);
    if (!successful)
    {
        xmlElemDump (stdout, NULL, tree);
    }

    xmlFreeNode (tree);

    return successful;
}

sixtp*
gnc_freqSpec_sixtp_parser_create (void)
{
    return sixtp_dom_parser_new (gnc_freqSpec_end_handler, NULL, NULL);
}

static void
common_parse (fsParseData* fspd, xmlNodePtr node, QofBook* book)
{
    gboolean        successful;

    fspd->book = book;
    successful = dom_tree_generic_parse (node, fs_dom_handlers, fspd);
    if (!successful)
    {
        xmlElemDump (stdout, NULL, node);
    }
}

GList*
dom_tree_freqSpec_to_recurrences (xmlNodePtr node, QofBook* book)
{
    fsParseData        fspd;
    fspd_init (&fspd);
    common_parse (&fspd, node, book);
    if (fspd.recurrence_list == NULL)
    {
        fspd.recurrence_list = g_list_append (fspd.recurrence_list, fspd.recurrence);
    }
    return fspd.recurrence_list;
}
