/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>

Change *
change_create(const char *parent, CommitInfo *ci)
{
  Change *chg = (Change *) GC_MALLOC(sizeof(Change));

  ser_init(chg, &Change_SerType, Change_SerType.ver);
  SER_MODIFIED(chg);

  CMSET(chg, commitInfoTrueName, ci ? ser_getTrueName(ci) : NULL);
  /* ZZZ  chg->entNames = tnvec_create(); */
  CMSET(chg, entities, obvec_create());
  CMSET(chg, parent, parent);
  CMSET(chg, mergeParent, 0);	/* unknown! */
  CMSET(chg, isPartialMerge, FALSE);	/* until proven otherwise */
  CMSET(chg, mergeHist, strvec_create());
  CMSET(chg,fileACLs,0);

  CMSET(chg, filterSetName, 0);

  CM_CANFREEZE(chg);

  return chg;
}

#if 0
void
change_append_entity(Change *chg, const char* s)
{
  SER_MODIFIED(chg);
  tnvec_append(chg->entNames, s);
}
#endif

void
change_append_entity(Change *chg, const Entity *e)
{
  assert(e);
  obvec_append(CMCLOBBER(chg, entities), e);
}

static int
fsname_sort(const void *v1, const void *v2)
{
  const Entity **e1 = (const Entity **) v1;
  const Entity **e2 = (const Entity **) v2;
  return strcmp((*e1)->fsName, (*e2)->fsName);
}

/* Ensure that the fsNames for all Entities in this Change are unique */
OC_bool
change_check(const void *ob)
{
  int k;
  const Change *chg = ob;
  OC_bool status = TRUE;

  if (vec_size(CMGET(chg,entities)) <= 1)
    return TRUE;

  {
    ObVec *dupEntities = obvec_shallow_copy(CMGET(chg, entities));

    /* Sort by entity's fsname, so binary search below will work */
    vec_sort_using(dupEntities, fsname_sort);

    /* Go through the list of Entity objects and look for duplicate
       fsnames.  Since the list is sorted, duplicates will be adjacent. */
    for (k = 0; k < vec_size(dupEntities)-1; k++) {
      Entity *ent1 = vec_fetch(dupEntities, k, Entity *);
      Entity *ent2 = vec_fetch(dupEntities, k+1, Entity *);

      if (strcmp(ent1->fsName, ent2->fsName) == 0) {
	report(0, "Duplicate Entity in Change:\n\t fsName => \"%s\"\n", 
	       ent1->fsName);
	status = FALSE;
      }
    }
  }

  return status;
}

void
change_show(const void *vp)
{
  int i;
  const Change *chg = vp;
  
  report(0, "Change:       \"%s\"\n", ser_getTrueName(chg));
  report(0, "File ACLs:    \"%s\"\n", CMGET(chg,fileACLs));
  report(0, "CommitInfo:   \"%s\"\n", CMGET(chg,commitInfoTrueName));
  report(0, "Parent:       \"%s\"\n", CMGET(chg,parent));
  report(0, "Merge Parent%s: \"%s\"\n",
	  CMGET(chg, isPartialMerge) ? "(partial)" : "",
	  CMGET(chg, mergeParent));
  report(0, "Filterset:    \"%s\"\n", CMGET(chg, filterSetName));

  /* FIX: mergeHist */
#if 0
  tnvec_sort(chg->entNames);

  report(0, "Entities:\n", ser_getTrueName(chg));
  for (i = 0; i < vec_size(CMGET(chg,entNames)); i++)
    report(0, "%4u  %s\n", i, vec_fetch(CMGET(chg,entNames), i));
#endif

  report(0, "Entities:\n");
  for (i = 0; i < vec_size(CMGET(chg,entities)); i++) {
    Entity *ent = vec_fetch(CMGET(chg,entities), i, Entity *);
    sdr_show(ent);
  }
   
}

/* NOTE: commitInfoTrueName always changes, and either parent or
 * mergeParent changes (sometimes both), therefore this I/O ordering
 * yields the most favorable delta compression. */
void
change_serialize(SDR_stream *strm, const void *vp)
{
  const Change *chg = vp;

  sdr_w_obname("parent", strm, CMGET(chg,parent));
  sdr_w_obname("fileACLs", strm, CMGET(chg,fileACLs));
  sdr_w_obname("commitInfoTrueName", strm, CMGET(chg,commitInfoTrueName));
  sdr_w_obname("mergeParent", strm, CMGET(chg,mergeParent));
  sdr_w_u8("isPartialMerge", strm, CMGET(chg,isPartialMerge));
  sdr_write("mergeHist", strm, CMGET(chg,mergeHist));

#if 0
  tnvec_sort(CMCLOBBER(chg,entNames));

  sdr_write("entNames", strm, CMGET(chg,entNames));
#endif

  obvec_sort(CMCLOBBER(chg,entities));

  sdr_write("entities", strm, CMGET(chg,entities));
  sdr_w_obname("filterSetName", strm, CMGET(chg,filterSetName));
}

void *
change_deserialize(const DeserializeInfo *di, SDR_stream *strm)
{
  Change *chg = (Change *) GC_MALLOC(sizeof(Change));

  ser_init(chg, &Change_SerType, di->ver);

  SER_MODIFIED(chg);

  CMSET(chg,parent,             sdr_r_obname("parent", strm));
  CMSET(chg,fileACLs,           sdr_r_obname("fileACLs", strm));
  CMSET(chg,commitInfoTrueName, sdr_r_obname("commitInfoTrueName", strm));
  CMSET(chg,mergeParent,        sdr_r_obname("mergeParent", strm));
  CMSET(chg,isPartialMerge,     sdr_r_u8("isPartialMerge", strm));
  CMSET(chg,mergeHist,          sdr_read("mergeHist", strm));

#if 0
  CMSET(chg,entNames,           sdr_read("entNames", strm));
#endif

  CMSET(chg,entities,           sdr_read("entities", strm));
  CMSET(chg,filterSetName,      sdr_r_obname("filterSetName", strm));

  CM_CANFREEZE(chg);

  return chg;
}

void
change_mark(Repository *r, const void *container,
	    const void *ob, rbtree *memObs)
{
  const Change *chg = ob;

  assert(container == ob);

  mark_addmark(container, memObs, CMGET(chg,parent));
  mark_addmark(container, memObs, CMGET(chg,fileACLs));
  mark_addmark(container, memObs, CMGET(chg,commitInfoTrueName));
  mark_addmark(container, memObs, CMGET(chg,mergeParent));
  /* FIX: xmark_addmark(container, memObs, CMGET(chg,mergeHist)); */
  mark_addmark(container, memObs, CMGET(chg,filterSetName));

  /* The change entities vector is a proper member, so we need to
     recurse on it: */
  ser_mark(r, container, CMGET(chg,entities), memObs);
}
