/* -*- Mode: C -*- */
/*======================================================================
  FILE: icalcomponent.c
  CREATOR: eric 28 April 1999
  
  $Id$


  (C) COPYRIGHT 1999 Eric Busboom 
  http://www.softwarestudio.org

  The contents of this file are subject to the Mozilla Public License
  Version 1.0 (the "License"); you may not use this file except in
  compliance with the License. You may obtain a copy of the License at
  http://www.mozilla.org/MPL/
 
  Software distributed under the License is distributed on an "AS IS"
  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  the License for the specific language governing rights and
  limitations under the License.

  The original author is Eric Busboom
  The original code is icalcomponent.c

======================================================================*/

#include "ical.h"
#include "pvl.h" /* "Pointer-to-void list" */
#include <stdlib.h>  /* for malloc */
#include <stdarg.h> /* for va_list, etc */
#include <errno.h>
#include "icalerror.h"
#include <assert.h>
#include <stdio.h> /* for fprintf */
#include "icalmemory.h"
#include "icalenums.h"


/* icalproperty functions that only components get to use */
void icalproperty_set_parent(icalproperty* property,
			     icalcomponent* component);

icalcomponent* icalproperty_get_parent(icalproperty* property);



struct icalcomponent_impl 
{
	char id[5];
	icalcomponent_kind kind;
	char* x_name;
	pvl_list properties;
	pvl_elem property_iterator;
	pvl_list components;
	pvl_elem component_iterator;
	icalcomponent* parent;
};

void icalcomponent_add_children(struct icalcomponent_impl *impl,va_list args)
{
    void* vp;
    
    while((vp = va_arg(args, void*)) != 0) {

	assert (icalcomponent_isa_component(vp) != 0 ||
		icalproperty_isa_property(vp) != 0 ) ;

	if (icalcomponent_isa_component(vp) != 0 ){

	    icalcomponent_add_component((icalcomponent*)impl,
				       (icalcomponent*)vp);

	} else if (icalproperty_isa_property(vp) != 0 ){

	    icalcomponent_add_property((icalcomponent*)impl,
				       (icalproperty*)vp);
	}
    }    
}

icalcomponent*
icalcomponent_new_impl (icalcomponent_kind kind)
{
    struct icalcomponent_impl* comp;

    if ( ( comp = (struct icalcomponent_impl*)
	   malloc(sizeof(struct icalcomponent_impl))) == 0) {
	errno = ENOMEM;
	icalerror_set_errno(ICAL_NEWFAILED_ERROR);
	return 0;
    }
    
    strcpy(comp->id,"comp");

    comp->kind = kind;
    comp->properties = pvl_newlist();
    comp->property_iterator = 0;
    comp->components = pvl_newlist();
    comp->component_iterator = 0;
    comp->x_name = 0;
    comp->parent = 0;

    return comp;
}

icalcomponent*
icalcomponent_new (icalcomponent_kind kind)
{
   return (icalcomponent*)icalcomponent_new_impl(kind);
}

icalcomponent*
icalcomponent_vanew (icalcomponent_kind kind, ...)
{
   va_list args;

   struct icalcomponent_impl *impl = icalcomponent_new_impl(kind);

    if (impl == 0){
	return 0;
    }

   va_start(args,kind);
   icalcomponent_add_children(impl, args);
   va_end(args);

   return (icalcomponent*) impl;
}

icalcomponent* icalcomponent_new_from_string(char* str)
{
    icalcomponent_kind kind;

    icalerror_check_arg_rz( (str!=0), "str");

    kind = icalenum_string_to_component_kind(str);

    if (kind == ICAL_NO_COMPONENT){
	return 0;
    }
    
    return icalcomponent_new(kind);
}

icalcomponent* icalcomponent_new_clone(icalcomponent* component)
{
    struct icalcomponent_impl *old = (struct icalcomponent_impl*)component;
    struct icalcomponent_impl *new;
    icalproperty *p;
    icalcomponent *c;

    icalerror_check_arg_rv( (component!=0), "component");

    new = icalcomponent_new_impl(old->kind);

    if (new == 0){
	return 0;
    }

    
   for(p = icalcomponent_get_first_property(old,ICAL_ANY_PROPERTY);
       p != 0;
       p = icalcomponent_get_next_property(old,ICAL_ANY_PROPERTY)){

       icalcomponent_add_property(new,icalproperty_new_clone(p));
   }
   
   
   for(c = icalcomponent_get_first_component(old,ICAL_ANY_COMPONENT);
       c != 0;
       c = icalcomponent_get_next_component(old,ICAL_ANY_COMPONENT)){
       
       icalcomponent_add_component(new,icalcomponent_new_clone(c));
   }

   return new;

}


void
icalcomponent_free (icalcomponent* component)
{
    icalproperty* prop;
    icalcomponent* comp;
    struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

    icalerror_check_arg_rv( (component!=0), "component");

#ifdef ICAL_FREE_ON_LIST_IS_ERROR
    icalerror_assert( (c->parent ==0),"Tried to free a component that is still attached to a parent component");
#else
    if(c->parent != 0){
	return;
    }
#endif

    if(component != 0 ){
       
       while( (prop=pvl_pop(c->properties)) != 0){
	   assert(prop != 0);
           icalproperty_set_parent(prop,0);
	   icalproperty_free(prop);
       }
       
       pvl_free(c->properties);

       while( (comp=pvl_data(pvl_head(c->components))) != 0){
	   assert(comp!=0);
	   icalcomponent_remove_component(component,comp);
	   icalcomponent_free(comp);
       }
       
       pvl_free(c->components);

	if (c->x_name != 0) {
	    free(c->x_name);
	}

	c->kind = ICAL_NO_COMPONENT;
	c->properties = 0;
	c->property_iterator = 0;
	c->components = 0;
	c->component_iterator = 0;
	c->x_name = 0;	
	c->id[0] = 'X';

	free(c);
    }
}

char*
icalcomponent_as_ical_string (icalcomponent* component)
{
   char* buf, *out_buf;
   char* tmp_buf;
   size_t buf_size = 1024;
   char* buf_ptr = 0;
   
   icalcomponent *c;
   icalproperty *p;
   icalcomponent_kind kind = icalcomponent_isa(component);

   char* kind_string;

   buf = icalmemory_new_buffer(buf_size);
   buf_ptr = buf; 

   icalerror_check_arg_rz( (component!=0), "component");
   icalerror_check_arg_rz( (kind!=ICAL_NO_COMPONENT), "component kind is ICAL_NO_COMPONENT");
   
   kind_string  = icalenum_component_kind_to_string(kind);

   icalerror_check_arg_rz( (kind_string!=0),"Unknown kind of component");

   icalmemory_append_string(&buf, &buf_ptr, &buf_size, "BEGIN:");
   icalmemory_append_string(&buf, &buf_ptr, &buf_size, kind_string);
   icalmemory_append_string(&buf, &buf_ptr, &buf_size, "\n");
   

   for(p = icalcomponent_get_first_property(component,ICAL_ANY_PROPERTY);
       p != 0;
       p = icalcomponent_get_next_property(component,ICAL_ANY_PROPERTY)){

       tmp_buf = icalproperty_as_ical_string(p);

       icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
   }
   
   
   for(c = icalcomponent_get_first_component(component,ICAL_ANY_COMPONENT);
       c != 0;
       c = icalcomponent_get_next_component(component,ICAL_ANY_COMPONENT)){
       
       tmp_buf = icalcomponent_as_ical_string(c);

       icalmemory_append_string(&buf, &buf_ptr, &buf_size, tmp_buf);
       
   }
   
   icalmemory_append_string(&buf, &buf_ptr, &buf_size, "END:");
   icalmemory_append_string(&buf, &buf_ptr, &buf_size, 
			    icalenum_component_kind_to_string(kind));
   icalmemory_append_string(&buf, &buf_ptr, &buf_size, "\n");

   out_buf = icalmemory_tmp_copy(buf);
   free(buf);

   return out_buf;
}


int
icalcomponent_is_valid (icalcomponent* component)
{
    struct icalcomponent_impl *impl = (struct icalcomponent_impl *)component;

	
    if ( (strcmp(impl->id,"comp") == 0) &&
	 impl->kind != ICAL_NO_COMPONENT){
	return 1;
    } else {
	return 0;
    }

}


icalcomponent_kind
icalcomponent_isa (icalcomponent* component)
{
    struct icalcomponent_impl *impl = (struct icalcomponent_impl *)component;
    icalerror_check_arg_rz( (component!=0), "component");

   if(component != 0)
   {
       return impl->kind;
   }

   return ICAL_NO_COMPONENT;
}


int
icalcomponent_isa_component (void* component)
{
    struct icalcomponent_impl *impl = (struct icalcomponent_impl *)component;

    icalerror_check_arg_rz( (component!=0), "component");

    if (strcmp(impl->id,"comp") == 0) {
	return 1;
    } else {
	return 0;
    }

}

int icalcomponent_property_sorter(void *a, void *b)
{
    icalproperty_kind kinda, kindb;
    char *ksa, *ksb;

    kinda = icalproperty_isa((icalproperty*)a);
    kindb = icalproperty_isa((icalproperty*)b);

    ksa = icalenum_property_kind_to_string(kinda);
    ksb = icalenum_property_kind_to_string(kindb);

    return strcmp(ksa,ksb);
}


void
icalcomponent_add_property (icalcomponent* component, icalproperty* property)
{
    struct icalcomponent_impl *impl;

    icalerror_check_arg_rv( (component!=0), "component");
    icalerror_check_arg_rv( (property!=0), "property");

     impl = (struct icalcomponent_impl*)component;

    icalerror_assert( (!icalproperty_get_parent(property)),"The property has already been added to a component. Remove the property with icalcomponent_remove_property before calling icalcomponent_add_property");

    icalproperty_set_parent(property,component);

#ifdef ICAL_INSERT_ORDERED
    pvl_insert_ordered(impl->properties,
		       icalcomponent_property_sorter,property);
#else
    pvl_push(impl->properties,property);
#endif

}


void
icalcomponent_remove_property (icalcomponent* component, icalproperty* property)
{
    struct icalcomponent_impl *impl;
    pvl_elem itr, next_itr;
    struct icalproperty_impl *pimpl;

    icalerror_check_arg_rv( (component!=0), "component");
    icalerror_check_arg_rv( (property!=0), "property");
    
    impl = (struct icalcomponent_impl*)component;

    pimpl = (struct icalproperty_impl*)property;

    icalerror_assert( (icalproperty_get_parent(property)),"The property is not a member of a component");

    
    for( itr = pvl_head(impl->properties);
	 itr != 0;
	 itr = next_itr)
    {
	next_itr = pvl_next(itr);
	
	if( pvl_data(itr) == (void*)property ){

	   if (impl->property_iterator == itr){
	       impl->property_iterator = pvl_next(itr);
	   }

	   pvl_remove( impl->properties, itr); 
	  icalproperty_set_parent(property,0);
	}
    }	
}

int
icalcomponent_count_properties (icalcomponent* component, icalproperty_kind kind)
{
    int count=0;
    icalproperty *p;
    icalerror_check_arg_rz( (component!=0), "component");

    for(p = icalcomponent_get_first_property(component,kind);
	p != 0;
	p = icalcomponent_get_next_property(component,kind)){
	
	count++;
    }


    return count;

}

icalproperty* icalcomponent_get_current_property (icalcomponent* component)
{

   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;
   icalerror_check_arg_rz( (component!=0),"component");

   if ((c->property_iterator==0)){
       return 0;
   }

   return (icalproperty*) pvl_data(c->property_iterator);

}

icalproperty*
icalcomponent_get_first_property (icalcomponent* component, icalproperty_kind kind)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;
   icalerror_check_arg_rz( (component!=0),"component");
  
   for( c->property_iterator = pvl_head(c->properties);
	c->property_iterator != 0;
	c->property_iterator = pvl_next(c->property_iterator)) {
	    
       icalproperty *p =  (icalproperty*) pvl_data(c->property_iterator);
	
	   if (icalproperty_isa(p) == kind || kind == ICAL_ANY_PROPERTY) {
	       
	       return p;
	   }
   }
   return 0;
}

icalproperty*
icalcomponent_get_next_property (icalcomponent* component, icalproperty_kind kind)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;
   icalerror_check_arg_rz( (component!=0),"component");

   if (c->property_iterator == 0){
       return 0;
   }

   for( c->property_iterator = pvl_next(c->property_iterator);
	c->property_iterator != 0;
	c->property_iterator = pvl_next(c->property_iterator)) {
	    
       icalproperty *p =  (icalproperty*) pvl_data(c->property_iterator);
	   
       if (icalproperty_isa(p) == kind || kind == ICAL_ANY_PROPERTY) {
	   
	   return p;
       }
   }

   return 0;
}


icalproperty**
icalcomponent_get_properties (icalcomponent* component, icalproperty_kind kind);


void
icalcomponent_add_component (icalcomponent* parent, icalcomponent* child)
{
    struct icalcomponent_impl *impl, *cimpl;

    icalerror_check_arg_rv( (parent!=0), "parent");
    icalerror_check_arg_rv( (child!=0), "child");
    
    impl = (struct icalcomponent_impl*)parent;
    cimpl = (struct icalcomponent_impl*)child;

    icalerror_assert( (cimpl->parent ==0),"The child component has already been added to a parent component. Remove the component with icalcomponent_remove_componenet before calling icalcomponent_add_component");

    cimpl->parent = parent;

    pvl_push(impl->components,child);
}


void
icalcomponent_remove_component (icalcomponent* parent, icalcomponent* child)
{
   struct icalcomponent_impl *impl,*cimpl;
   pvl_elem itr, next_itr;

   icalerror_check_arg_rv( (parent!=0), "parent");
   icalerror_check_arg_rv( (child!=0), "child");
   
   impl = (struct icalcomponent_impl*)parent;
   cimpl = (struct icalcomponent_impl*)child;
   
   for( itr = pvl_head(impl->components);
	itr != 0;
	itr = next_itr)
   {
       next_itr = pvl_next(itr);
       
       if( pvl_data(itr) == (void*)child ){

	   if (impl->component_iterator == itr){
	       /*   impl->component_iterator = pvl_next(itr);*/
	   }
	   pvl_remove( impl->components, itr); 
	   cimpl->parent = 0;
       }
   }	
}


int
icalcomponent_count_components (icalcomponent* component, icalcomponent_kind kind)
{
    int count=0;
    icalcomponent *c;
    icalerror_check_arg_rz( (component!=0), "component");

    for(c = icalcomponent_get_first_component(component,kind);
	c != 0;
	c = icalcomponent_get_next_component(component,kind)){
	
	count++;
    }


    return count;
}

icalcomponent*
icalcomponent_get_current_component(icalcomponent* component)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

   icalerror_check_arg_rz( (component!=0),"component");

   if (c->component_iterator == 0){
       return 0;
   }

   return (icalcomponent*) pvl_data(c->component_iterator);
}

icalcomponent*
icalcomponent_get_first_component (icalcomponent* component, 
				   icalcomponent_kind kind)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

   icalerror_check_arg_rz( (component!=0),"component");
  
   for( c->component_iterator = pvl_head(c->components);
	c->component_iterator != 0;
	c->component_iterator = pvl_next(c->component_iterator)) {
	    
       icalcomponent *p =  (icalcomponent*) pvl_data(c->component_iterator);
	
	   if (icalcomponent_isa(p) == kind || kind == ICAL_ANY_COMPONENT) {
	       
	       return p;
	   }
   }

   return 0;
}


icalcomponent*
icalcomponent_get_next_component (icalcomponent* component, icalcomponent_kind kind)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

   icalerror_check_arg_rz( (component!=0),"component");
  
   if (c->component_iterator == 0){
       return 0;
   }

   for( c->component_iterator = pvl_next(c->component_iterator);
	c->component_iterator != 0;
	c->component_iterator = pvl_next(c->component_iterator)) {
	    
       icalcomponent *p =  (icalcomponent*) pvl_data(c->component_iterator);
	
	   if (icalcomponent_isa(p) == kind || kind == ICAL_ANY_COMPONENT) {
	       
	       return p;
	   }
   }

   return 0;
}


icalproperty**
icalcomponent_get_component (icalcomponent* component, icalproperty_kind kind);


int icalcomponent_count_errors(icalcomponent* component)
{
    int errors = 0;
    icalproperty *p;
    icalcomponent *c;

    for(p = icalcomponent_get_first_property(component,ICAL_ANY_PROPERTY);
	p != 0;
	p = icalcomponent_get_next_property(component,ICAL_ANY_PROPERTY)){
	
	if(icalproperty_isa(p) == ICAL_XLICERROR_PROPERTY)
	{
	    errors++;
	}
    }

    for(c = icalcomponent_get_first_component(component,ICAL_ANY_COMPONENT);
	c != 0;
	c = icalcomponent_get_next_component(component,ICAL_ANY_COMPONENT)){
	
	errors += icalcomponent_count_errors(c);
	
    }

    return errors;
}


void icalcomponent_strip_errors(icalcomponent* component)
{
    icalproperty *p, *next_p;
    icalcomponent *c;

    for(p = icalcomponent_get_first_property(component,ICAL_ANY_PROPERTY);
	p != 0;
	p = next_p){
	
	next_p = icalcomponent_get_next_property(component,ICAL_ANY_PROPERTY);

	if(icalproperty_isa(p) == ICAL_XLICERROR_PROPERTY)
	{
	    icalcomponent_remove_property(component,p);
	}
    }

    for(c = icalcomponent_get_first_component(component,ICAL_ANY_COMPONENT);
	c != 0;
	c = icalcomponent_get_next_component(component,ICAL_ANY_COMPONENT)){
	
	icalcomponent_strip_errors(c);
    }

}

icalcomponent* icalcomponent_get_parent(icalcomponent* component)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

   return c->parent;
}

void icalcomponent_set_parent(icalcomponent* component, icalcomponent* parent)
{
   struct icalcomponent_impl *c = (struct icalcomponent_impl*)component;

   c->parent = parent;
}


