#include "mystring.h"
#include "trace.h"
#include <ctype.h>
#include <string.h>

mystringrep nil = { 0, 1, 1, "" };

static const unsigned replength = sizeof(unsigned)*3;

static const unsigned sizestep = sizeof(unsigned);
static const unsigned slackdiv = 4;
static const unsigned slackmax = 16;

#ifdef MYSTRINGREP_STATS

#include "fdbuf.h"

struct _rep_stats
{
  unsigned allocs;
  unsigned alloc_size;
  unsigned alloc_len;
  
  unsigned appends;
  unsigned appends_dup;

  _rep_stats()
    : allocs(0)
    {
    }
  
  void stat(const char* name, unsigned value)
    {
      ferr << "mystringrep: " << name << ": " << value << '\n';
    }
  void pcnt(const char* name, unsigned denom, unsigned divis)
    {
      ferr << "mystringrep: " << name << ": "
	   << denom << '/' << divis << '=';
      if(divis) ferr << denom * 100 / divis << '%';
      else ferr << "N/A";
      ferr << '\n';
    }
  
  ~_rep_stats()
    {
      stat("     size step", sizestep);
      stat(" slack divisor", slackdiv);
      stat(" slack maximum", slackmax);
      stat("        allocs", allocs);
      stat("  alloc length", alloc_len);
      stat("    alloc size", alloc_size);
      pcnt("   alloc slack", alloc_size-alloc_len, alloc_len);
      stat("alloc overhead", allocs*replength);
      pcnt("  appends->dup", appends_dup, appends);
    }
};

static _rep_stats stats;

#define ACCOUNT(NAME,VALUE) stats. NAME += VALUE

#else // MYSTRINGREP_STATS

#define ACCOUNT(NAME,VALUE)

#endif // MYSTRINGREP_STATS

///////////////////////////////////////////////////////////////////////////////
// class mystringrep
///////////////////////////////////////////////////////////////////////////////
mystringrep* mystringrep::alloc(unsigned length)
{
  ACCOUNT(allocs, 1);
  trace_static("length=" << length);
  if(length == 0)
    return &nil;

  ACCOUNT(alloc_len, length);
  unsigned slack = length / slackdiv;
  if(slack > slackmax)
    slack = slackmax;
  unsigned size = length+1 + sizestep-1 + slack;
  size = size - size % sizestep;
  ACCOUNT(alloc_size, size);

  mystringrep* ptr = (mystringrep*)new char[size+replength];
  ptr->length = length;
  ptr->references = 0;
  ptr->size = size;
  return ptr;
}

mystringrep* mystringrep::dup(const char* str, unsigned length)
{
  trace_static("str=" << (void*)str << " length=" << length);
  if(length == 0)
    return &nil;
  mystringrep* ptr = alloc(length);
  memcpy(ptr->buf, str, length);
  ptr->buf[length] = 0;
  return ptr;
}

mystringrep* mystringrep::dup(const char* str1, unsigned length1,
			      const char* str2, unsigned length2)
{
  trace_static("");
  if(length1+length2 == 0)
    return &nil;
  mystringrep* ptr = alloc(length1+length2);
  memcpy(ptr->buf, str1, length1);
  memcpy(ptr->buf+length1, str2, length2);
  ptr->buf[length1+length2] = 0;
  return ptr;
}

mystringrep* mystringrep::append(const char* str, unsigned len)
{
  ACCOUNT(appends, 1);
  unsigned newlen = length + len;
  // If there are more than one references, always make a duplicate
  // Also, if this does not have enough space to add the new string, dup it
  if(references > 1 || newlen >= size) {
    ACCOUNT(appends_dup, 1);
    mystringrep* tmp = dup(buf, length, str, len);
    tmp->attach();
    detach();
    return tmp;
  }
  // Otherwise, just add the new string to the end of this
  else {
    memcpy(buf+length, str, len);
    buf[newlen] = 0;
    length = newlen;
    return this;
  }
}

#ifdef MYSTRING_TRACE    
void mystringrep::attach()
{
  trace("references=" << references);
  ++references;
}
#endif

void mystringrep::detach()
{
  trace("references=" << references);
  
  --references;
  if(!references) {
    trace("deleting this");
    delete[] (char*)this;
  }
}
