/*
  This file is part of drd, a thread error detector.

  Copyright (C) 2006-2017 Bart Van Assche <bvanassche@acm.org>.

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  02111-1307, USA.

  The GNU General Public License is contained in the file COPYING.
*/


#include "drd_vc.h"
#include "pub_tool_basics.h"      // Addr, SizeT
#include "pub_tool_libcassert.h"  // tl_assert()
#include "pub_tool_libcbase.h"    // VG_(memcpy)
#include "pub_tool_libcprint.h"   // VG_(printf)
#include "pub_tool_mallocfree.h"  // VG_(malloc), VG_(free)


/* Local function declarations. */

static
void DRD_(vc_reserve)(VectorClock* const vc, const unsigned new_capacity);


/* Function definitions. */

/**
 * Initialize the memory 'vc' points at as a vector clock with size 'size'.
 * If the pointer 'vcelem' is not null, it is assumed to be an array with
 * 'size' elements and it becomes the initial value of the vector clock.
 */
void DRD_(vc_init)(VectorClock* const vc,
                   const VCElem* const vcelem,
                   const unsigned size)
{
   tl_assert(vc);
   vc->size = 0;
   vc->capacity = 0;
   vc->vc = 0;
   DRD_(vc_reserve)(vc, size);
   tl_assert(size == 0 || vc->vc != 0);
   if (vcelem)
   {
      VG_(memcpy)(vc->vc, vcelem, size * sizeof(vcelem[0]));
      vc->size = size;
   }
#ifdef ENABLE_DRD_CONSISTENCY_CHECKS
   DRD_(vc_check)(vc);
#endif
}

/** Reset vc to the empty vector clock. */
void DRD_(vc_cleanup)(VectorClock* const vc)
{
   DRD_(vc_reserve)(vc, 0);
}

/** Copy constructor -- initializes *new. */
void DRD_(vc_copy)(VectorClock* const new, const VectorClock* const rhs)
{
   DRD_(vc_init)(new, rhs->vc, rhs->size);
}

/** Assignment operator -- *lhs is already a valid vector clock. */
void DRD_(vc_assign)(VectorClock* const lhs, const VectorClock* const rhs)
{
   DRD_(vc_cleanup)(lhs);
   DRD_(vc_copy)(lhs, rhs);
}

/** Increment the clock of thread 'tid' in vector clock 'vc'. */
void DRD_(vc_increment)(VectorClock* const vc, DrdThreadId const tid)
{
   unsigned i;
   for (i = 0; i < vc->size; i++)
   {
      if (vc->vc[i].threadid == tid)
      {
         typeof(vc->vc[i].count) const oldcount = vc->vc[i].count;
         vc->vc[i].count++;
         // Check for integer overflow.
         tl_assert(oldcount < vc->vc[i].count);
         return;
      }
   }

   /*
    * The specified thread ID does not yet exist in the vector clock
    * -- insert it.
    */
   {
      const VCElem vcelem = { tid, 1 };
      VectorClock vc2;
      DRD_(vc_init)(&vc2, &vcelem, 1);
      DRD_(vc_combine)(vc, &vc2);
      DRD_(vc_cleanup)(&vc2);
   }
}

/**
 * @return True if vector clocks vc1 and vc2 are ordered, and false otherwise.
 * Order is as imposed by thread synchronization actions ("happens before").
 */
Bool DRD_(vc_ordered)(const VectorClock* const vc1,
                      const VectorClock* const vc2)
{
   return DRD_(vc_lte)(vc1, vc2) || DRD_(vc_lte)(vc2, vc1);
}

/** Compute elementwise minimum. */
void DRD_(vc_min)(VectorClock* const result, const VectorClock* const rhs)
{
   unsigned i;
   unsigned j;

   tl_assert(result);
   tl_assert(rhs);

   DRD_(vc_check)(result);

   /* Next, combine both vector clocks into one. */
   i = 0;
   for (j = 0; j < rhs->size; j++)
   {
      while (i < result->size && result->vc[i].threadid < rhs->vc[j].threadid)
      {
         /* Thread ID is missing in second vector clock. Clear the count. */
         result->vc[i].count = 0;
         i++;
      }
      if (i >= result->size)
      {
         break;
      }
      if (result->vc[i].threadid <= rhs->vc[j].threadid)
      {
         /* The thread ID is present in both vector clocks. Compute the */
         /* minimum of vc[i].count and vc[j].count. */
         tl_assert(result->vc[i].threadid == rhs->vc[j].threadid);
         if (rhs->vc[j].count < result->vc[i].count)
         {
            result->vc[i].count = rhs->vc[j].count;
         }
      }
   }
   DRD_(vc_check)(result);
}

/**
 * Compute elementwise maximum.
 */
void DRD_(vc_combine)(VectorClock* const result, const VectorClock* const rhs)
{
   unsigned i;
   unsigned j;
   unsigned shared;
   unsigned new_size;

   tl_assert(result);
   tl_assert(rhs);

   // First count the number of shared thread id's.
   j = 0;
   shared = 0;
   for (i = 0; i < result->size; i++)
   {
      while (j < rhs->size && rhs->vc[j].threadid < result->vc[i].threadid)
         j++;
      if (j >= rhs->size)
         break;
      if (result->vc[i].threadid == rhs->vc[j].threadid)
         shared++;
   }

   DRD_(vc_check)(result);

   new_size = result->size + rhs->size - shared;
   if (new_size > result->capacity)
      DRD_(vc_reserve)(result, new_size);

   DRD_(vc_check)(result);

   // Next, combine both vector clocks into one.
   i = 0;
   for (j = 0; j < rhs->size; j++)
   {
      /* First of all, skip those clocks in result->vc[] for which there */
      /* is no corresponding clock in rhs->vc[].                         */
      while (i < result->size && result->vc[i].threadid < rhs->vc[j].threadid)
      {
         i++;
      }
      /* If the end of *result is met, append rhs->vc[j] to *result. */
      if (i >= result->size)
      {
         result->size++;
         result->vc[i] = rhs->vc[j];
      }
      /* If clock rhs->vc[j] is not in *result, insert it. */
      else if (result->vc[i].threadid > rhs->vc[j].threadid)
      {
         unsigned k;
         for (k = result->size; k > i; k--)
         {
            result->vc[k] = result->vc[k - 1];
         }
         result->size++;
         result->vc[i] = rhs->vc[j];
      }
      /* Otherwise, both *result and *rhs have a clock for thread            */
      /* result->vc[i].threadid == rhs->vc[j].threadid. Compute the maximum. */
      else
      {
         tl_assert(result->vc[i].threadid == rhs->vc[j].threadid);
         if (rhs->vc[j].count > result->vc[i].count)
         {
            result->vc[i].count = rhs->vc[j].count;
         }
      }
   }
   DRD_(vc_check)(result);
   tl_assert(result->size == new_size);
}

/** Print the contents of vector clock 'vc'. */
void DRD_(vc_print)(const VectorClock* const vc)
{
   HChar* str;

   if ((str = DRD_(vc_aprint)(vc)) != NULL)
   {
      VG_(printf)("%s", str);
      VG_(free)(str);
   }
}

/**
 * Print the contents of vector clock 'vc' to a newly allocated string.
 * The caller must call VG_(free)() on the return value of this function.
 */
HChar* DRD_(vc_aprint)(const VectorClock* const vc)
{
   unsigned i;
   unsigned reserved;
   unsigned size;
   HChar* str = 0;

   tl_assert(vc);
   reserved = 64;
   size = 0;
   str = VG_(realloc)("drd.vc.aprint.1", str, reserved);
   if (! str)
      return str;
   size += VG_(snprintf)(str, reserved, "[");
   for (i = 0; i < vc->size; i++)
   {
      tl_assert(vc->vc);
      if (VG_(strlen)(str) + 32 > reserved)
      {
         reserved *= 2;
         str = VG_(realloc)("drd.vc.aprint.2", str, reserved);
         if (! str)
            return str;
      }
      size += VG_(snprintf)(str + size, reserved - size,
                            "%s %u: %u", i > 0 ? "," : "",
                            vc->vc[i].threadid, vc->vc[i].count);
   }
   size += VG_(snprintf)(str + size, reserved - size, " ]");

   return str;
}

/**
 * Invariant test.
 *
 * The function below tests whether the following two conditions are
 * satisfied:
 * - size <= capacity.
 * - Vector clock elements are stored in thread ID order.
 *
 * If one of these conditions is not met, an assertion failure is triggered.
 */
void DRD_(vc_check)(const VectorClock* const vc)
{
   unsigned i;

   tl_assert(vc->size <= vc->capacity);

   for (i = 1; i < vc->size; i++)
      tl_assert(vc->vc[i-1].threadid < vc->vc[i].threadid);
}

/**
 * Change the size of the memory block pointed at by vc->vc.
 * Changes capacity, but does not change size. If the size of the memory
 * block is increased, the newly allocated memory is not initialized.
 */
static
void DRD_(vc_reserve)(VectorClock* const vc, const unsigned new_capacity)
{
   tl_assert(vc);
   tl_assert(vc->capacity > VC_PREALLOCATED
             || vc->vc == 0
             || vc->vc == vc->preallocated);

   if (new_capacity > vc->capacity)
   {
      if (vc->vc && vc->capacity > VC_PREALLOCATED)
      {
         tl_assert(vc->vc
                   && vc->vc != vc->preallocated
                   && vc->capacity > VC_PREALLOCATED);
         vc->vc = VG_(realloc)("drd.vc.vr.1",
                               vc->vc, new_capacity * sizeof(vc->vc[0]));
      }
      else if (vc->vc && new_capacity > VC_PREALLOCATED)
      {
         tl_assert((vc->vc == 0 || vc->vc == vc->preallocated)
                   && new_capacity > VC_PREALLOCATED
                   && vc->capacity <= VC_PREALLOCATED);
         vc->vc = VG_(malloc)("drd.vc.vr.2",
                              new_capacity * sizeof(vc->vc[0]));
         VG_(memcpy)(vc->vc, vc->preallocated,
                     vc->capacity * sizeof(vc->vc[0]));
      }
      else if (vc->vc)
      {
         tl_assert(vc->vc == vc->preallocated
                   && new_capacity <= VC_PREALLOCATED
                   && vc->capacity <= VC_PREALLOCATED);
      }
      else if (new_capacity > VC_PREALLOCATED)
      {
         tl_assert(vc->vc == 0
                   && new_capacity > VC_PREALLOCATED
                   && vc->capacity == 0);
         vc->vc = VG_(malloc)("drd.vc.vr.3",
                              new_capacity * sizeof(vc->vc[0]));
      }
      else
      {
         tl_assert(vc->vc == 0
                   && new_capacity <= VC_PREALLOCATED
                   && vc->capacity == 0);
         vc->vc = vc->preallocated;
      }
      vc->capacity = new_capacity;
   }
   else if (new_capacity == 0 && vc->vc)
   {
      if (vc->capacity > VC_PREALLOCATED)
         VG_(free)(vc->vc);
      vc->vc = 0;
      vc->capacity = 0;
   }

   tl_assert(new_capacity == 0 || vc->vc != 0);
   tl_assert(vc->capacity > VC_PREALLOCATED
             || vc->vc == 0
             || vc->vc == vc->preallocated);
}
