// Copyright 2008 and onwards Google Inc.  All rights reserved.

#include "gutil/strings/strcat.h"

#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <common/logging.h>
#include "gutil/gscoped_ptr.h"
#include "gutil/strings/ascii_ctype.h"
#include "gutil/strings/escaping.h"
#include "gutil/stl_util.h"

AlphaNum gEmptyAlphaNum("");

// ----------------------------------------------------------------------
// StrCat()
//    This merges the given strings or integers, with no delimiter.  This
//    is designed to be the fastest possible way to construct a string out
//    of a mix of raw C strings, StringPieces, strings, and integer values.
// ----------------------------------------------------------------------

// Append is merely a version of memcpy that returns the address of the byte
// after the area just overwritten.  It comes in multiple flavors to minimize
// call overhead.
static char *Append1(char *out, const AlphaNum &x) {
  memcpy(out, x.data(), x.size());
  return out + x.size();
}

static char *Append2(char *out, const AlphaNum &x1, const AlphaNum &x2) {
  memcpy(out, x1.data(), x1.size());
  out += x1.size();

  memcpy(out, x2.data(), x2.size());
  return out + x2.size();
}

static char *Append4(char *out,
                     const AlphaNum &x1, const AlphaNum &x2,
                     const AlphaNum &x3, const AlphaNum &x4) {
  memcpy(out, x1.data(), x1.size());
  out += x1.size();

  memcpy(out, x2.data(), x2.size());
  out += x2.size();

  memcpy(out, x3.data(), x3.size());
  out += x3.size();

  memcpy(out, x4.data(), x4.size());
  return out + x4.size();
}

string StrCat(const AlphaNum &a) {
  return string(a.data(), a.size());
}

string StrCat(const AlphaNum &a, const AlphaNum &b) {
  string result;
  STLStringResizeUninitialized(&result, a.size() + b.size());
  char *const begin = &*result.begin();
  char *out = Append2(begin, a, b);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c) {
  string result;
  STLStringResizeUninitialized(&result, a.size() + b.size() + c.size());
  char *const begin = &*result.begin();
  char *out = Append2(begin, a, b);
  out = Append1(out, c);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
              const AlphaNum &d) {
  string result;
  STLStringResizeUninitialized(&result,
                               a.size() + b.size() + c.size() + d.size());
  char *const begin = &*result.begin();
  char *out = Append4(begin, a, b, c, d);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
              const AlphaNum &d, const AlphaNum &e) {
  string result;
  STLStringResizeUninitialized(&result,
      a.size() + b.size() + c.size() + d.size() + e.size());
  char *const begin = &*result.begin();
  char *out = Append4(begin, a, b, c, d);
  out = Append1(out, e);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
              const AlphaNum &d, const AlphaNum &e, const AlphaNum &f) {
  string result;
  STLStringResizeUninitialized(&result,
      a.size() + b.size() + c.size() + d.size() + e.size() + f.size());
  char *const begin = &*result.begin();
  char *out = Append4(begin, a, b, c, d);
  out = Append2(out, e, f);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
              const AlphaNum &d, const AlphaNum &e, const AlphaNum &f,
              const AlphaNum &g) {
  string result;
  STLStringResizeUninitialized(&result,
      a.size() + b.size() + c.size() + d.size() + e.size()
               + f.size() + g.size());
  char *const begin = &*result.begin();
  char *out = Append4(begin, a, b, c, d);
  out = Append2(out, e, f);
  out = Append1(out, g);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

string StrCat(const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
              const AlphaNum &d, const AlphaNum &e, const AlphaNum &f,
              const AlphaNum &g, const AlphaNum &h) {
  string result;
  STLStringResizeUninitialized(&result,
      a.size() + b.size() + c.size() + d.size() + e.size()
               + f.size() + g.size() + h.size());
  char *const begin = &*result.begin();
  char *out = Append4(begin, a, b, c, d);
  out = Append4(out, e, f, g, h);
  DCHECK_EQ(out, begin + result.size());
  return result;
}

namespace strings {
namespace internal {

// StrCat with this many params is exceedingly rare, but it has been
// requested...  therefore we'll rely on default arguments to make calling
// slightly less efficient, to preserve code size.
string StrCatNineOrMore(const AlphaNum *a, ...) {
  string result;

  va_list args;
  va_start(args, a);
  size_t size = a->size();
  while (const AlphaNum *arg = va_arg(args, const AlphaNum *)) {
    size += arg->size();
  }
  STLStringResizeUninitialized(&result, size);
  va_end(args);
  va_start(args, a);
  char *const begin = &*result.begin();
  char *out = Append1(begin, *a);
  while (const AlphaNum *arg = va_arg(args, const AlphaNum *)) {
    out = Append1(out, *arg);
  }
  va_end(args);
  DCHECK_EQ(out, begin + size);
  return result;
}

}  // namespace internal
}  // namespace strings

// It's possible to call StrAppend with a StringPiece that is itself a fragment
// of the string we're appending to.  However the results of this are random.
// Therefore, check for this in debug mode.  Use unsigned math so we only have
// to do one comparison.
#define DCHECK_NO_OVERLAP(dest, src) \
    DCHECK_GT(uintptr_t((src).data() - (dest).data()), uintptr_t((dest).size()))

void StrAppend(string *result, const AlphaNum &a) {
  DCHECK_NO_OVERLAP(*result, a);
  result->append(a.data(), a.size());
}

void StrAppend(string *result, const AlphaNum &a, const AlphaNum &b) {
  DCHECK_NO_OVERLAP(*result, a);
  DCHECK_NO_OVERLAP(*result, b);
  string::size_type old_size = result->size();
  STLStringResizeUninitialized(result, old_size + a.size() + b.size());
  char *const begin = &*result->begin();
  char *out = Append2(begin + old_size, a, b);
  DCHECK_EQ(out, begin + result->size());
}

void StrAppend(string *result,
               const AlphaNum &a, const AlphaNum &b, const AlphaNum &c) {
  DCHECK_NO_OVERLAP(*result, a);
  DCHECK_NO_OVERLAP(*result, b);
  DCHECK_NO_OVERLAP(*result, c);
  string::size_type old_size = result->size();
  STLStringResizeUninitialized(result,
                               old_size + a.size() + b.size() + c.size());
  char *const begin = &*result->begin();
  char *out = Append2(begin + old_size, a, b);
  out = Append1(out, c);
  DCHECK_EQ(out, begin + result->size());
}

void StrAppend(string *result,
               const AlphaNum &a, const AlphaNum &b,
               const AlphaNum &c, const AlphaNum &d) {
  DCHECK_NO_OVERLAP(*result, a);
  DCHECK_NO_OVERLAP(*result, b);
  DCHECK_NO_OVERLAP(*result, c);
  DCHECK_NO_OVERLAP(*result, d);
  string::size_type old_size = result->size();
  STLStringResizeUninitialized(result,
      old_size + a.size() + b.size() + c.size() + d.size());
  char *const begin = &*result->begin();
  char *out = Append4(begin + old_size, a, b, c, d);
  DCHECK_EQ(out, begin + result->size());
}

// StrAppend with this many params is even rarer than with StrCat.
// Therefore we'll again rely on default arguments to make calling
// slightly less efficient, to preserve code size.
void StrAppend(string *result,
               const AlphaNum &a, const AlphaNum &b, const AlphaNum &c,
               const AlphaNum &d, const AlphaNum &e, const AlphaNum &f,
               const AlphaNum &g, const AlphaNum &h, const AlphaNum &i) {
  DCHECK_NO_OVERLAP(*result, a);
  DCHECK_NO_OVERLAP(*result, b);
  DCHECK_NO_OVERLAP(*result, c);
  DCHECK_NO_OVERLAP(*result, d);
  DCHECK_NO_OVERLAP(*result, e);
  DCHECK_NO_OVERLAP(*result, f);
  DCHECK_NO_OVERLAP(*result, g);
  DCHECK_NO_OVERLAP(*result, h);
  DCHECK_NO_OVERLAP(*result, i);
  string::size_type old_size = result->size();
  STLStringResizeUninitialized(result,
      old_size + a.size() + b.size() + c.size() + d.size()
               + e.size() + f.size() + g.size() + h.size() + i.size());
  char *const begin = &*result->begin();
  char *out = Append4(begin + old_size, a, b, c, d);
  out = Append4(out, e, f, g, h);
  out = Append1(out, i);
  DCHECK_EQ(out, begin + result->size());
}
