/*======================================================================
 FILE: regression-storage.c
 CREATOR: eric 03 April 1999

 (C) COPYRIGHT 1999 Eric Busboom <eric@civicknowledge.com>

 DESCRIPTION:

 This library is free software; you can redistribute it and/or modify
 it under the terms of either:

    The LGPL as published by the Free Software Foundation, version
    2.1, available at: https://www.gnu.org/licenses/lgpl-2.1.html

 Or:

    The Mozilla Public License Version 2.0. You may obtain a copy of
    the License at https://www.mozilla.org/MPL/

 The original author is Eric Busboom
 The original code is usecases.c
======================================================================*/

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

#include "regression.h"
#include "libical/ical.h"
#include "libicalss/icalss.h"

#include <stdlib.h>

#define OUTPUT_FILE "filesetout.ics"

/* define sample calendar struct */
struct calendar
{
    int ID;
    size_t total_size;

    /* offsets */
    size_t total_size_offset;
    size_t vcalendar_size_offset;
    size_t vcalendar_offset;
    size_t title_size_offset;
    size_t title_offset;

    /* data */
    size_t vcalendar_size;
    char *vcalendar;

    size_t title_size;
    char *title;

};

int vcalendar_init(struct calendar **cal, const char *vcalendar, const char *title);

#if defined(HAVE_BDB)
#include <db.h>

int get_title(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey);
char *parse_vcalendar(const DBT *dbt);
char *pack_calendar(struct calendar *cal, size_t size);
struct calendar *unpack_calendar(char *str, size_t size);
#endif

//krazy:cond=style
/*
static char str[] = "BEGIN:VCALENDAR\n\
PRODID:\"-//RDU Software//NONSGML HandCal//EN\"\n\
VERSION:2.0\n\
BEGIN:VTIMEZONE\n\
TZID:US-Eastern\n\
BEGIN:STANDARD\n\
DTSTART:19981025T020000\n\
RDATE:19981025T020000\n\
TZOFFSETFROM:-0400\n\
TZOFFSETTO:-0500\n\
TZNAME:EST\n\
END:STANDARD\n\
BEGIN:DAYLIGHT\n\
DTSTART:19990404T020000\n\
RDATE:19990404T020000\n\
TZOFFSETFROM:-0500\n\
TZOFFSETTO:-0400\n\
TZNAME:EDT\n\
END:DAYLIGHT\n\
END:VTIMEZONE\n\
BEGIN:VEVENT\n\
DTSTAMP:19980309T231000Z\n\
UID:guid-1.host1.com\n\
ORGANIZER;ROLE=CHAIR:MAILTO:mrbig@host.com\n\
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:MAILTO:employee-A@host.com\n\
DESCRIPTION:Project XYZ Review Meeting\n\
CATEGORIES:MEETING\n\
CLASS:PUBLIC\n\
CREATED:19980309T130000Z\n\
SUMMARY:XYZ Project Review\n\
DTSTART;TZID=US-Eastern:19980312T083000\n\
DTEND;TZID=US-Eastern:19980312T093000\n\
LOCATION:1CP Conference Room 4350\n\
END:VEVENT\n\
BEGIN:BOOGA\n\
DTSTAMP:19980309T231000Z\n\
X-LIC-FOO:Booga\n\
DTSTOMP:19980309T231000Z\n\
UID:guid-1.host1.com\n\
END:BOOGA\n\
END:VCALENDAR";
*/
char str2[] = "BEGIN:VCALENDAR\n\
PRODID:\"-//RDU Software//NONSGML HandCal//EN\"\n\
VERSION:2.0\n\
BEGIN:VEVENT\n\
DTSTAMP:19980309T231000Z\n\
UID:guid-1.host1.com\n\
ORGANIZER;ROLE=CHAIR:MAILTO:mrbig@host.com\n\
ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:MAILTO:employee-A@host.com\n\
DESCRIPTION:Project XYZ Review Meeting\n\
CATEGORIES:MEETING\n\
CLASS:PUBLIC\n\
CREATED:19980309T130000Z\n\
SUMMARY:XYZ Project Review\n\
DTSTART;TZID=US-Eastern:19980312T083000\n\
DTEND;TZID=US-Eastern:19980312T093000\n\
LOCATION:1CP Conference Room 4350\n\
END:VEVENT\n\
END:VCALENDAR\n\
";
//krazy:endcond=style

void test_fileset_extended(void)
{
    icalset *cout;
    int month = 0;
    int count = 0;
    struct icaltimetype start, end;
    icalcomponent *c, *clone, *itr;
    icalsetiter iter;

    start = icaltime_from_timet_with_zone(time(0), 0, NULL);
    end = start;
    end.hour++;

    cout = icalfileset_new(OUTPUT_FILE);
    ok("Opening output file", (cout != 0));
    assert(cout != 0);

    c = icalparser_parse_string(str2);
    ok("Parsing str2", (c != 0));
    assert(c != 0);

    icalset_free(cout);

    /* Add data to the file */

    for (month = 1; month < 10; month++) {
        icalcomponent *event;
        icalproperty *dtstart, *dtend;

        cout = icalfileset_new(OUTPUT_FILE);
        ok("Opening output file", (cout != 0));
        assert(cout != 0);

        start.month = month;
        end.month = month;

        clone = icalcomponent_new_clone(c);
        ok("Making clone of output file", (clone != 0));
        assert(clone != 0);

        event = icalcomponent_get_first_component(clone, ICAL_VEVENT_COMPONENT);
        ok("Getting first event from clone", (event != 0));
        assert(event != 0);

        dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
        ok("find DTSTART", (dtstart != 0));
        assert(dtstart != 0);

        icalproperty_set_dtstart(dtstart, start);

        dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);
        ok("find DTEND", (dtend != 0));

        assert(dtend != 0);

        icalproperty_set_dtend(dtend, end);

        (void)icalfileset_add_component(cout, clone);
        (void)icalfileset_commit(cout);

        icalcomponent_free(clone);
        icalset_free(cout);
    }

    /* Print them out */

    cout = icalfileset_new(OUTPUT_FILE);

    ok("Opening output file", (cout != 0));
    assert(cout != 0);

    for (iter = icalfileset_begin_component(cout, ICAL_ANY_COMPONENT, 0, NULL);
         icalsetiter_deref(&iter) != 0; icalsetiter_next(&iter)) {
        icalcomponent *event;
        icalproperty *dtstart, *dtend;

        itr = icalsetiter_deref(&iter);
        count++;

        event = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);

        dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
        dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);

        if (VERBOSE) {
            printf("%d %s %s\n", count, icalproperty_as_ical_string(dtstart),
                   icalproperty_as_ical_string(dtend));
        }
    }

    /* Remove all of them */

    icalset_free(cout);

    cout = icalfileset_new(OUTPUT_FILE);
    ok("Opening output file", (cout != 0));
    assert(cout != 0);

    /* need to advance the iterator first before calling remove_componenet() */
    /* otherwise, iter will contain a "removed" component and icalsetiter_next(&iter) */
    /* will fail. */

    iter = icalfileset_begin_component(cout, ICAL_ANY_COMPONENT, 0, NULL);
    itr = icalsetiter_deref(&iter);
    while (itr != 0) {
        (void)icalsetiter_next(&iter);
        (void)icalfileset_remove_component(cout, itr);
        icalcomponent_free(itr);
        itr = icalsetiter_deref(&iter);
    }

    icalset_free(cout);

    /* Print them out again */

    cout = icalfileset_new(OUTPUT_FILE);
    ok("Opening output file", (cout != 0));
    assert(cout != 0);
    count = 0;

    for (itr = icalfileset_get_first_component(cout);
         itr != 0; itr = icalfileset_get_next_component(cout)) {

        icalcomponent *event;
        icalproperty *dtstart, *dtend;

        count++;

        event = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);

        dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
        dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);

        printf("%d %s %s\n", count, icalproperty_as_ical_string(dtstart),
               icalproperty_as_ical_string(dtend));
    }

    icalset_free(cout);
    icalcomponent_free(c);
}

#if defined(HAVE_BDB)

/*
   In this example, we're storing a calendar with several components
   under the reference id "calendar_7286" and retrieving records based
   on title, "month_1" through "month_10".  We use a number of the
   "optional" arguments to specify secondary indices, sub-databases
   (i.e. having multiple databases residing within a single Berkeley
   DB file), and keys for storage and retrieval.
*/

void test_bdbset(void)
{
    icalset *cout;
    int month = 0;
    int count = 0;

/*    int num_components=0;*/
/*    int szdata_len=0;*/
/*    int ret=0;*/
/*    char *subdb, *szdata, *szpacked_data;*/
/*    char uid[255];*/
    struct icaltimetype start, end;
    icalcomponent *c, *clone, *itr;
    DBT key, data;

/*    DBC *dbcp;*/

/*    struct calendar *cal;*/
/*    int cal_size;*/

    return;     // for now... TODO fix these broken tests..

#pragma clang diagnostic push /* remove when/if we remove the proceeding return statement */
#pragma clang diagnostic ignored "-Wunreachable-code"
    start = icaltime_from_timet_with_zone(time(0), 0, NULL);
    end = start;
    end.hour++;

    /* Note: as per the Berkeley DB ref pages:
     *
     * The database argument is optional, and allows applications to
     * have multiple databases in a single file. Although no database
     * argument needs to be specified, it is an error to attempt to
     * open a second database in a file that was not initially created
     * using a database name.
     *
     */

    /*subdb = "calendar_id"; */

    /* open database, using subdb */
    cout = icalbdbset_new("calendar.db", ICALBDB_EVENTS, DB_HASH, 0);
    /*
       sdbp = icalbdbset_secondary_open(dbp,
       DATABASE,
       "title",
       get_title,
       DB_HASH);
     */

    c = icalparser_parse_string(str2);

    assert(c != 0);

    /* Add data to the file */

    for (month = 1; month < 10; month++) {
        icalcomponent *event;
        icalproperty *dtstart, *dtend;

        /* retrieve data */
        //      cout = icalbdbset_new(dbp, sdbp, NULL);
        //      assert(cout != 0);

        start.month = month;
        end.month = month;

        clone = icalcomponent_new_clone(c);
        assert(clone != 0);
        event = icalcomponent_get_first_component(clone, ICAL_VEVENT_COMPONENT);
        assert(event != 0);

        dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
        assert(dtstart != 0);
        icalproperty_set_dtstart(dtstart, start);

        dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);
        assert(dtend != 0);
        icalproperty_set_dtend(dtend, end);

        assert(icalcomponent_get_first_property(event, ICAL_LOCATION_PROPERTY) != 0);

#if 0
        /* change the uid to include the month */
        sprintf(uid, "%s_%d", icalcomponent_get_uid(clone), month);
        icalcomponent_set_uid(clone, uid);
#endif

        (void)icalbdbset_add_component(cout, clone);

        /* commit changes */
        (void)icalbdbset_commit(cout);

        (void)icalcomponent_count_components(clone, ICAL_ANY_COMPONENT);

        icalset_free(cout);
    }

    /* try out the cursor operations */
    memset(&key, 0, sizeof(DBT));
    memset(&data, 0, sizeof(DBT));

#if 0
    ret = icalbdbset_acquire_cursor(dbp, &dbcp);
    ret = icalbdbset_get_first(dbcp, &key, &data);
    ret = icalbdbset_get_next(dbcp, &key, &data);
    ret = icalbdbset_get_last(dbcp, &key, &data);
#endif
    /* Print them out */

    for (month = 1, count = 0; month < 10; month++) {
        for (itr = icalbdbset_get_first_component(cout);
             itr != 0; itr = icalbdbset_get_next_component(cout)) {

            icalcomponent *event;
            icalproperty *dtstart, *dtend;

            count++;

            event = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);

            dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
            dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);

            printf("%d %s %s\n", count, icalproperty_as_ical_string(dtstart),
                   icalproperty_as_ical_string(dtend));
        }
        icalset_free(cout);
    }

    /* open database */
    //    cout = icalbdbset_bdb_open("calendar.db", "title", DB_HASH, 0644);
    /*    sdbp = icalbdbset_secondary_open(dbp,
       DATABASE,
       "title",
       get_title,
       DB_HASH);
     */
    /* Remove all of them */
    for (month = 1; month < 10; month++) {
        for (itr = icalbdbset_get_first_component(cout);
             itr != 0; itr = icalbdbset_get_next_component(cout)) {

            (void)icalbdbset_remove_component(cout, itr);
        }

        (void)icalbdbset_commit(cout);
        icalset_free(cout);
    }

    /* Print them out again */

    for (month = 1, count = 0; month < 10; month++) {
        for (itr = icalbdbset_get_first_component(cout);
             itr != 0; itr = icalbdbset_get_next_component(cout)) {

            icalcomponent *event;
            icalproperty *dtstart, *dtend;

            count++;

            event = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);

            dtstart = icalcomponent_get_first_property(event, ICAL_DTSTART_PROPERTY);
            dtend = icalcomponent_get_first_property(event, ICAL_DTEND_PROPERTY);

            printf("%d %s %s\n", count, icalproperty_as_ical_string(dtstart),
                   icalproperty_as_ical_string(dtend));
        }
        icalset_free(cout);
    }
#pragma clang diagnostic pop
}

#endif

int vcalendar_init(struct calendar **rcal, const char *vcalendar, const char *title)
{
    size_t vcalendar_size, title_size, total_size;
    struct calendar *cal;

    if (vcalendar) {
        vcalendar_size = strlen(vcalendar);
    } else {
        vcalendar = "";
        vcalendar_size = strlen(vcalendar);
    }

    if (title) {
        title_size = strlen(title);
    } else {
        title = "";
        title_size = strlen(title);
    }

    total_size = sizeof(struct calendar) + vcalendar_size + title_size;

    if ((cal = (struct calendar *)malloc(total_size)) == NULL) {
        return 0;
    }
    memset(cal, 0, total_size);

    /* offsets */
    cal->total_size_offset = sizeof(int);
    cal->vcalendar_size_offset = (sizeof(int) * 7);
    cal->vcalendar_offset = cal->vcalendar_size_offset + sizeof(int);
    cal->title_size_offset = cal->vcalendar_offset + vcalendar_size;
    cal->title_offset = cal->title_size_offset + sizeof(int);

    /* sizes */
    cal->total_size = total_size;
    cal->vcalendar_size = vcalendar_size;
    cal->title_size = title_size;

    if (*vcalendar) /* we know that vcalendar is not NULL here */
        cal->vcalendar = strdup(vcalendar);

    if (*title) /* we know that title is not NULL here */
        cal->title = strdup(title);

    *rcal = cal;

    return 0;
}

/* get_title -- extracts a secondary key (the vcalendar)
 * from a primary key/data pair */

/* just create a random title for now */
#if defined(HAVE_BDB)

int get_title(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey)
{
    icalcomponent *cl;
    static char title[255];

    _unused(dbp);
    _unused(pkey);

    memset(skey, 0, sizeof(DBT));

    cl = icalparser_parse_string((char *)pdata->data);
    snprintf(title, sizeof(title), "title_%s", icalcomponent_get_uid(cl));

    skey->data = strdup(title);
    skey->size = (u_int32_t) strlen(skey->data);
    return (0);
}

char *pack_calendar(struct calendar *cal, size_t size)
{
    char *str;

    if ((str = (char *)malloc(sizeof(char) * size)) == NULL)
        return 0;

    /* ID */
    memcpy(str, &cal->ID, sizeof(cal->ID));

    /* total_size */
    memcpy(str + cal->total_size_offset, &cal->total_size, sizeof(cal->total_size));

    /* vcalendar_size */
    memcpy(str + cal->vcalendar_size_offset, &cal->vcalendar_size, sizeof(cal->vcalendar_size));

    /* vcalendar */
    memcpy(str + cal->vcalendar_offset, cal->vcalendar, cal->vcalendar_size);

    /* title_size */
    memcpy(str + cal->title_size_offset, &cal->title_size, sizeof(cal->title_size));

    /* title */
    memcpy(str + cal->title_offset, cal->title, cal->title_size);

    return str;
}

struct calendar *unpack_calendar(char *str, size_t size)
{
    struct calendar *cal;

    if ((cal = (struct calendar *)malloc(size)) == NULL) {
        return 0;
    }
    memset(cal, 0, size);

    /* offsets */
    cal->total_size_offset = sizeof(int);
    cal->vcalendar_size_offset = (sizeof(int) * 7);
    cal->vcalendar_offset = cal->vcalendar_size_offset + sizeof(int);

    /* ID */
    memcpy(&cal->ID, str, sizeof(cal->ID));

    /* total_size */
    memcpy(&cal->total_size, str + cal->total_size_offset, sizeof(cal->total_size));

    /* vcalendar_size */
    memcpy(&cal->vcalendar_size, str + cal->vcalendar_size_offset, sizeof(cal->vcalendar_size));

    if ((cal->vcalendar = (char *)malloc(sizeof(char) * cal->vcalendar_size)) == NULL) {
        free(cal);
        return 0;
    }

    /* vcalendar */
    memcpy(cal->vcalendar, (char *)(str + cal->vcalendar_offset), cal->vcalendar_size);

    cal->title_size_offset = cal->vcalendar_offset + cal->vcalendar_size;
    cal->title_offset = cal->title_size_offset + sizeof(int);

    /* title_size */
    memcpy(&cal->title_size, str + cal->title_size_offset, sizeof(cal->title_size));

    if ((cal->title = (char *)malloc(sizeof(char) * cal->title_size)) == NULL) {
        free(cal->vcalendar);
        free(cal);
        return 0;
    }

    /* title */
    memcpy(cal->title, (char *)(str + cal->title_offset), cal->title_size);

    return cal;
}

char *parse_vcalendar(const DBT *dbt)
{
    char *str;
    struct calendar *cal;

    str = (char *)dbt->data;
    cal = unpack_calendar(str, dbt->size);

    str = cal->vcalendar;
    free(cal);
    return str;
}

#endif

void test_dirset_extended(void)
{
    icalcomponent *c;
    icalgauge *gauge;
    icalerrorenum error;
    icalcomponent *itr;
    icalset *cluster;
    struct icalperiodtype rtime;
    icalset *s = icaldirset_new("store");
    icalset *s2 = icaldirset_new("store-new");
    int i, count = 0;

    ok("Open dirset 'store'", (s != 0));
    assert(s != 0);

    rtime.start = icaltime_from_timet_with_zone(time(0), 0, NULL);

    cluster = icalfileset_new(OUTPUT_FILE);

    ok("Open fileset to duplicate 4 times", (cluster != 0));
    assert(cluster != 0);

#define NUMCOMP 4

    /* Duplicate every component in the cluster NUMCOMP times */

    icalerror_clear_errno();

    for (i = 1; i < NUMCOMP + 1; i++) {

        /*rtime.start.month = i%12; */
        rtime.start.month = i;
        rtime.end = rtime.start;
        rtime.end.hour++;

        for (itr = icalfileset_get_first_component(cluster);
             itr != 0; itr = icalfileset_get_next_component(cluster)) {
            icalcomponent *inner;
            icalproperty *p;

            inner = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);
            if (inner == 0) {
                continue;
            }

            /* Change the dtstart and dtend times in the component
               pointed to by Itr */

            (void)icalcomponent_new_clone(itr);
            inner = icalcomponent_get_first_component(itr, ICAL_VEVENT_COMPONENT);

            ok("Duplicating component...", (icalerrno == ICAL_NO_ERROR) && (inner != 0));

            assert(icalerrno == ICAL_NO_ERROR);
            assert(inner != 0);

            /* DTSTART */
            p = icalcomponent_get_first_property(inner, ICAL_DTSTART_PROPERTY);
            ok("Fetching DTSTART", (icalerrno == ICAL_NO_ERROR));
            assert(icalerrno == ICAL_NO_ERROR);

            if (p == 0) {
                p = icalproperty_new_dtstart(rtime.start);
                icalcomponent_add_property(inner, p);
            } else {
                icalproperty_set_dtstart(p, rtime.start);
            }

            ok("Adding DTSTART property", (icalerrno == ICAL_NO_ERROR));
            assert(icalerrno == ICAL_NO_ERROR);

            /* DTEND */
            p = icalcomponent_get_first_property(inner, ICAL_DTEND_PROPERTY);
            ok("Fetching DTEND property", (icalerrno == ICAL_NO_ERROR));
            assert(icalerrno == ICAL_NO_ERROR);

            if (p == 0) {
                p = icalproperty_new_dtstart(rtime.end);
                icalcomponent_add_property(inner, p);
            } else {
                icalproperty_set_dtstart(p, rtime.end);
            }
            ok("Setting DTEND property", (icalerrno == ICAL_NO_ERROR));
            assert(icalerrno == ICAL_NO_ERROR);

            if (VERBOSE)
                printf("\n----------\n%s\n---------\n", icalcomponent_as_ical_string(inner));

            error = icaldirset_add_component(s, icalcomponent_new_clone(itr));

            ok("Adding component to dirset", (icalerrno == ICAL_NO_ERROR));
            assert(error == ICAL_NO_ERROR);
            _unused(error);
        }
    }

    gauge = icalgauge_new_from_sql(
                "SELECT * FROM VEVENT WHERE "
                "VEVENT.SUMMARY = 'Submit Income Taxes' OR "
                "VEVENT.SUMMARY = 'Bastille Day Party'",
                0);

    ok("Creating complex Gauge", (gauge != 0));

    (void)icaldirset_select(s, gauge);

    for (c = icaldirset_get_first_component(s); c != 0; c = icaldirset_get_next_component(s)) {

        printf("Got one! (%d)\n", count++);

        printf("%s", icalcomponent_as_ical_string(c));
        if (icaldirset_add_component(s2, c) == 0) {
            printf("Failed to write!\n");
        }
        icalcomponent_free(c);
    }

    icalset_free(s2);

    for (c = icaldirset_get_first_component(s);
         c != 0;
         c = icaldirset_get_next_component(s)) {

        printf("%s", icalcomponent_as_ical_string(c));
    }

    /* Remove all of the components */
    i = 0;
    while ((c = icaldirset_get_current_component(s)) != 0) {
        i++;

        (void)icaldirset_remove_component(s, c);
    }

    icalset_free(s);
    icalset_free(cluster);
}
