/*======================================================================
 File: icalrestriction.c

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

 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/
 ======================================================================*/
/*#line 7 "icalrestriction.c.in"*/

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

#include "icalrestriction.h"
#include "icalerror.h"

#include <assert.h>

/* Define the structs for the restrictions. these data are filled out
in machine generated code below */

struct icalrestriction_property_record;

typedef const char *(*restriction_func) (const struct icalrestriction_property_record * rec,
                                         icalcomponent *comp, icalproperty *prop);

typedef struct icalrestriction_property_record
{
    icalproperty_method method;
    icalcomponent_kind component;
    icalproperty_kind property;
    icalrestriction_kind restriction;
    restriction_func function;
} icalrestriction_property_record;

typedef struct icalrestriction_component_record
{
    icalproperty_method method;
    icalcomponent_kind component;
    icalcomponent_kind subcomponent;
    icalrestriction_kind restriction;
    restriction_func function;
} icalrestriction_component_record;

static const icalrestriction_property_record *icalrestriction_get_property_restriction(
    icalproperty_method method, icalcomponent_kind component, icalproperty_kind property);

static const icalrestriction_property_record null_prop_record =
    { ICAL_METHOD_NONE, ICAL_NO_COMPONENT, ICAL_NO_PROPERTY, ICAL_RESTRICTION_UNKNOWN, NULL };

/** Each row gives the result of comparing a restriction against a count.
   The columns in each row represent 0,1,2+. '-1' indicates
   'invalid, 'don't care' or 'needs more analysis' So, for
   ICAL_RESTRICTION_ONE, if there is 1 of a property with that
   restriction, it passes, but if there are 0 or 2+, it fails. */

static const char compare_map[ICAL_RESTRICTION_UNKNOWN + 1][3] = {
    {1, 1, 1}, /*ICAL_RESTRICTION_NONE */
    {1, 0, 0}, /*ICAL_RESTRICTION_ZERO */
    {0, 1, 0}, /*ICAL_RESTRICTION_ONE */
    {1, 1, 1}, /*ICAL_RESTRICTION_ZEROPLUS */
    {0, 1, 1}, /*ICAL_RESTRICTION_ONEPLUS */
    {1, 1, 0}, /*ICAL_RESTRICTION_ZEROORONE */
    {1, 1, 0}, /*ICAL_RESTRICTION_ONEEXCLUSIVE */
    {1, 1, 0}, /*ICAL_RESTRICTION_ONEMUTUAL */
    {1, 1, 1}   /*ICAL_RESTRICTION_UNKNOWN */
};

static const char restr_string_map[ICAL_RESTRICTION_UNKNOWN + 1][60] = {
    "unknown number", /*ICAL_RESTRICTION_NONE */
    "0", /*ICAL_RESTRICTION_ZERO */
    "1", /*ICAL_RESTRICTION_ONE */
    "zero or more", /*ICAL_RESTRICTION_ZEROPLUS */
    "one or more", /*ICAL_RESTRICTION_ONEPLUS */
    "zero or one", /*ICAL_RESTRICTION_ZEROORONE */
    "zero or one, exclusive with another property", /*ICAL_RESTRICTION_ONEEXCLUSIVE */
    "zero or one, mutual with another property", /*ICAL_RESTRICTION_ONEMUTUAL */
    "unknown number"    /*ICAL_RESTRICTION_UNKNOWN */
};

int icalrestriction_compare(icalrestriction_kind restr, int count)
{
    /* restr is an unsigned int and ICAL_RESTRICTION_NONE == 0,
       so no need to check if restr < ICAL_RESTRICTION_NONE */
    if (restr > ICAL_RESTRICTION_UNKNOWN || count < 0) {
        return -1;
    }

    if (count > 2) {
        count = 2;
    }

    return compare_map[restr][count];
}

/* Special case routines */

static const char *icalrestriction_may_be_draft_final_canceled(
    const icalrestriction_property_record *rec, icalcomponent *comp, icalproperty *prop)
{
    icalproperty_status stat = icalproperty_get_status(prop);

    _unused(rec);
    _unused(comp);

    if (!(stat == ICAL_STATUS_DRAFT ||
          stat == ICAL_STATUS_FINAL ||
          stat == ICAL_STATUS_CANCELLED)) {
        return
            "Failed iTIP restrictions for STATUS property. "
            "Value must be one of DRAFT, FINAL, or CANCELED";
    }

    return 0;
}

static const char *icalrestriction_may_be_comp_need_process(const icalrestriction_property_record *
                                                            rec, icalcomponent *comp,
                                                            icalproperty *prop)
{
    icalproperty_status stat = icalproperty_get_status(prop);

    _unused(rec);
    _unused(comp);

    if (!(stat == ICAL_STATUS_COMPLETED ||
          stat == ICAL_STATUS_NEEDSACTION || stat == ICAL_STATUS_INPROCESS)) {
        return
            "Failed iTIP restrictions for STATUS property. "
            "Value must be one of COMPLETED, NEEDS-ACTION or IN-PROCESS";
    }

    return 0;
}

static const char *icalrestriction_may_be_tent_conf(const icalrestriction_property_record * rec,
                                                    icalcomponent *comp, icalproperty *prop)
{
    icalproperty_status stat = icalproperty_get_status(prop);

    _unused(rec);
    _unused(comp);

    if (!(stat == ICAL_STATUS_TENTATIVE || stat == ICAL_STATUS_CONFIRMED)) {
        return
            "Failed iTIP restrictions for STATUS property. "
            "Value must be one of TENTATIVE or CONFIRMED";
    }

    return 0;
}

static const char *icalrestriction_may_be_tent_conf_cancel(const icalrestriction_property_record *
                                                           rec, icalcomponent *comp,
                                                           icalproperty *prop)
{
    icalproperty_status stat = icalproperty_get_status(prop);

    _unused(rec);
    _unused(comp);

    if (!(stat == ICAL_STATUS_TENTATIVE ||
          stat == ICAL_STATUS_CONFIRMED || stat == ICAL_STATUS_CANCELLED)) {
        return
            "Failed iTIP restrictions for STATUS property. "
            "Value must be one of TENTATIVE, CONFIRMED or CANCELED";
    }

    return 0;
}

static const char *icalrestriction_must_be_cancel_if_present(
    const icalrestriction_property_record *rec, icalcomponent *comp, icalproperty *prop)
{
    /* This routine will not be called if prop == 0 */
    icalproperty_status stat = icalproperty_get_status(prop);

    _unused(rec);
    _unused(comp);

    if (stat != ICAL_STATUS_CANCELLED) {
        return "Failed iTIP restrictions for STATUS property. Value must be CANCELLED";
    }

    return 0;
}

static const char *icalrestriction_must_be_canceled_no_attendee(
    const icalrestriction_property_record *rec, icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(comp);
    _unused(prop);

    /* Hack. see rfc5546, 3.2.5 CANCEL for property STATUS. I don't
       understand the note */

    return 0;
}

static const char *icalrestriction_must_be_recurring(const icalrestriction_property_record * rec,
                                                     icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(comp);
    _unused(prop);
    return 0;
}

static const char *icalrestriction_must_have_duration(const icalrestriction_property_record * rec,
                                                      icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(prop);
    if (!icalcomponent_get_first_property(comp, ICAL_DURATION_PROPERTY)) {
        return
            "Failed iTIP restrictions for DURATION property. "
            "This component must have a DURATION property";
    }

    return 0;
}

static const char *icalrestriction_must_have_repeat(const icalrestriction_property_record * rec,
                                                    icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(prop);
    if (!icalcomponent_get_first_property(comp, ICAL_REPEAT_PROPERTY)) {
        return
            "Failed iTIP restrictions for REPEAT property. "
            "This component must have a REPEAT property";
    }

    return 0;
}

const char *icalrestriction_must_if_tz_ref(const icalrestriction_property_record * rec,
                                           icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(comp);
    _unused(prop);
    return 0;
}

static const char *icalrestriction_no_dtend(const icalrestriction_property_record * rec,
                                            icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(prop);
    if (icalcomponent_get_first_property(comp, ICAL_DTEND_PROPERTY)) {
        return
            "Failed iTIP restrictions for DTEND property. "
            "The component must not have both DURATION and DTEND";
    }

    return 0;
}

static const char *icalrestriction_no_duration(const icalrestriction_property_record * rec,
                                               icalcomponent *comp, icalproperty *prop)
{
    _unused(rec);
    _unused(comp);
    _unused(prop);

    /* _no_dtend takes care of this one */
    return 0;
}

static const char *icalrestriction_must_be_email(const icalrestriction_property_record * rec,
                                                 icalcomponent *comp, icalproperty *prop)
{
    icalproperty_action stat = icalproperty_get_action(prop);

    _unused(rec);
    _unused(comp);

    if (!(stat == ICAL_ACTION_EMAIL)) {
        return "Failed iTIP restrictions for ACTION property. Value must be EMAIL.";
    }

    return 0;
}

static int icalrestriction_check_component(icalproperty_method method, icalcomponent *comp)
{
    icalproperty_kind kind;
    icalcomponent_kind comp_kind;
    icalrestriction_kind restr;
    const icalrestriction_property_record *prop_record;
    const char *funcr = 0;
    icalproperty *prop;

    int count;
    int compare;
    int valid = 1;

    comp_kind = icalcomponent_isa(comp);

    /* Check all of the properties in this component */

    for (kind = ICAL_ANY_PROPERTY + 1; kind != ICAL_NO_PROPERTY; kind++) {
        count = icalcomponent_count_properties(comp, kind);

        prop_record = icalrestriction_get_property_restriction(method, comp_kind, kind);

        restr = prop_record->restriction;

        if (restr == ICAL_RESTRICTION_ONEEXCLUSIVE || restr == ICAL_RESTRICTION_ONEMUTUAL) {

            /* First treat is as a 0/1 restriction */
            restr = ICAL_RESTRICTION_ZEROORONE;
            compare = icalrestriction_compare(restr, count);

        } else {

            compare = icalrestriction_compare(restr, count);
        }

        assert(compare != -1);

        if (compare == 0) {
#if !defined(__COVERITY__)
            /* Coverity doesn't think the memory allocated by icalparameter_new_xlicerrortype()
             * is freed, even though it should be freed in icalproperty_free()
             */
            #define TMP_BUF_SIZE 1024
            char temp[TMP_BUF_SIZE];
            icalproperty *errProp;

            snprintf(temp, TMP_BUF_SIZE,
                     "Failed iTIP restrictions for %s property. "
                     "Expected %s instances of the property and got %d",
                     icalenum_property_kind_to_string(kind), restr_string_map[restr], count);
            errProp =
                icalproperty_vanew_xlicerror(
                    temp,
                    icalparameter_new_xlicerrortype(ICAL_XLICERRORTYPE_INVALIDITIP), (void *)0);
            icalcomponent_add_property(comp, errProp);
            icalproperty_free(errProp);
#endif
        }

        prop = icalcomponent_get_first_property(comp, kind);

        if (prop != 0 && prop_record->function != NULL) {
            funcr = prop_record->function(prop_record, comp, prop);
        }

        if (funcr != 0) {
#if !defined(__COVERITY__)
            /* Coverity doesn't think the memory allocated by icalparameter_new_xlicerrortype()
             * is freed, even though it should be freed in icalproperty_free()
             */
            icalproperty *errProp;

            errProp =
                icalproperty_vanew_xlicerror(
                    funcr,
                    icalparameter_new_xlicerrortype(ICAL_XLICERRORTYPE_INVALIDITIP), (void *)0);
            icalcomponent_add_property(comp, errProp);
            icalproperty_free(errProp);
            compare = 0;
#endif
        }

        valid = valid && compare;
    }

    return valid;
}

int icalrestriction_check(icalcomponent *outer_comp)
{
    icalcomponent_kind comp_kind;
    icalproperty_method method;
    icalcomponent *inner_comp;
    icalproperty *method_prop;
    int valid;

    icalerror_check_arg_rz((outer_comp != 0), "outer comp");

    /* Get the Method value from the outer component */

    comp_kind = icalcomponent_isa(outer_comp);

    if (comp_kind != ICAL_VCALENDAR_COMPONENT) {
        icalerror_set_errno(ICAL_BADARG_ERROR);
        return 0;
    }

    method_prop = icalcomponent_get_first_property(outer_comp, ICAL_METHOD_PROPERTY);

    if (method_prop == 0) {
        method = ICAL_METHOD_NONE;
    } else {
        method = icalproperty_get_method(method_prop);
    }

    /* Check the VCALENDAR wrapper */
    valid = icalrestriction_check_component(ICAL_METHOD_NONE, outer_comp);

    /* Now check the inner components */

    for (inner_comp = icalcomponent_get_first_component(outer_comp, ICAL_ANY_COMPONENT);
         inner_comp != 0;
         inner_comp = icalcomponent_get_next_component(outer_comp, ICAL_ANY_COMPONENT)) {
        valid = valid && icalrestriction_check_component(method, inner_comp);
    }

    return valid;
}

<insert_code_here>

static const icalrestriction_property_record *icalrestriction_get_property_restriction(
    icalproperty_method method, icalcomponent_kind component, icalproperty_kind property)
{
    int i;

    for (i = 0; icalrestriction_property_records[i].restriction != ICAL_RESTRICTION_NONE; i++) {

        if (method == icalrestriction_property_records[i].method &&
            component == icalrestriction_property_records[i].component &&
            property == icalrestriction_property_records[i].property) {
            return &icalrestriction_property_records[i];
        }
    }

    return &null_prop_record;
}
