#ifndef cstring_h
#define cstring_h

#include <cstddef> //size_t
#include <cstring> //memcpy, strlen
#include <cstdlib> //malloc&free
#include <cstdio> //snprintf

//#define NDEBUG //uncomment to disable slow debugging assertions
#include <cassert> //assert

// NOTES:
// - possible undefined behavior is described by assert()
// - UB may also happen if the methods are called with invalid const char*,
//   or const char* that points into memory owned by another CString

class CString {
	char* d; //nullptr if string is "", otherwise points to managed memory
	typedef const char* cp;
	typedef const CString& cref;
	typedef CString& ref;
	typedef CString&& xref; //move reference

	void asgn(cp a, cp b=nullptr) {
		//TRICKY: if a==d or b==d, eg. when doing str+=str,
		//prevent destroying a/b before it's actually used
		CString tmp; tmp.d = d; d = nullptr;

		size_t la = a? strlen(a):0, lb = b? strlen(b):0;
		if (la+lb) { 
			d = (char*) malloc(la+lb+1); assert(d);
			a && memcpy(d, a, la);
			b && memcpy(d+la, b, lb);
			d[la + lb] = 0;
		}
	}

	CString(cp a, cp b) : CString() { asgn(a, b); } //helper

	void mkbuf(size_t n) {
		clear();
		if(n) { d = (char*)malloc(n+1); assert(d); d[n] = 0; }
	}
public:
	CString() : d(nullptr) {}
	CString(cp s) : CString() { asgn(s); }
	CString(cref a) : CString() { asgn(a.d); }
	CString(xref a) { d = a.d; a.d = nullptr; }
	~CString() { clear(); }
	void clear() { if(d) free(d); d = nullptr; }
	bool empty() const { return !d; }
	size_t length() const { return d? strlen(d):0; }
	cp c_str() const { return d? d:""; }

	char& operator[](size_t idx)
	{ assert (idx < length()); return d[idx]; }
	const char& operator[](size_t idx) const
	{ assert (idx < length()); return d[idx]; }

	CString sub(size_t i, size_t n=-1) {
		cp b = d; while(*b && i) ++b, --i;
		cp e = b; while(*e && n) ++e, --n;
		CString r; r.mkbuf(e-b); memcpy(r.d, b, e-b);
		return r;
	}

#define _R return *this
	ref operator=(cp s) { asgn(s); _R;}
	ref operator=(cref a) { asgn(a.d); _R; }
	ref operator=(xref a) { char*t = d; d = a.d; a.d = t; _R; }
	ref operator+=(cp s) { asgn(d, s); _R; }
	ref operator+=(cref a) { asgn(d, a.d); _R; }
	CString operator+(cp s) const { return CString(d, s); }
	CString operator+(cref a) const { return CString(d, a.d); }

//history corner-- templates from 1981! (alternative: templated std::to_string)
#define _makeconv(T,fmt) \
	ref operator=(T a) { size_t tmp = snprintf(d, 0, fmt, a); \
		mkbuf(tmp); if(tmp) snprintf(d, ++tmp, fmt, a); _R; } \
	ref operator+=(T a) { CString b; b = a; asgn(d, b.d); _R; } \
	CString operator+(T a) const { CString b; b = a; return CString(d, b.d); }
	_makeconv(float, "%g")
	_makeconv(int,   "%d")
	_makeconv(char,  "%c")
#undef _makeconv
#undef _R

#define _makecmp(op) \
	bool operator op(cref a) const { return strcmp(d, a.d) op 0; }
	_makecmp(==) _makecmp(<=) _makecmp(>=)
	_makecmp(!=) _makecmp(<)  _makecmp(>)
#undef _makecmp
};

template<typename A> CString operator+(A a, const CString&b) { return b+a; }

#endif // cstring_h
