
/*---------------------------------------------------------------------------*/
/* conference-invitations_mail.cpp                                           */
/* copyright (c) innovaphone 2025                                            */
/*---------------------------------------------------------------------------*/

#include "platform/platform.h"
#include "common/os/iomux.h"

#include <algorithm>
#include <string_view>
#include <stdexcept>
#include <sstream>
#include <iterator>

#include "sdk/common/lib/ical.h"

#define CRLF "\r\n"

//---------------------------------------------------------------------------
// Helper functions
//---------------------------------------------------------------------------

namespace strtools
{
    std::string RTrim(std::string & s)
    {
        auto i = std::find_if(s.rbegin(), s.rend(), [](char c) { return !std::isspace(c); });
        s.erase(i.base(), s.end());
        return s;
    }

    std::string LTrim(std::string & s)
    {
        auto i = std::find_if(s.begin(), s.end(), [](char c) { return !std::isspace(c); });
        s.erase(s.begin(), i);
        return s;
    }

    std::string Trim(std::string & s)
    {
        RTrim(s);
        LTrim(s);
        return s;
    }
    
    std::string Trim(const char * s)
    {
        std::string result;
        if (s != nullptr) {
            result = s;
            Trim(result);
        }
        return result;
    }
    
    std::list<std::string> Split(std::string str, char token)
    {
        std::list<std::string> result;
	
        if (!str.empty()) {
            std::string::iterator begin = str.begin();
            std::string::iterator end = str.end();
            std::string::iterator i = begin;
            std::string s;

            while (true) {
                i = std::find_if(i, end, [token](char c) { return c == token; });
                s.assign(begin, i);
	    
                Trim(s);
                if (!s.empty()) {			
                    result.emplace_back(std::move(s));
                }

                if (i == end) break;
                ++i;
                begin = i;
            }
        }
	
        return result;
    }
}


const char * ICalComponentTypeToStr(ical_component_t component)
{
    switch (component) {
    case ical_component_t::VCALENDAR: return "VCALENDAR";
    case ical_component_t::VEVENT: return "VEVENT";
    case ical_component_t::VTODO: return "VTODO";
    case ical_component_t::VJOURNAL: return "VJOURNAL";
    case ical_component_t::VFREEBUSY: return "VFREEBUSY";
    case ical_component_t::VTIMEZONE: return "VTIMEZONE";
    case ical_component_t::STANDARD: return "STANDARD";
    case ical_component_t::DAYLIGHT: return "DAYLIGHT";
    case ical_component_t::VALARM: return "VALARM";
    case ical_component_t::VAVAILABILITY: return "VAVAILABILITY";
    case ical_component_t::AVAILABLE: return "AVAILABLE";
    case ical_component_t::PARTICIPANT: return "PARTICIPANT";
    case ical_component_t::VLOCATION: return "VLOCATION";
    case ical_component_t::VRESOURCE: return "VRESOURCE";
    default:
        return "UNKOWN";
    }
}

ical_component_t StrToICalComponentType(const std::string & s)
{
    if (s == "VCALENDAR") return ical_component_t::VCALENDAR;
    if (s == "VEVENT") return ical_component_t::VEVENT;
    if (s == "VTODO") return ical_component_t::VTODO;
    if (s == "VJOURNAL") return ical_component_t::VJOURNAL;
    if (s == "VFREEBUSY") return ical_component_t::VFREEBUSY;
    if (s == "VTIMEZONE") return ical_component_t::VTIMEZONE;
    if (s == "STANDARD") return ical_component_t::STANDARD;
    if (s == "DAYLIGHT") return ical_component_t::DAYLIGHT;
    if (s == "VALARM") return ical_component_t::VALARM;
    if (s == "VAVAILABILITY") return ical_component_t::VAVAILABILITY;
    if (s == "AVAILABLE") return ical_component_t::AVAILABLE;
    if (s == "PARTICIPANT") return ical_component_t::PARTICIPANT;
    if (s == "VLOCATION") return ical_component_t::VLOCATION;
    if (s == "VRESOURCE") return ical_component_t::VRESOURCE;
    return ical_component_t::UNKOWN;
}

ical_component_t StrToICalComponentType(const char * componentName)
{
    std::string s = componentName;
    for (char & c : s) c = std::toupper(c);
    return StrToICalComponentType(s);
}


const char * ICalPropertyTypeToStr(ical_property_t property)
{
    switch (property) {
    case ical_property_t::CALSCALE: return "CALSCALE";
    case ical_property_t::METHOD: return "METHOD";
    case ical_property_t::PRODID: return "PRODID";
    case ical_property_t::VERSION: return "VERSION";
    case ical_property_t::X_CALEND: return "X-CALEND";
    case ical_property_t::X_CALSTART: return "X-CALSTART";
    case ical_property_t::X_CLIPEND: return "X-CLIPEND";
    case ical_property_t::X_CLIPSTART: return "X-CLIPSTART";
    case ical_property_t::X_MICROSOFT_CALSCALE: return "X-MICROSOFT-CALSCALE";
    case ical_property_t::X_MS_OLK_FORCEINSPECTOROPEN: return "X-MS-OLK-FORCEINSPECTOROPEN";
    case ical_property_t::X_MS_WKHRDAYS: return "X-MS-WKHRDAYS";
    case ical_property_t::X_MS_WKHREND: return "X-MS-WKHREND";
    case ical_property_t::X_MS_WKHRSTART: return "X-MS-WKHRSTART";
    case ical_property_t::X_OWNER: return "X-OWNER";
    case ical_property_t::X_PRIMARY_CALENDAR: return "X-PRIMARY-CALENDAR";
    case ical_property_t::X_PUBLISHED_TTL: return "X-PUBLISHED-TTL";
    case ical_property_t::X_WR_CALDESC: return "X-WR-CALDESC";
    case ical_property_t::X_WR_CALNAME: return "X-WR-CALNAME";
    case ical_property_t::X_WR_RELCALID: return "X-WR-RELCALID";
    case ical_property_t::ATTACH: return "ATTACH";
    case ical_property_t::CATEGORIES: return "CATEGORIES";
    case ical_property_t::CLASS: return "CLASS";
    case ical_property_t::COMMENT: return "COMMENT";
    case ical_property_t::DESCRIPTION: return "DESCRIPTION";
    case ical_property_t::GEO: return "GEO";
    case ical_property_t::LOCATION: return "LOCATION";
    case ical_property_t::PERCENT_COMPLETE: return "PERCENT-COMPLETE";
    case ical_property_t::PRIORITY: return "PRIORITY";
    case ical_property_t::RESOURCES: return "RESOURCES";
    case ical_property_t::STATUS: return "STATUS";
    case ical_property_t::SUMMARY: return "SUMMARY";
    case ical_property_t::COMPLETED: return "COMPLETED";
    case ical_property_t::DTEND: return "DTEND";
    case ical_property_t::DUE: return "DUE";
    case ical_property_t::DTSTART: return "DTSTART";
    case ical_property_t::DURATION: return "DURATION";
    case ical_property_t::FREEBUSY: return "FREEBUSY";
    case ical_property_t::TRANSP: return "TRANSP";
    case ical_property_t::TZID: return "TZID";
    case ical_property_t::TZNAME: return "TZNAME";
    case ical_property_t::TZOFFSETFROM: return "TZOFFSETFROM";
    case ical_property_t::TZOFFSETTO: return "TZOFFSETTO";
    case ical_property_t::TZURL: return "TZURL";
    case ical_property_t::ATTENDEE: return "ATTENDEE";
    case ical_property_t::CONTACT: return "CONTACT";
    case ical_property_t::ORGANIZER: return "ORGANIZER";
    case ical_property_t::RECURRENCE_ID: return "RECURRENCE-ID";
    case ical_property_t::RELATED_TO: return "RELATED-TO";
    case ical_property_t::URL: return "URL";
    case ical_property_t::UID: return "UID";
    case ical_property_t::EXDATE: return "EXDATE";
    case ical_property_t::RDATE: return "RDATE";
    case ical_property_t::RRULE: return "RRULE";
    case ical_property_t::ACTION: return "ACTION";
    case ical_property_t::REPEAT: return "REPEAT";
    case ical_property_t::TRIGGER: return "TRIGGER";
    case ical_property_t::CREATED: return "CREATED";
    case ical_property_t::DTSTAMP: return "DTSTAMP";
    case ical_property_t::LAST_MODIFIED: return "LAST-MODIFIED";
    case ical_property_t::SEQUENCE: return "SEQUENCE";
    case ical_property_t::REQUEST_STATUS: return "REQUEST-STATUS";
    case ical_property_t::X_ALT_DESC: return "X-ALT-DESC";
    case ical_property_t::X_MICROSOFT_CDO_ALLDAYEVENT: return "X-MICROSOFT-CDO-ALLDAYEVENT";
    case ical_property_t::X_MICROSOFT_CDO_APPT_SEQUENCE: return "X-MICROSOFT-CDO-APPT-SEQUENCE";
    case ical_property_t::X_MICROSOFT_CDO_ATTENDEE_CRITICAL_CHANGE: return "X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE";
    case ical_property_t::X_MICROSOFT_CDO_BUSYSTATUS: return "X-MICROSOFT-CDO-BUSYSTATUS";
    case ical_property_t::X_MICROSOFT_CDO_IMPORTANCE: return "X-MICROSOFT-CDO-IMPORTANCE";
    case ical_property_t::X_MICROSOFT_CDO_INSTTYPE: return "X-MICROSOFT-CDO-INSTTYPE";
    case ical_property_t::X_MICROSOFT_CDO_INTENDEDSTATUS: return "X-MICROSOFT-CDO-INTENDEDSTATUS";
    case ical_property_t::X_MICROSOFT_CDO_OWNERAPPTID: return "X-MICROSOFT-CDO-OWNERAPPTID";
    case ical_property_t::X_MICROSOFT_CDO_OWNER_CRITICAL_CHANGE: return "X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE";
    case ical_property_t::X_MICROSOFT_CDO_REPLYTIME: return "X-MICROSOFT-CDO-REPLYTIME";
    case ical_property_t::X_MICROSOFT_DISALLOW_COUNTER: return "X-MICROSOFT-DISALLOW-COUNTER";
    case ical_property_t::X_MICROSOFT_EXDATE: return "X-MICROSOFT-EXDATE";
    case ical_property_t::X_MICROSOFT_ISDRAFT: return "X-MICROSOFT-ISDRAFT";
    case ical_property_t::X_MICROSOFT_MSNCALENDAR_ALLDAYEVENT: return "X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT";
    case ical_property_t::X_MICROSOFT_MSNCALENDAR_BUSYSTATUS: return "X-MICROSOFT-MSNCALENDAR-BUSYSTATUS";
    case ical_property_t::X_MICROSOFT_MSNCALENDAR_IMPORTANCE: return "X-MICROSOFT-MSNCALENDAR-IMPORTANCE";
    case ical_property_t::X_MICROSOFT_MSNCALENDAR_INTENDEDSTATUS: return "X-MICROSOFT-MSNCALENDAR-INTENDEDSTATUS";
    case ical_property_t::X_MICROSOFT_RRULE: return "X-MICROSOFT-RRULE";
    case ical_property_t::X_MS_OLK_ALLOWEXTERNCHECK: return "X-MS-OLK-ALLOWEXTERNCHECK";
    case ical_property_t::X_MS_OLK_APPTLASTSEQUENCE: return "X-MS-OLK-APPTLASTSEQUENCE";
    case ical_property_t::X_MS_OLK_APPTSEQTIME: return "X-MS-OLK-APPTSEQTIME";
    case ical_property_t::X_MS_OLK_AUTOFILLLOCATION: return "X-MS-OLK-AUTOFILLLOCATION";
    case ical_property_t::X_MS_OLK_AUTOSTARTCHECK: return "X-MS-OLK-AUTOSTARTCHECK";
    case ical_property_t::X_MS_OLK_COLLABORATEDOC: return "X-MS-OLK-COLLABORATEDOC";
    case ical_property_t::X_MS_OLK_CONFCHECK: return "X-MS-OLK-CONFCHECK";
    case ical_property_t::X_MS_OLK_CONFTYPE: return "X-MS-OLK-CONFTYPE";
    case ical_property_t::X_MS_OLK_DIRECTORY: return "X-MS-OLK-DIRECTORY";
    case ical_property_t::X_MS_OLK_MWSURL: return "X-MS-OLK-MWSURL";
    case ical_property_t::X_MS_OLK_NETSHOWURL: return "X-MS-OLK-NETSHOWURL";
    case ical_property_t::X_MS_OLK_ONLINEPASSWORD: return "X-MS-OLK-ONLINEPASSWORD";
    case ical_property_t::X_MS_OLK_ORGALIAS: return "X-MS-OLK-ORGALIAS";
    case ical_property_t::X_MS_OLK_SENDER: return "X-MS-OLK-SENDER";
    case ical_property_t::BUSYTYPE: return "BUSYTYPE";
    case ical_property_t::NAME: return "NAME";
    case ical_property_t::REFRESH_INTERVAL: return "REFRESH-INTERVAL";
    case ical_property_t::SOURCE: return "SOURCE";
    case ical_property_t::COLOR: return "COLOR";
    case ical_property_t::IMAGE: return "IMAGE";
    case ical_property_t::CONFERENCE: return "CONFERENCE";
    case ical_property_t::CALENDAR_ADDRESS: return "CALENDAR-ADDRESS";
    case ical_property_t::LOCATION_TYPE: return "LOCATION-TYPE";
    case ical_property_t::PARTICIPANT_TYPE: return "PARTICIPANT-TYPE";
    case ical_property_t::RESOURCE_TYPE: return "RESOURCE-TYPE";
    case ical_property_t::STRUCTURED_DATA: return "STRUCTURED-DATA";
    case ical_property_t::STYLED_DESCRIPTION: return "STYLED-DESCRIPTION";
    case ical_property_t::ACKNOWLEDGED: return "ACKNOWLEDGED";
    case ical_property_t::PROXIMITY: return "PROXIMITY";
    case ical_property_t::CONCEPT: return "CONCEPT";
    case ical_property_t::LINK: return "LINK";
    case ical_property_t::REFID: return "REFID";        
    default: return "UNKOWN";
    }
}

ical_property_t StrToICalPropertyType(const std::string & s)
{
    if (s == "CALSCALE") return ical_property_t::CALSCALE;
    if (s == "METHOD") return ical_property_t::METHOD;
    if (s == "PRODID") return ical_property_t::PRODID;
    if (s == "VERSION") return ical_property_t::VERSION;
    if (s == "X-CALEND") return ical_property_t::X_CALEND;
    if (s == "X-CALSTART") return ical_property_t::X_CALSTART;
    if (s == "X-CLIPEND") return ical_property_t::X_CLIPEND;
    if (s == "X-CLIPSTART") return ical_property_t::X_CLIPSTART;
    if (s == "X-MICROSOFT-CALSCALE") return ical_property_t::X_MICROSOFT_CALSCALE;
    if (s == "X-MS-OLK-FORCEINSPECTOROPEN") return ical_property_t::X_MS_OLK_FORCEINSPECTOROPEN;
    if (s == "X-MS-WKHRDAYS") return ical_property_t::X_MS_WKHRDAYS;
    if (s == "X-MS-WKHREND") return ical_property_t::X_MS_WKHREND;
    if (s == "X-MS-WKHRSTART") return ical_property_t::X_MS_WKHRSTART;
    if (s == "X-OWNER") return ical_property_t::X_OWNER;
    if (s == "X-PRIMARY-CALENDAR") return ical_property_t::X_PRIMARY_CALENDAR;
    if (s == "X-PUBLISHED-TTL") return ical_property_t::X_PUBLISHED_TTL;
    if (s == "X-WR-CALDESC") return ical_property_t::X_WR_CALDESC;
    if (s == "X-WR-CALNAME") return ical_property_t::X_WR_CALNAME;
    if (s == "X-WR-RELCALID") return ical_property_t::X_WR_RELCALID;
    if (s == "ATTACH") return ical_property_t::ATTACH;
    if (s == "CATEGORIES") return ical_property_t::CATEGORIES;
    if (s == "CLASS") return ical_property_t::CLASS;
    if (s == "COMMENT") return ical_property_t::COMMENT;
    if (s == "DESCRIPTION") return ical_property_t::DESCRIPTION;
    if (s == "GEO") return ical_property_t::GEO;
    if (s == "LOCATION") return ical_property_t::LOCATION;
    if (s == "PERCENT-COMPLETE") return ical_property_t::PERCENT_COMPLETE;
    if (s == "PRIORITY") return ical_property_t::PRIORITY;
    if (s == "RESOURCES") return ical_property_t::RESOURCES;
    if (s == "STATUS") return ical_property_t::STATUS;
    if (s == "SUMMARY") return ical_property_t::SUMMARY;
    if (s == "COMPLETED") return ical_property_t::COMPLETED;
    if (s == "DTEND") return ical_property_t::DTEND;
    if (s == "DUE") return ical_property_t::DUE;
    if (s == "DTSTART") return ical_property_t::DTSTART;
    if (s == "DURATION") return ical_property_t::DURATION;
    if (s == "FREEBUSY") return ical_property_t::FREEBUSY;
    if (s == "TRANSP") return ical_property_t::TRANSP;
    if (s == "TZID") return ical_property_t::TZID;
    if (s == "TZNAME") return ical_property_t::TZNAME;
    if (s == "TZOFFSETFROM") return ical_property_t::TZOFFSETFROM;
    if (s == "TZOFFSETTO") return ical_property_t::TZOFFSETTO;
    if (s == "TZURL") return ical_property_t::TZURL;
    if (s == "ATTENDEE") return ical_property_t::ATTENDEE;
    if (s == "CONTACT") return ical_property_t::CONTACT;
    if (s == "ORGANIZER") return ical_property_t::ORGANIZER;
    if (s == "RECURRENCE-ID") return ical_property_t::RECURRENCE_ID;
    if (s == "RELATED-TO") return ical_property_t::RELATED_TO;
    if (s == "URL") return ical_property_t::URL;
    if (s == "UID") return ical_property_t::UID;
    if (s == "EXDATE") return ical_property_t::EXDATE;
    if (s == "RDATE") return ical_property_t::RDATE;
    if (s == "RRULE") return ical_property_t::RRULE;
    if (s == "ACTION") return ical_property_t::ACTION;
    if (s == "REPEAT") return ical_property_t::REPEAT;
    if (s == "TRIGGER") return ical_property_t::TRIGGER;
    if (s == "CREATED") return ical_property_t::CREATED;
    if (s == "DTSTAMP") return ical_property_t::DTSTAMP;
    if (s == "LAST-MODIFIED") return ical_property_t::LAST_MODIFIED;
    if (s == "SEQUENCE") return ical_property_t::SEQUENCE;
    if (s == "REQUEST-STATUS") return ical_property_t::REQUEST_STATUS;
    if (s == "X-ALT-DESC") return ical_property_t::X_ALT_DESC;
    if (s == "X-MICROSOFT-CDO-ALLDAYEVENT") return ical_property_t::X_MICROSOFT_CDO_ALLDAYEVENT;
    if (s == "X-MICROSOFT-CDO-APPT-SEQUENCE") return ical_property_t::X_MICROSOFT_CDO_APPT_SEQUENCE;
    if (s == "X-MICROSOFT-CDO-ATTENDEE-CRITICAL-CHANGE") return ical_property_t::X_MICROSOFT_CDO_ATTENDEE_CRITICAL_CHANGE;
    if (s == "X-MICROSOFT-CDO-BUSYSTATUS") return ical_property_t::X_MICROSOFT_CDO_BUSYSTATUS;
    if (s == "X-MICROSOFT-CDO-IMPORTANCE") return ical_property_t::X_MICROSOFT_CDO_IMPORTANCE;
    if (s == "X-MICROSOFT-CDO-INSTTYPE") return ical_property_t::X_MICROSOFT_CDO_INSTTYPE;
    if (s == "X-MICROSOFT-CDO-INTENDEDSTATUS") return ical_property_t::X_MICROSOFT_CDO_INTENDEDSTATUS;
    if (s == "X-MICROSOFT-CDO-OWNERAPPTID") return ical_property_t::X_MICROSOFT_CDO_OWNERAPPTID;
    if (s == "X-MICROSOFT-CDO-OWNER-CRITICAL-CHANGE") return ical_property_t::X_MICROSOFT_CDO_OWNER_CRITICAL_CHANGE;
    if (s == "X-MICROSOFT-CDO-REPLYTIME") return ical_property_t::X_MICROSOFT_CDO_REPLYTIME;
    if (s == "X-MICROSOFT-DISALLOW-COUNTER") return ical_property_t::X_MICROSOFT_DISALLOW_COUNTER;
    if (s == "X-MICROSOFT-EXDATE") return ical_property_t::X_MICROSOFT_EXDATE;
    if (s == "X-MICROSOFT-ISDRAFT") return ical_property_t::X_MICROSOFT_ISDRAFT;
    if (s == "X-MICROSOFT-MSNCALENDAR-ALLDAYEVENT") return ical_property_t::X_MICROSOFT_MSNCALENDAR_ALLDAYEVENT;
    if (s == "X-MICROSOFT-MSNCALENDAR-BUSYSTATUS") return ical_property_t::X_MICROSOFT_MSNCALENDAR_BUSYSTATUS;
    if (s == "X-MICROSOFT-MSNCALENDAR-IMPORTANCE") return ical_property_t::X_MICROSOFT_MSNCALENDAR_IMPORTANCE;
    if (s == "X-MICROSOFT-MSNCALENDAR-INTENDEDSTATUS") return ical_property_t::X_MICROSOFT_MSNCALENDAR_INTENDEDSTATUS;
    if (s == "X-MICROSOFT-RRULE") return ical_property_t::X_MICROSOFT_RRULE;
    if (s == "X-MS-OLK-ALLOWEXTERNCHECK") return ical_property_t::X_MS_OLK_ALLOWEXTERNCHECK;
    if (s == "X-MS-OLK-APPTLASTSEQUENCE") return ical_property_t::X_MS_OLK_APPTLASTSEQUENCE;
    if (s == "X-MS-OLK-APPTSEQTIME") return ical_property_t::X_MS_OLK_APPTSEQTIME;
    if (s == "X-MS-OLK-AUTOFILLLOCATION") return ical_property_t::X_MS_OLK_AUTOFILLLOCATION;
    if (s == "X-MS-OLK-AUTOSTARTCHECK") return ical_property_t::X_MS_OLK_AUTOSTARTCHECK;
    if (s == "X-MS-OLK-COLLABORATEDOC") return ical_property_t::X_MS_OLK_COLLABORATEDOC;
    if (s == "X-MS-OLK-CONFCHECK") return ical_property_t::X_MS_OLK_CONFCHECK;
    if (s == "X-MS-OLK-CONFTYPE") return ical_property_t::X_MS_OLK_CONFTYPE;
    if (s == "X-MS-OLK-DIRECTORY") return ical_property_t::X_MS_OLK_DIRECTORY;
    if (s == "X-MS-OLK-MWSURL") return ical_property_t::X_MS_OLK_MWSURL;
    if (s == "X-MS-OLK-NETSHOWURL") return ical_property_t::X_MS_OLK_NETSHOWURL;
    if (s == "X-MS-OLK-ONLINEPASSWORD") return ical_property_t::X_MS_OLK_ONLINEPASSWORD;
    if (s == "X-MS-OLK-ORGALIAS") return ical_property_t::X_MS_OLK_ORGALIAS;
    if (s == "X-MS-OLK-SENDER") return ical_property_t::X_MS_OLK_SENDER;
    if (s == "BUSYTYPE") return ical_property_t::BUSYTYPE;
    if (s == "NAME") return ical_property_t::NAME;
    if (s == "REFRESH-INTERVAL") return ical_property_t::REFRESH_INTERVAL;
    if (s == "SOURCE") return ical_property_t::SOURCE;
    if (s == "COLOR") return ical_property_t::COLOR;
    if (s == "IMAGE") return ical_property_t::IMAGE;
    if (s == "CONFERENCE") return ical_property_t::CONFERENCE;
    if (s == "CALENDAR-ADDRESS") return ical_property_t::CALENDAR_ADDRESS;
    if (s == "LOCATION-TYPE") return ical_property_t::LOCATION_TYPE;
    if (s == "PARTICIPANT-TYPE") return ical_property_t::PARTICIPANT_TYPE;
    if (s == "RESOURCE-TYPE") return ical_property_t::RESOURCE_TYPE;
    if (s == "STRUCTURED-DATA") return ical_property_t::STRUCTURED_DATA;
    if (s == "STYLED-DESCRIPTION") return ical_property_t::STYLED_DESCRIPTION;
    if (s == "ACKNOWLEDGED") return ical_property_t::ACKNOWLEDGED;
    if (s == "PROXIMITY") return ical_property_t::PROXIMITY;
    if (s == "CONCEPT") return ical_property_t::CONCEPT;
    if (s == "LINK") return ical_property_t::LINK;
    if (s == "REFID") return ical_property_t::REFID;
    return ical_property_t::UNKOWN;
}


ical_property_t StrToICalPropertyType(const char * propertyName)
{
    std::string s = propertyName;
    for (char & c : s) c = std::toupper(c);
    return StrToICalPropertyType(s);
}


const char * ICalParameterTypeToStr(ical_parameter_t param)
{
    switch (param) {
    case ical_parameter_t::ALTREP: return "ALTREP";
    case ical_parameter_t::CN: return "CN";
    case ical_parameter_t::CUTYPE: return "CUTYPE";
    case ical_parameter_t::DELEGATED_FROM: return "DELEGATED-FROM";
    case ical_parameter_t::DELEGATED_TO : return "DELEGATED-TO ";
    case ical_parameter_t::DIR: return "DIR";
    case ical_parameter_t::ENCODING: return "ENCODING";
    case ical_parameter_t::FMTTYPE: return "FMTTYPE";
    case ical_parameter_t::FBTYPE: return "FBTYPE";
    case ical_parameter_t::LANGUAGE: return "LANGUAGE";
    case ical_parameter_t::MEMBER: return "MEMBER";
    case ical_parameter_t::PARTSTAT: return "PARTSTAT";
    case ical_parameter_t::RANGE: return "RANGE";
    case ical_parameter_t::RELATED: return "RELATED";
    case ical_parameter_t::RELTYPE: return "RELTYPE";
    case ical_parameter_t::ROLE: return "ROLE";
    case ical_parameter_t::RSVP: return "RSVP";
    case ical_parameter_t::SENT_BY: return "SENT-BY";
    case ical_parameter_t::TZID: return "TZID";
    case ical_parameter_t::VALUE: return "VALUE";
    case ical_parameter_t::X_FILENAME: return "X-FILENAME";
    case ical_parameter_t::X_MS_OLK_RESPTIME: return "X-MS-OLK-RESPTIME";
    case ical_parameter_t::X_MICROSOFT_ISLEAPMONTH: return "X-MICROSOFT-ISLEAPMONTH";
    case ical_parameter_t::DISPLAY: return "DISPLAY";
    case ical_parameter_t::EMAIL: return "EMAIL";
    case ical_parameter_t::FEATURE: return "FEATURE";
    case ical_parameter_t::LABEL: return "LABEL";
    case ical_parameter_t::ORDER: return "ORDER";
    case ical_parameter_t::SCHEMA: return "SCHEMA";
    case ical_parameter_t::DERIVED: return "DERIVED";
    case ical_parameter_t::GAP: return "GAP";
    case ical_parameter_t::LINKREL: return "LINKREL";
    default: return "UNKOWN";
    }
}

ical_parameter_t StrToICalParameterType(const std::string & s)
{
    if (s == "ALTREP") return ical_parameter_t::ALTREP;
    if (s == "CN") return ical_parameter_t::CN;
    if (s == "CUTYPE") return ical_parameter_t::CUTYPE;
    if (s == "DELEGATED-FROM") return ical_parameter_t::DELEGATED_FROM;
    if (s == "DELEGATED-TO ") return ical_parameter_t::DELEGATED_TO ;
    if (s == "DIR") return ical_parameter_t::DIR;
    if (s == "ENCODING") return ical_parameter_t::ENCODING;
    if (s == "FMTTYPE") return ical_parameter_t::FMTTYPE;
    if (s == "FBTYPE") return ical_parameter_t::FBTYPE;
    if (s == "LANGUAGE") return ical_parameter_t::LANGUAGE;
    if (s == "MEMBER") return ical_parameter_t::MEMBER;
    if (s == "PARTSTAT") return ical_parameter_t::PARTSTAT;
    if (s == "RANGE") return ical_parameter_t::RANGE;
    if (s == "RELATED") return ical_parameter_t::RELATED;
    if (s == "RELTYPE") return ical_parameter_t::RELTYPE;
    if (s == "ROLE") return ical_parameter_t::ROLE;
    if (s == "RSVP") return ical_parameter_t::RSVP;
    if (s == "SENT-BY") return ical_parameter_t::SENT_BY;
    if (s == "TZID") return ical_parameter_t::TZID;
    if (s == "VALUE") return ical_parameter_t::VALUE;
    if (s == "X-FILENAME") return ical_parameter_t::X_FILENAME;
    if (s == "X-MS-OLK-RESPTIME") return ical_parameter_t::X_MS_OLK_RESPTIME;
    if (s == "X-MICROSOFT-ISLEAPMONTH") return ical_parameter_t::X_MICROSOFT_ISLEAPMONTH;
    if (s == "DISPLAY") return ical_parameter_t::DISPLAY;
    if (s == "EMAIL") return ical_parameter_t::EMAIL;
    if (s == "FEATURE") return ical_parameter_t::FEATURE;
    if (s == "LABEL") return ical_parameter_t::LABEL;
    if (s == "ORDER") return ical_parameter_t::ORDER;
    if (s == "SCHEMA") return ical_parameter_t::SCHEMA;
    if (s == "DERIVED") return ical_parameter_t::DERIVED;
    if (s == "GAP") return ical_parameter_t::GAP;
    if (s == "LINKREL") return ical_parameter_t::LINKREL;
    return ical_parameter_t::UNKOWN;
}

ical_parameter_t StrToICalParameterType(const char * paramName)
{
    std::string s(paramName);
    for (char & c : s) c = std::toupper(c);
    return StrToICalParameterType(s);
}


//---------------------------------------------------------------------------
// Template functions.
//---------------------------------------------------------------------------

template<typename T, typename E>
T * GetItemByType(std::list<T> & list, E type)
{
    T * result = nullptr;
    if (!list.empty()) {
        auto x = std::find_if(list.begin(), list.end(), [type](T & obj) { return obj.Type() == type; });
        if (x != list.end()) result = &*x;
    }
    return result;
}

template<typename T>
T * GetItemByName(std::list<T> & list, const char * name)
{
    T * result = nullptr;
    if (!list.empty() && name != nullptr && *name != '\0') {
        auto x = std::find_if(list.begin(), list.end(), [name](T & item) { return item.Name() == name; });
        if (x != list.end()) result = &*x;
    }
    return result;
}

template<typename T>
T * GetItemByIndex(std::list<T> & list, size_t idx)
{
    if (idx >= list.size()) return nullptr;
        
    auto x = list.begin();
    std::advance(x, idx);
    return &*x;
}

template<typename T>
T * GetNextItem(std::list<T> & list, const T * fromItem)
{
    T * result = nullptr;
    if (!list.empty()) {
        if (fromItem == nullptr) result = &list.front();
        else {
            for (auto x = list.begin(); x != list.end(); ++x) {
                if (&*x == fromItem) {
                    ++x;
                    if (x != list.end()) result = &*x;
                    break;
                }
            }
        }
    }
    return result;
}

template<typename T, typename E>
T * GetNextItem(std::list<T> & list, const T * fromItem, E forItemType)
{
    T * result;
    do {
        result = GetNextItem(list, fromItem);
        if (result != nullptr && result->Type() == forItemType) break;
    } while (result != nullptr);
        
    return result;
}

template<typename T, typename E>
size_t GetItemCountByTpe(std::list<T> & list, E itemType)
{
    size_t result = 0;
    for (auto & i : list) if (i.Type() == itemType) ++result;
    return result;

}

template<typename T>
void RemoveItem(std::list<T> & list, T * item)
{
    if (item != nullptr) {
        for (auto i = list.begin(); i != list.end(); ++i) {
            if (&*i == item) {
                list.erase(i);
                return;
            }
        }
    }
}


//---------------------------------------------------------------------------
// class declarations
//---------------------------------------------------------------------------

class ICalParameter : public IICalParameter {
private:
    ical_parameter_t type;
    std::string name;
    std::string value;
    
public:
    ICalParameter(const std::string & _name, const std::string & _value);
    ICalParameter(ical_parameter_t _type, const std::string & _value);
    
    virtual ~ICalParameter() {}

    virtual ical_parameter_t Type() override { return this->type; }
    virtual const char * TypeStr() override { return ICalParameterTypeToStr(this->type); }
    virtual const char * Name() override { return this->name.c_str(); }
    virtual const char * Value() override { return this->value.c_str(); }
    virtual void Value(const char * value) override { if (value != nullptr) this->value = value; }
    void Name(const std::string & name) { this->name = name; }
};


class ICalProperty : public IICalProperty {
private:
    ical_property_t type;
    std::string name;
    std::string value;
    std::list<ICalParameter> parameters;
    
public:
    ICalProperty(const std::string & _name, const std::string & _value);
    ICalProperty(ical_property_t _type, const std::string & _value);
    
    virtual ~ICalProperty() {}
    
    virtual ical_property_t Type() override { return this->type; }
    virtual const char * TypeStr() override { return ICalPropertyTypeToStr(this->type); }
    virtual const char * Name() override { return this->name.c_str(); }
    virtual const char * Value() override { return this->value.c_str(); }
    virtual void Value(const char * value) override { if (value != nullptr) this->value = value; }
    void Name(const std::string & name) { this->name = name; }
    
    virtual size_t ParameterCount() override { return this->parameters.size(); }
    virtual IICalParameter * Parameter(ical_parameter_t parameterType) override { return GetItemByType(this->parameters, parameterType); }
    virtual IICalParameter * Parameter(const char * name) override { return GetItemByName(this->parameters, name); }
    virtual IICalParameter * Parameter(size_t index) override { return GetItemByIndex(this->parameters, index); }
    virtual IICalParameter * NextParameter(const IICalParameter * fromParam) override { return GetNextItem(this->parameters, static_cast<const ICalParameter *>(fromParam)); }
    
    virtual IICalParameter * AddParameter(ical_parameter_t parameterType, const char * value) override;
    virtual IICalParameter * AddParameter(const char * parameterName, const char * value) override;
    virtual void RemoveParameter(IICalParameter * parameter) override { RemoveItem(this->parameters, static_cast<ICalParameter *>(parameter)); }
    virtual void RemoveParameter(const char * parameter) override;
    void Encode(std::ostringstream & buffer);
};


class ICalComponent : public IICalComponent {
private:
    ical_component_t type;
    std::list<ICalComponent> components;
    std::list<ICalProperty> properties;
    
public:
    ICalComponent(ical_component_t componentType) : type(componentType) {}
    virtual ~ICalComponent() {}
    
    void Clear();

    virtual ical_component_t Type() override { return this->type; }
    virtual const char * TypeStr() override { return ICalComponentTypeToStr(this->type); }
    const char * Name() { return this->TypeStr(); } // Needed for the GetItemByName template function.
    
    virtual size_t ComponentCount() override { return this->components.size(); }
    virtual size_t ComponentCount(ical_component_t type) override { return GetItemCountByTpe(this->components, type); }
    virtual IICalComponent * Component(ical_component_t componentType) override { return GetItemByType(this->components, componentType); }
    virtual ICalComponent * Component(size_t index) override { return GetItemByIndex(this->components, index); }
    virtual ICalComponent * Component(const char * name) override { return GetItemByName(this->components, name); }
    virtual ICalComponent * NextComponent(const IICalComponent * fromCompoment) override { return GetNextItem(this->components, static_cast<const ICalComponent *>(fromCompoment)); }
    virtual IICalComponent * NextComponent(const IICalComponent * fromCompoment, ical_component_t filter) override { return GetNextItem(this->components, static_cast<const ICalComponent *>(fromCompoment), filter); }

    virtual size_t PropertyCount() override { return this->properties.size(); }
    virtual size_t PropertyCount(ical_property_t type) override { return GetItemCountByTpe(this->properties, type); }    
    virtual IICalProperty * Property(ical_property_t propertyType) override { return GetItemByType(this->properties, propertyType); }
    virtual IICalProperty * Property(size_t index) override { return GetItemByIndex(this->properties, index); };
    virtual IICalProperty * Property(const char * name) override { return GetItemByName(this->properties, name); }
    virtual IICalProperty * NextProperty(const IICalProperty * fromProperty) override { return GetNextItem(this->properties, static_cast<const ICalProperty *>(fromProperty)); }
    virtual IICalProperty * NextProperty(const IICalProperty * fromProperty, ical_property_t type) override { return GetNextItem(this->properties, static_cast<const ICalProperty *>(fromProperty), type); }

    virtual IICalComponent * AddComponent(ical_component_t component) override;
    virtual void RemoveComponent(IICalComponent * component) override;
    virtual void RemoveComponent(ical_component_t component, bool removeFirstOnly = true) override;
    virtual IICalProperty * AddProperty(ical_property_t property, const char * value) override;
    virtual IICalProperty * AddProperty(const char * propertyName, const char * value) override;
    virtual void RemoveProperty(IICalProperty * property) override { RemoveItem(this->properties, static_cast<ICalProperty *>(property)); }
    virtual void RemoveProperty(const char * property, bool removeFirstOnly = true) override;
    virtual void RemoveProperty(ical_property_t type, bool removeFirstOnly = true) override;

    void Parse(std::string::iterator & pos, std::string::iterator & start, std::string::iterator & end);
    void Validate();
    void Encode(std::ostringstream & buffer);
};


class ICal : public IICal {
private:
    class IInstanceLog * log;
    ICalComponent root;
    std::vector<byte> buffer;
    
    
    bool DoParse(const std::string & str);
    
public:
    ICal(IInstanceLog * log) : log(log), root(ical_component_t::VCALENDAR) {}
    virtual ~ICal() {}
    
    virtual bool Parse(const byte * data, size_t dataSize) override;
    virtual bool Parse(const char * data) override;
    virtual void Init(const char * productIdentifier, ical_version_t version = ical_version_t::VERSION_2_0) override;
    virtual void Init(const char * vendorID, const char * productID, const char * lng = "EN", bool isRegisteredVendor = false, ical_version_t version = ical_version_t::VERSION_2_0) override;

    virtual bool Encode() override;
    virtual const byte * Buffer() override { return this->BufferSize() == 0 ? nullptr : this->buffer.data(); };
    virtual size_t BufferSize() override { return this->buffer.empty() ? 0 : this->buffer.size() - 1; } // there is a closing 0 added in case someone whant to use it as string.
    virtual IICalComponent * Root() override { return &this->root; }
};


IICal * IICal::Create(class IInstanceLog * log) { return new ICal(log); }


//---------------------------------------- Implementations ----------------------------------- //

//---------------------------------------------------------------------------
// class IICalParameter
//---------------------------------------------------------------------------

ICalParameter::ICalParameter(const std::string & _name, const std::string & _value)
    : value(_value)
{
    this->name.reserve(_name.size());
    for (const char & c : _name) {
        this->name.push_back(std::toupper(c));
    }
    this->type = StrToICalParameterType(this->name);
}
    
ICalParameter::ICalParameter(ical_parameter_t _type, const std::string & _value)
    : type(_type),
        name(ICalParameterTypeToStr(_type)),
        value(_value)
{
}


//---------------------------------------------------------------------------
// class ICalProperty
//---------------------------------------------------------------------------

ICalProperty::ICalProperty(const std::string & _name, const std::string & _value)
    : value(_value)
{
    std::list<std::string> tmp = strtools::Split(_name, ';');
    if (tmp.empty()) throw std::runtime_error("Empty name for property not allowed.");
        
    auto i = tmp.begin();
    this->name.reserve(i->size());
    for (const char & c : *i) {
        this->name.push_back(std::toupper(c));
    }
    this->type = StrToICalPropertyType(this->name);
        
    ++i;
    while (i != tmp.end()) {
        size_t p = i->find('=');
        if (p == std::string::npos) {
            std::ostringstream e;
            e << "Property " << this->name << " has a malformed parameter (" << *i << " - seperator '=' missing)";
            throw std::runtime_error(e.str());
        }

        std::string paramName = i->substr(0, p);
        std::string paramValue = i->substr(p + 1);

        if (paramName.empty()) {
            std::ostringstream e;
            e << "Property " << this->name << " has a malformed parameter (" << *i << " - no parameter name given)";
            throw std::runtime_error(e.str());
        }

        if (paramValue.empty()) {
            std::ostringstream e;
            e << "Property " << this->name << " has a malformed parameter (" << *i << " - no parameter value)";
            throw std::runtime_error(e.str());
        }

        if (this->Parameter(paramName.c_str()) != nullptr) {
            std::ostringstream e;
            e << "Property " << this->name << " parameter found multiple times (" << *i << " - no parameter value)";
            throw std::runtime_error(e.str());
        }

        this->parameters.emplace_back(paramName, paramValue);
        ++i;
    }
}
    
ICalProperty::ICalProperty(ical_property_t _type, const std::string & _value)
    : type(_type),
      name(ICalPropertyTypeToStr(_type)),
      value(_value)
{
}

IICalParameter * ICalProperty::AddParameter(ical_parameter_t parameterType, const char * value)
{
    std::string val = strtools::Trim(value);
    if (parameterType == ical_parameter_t::UNKOWN || val.empty()) return nullptr;
        
    IICalParameter * result = this->Parameter(parameterType);
    if (result == nullptr) {
        this->parameters.emplace_back(parameterType, value);
        this->parameters.back().Value(val.c_str());
        result = &this->parameters.back();
    }
    return result;
}
    
IICalParameter * ICalProperty::AddParameter(const char * parameterName, const char * value)
{
    std::string pn = strtools::Trim(parameterName);
    std::string val = strtools::Trim(value);
    if (pn.empty() || val.empty()) return nullptr;
        
    IICalParameter * result = this->Parameter(pn.c_str());
    if (result == nullptr) {
        this->parameters.emplace_back(pn, value);
        this->parameters.back().Value(val.c_str());
        result = &this->parameters.back();
    }
    return result;
}
    
void ICalProperty::RemoveParameter(const char * parameter)
{
    std::string pn = strtools::Trim(parameter);
    if (!pn.empty()) {
        auto i = std::find_if(this->parameters.begin(), this->parameters.end(), [pn](ICalParameter & p){ return p.Name() == pn; });
        if (i != this->parameters.end()) this->parameters.erase(i);
    }
}
    
void ICalProperty::Encode(std::ostringstream & buffer)
{
    if (*this->Value() == '\0') {
        std::ostringstream s;
        s << "Parameter " << this->Name() << " has no value.";
        throw std::runtime_error(s.str());
    }
        
    buffer << this->Name();
    for (auto & i : this->parameters) {
        if (*i.Value() == '\0') {
            std::ostringstream s;
            s << "Parameter " << this->Name() << " has property " << i.Name() << " with no value.";
            throw std::runtime_error(s.str());
        }
        buffer << ";" << i.Name() << "=" << i.Value();
    }
    buffer << ":" << this->Value() << CRLF;
}


//---------------------------------------------------------------------------
// class ICalComponent
//---------------------------------------------------------------------------

void ICalComponent::Clear()
{
    this->components.clear();
    this->properties.clear();
}
    
IICalComponent * ICalComponent::AddComponent(ical_component_t component)
{
    IICalComponent * result = nullptr;
    if (component != ical_component_t::UNKOWN && component != ical_component_t::VCALENDAR) {
        this->components.emplace_back(component);
        result = &this->components.back();
    }
    return result;
}
    
void ICalComponent::RemoveComponent(IICalComponent * component)
{ 
    if (component->Type() != ical_component_t::UNKOWN && component->Type() != ical_component_t::VCALENDAR) {
        RemoveItem(this->components, static_cast<ICalComponent *>(component));
    }
}
    
void ICalComponent::RemoveComponent(ical_component_t component, bool removeFirstOnly)
{
    if (component != ical_component_t::UNKOWN && component != ical_component_t::VCALENDAR) {
        if (removeFirstOnly) {
            IICalComponent * p = this->Component(component);
            this->RemoveComponent(p);
        }
        else {
            this->components.remove_if([component](ICalComponent & p) { return p.Type() == component; });
        }
    }
}

IICalProperty * ICalComponent::AddProperty(ical_property_t property, const char * value)
{ 
    this->properties.emplace_back(property, value);
    return &this->properties.back();
}

IICalProperty * ICalComponent::AddProperty(const char * propertyName, const char * value)
{
    if (propertyName == nullptr) return nullptr;
    this->properties.emplace_back(propertyName, value);
    return &this->properties.back();
}
    
void ICalComponent::RemoveProperty(const char * property, bool removeFirstOnly)
{
    std::string pn = strtools::Trim(property);
    if (!pn.empty()) {
        if (removeFirstOnly) {
            IICalProperty * p = this->Property(pn.c_str());
            this->RemoveProperty(p);
        }
        else {
            this->properties.remove_if([pn](ICalProperty & p) { return p.Name() == pn; });
        }
    }
}

void ICalComponent::RemoveProperty(ical_property_t type, bool removeFirstOnly)
{
    if (removeFirstOnly) {
        IICalProperty * p = this->Property(type);
        this->RemoveProperty(p);
    }
    else {
        this->properties.remove_if([type](ICalProperty & p) { return p.Type() == type; });
    }
}

void ICalComponent::Parse(std::string::iterator & pos, std::string::iterator & start, std::string::iterator & end)
{
    std::string compType = this->TypeStr();
        
    bool parsingKey = true;
    bool waitStringClose = false;
    std::string key, value;
        
    while (pos != end) {
        if (waitStringClose) {
            if (*pos == '"') waitStringClose = false;
        }
        else if (*pos == '"') waitStringClose = true;
        else if (parsingKey) {
            if (*pos == ':') {
                key.append(start, pos);
                start = pos + 1;
                parsingKey = false;
                value.clear();
            }
            else if (*pos == '\r') throw std::runtime_error("Found end of line while scanning for keyword before ':'.");
        }
        else {
            if (*pos == '\r') {
                value.append(start, pos);
                ++pos;
                if (pos == end) throw std::runtime_error("Unexpectede end of data found.");
                if (*pos != '\n') throw std::runtime_error("Missing correct end of line deleminter.");
                
                start = pos + 1;

                // iCal format allows to break lines if they're to long. In that case, the line starts with \t or a space.
                if (*start == '\t' || *start == ' ') {
                    ++start;
                    ++pos;
                }
                else {
                    if (key == "BEGIN") {
                        this->components.emplace_back(StrToICalComponentType(value));
                        this->components.back().Parse(pos, start, end);
                    }
                    else if (key == "END") {
                        if (value != compType) {
                            std::ostringstream s;
                            s << "Unexpected END: line found (expected END:" << compType << ", found END:" << value << ")";
                            throw std::runtime_error(s.str());
                        }
                        return;
                    }
                    else {
                        this->AddProperty(key.c_str(), value.c_str());
                    }

                    key.clear();
                    value.clear();
                    parsingKey = true;
                }
            }
        }
        ++pos;
    }
    
    if (waitStringClose) throw std::runtime_error("End of string not fouind.");
}
    
void ICalComponent::Validate()
{
    switch (this->type) {
    case ical_component_t::UNKOWN: throw std::runtime_error("Component of type unkown not allowed.");
    case ical_component_t::VCALENDAR:
        if (this->Property(ical_property_t::VERSION) == nullptr) throw std::runtime_error("Missing VERSION property for component type VCALENDAR.");
        if (this->Property(ical_property_t::PRODID) == nullptr) throw std::runtime_error("Missing PRODID property for component type VCALENDAR.");
        if (this->components.empty()) throw std::runtime_error("No components defined for VCALENDAR. Must be at least one (like VEVENT, VTODO, ....)");
        break;
            
    case ical_component_t::VEVENT:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VEVENT.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type VEVENT.");
        if (this->Property(ical_property_t::DTSTART) == nullptr) throw std::runtime_error("Missing DTSTART property for component type VEVENT.");
        for (auto & i : this->components) {
            if (i.Type() != ical_component_t::VALARM) {
                std::ostringstream s;
                s << "VEVENT has invalid child component " << i.TypeStr() << ". Allowed child component is VALARM only.";
                throw std::runtime_error(s.str());
            }
        }
        break;
            
    case ical_component_t::VTODO:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VTODO.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type VTODO.");
        for (auto & i : this->components) {
            if (i.Type() != ical_component_t::VALARM) {
                std::ostringstream s;
                s << "VTODO has invalid child component " << i.TypeStr() << ". Allowed child component is VALARM only.";
                throw std::runtime_error(s.str());
            }
        }
        break;
            
    case ical_component_t::VJOURNAL:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VJOURNAL.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type VJOURNAL.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for VJOURNAL.");
        break;
            
    case ical_component_t::VFREEBUSY:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VFREEBUSY.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type VFREEBUSY.");
        if (this->Property(ical_property_t::DTSTART) == nullptr) throw std::runtime_error("Missing DTSTART property for component type VFREEBUSY.");
        if (this->Property(ical_property_t::DTEND) == nullptr) throw std::runtime_error("Missing DTEND property for component type VFREEBUSY.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for VFREEBUSY.");
        break;
            
    case ical_component_t::VTIMEZONE:
        if (this->Property(ical_property_t::TZID) == nullptr) throw std::runtime_error("Missing TZID property for component type VTIMEZONE.");
        if (this->components.empty()) throw std::runtime_error("No child components defined for VTIMEZONE. Must be at least one STANDARD or DAYLIGHT.");
        for (auto & i : this->components) {
            if (i.Type() != ical_component_t::STANDARD && i.Type() != ical_component_t::DAYLIGHT) {
                std::ostringstream s;
                s << "VTIMEZONE has invalid child component " << i.TypeStr() << ". Allowed child component is STANDARD or DAYLIGH.";
                throw std::runtime_error(s.str());
            }
        }
        break;
            
    case ical_component_t::STANDARD:
        if (this->Property(ical_property_t::DTSTART) == nullptr) throw std::runtime_error("Missing DTSTART property for component type STANDARD.");
        if (this->Property(ical_property_t::TZOFFSETFROM) == nullptr) throw std::runtime_error("Missing TZOFFSETFROM property for component type STANDARD.");
        if (this->Property(ical_property_t::TZOFFSETTO) == nullptr) throw std::runtime_error("Missing TZOFFSETTO property for component type STANDARD.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for STANDARD.");
        break;
            
    case ical_component_t::DAYLIGHT:
        if (this->Property(ical_property_t::DTSTART) == nullptr) throw std::runtime_error("Missing DTSTART property for component type DAYLIGHT.");
        if (this->Property(ical_property_t::TZOFFSETFROM) == nullptr) throw std::runtime_error("Missing TZOFFSETFROM property for component type DAYLIGHT.");
        if (this->Property(ical_property_t::TZOFFSETTO) == nullptr) throw std::runtime_error("Missing TZOFFSETTO property for component type DAYLIGHT.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for DAYLIGHT.");
        break;
            
    case ical_component_t::VALARM:
        if (this->Property(ical_property_t::ACTION) == nullptr) throw std::runtime_error("Missing ACTION property for component type VALARM.");
        if (this->Property(ical_property_t::TRIGGER) == nullptr) throw std::runtime_error("Missing TRIGGER property for component type VALARM.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for VALARM.");
        break;
            
    case ical_component_t::VAVAILABILITY:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VAVAILABILITY.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type VAVAILABILITY.");
        if (this->components.empty()) throw std::runtime_error("No child components defined for VAVAILABILITY. Must be at least one AVAILABLE.");
        for (auto & i : this->components) {
            if (i.Type() != ical_component_t::AVAILABLE) {
                std::ostringstream s;
                s << "VAVAILABILITY has invalid child component " << i.TypeStr() << ". Allowed child component is AVAILABLE.";
                throw std::runtime_error(s.str());
            }
        }
        break;
            
    case ical_component_t::AVAILABLE:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type AVAILABLE.");
        if (this->Property(ical_property_t::DTSTAMP) == nullptr) throw std::runtime_error("Missing DTSTAMP property for component type AVAILABLE.");
        if (this->Property(ical_property_t::DTEND) == nullptr || this->Property(ical_property_t::DURATION) == nullptr) throw std::runtime_error("Missing DTEND or DURATION property for component type AVAILABLE.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for AVAILABLE.");
        break;
            
    case ical_component_t::PARTICIPANT:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type PARTICIPANT.");
        if (this->Property(ical_property_t::PARTICIPANT_TYPE) == nullptr) throw std::runtime_error("Missing PARTICIPANT-TYPE property for component type PARTICIPANT.");
        for (auto & i : this->components) {
            if (i.Type() != ical_component_t::VLOCATION && i.Type() != ical_component_t::VRESOURCE) {
                std::ostringstream s;
                s << "PARTICIPANT has invalid child component " << i.TypeStr() << ". Allowed child components are VLOCATION and VRESOURCE.";
                throw std::runtime_error(s.str());
            }
        }
        break;
            
    case ical_component_t::VLOCATION:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VLOCATION.");
        if (this->Property(ical_property_t::NAME) == nullptr) throw std::runtime_error("Missing NAME property for component type VLOCATION.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for VLOCATION.");
        break;
            
    case ical_component_t::VRESOURCE:
        if (this->Property(ical_property_t::UID) == nullptr) throw std::runtime_error("Missing UID property for component type VRESOURCE.");
        if (this->Property(ical_property_t::NAME) == nullptr) throw std::runtime_error("Missing NAME property for component type VRESOURCE.");
        if (!this->components.empty()) throw std::runtime_error("No child components allowed for VRESOURCE.");
        break;
    }
}
    
void ICalComponent::Encode(std::ostringstream & buffer)
{
    std::string compName = this->TypeStr();
    buffer << "BEGIN:" << compName << CRLF;
    for (auto & i : this->properties) i.Encode(buffer);
    for (auto & i : this->components) i.Encode(buffer);
    buffer << "END:" << compName << CRLF;
}


//---------------------------------------------------------------------------
// class ICal
//---------------------------------------------------------------------------

bool ICal::DoParse(const std::string & str)
{
    if (this->log != nullptr) this->log->Log(LOG_APP, "IICal::Parse(): Parsing ical data...");
    this->buffer.clear();
    this->root.Clear();
        
    bool result = true;
    try
    {
        size_t iCalStart = str.find("BEGIN:VCALENDAR");
        if (iCalStart == std::string::npos) throw std::runtime_error("BEGIN:VCALENDAR line not found.");
            
        iCalStart += sizeof("BEGIN:VCALENDAR") + 1; // sizeof includes the closing 0, so only add one byte more for the \r\n at the end of the line.    
    
        size_t iCalEnd = str.find("END:VCALENDAR", iCalStart + sizeof("BEGIN:VCALENDAR"));
        if (iCalEnd == std::string::npos) throw std::runtime_error("END:VCALENDAR line not found.");
    
        std::string iCalData = str.substr(iCalStart, iCalEnd - iCalStart);
        std::string::iterator start = iCalData.begin();
        std::string::iterator pos = start;
        std::string::iterator end = iCalData.end();
    
        this->root.Parse(pos, start, end);
        if (this->log != nullptr) this->log->Log(LOG_APP, "IICal::Parse(): Parsing complete.");
    }
    catch (std::runtime_error & err)
    {
        if (this->log != nullptr) this->log->Log(LOG_APP, "IICal::Parse(): Parsing failed: %s", err.what());
        result = false;
    }
    return result;
}
    
bool ICal::Parse(const byte * data, size_t dataSize)
{
    if (data == nullptr) {
        if (this->log != nullptr) this->log->Log(LOG_APP, "IICal::Parse(const byte *, size_t): unsupported value for data (data = nullptr).");
        return false;
    }
        
    return this->DoParse(std::string(reinterpret_cast<const char *>(data), dataSize));
}
    
bool ICal::Parse(const char * data)
{
    if (data == nullptr) {
        if (this->log != nullptr) this->log->Log(LOG_APP, "IICal::Parse(const char *): unsupported value for data (data = nullptr).");
        return false;
    }
        
    return this->DoParse(data);
}

void ICal::Init(const char * vendorID, const char * productID, const char * lng, bool isRegisteredVendor, ical_version_t version)
{
    std::string s = isRegisteredVendor ? "+" : "-";
    s += "//";
    s += vendorID;
    s += "//";
    s += productID;
    s += "//";
    s += lng;
    this->Init(s.c_str(), version);
}
    
void ICal::Init(const char * productIdentifier, ical_version_t version)
{
    if (productIdentifier == nullptr) productIdentifier = "";

    this->buffer.clear();
    this->root.Clear();
    this->root.AddProperty(ical_property_t::VERSION, (version == ical_version_t::VERSION_1_0 ? "1.0" : "2.0"));
    this->root.AddProperty(ical_property_t::PRODID, productIdentifier);
}
    
bool ICal::Encode()
{
    bool validating = true;
    try {
        this->log->Log(LOG_APP, "IICal::Encode(): Validating ical components...");
        this->root.Validate();
        validating = false;
        this->log->Log(LOG_APP, "IICal::Encode(): Validating ical components complete. Encoding ...");
        std::ostringstream tmpBuf;
        this->root.Encode(tmpBuf);

        std::string tmpStr(std::move(tmpBuf.str()));

        this->buffer.resize(tmpStr.size() + 1); // =1 to add a terminating 0
        this->buffer.assign(tmpStr.begin(), tmpStr.end());
        this->buffer.push_back(0);
        return true;
    }
    catch (std::runtime_error & err) {
        this->log->Log(LOG_APP, "IICal::Encode(): %s failed - %s", validating ? "Validating" : "Encoding", err.what());
        return false;
    }
}
