/*
 * 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>
#include <repos/Repository.h>
#include "opencmclient.h"

static const char *
make_comment(const char *instr)
{
  return xstrcat(COMMENT_LEADER, instr);
}

PendingChange*
pendingchange_create(Repository *r,
		     const char *branchURI, uint64_t nRevs)
{
  Revision *rev;
  Change *chg;
  PendingChange *pc = 
    (PendingChange *) GC_MALLOC(sizeof(PendingChange));

  ser_init(pc, &PendingChange_SerType, PendingChange_SerType.ver);
  SER_MODIFIED(pc);

  pc->reposURI = r->uri->URI;
  pc->userName = opt_user;
  pc->branchURI = branchURI;
  pc->creator = pubkey_getid(r->authUser->pubKey);
  
  if (nRevs > 0) {
    rev = repos_GetRevision(r, branchURI, nRevs-1);
  }
  else {
    rev = NULL;
  }

  pc->nRevisions = nRevs;
  pc->baseChangeName = rev ? rev->newObject : 0;
  pc->baseCmtInfoName = 0;

  pc->filterSetName = 0;
  pc->filterSet = 0;

  pc->entSet = obvec_create();

  if (pc->baseChangeName) {
    unsigned i;

    chg = (Change *) repos_GetEntity(r, branchURI, pc->baseChangeName);
    assert(GETTYPE(chg) == TY_Change);

    pc->baseCmtInfoName = CMGET(chg,commitInfoTrueName);
    pc->filterSetName = CMGET(chg,filterSetName);
    if (pc->filterSetName)
      pc->filterSet = 
	(Buffer *) repos_GetEntity(r, branchURI, pc->filterSetName);

    for (i = 0; i < vec_size(CMGET(chg,entities)); i++) {
      WsEntity *went = wsentity_fromEntity(
        vec_fetch(CMGET(chg,entities), i, const Entity*));
      obvec_append(pc->entSet, went);
    }
  }

  SER_MODIFIED(pc);

  pc->mergedChange = 0;		/* until proven otherwise */
  pc->isPartialMerge = FALSE;	/* until proven otherwise */

  pc->notes = 0;
  
  pc->isSorted = FALSE;

  return pc;
}

#define MORE_ENTS 64

void
pendingchange_InsertEntity(PendingChange *pc, WsEntity *e)
{
  SER_MODIFIED(pc);
  obvec_append(pc->entSet, e);
  
  pc->isSorted = FALSE;
}

/* Sorting routine */
static int
entity_cmp(const void *v1, const void *v2)
{
  const WsEntity **e1 = (const WsEntity **) v1;
  const WsEntity **e2 = (const WsEntity **) v2;
  return strcmp((*e1)->cur_fsName, (*e2)->cur_fsName);
}

static int
entity_keycmp(const void *vkey, const void *vmember)
{
  const char *key = (const char *) vkey;
  const WsEntity **member = (const WsEntity **) vmember;
  return strcmp(key, (*member)->cur_fsName);
}

static void
pendingchange_SortEntities(PendingChange *pc)
{
  if (pc->isSorted)
    return;
  
  SER_MODIFIED(pc);

  vec_sort_using(pc->entSet, entity_cmp);

  pc->isSorted = TRUE;
}

WsEntity *
pendingchange_FindEntity(PendingChange *pc, const char * name)
{
  int index;

  pendingchange_SortEntities(pc);

  index = vec_bsearch(pc->entSet, name, entity_keycmp);

  if(index < 0)
    return 0;

  return vec_fetch(pc->entSet, index, WsEntity *);
}

void
pendingchange_RemoveEntity(PendingChange *pc, WsEntity *we)
{
  unsigned u;
  
  for (u = 0; u < vec_size(pc->entSet); u++) {
    const WsEntity* x = vec_fetch(pc->entSet, u, const WsEntity *);
    if (x == we) {
      vec_remove(pc->entSet, u);
      SER_MODIFIED(pc);
      return;
    }
  }
}

void
pendingchange_RecomputeStatus(PendingChange *pc, StrVec *names)
{
  unsigned u;
  
  if (names) strvec_sort(names);

  for (u = 0; u < vec_size(pc->entSet); u++) {
    WsEntity *wse_u = vec_fetch(pc->entSet, u, WsEntity *);

    if (names == 0 || strvec_bsearch(names, wse_u->cur_fsName) >= 0)
      wsentity_RecomputeStatus(wse_u);
  }
}

void
pendingchange_ReportStatus(PendingChange *pc, StrVec *names)
{
  unsigned u;  
#if 0
  if (opt_Verbosity > 0) {
    report(0,"PROJECT: %s\n", pc->projPetName);
    report(0, "BRANCH : %s\n", pc->branchPetName);
  }
#endif

  if (names) strvec_sort(names);

  for (u = 0; u < vec_size(pc->entSet); u++) {
    WsEntity *wse_u = vec_fetch(pc->entSet, u, WsEntity *);
    if (wse_u->flags || opt_Verbosity > 0) {
      if (names == 0 || strvec_bsearch(names, wse_u->cur_fsName) >= 0)
	wsentity_ReportStatus(wse_u);
    }
  }
}

void
pendingchange_diff(PendingChange *pc, Repository *r, StrVec *nmList)
{
  unsigned u;
  
  for (u = 0; u < vec_size(pc->entSet); u++) {
    WsEntity *wse_u = vec_fetch(pc->entSet, u, WsEntity *);

    if (nmlist_matches(nmList, wse_u->cur_fsName) == 0)
      continue;
    
    wsentity_RecomputeStatus(wse_u);
    wsentity_dodiff(wse_u, r, pc->branchURI);
  }
}

StrVec *
pendingchange_build_description(PendingChange *pc)
{
  unsigned u;
  StrVec *chgDescrip = strvec_create();

  strvec_append(chgDescrip, make_comment("------------------------------------"
	"----------------------------\n"));
  strvec_append(chgDescrip, make_comment("NOTE: Lines beginning with '"
		      COMMENT_LEADER "' are removed automatically.\n"));
  strvec_append(chgDescrip, make_comment("\n"));
  strvec_append(chgDescrip, make_comment("Changes included "
      "in this commit:\n"));
  strvec_append(chgDescrip, make_comment("\n"));

  /* Make a first pass to build the text description of the commit we
   * are doing, so that we can append it to the commit information: */
  
  for (u = 0; u < vec_size(pc->entSet); u++) {
    char *chg_line = "";

    WsEntity *wse_u = vec_fetch(pc->entSet, u, WsEntity *);

    if (wse_u->flags & NEF_DELETED) {
      chg_line = xstrcat("Deleted: ", wse_u->cur_fsName);
      chg_line = xstrcat(chg_line, "\n");

      strvec_append(chgDescrip, make_comment(chg_line));
      continue;
    }
    
    if (wse_u->flags == 0)
      continue;
    
    assert(wse_u->flags);
    assert((wse_u->flags & NEF_DELETED) == 0);

    if (wse_u->flags & NEF_MODIFIED)
      chg_line = xstrcat(chg_line, "Modified");

    if (wse_u->flags & NEF_RENAMED) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Renamed");
    }

    if (wse_u->flags & NEF_MERGED) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Merged");
    }

    if (wse_u->flags & NEF_UPDATED) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Updated");
    }

    if (wse_u->flags & NEF_ADDED) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Added");
    }

    if (wse_u->flags & NEF_JADD) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Added by 'merge'");
    }

    if (wse_u->flags & NEF_PERMS) {
      if (*chg_line)
	chg_line = xstrcat(chg_line, ", ");
      chg_line = xstrcat(chg_line, "Perms");
    }

    chg_line = xstrcat(chg_line, ": ");
    chg_line = xstrcat(chg_line, wse_u->cur_fsName);
    chg_line = xstrcat(chg_line, "\n");
    strvec_append(chgDescrip, make_comment(chg_line));
  }

  strvec_append(chgDescrip, make_comment("------------------------------------"
	"----------------------------\n"));
  return chgDescrip;
}

OC_bool
pendingchange_check(const void *ob)
{
  int k;
  PendingChange *pc = (PendingChange *) ob;
  OC_bool status = TRUE;

  /* The list must be sorted by cur_fsName for this check to work */
  ObVec *entSet = obvec_shallow_copy(pc->entSet);

  if (vec_size(entSet) <= 1)
    return TRUE;

  vec_sort_using(entSet, entity_cmp);

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

    if (strcmp(wse1->cur_fsName, wse2->cur_fsName) == 0) {
      report(0, "Duplicate Entity in WorkSpace:\n\t fsName => \"%s\"\n", 
	     wse1->cur_fsName);
      status = FALSE;
    }
  }

  return status;
}

void
pendingchange_show(const void *ob)
{
  unsigned u;
  const PendingChange *pc = ob;
  
  report(0, "Creator:          %s\n", pc->creator);
  report(0, "Derived from:     %s/%s\n",
	 pc->branchURI, xunsigned64_str(pc->nRevisions));
  report(0, "FilterSet:        %s\n", 
	  pc->filterSet ? buffer_asString(pc->filterSet) : "<none>");
  report(0, "FilterSetName:    %s\n", pc->filterSetName);
  report(0, "Entities:         %u\n", vec_size(pc->entSet));

  for (u = 0; u < vec_size(pc->entSet); u++)
    sdr_show(vec_fetch(pc->entSet, u, Serializable *));
}

void
pendingchange_serialize(SDR_stream *strm, const void *ob)
{
  const PendingChange *pc = ob;

  assert(sdr_check(pc));

  pendingchange_SortEntities((PendingChange *)pc);
 
  sdr_w_obname("reposURI", strm, pc->reposURI);
  sdr_w_obname("userName", strm, pc->userName);
  sdr_w_obname("branchURI", strm, pc->branchURI);
  sdr_w_obname("creator", strm, pc->creator);

  sdr_w_u64("nRevisions", strm, pc->nRevisions);
  sdr_w_obname("baseChangeName", strm, pc->baseChangeName);
  sdr_w_obname("baseCmtInfoName", strm, pc->baseCmtInfoName);

  sdr_w_obname("mergedChange", strm, pc->mergedChange);
  sdr_w_u8("isPartialMerge", strm, pc->isPartialMerge);

  sdr_write("entSet", strm, pc->entSet);
  sdr_w_string("notes", strm, pc->notes);
  sdr_w_obname("filterSetName", strm, pc->filterSetName);
  sdr_write("filterSet", strm, pc->filterSet);
}

void *
pendingchange_deserialize(const DeserializeInfo *di, SDR_stream *strm)
{
  PendingChange *pc = (PendingChange *) 
    GC_MALLOC(sizeof(PendingChange));

  ser_init(pc, &PendingChange_SerType, di->ver);
  SER_MODIFIED(pc);

  pc->reposURI         = sdr_r_obname("reposURI", strm);

  pc->userName         = 0;
  if (di->ver > 0)
    pc->userName       = sdr_r_obname("userName", strm);

  pc->branchURI        = sdr_r_obname("branchURI", strm);
  pc->creator          = sdr_r_obname("creator", strm);

  pc->nRevisions       = sdr_r_u64("nRevisions", strm);
  pc->baseChangeName   = sdr_r_obname("baseChangeName", strm);
  pc->baseCmtInfoName  = sdr_r_obname("baseCmtInfoName", strm);

  pc->mergedChange     = sdr_r_obname("mergedChange", strm);
  pc->isPartialMerge   = sdr_r_u8 ("isPartialMerge", strm);

  pc->entSet           = sdr_read("entSet", strm);
  pc->notes            = sdr_r_string("notes", strm);

  pc->filterSetName    = sdr_r_obname ("filterSetName", strm);
  pc->filterSet        = sdr_read("filterSet", strm);

  pc->isSorted = FALSE;

  assert(sdr_check(pc));

  return pc;
}

void
pendingchange_mark(Repository *r, const void *container,
		   const void *ob, rbtree *memObs)
{
  assert(container == ob);
  THROW(ExBadValue, "bad call to pendingchange_mark()");
}

