/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <stdlib.h>
#include <string.h>
#include <netinfo/ni.h>
#include "index.h"
#include "index_manager.h"
#include "listops.h"
#include <NetInfo/mm.h>
#include "strstore.h"

typedef struct im_holder {
	ni_index id;
	int refcount;
	ni_name propname;
	index_handle *index;
	ni_entrylist *entries;
} im_holder;

typedef struct im_private {
	ni_index nheld;
	im_holder *held;
} im_private;

static im_holder *findholder(im_handle *, ni_index, ni_name_const);

im_handle
im_alloc(
	 void
	 )
{
	im_private *res;
	im_handle handle;

	res = malloc(sizeof(*res));
	res->nheld = 0;
	res->held = NULL;
	handle.private = res;
	return (handle);
}

static void
im_holder_unalloc(
		  im_holder *held
		  )
{
	ss_unalloc(held->propname);
	if (held->index != NULL) {
		index_unalloc(held->index);
		free(held->index);
	}
	if (held->entries != NULL) {
		im_entrylist_unalloc(held->entries);
		free(held->entries);
	}
}

void
im_forget(
	  im_handle *handle
	  )
{
	ni_index i;
	im_private *priv = (im_private *)handle->private;

	for (i = 0; i < priv->nheld; i++) {
		if (priv->held[i].refcount == 0) {
			im_holder_unalloc(&priv->held[i]);
			priv->held[i] = priv->held[priv->nheld - 1];
			MM_SHRINK_ARRAY(priv->held, priv->nheld);
			priv->nheld--;
			i--;
		} else {
			priv->held[i].refcount = 0;
		}
	}
}

void
im_free(
	im_handle *handle
	)
{
	im_private *priv = (im_private *)handle->private;
	ni_index i;

	for (i = 0; i < priv->nheld; i++) {
		im_holder_unalloc(&priv->held[i]);
	}
	free(priv->held);
	free(priv);
}

/*
** GRS - 12/18/92 - Cleaved im_create_all away from _im_create_all.
** im_create_all used to just call _im_create_all with shouldgrow = 0
** and where = NI_INDEX_NULL.  The problem with this was that when a 
** new directory was added, it didn't go into all the ni_list caches for 
** its parent, only the ones for which it had a property.  Later ni_lists
** could fail to see the newly-added directory.  
**
** Now I've put the old _im_create_all behavior into im_create_all, which
** is only called by ni_write.  In _im_create_all, called only by im_newnode,
** I've added a loop to iterate through all the cached entries so that 
** something is added for each property, even if the new directory doesn't
** have a matching property.
*/

void
im_create_all(
    im_handle *handle,
    ni_object *obj,
    ni_proplist props
    )
{
    im_holder *held;
    ni_index i;

    for (i = 0; i < props.nipl_len; i++) {
	held = findholder(handle, obj->nio_parent, 
	    props.nipl_val[i].nip_name);
	if (held == NULL) {
	    continue;
	}
	if (held->index != NULL) {
	    index_insert_list(held->index, 
	    props.nipl_val[i].nip_val,
	    obj->nio_id.nii_object);
	}
	if (held->entries != NULL) {
	    list_insert(held->entries,
	    props.nipl_val[i].nip_val,
	    obj->nio_id.nii_object, 0,
	    NI_INDEX_NULL);
		
	}
    }
}


static void
_im_create_all(
    im_handle *handle,
    ni_object *obj,
    ni_proplist props,
    int shouldgrow,
    ni_index where
    )
{
    im_holder *held;
    ni_index i, p;
    im_private *priv = (im_private *)handle->private;
    ni_name prop_name;
    int found;
    ni_namelist nl = {0, NULL};

    for (p = 0; p < priv->nheld; p++) {
	if (!(held = &priv->held[p]) || !held->propname) {
	    continue;
	}
	if (held->id == obj->nio_parent) {
	    found = 0;
	    for (i = 0; i < props.nipl_len; i++) {
		prop_name = props.nipl_val[i].nip_name;
		if (ni_name_match(held->propname, prop_name)) {
		    held->refcount++;
		    if (held->index != NULL) {
			index_insert_list(held->index, 
			    props.nipl_val[i].nip_val,
			    obj->nio_id.nii_object);
			}
		    if (held->entries != NULL) {
			list_insert(held->entries,
			    props.nipl_val[i].nip_val,
			    obj->nio_id.nii_object, shouldgrow,
			    where);
			
		    }
		    found = 1;
		    break;
		}
	    }
	    if (!found && (held->entries != NULL)) {
		list_insert(held->entries, nl, obj->nio_id.nii_object, 
		    shouldgrow, where);
	    }
	}
    }
}

void
im_newnode(
	   im_handle *handle,
	   ni_object *obj,
	   ni_index where
	   )
{
	return (_im_create_all(handle, obj, obj->nio_props, 1, where));
}

static void
_im_destroy_all(
		im_handle *handle,
		ni_object *obj,
		ni_proplist props,
		int shouldshrink
		)
{
	im_holder *held;
	ni_index i;
	ni_index j;

	for (j = 0; j < props.nipl_len; j++) {
		held = findholder(handle, obj->nio_parent, 
				 props.nipl_val[j].nip_name);
		if (held == NULL) {
			continue;
		}
		for (i = 0; i < props.nipl_val[j].nip_val.ninl_len; i++) {
			if (held->index != NULL) {
				index_delete(held->index, 
					     props.nipl_val[j].nip_val.ninl_val[i], 
					     obj->nio_id.nii_object);
			}
		}
		if (held->entries != NULL) {
			list_delete(held->entries,
				    obj->nio_id.nii_object, shouldshrink);
		}
	}
}

void
im_destroy_all(
		im_handle *handle,
		ni_object *obj,
		ni_proplist props
		)
{
	return (_im_destroy_all(handle, obj, props, 0));
}

void
im_remove(
	  im_handle *handle,
	  ni_object *obj
	  )
{
	im_private *priv = (im_private *)handle->private;
	ni_index i;
	im_holder *save;

	for (i = 0; i < priv->nheld; i++) {
		if (priv->held[i].id != obj->nio_id.nii_object) {
			continue;
		}

		if (priv->held[i].index != NULL) {
			index_unalloc(priv->held[i].index);
			free(priv->held[i].index);
		}
		if (priv->held[i].entries != NULL) {
			im_entrylist_unalloc(priv->held[i].entries);
			free(priv->held[i].entries);
		}
		ss_unalloc(priv->held[i].propname);
		
		save = &priv->held[priv->nheld - 1];
		priv->held[i] = *save;

		MM_SHRINK_ARRAY(priv->held, priv->nheld);
		priv->nheld--;
	}
	_im_destroy_all(handle, obj, obj->nio_props, 1);
}


int
im_has_indexed_dir(
		   im_handle *handle,
		   ni_index which,
		   ni_name_const key,
		   ni_name_const val,
		   ni_index **dirs,
		   ni_index *ndirs
		   )
{
	im_holder *held;

	held = findholder(handle, which, key);
	if (held == NULL || held->index == NULL) {
		return (0);
	}
	*ndirs = index_lookup(*held->index, val, dirs);
	return (1);
}


void
im_store_index(
	       im_handle *handle,
	       index_handle index,
	       ni_index which,
	       ni_name_const val
	       )
{
	im_private *priv = (im_private *)handle->private;
	im_holder *held;

	held = findholder(handle, which, val);
	if (held == NULL) {
		MM_GROW_ARRAY(priv->held, priv->nheld);
		held = &priv->held[priv->nheld];
		priv->nheld++;
		held->id = which;
		held->propname = (char *)ss_alloc(val);
		held->entries = NULL;
		held->refcount = 0;
	}
	held->index = malloc(sizeof(index));
	*held->index = index;
}


void
im_destroy_list(
		im_handle *handle,
		ni_object *obj,
		ni_name_const key,
		ni_namelist vals
		)
{
	im_holder *held;
	ni_index i;

	held = findholder(handle, obj->nio_parent, key);
	if (held == NULL) {
		return;
	}
	for (i = 0; i < vals.ninl_len; i++) {
		if (held->index != NULL) {
			index_delete(held->index, vals.ninl_val[i], obj->nio_id.nii_object);
		}
	}
	if (held->entries != NULL) {
		list_delete(held->entries, 
			    obj->nio_id.nii_object, 0);
	}
}


void
im_create_list(
	       im_handle *handle,
	       ni_object *obj,
	       ni_name_const key,
	       ni_namelist vals
	       )
{
	im_holder *held;

	held = findholder(handle, obj->nio_parent, key);
	if (held == NULL) {
		return;
	}
	if (held->index != NULL) {
		index_insert_list(held->index, vals, obj->nio_id.nii_object);
	}
	if (held->entries != NULL) {
		list_insert(held->entries, vals, obj->nio_id.nii_object, 0,
			    NI_INDEX_NULL);
	}
}


void
im_destroy(
	   im_handle *handle,
	   ni_object *obj,
	   ni_name_const key,
	   ni_name_const val,
	   ni_index where
	   )
{
	im_holder *held;

	held = findholder(handle, obj->nio_parent, key);
	if (held == NULL) {
		return;
	}
	if (held->index != NULL) {
		index_delete(held->index, val, obj->nio_id.nii_object);
	}
	if (held->entries != NULL) {
		list_delete_one(held->entries, obj->nio_id.nii_object, where);
	}
}


void
im_create(
	  im_handle *handle,
	  ni_object *obj,
	  ni_name_const key,
	  ni_name_const val,
	  ni_index where
	  )
{
	im_holder *held;

	held = findholder(handle, obj->nio_parent, key);
	if (held == NULL) {
		return;
	}
	if (held->index != NULL) {
		index_insert(held->index, val, obj->nio_id.nii_object);
	}
	if (held->entries != NULL) {
		list_insert_one(held->entries, val, obj->nio_id.nii_object, where);
	}
}

static im_holder *
findholder(
	  im_handle *handle, 
	  ni_index which, 
	  ni_name_const key
	  )
{
	im_private *priv = (im_private *)handle->private;
	ni_index i;

	for (i = 0; i < priv->nheld; i++) {
		if (priv->held[i].id == which &&
		    ni_name_match(priv->held[i].propname, key)) {
			priv->held[i].refcount++;
			return (&priv->held[i]);
		}
	}
	return (NULL);
}


#ifdef notdef
static ni_entrylist
ni_entrylist_dup(
		 ni_entrylist entries
		 )
{
	ni_index i;
	ni_entry *entry;
	ni_entrylist ret;
	
	ret.niel_len = entries.niel_len;
	ret.niel_val = malloc(entries.niel_len * sizeof(entries.niel_val[0]));
	for (i = 0; i < entries.niel_len; i++) {
		entry = &entries.niel_val[i];
		ret.niel_val[i].id = entry->id;
		if (entry->names != NULL) {
			ret.niel_val[i].names = malloc(sizeof(ni_namelist));
			(*ret.niel_val[i].names = 
			 ni_namelist_dup(*entry->names));
		} else {
			ret.niel_val[i].names = NULL;
		}
	}
	return (ret);

}
#endif

int 
im_has_saved_list(
		  im_handle *handle, 
		  ni_index id, 
		  ni_name_const pname, 
		  ni_entrylist *entries
		  )
{
	im_holder *held;

	held = findholder(handle, id, pname);
	if (held == NULL || held->entries == NULL) {
		return (0);
	}
	*entries = *held->entries;
	return (1);
}

void 
im_store_list(
	      im_handle *handle, 
	      ni_index id, 
	      ni_name_const pname, 
	      ni_entrylist entries
	      )
{
	im_private *priv = (im_private *)handle->private;
	im_holder *held;

	held = findholder(handle, id, pname);
	if (held == NULL) {
		MM_GROW_ARRAY(priv->held, priv->nheld);
		held = &priv->held[priv->nheld];
		priv->nheld++;
		held->id = id;
		held->propname = (char *)ss_alloc(pname);
		held->index = NULL;
		held->refcount = 0;
	}
	held->entries = malloc(sizeof(ni_entrylist));
	*held->entries = im_entrylist_dup(entries);
}
