/* Copyright (C) 2019 Momi-g

 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 3 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, see <http://www.gnu.org/licenses/>.
*/

/*-*
@_name	stringrow
@auther momi-g
@brief	auto growing str/bin buffer. fast.
@_synopsys
	typedef struct stringrow_tag {
		char* p;
		int ofst;
		int sz;
		int msz;
	} sg_t;
	
	sg_t*	sg_new(void);
	void	sg_free(sg_t* obj)
	int		sg_add(sg_t* obj, const char* bin [,int binsz] )
	int		sg_addf(sg_t* obj, const char* fmt, ...)
	int		sg_ins(sg_t* obj, const char* bin [,int binsz] )
	int		sg_insf(sg_t* obj, const char* fmt, ...)
	
	void	sg_bs(sg_t* obj, int num);
	void	sg_del(sg_t* obj, int num);
	
	char*	sg_ptr(obj [,pos])		// same as (obj->p)+pos
	int		sg_sz(obj)				// same as obj->sz
	void	sg_clear(sg_t* obj);

@_eg
	#include "stringrow.h"
	#include <stdio.h>

	int main() {
		sg_t* obj = sg_new();		// ""
		int rc= sg_add(obj, "foo"); // "foo", rc=3: addsz
		char* p = sg_ptr(obj);		// get headptr
		puts(p);					// >> 'foo'
		p = sg_ptr(obj, 1);			// &(p[1])
		puts(p);					// >> 'oo'
		int sz = sg_sz(obj);		// >> 3

		sg_add(obj, "bar");			// "foobar"
		rc = sg_ins(obj, "baz");	// "bazfoobar", rc=3
		sg_ins(obj, "\0abc");		// "bazfoobar"	,read as str if ag3 isnt
		sg_ins(obj, "\0abc", 2);	// "(\0)abazfoobar"
		sz = sg_sz(obj);			//	11,	sz is holding bin size
		sg_clear(obj);				// ""
		
		sg_addf(obj,"%#x",17);		// "0x11", fmt is same as printf()
		rc = sg_addf(obj,"\0");		// "0x11", rc=0
		rc = sg_addf(obj,"%c", 0);	// "0x11(\0)", rc=1
		sg_bs(obj, 2);				// "0x1"	,del tail 2byte
		sg_del(obj,2);				// "1"		,del head 2byte
		puts(obj->p);				// >> "1"

		sg_add(obj, obj->p);		// >> "11", allow to use inner ptr
		sg_insf(obj,"%s",obj->p);	// >> "1111",
	
		sg_free(obj);
		return 0;
	}
	// ~$ gcc src.c stringrow.c

@param obj	main struct. holds realloc()ed ptr, max memsz, used sz
@param bin	str/binary you want to add
@param binsz addsize >= -2. -1:strlen(bin), -2:strlen(bin)+1. use -1 if noset
@param fmt	add printf() fmt strings instead of normal bindata
@param num	delete byte size
@param pos	optional args. ptr offset byte. use 0 if no set
@return	- sg_add(f)/ins(f) rtns add datasz
@details
 - data is always terminated with '\0'. this last byte is out of sg_sz() cnt.
   so puts(sg_ptr(obj)) never causes SEGV err.
	sg_add(obj, "foo");		// sz=3	puts(sg_ptr(obj) ) >> "foo" 
	sg_add(obj, "\0ab",4);	// sz=7	puts(sg_ptr(obj) ) >> "foo" 
	sg_clear(obj);			// sz=0	puts(sg_ptr(obj) ) >> ""
	sg_ins(obj, "foo");		// sz=3	puts(sg_ptr(obj) ) >> "foo" 

 - str adding funcs rtns added sz
	sg_t* strpool = sg_new();
	char* s = "hw";
	int p1 = sg_add(strpool, s, strlen(s)+1 );	//+1=='\0', p1==3
	s = "gw";
	int p2 = sg_add(strpool, s, -2 );	//-2==slen(s)+1, p2==3, "hw(\0)gw(\0)"
	puts( sg_ptr(strpool)+p1 );	//>>gw
	puts( strpool->p );		//>>hw
 
 - sg_ptr(obj) is almost the same as obj->p +0. sg_ptr(obj, 4) is obj->p +4
 - sg_clear() doesnt shrink heap
 - sloppy benchmark: 	while(i-- >0){ sg_add(obj, "123"); }
	loop(100*1000)
 real	8.652 ms	: msg:add
 real	43.633 ms	: msg:addf
 real	6.422 ms	: msg:ins
 real	32.289 ms	: msg:insf
 real	7.121 ms	: msg:strdup()
 
 	loop( 10*1000), too slow + causes memclash
 real	244.710 ms	: msg:gnu asprintf(), x10 == 2.4s
 real	978.828 ms	: msg:apr_psprintf(), x10 == 9.7s

 FAST: strdup/sg_add(1) >> sg_addf(5) >>>> asprintf(300) >> libapr(1000) :SLOW
@_note -
@conforming posix-2001+
@version 2.1.0, 2021-06-02
-*/
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "stringrow.h"

/* tool macros */
#ifndef ERRact
 #include <stdio.h>
 #include <errno.h>
 #include <unistd.h>
 #if  (200112L <= _POSIX_C_SOURCE +0)  /* nealy 200112L, _POSIX_C_SOURCE	c99*/
 #define ERRact(xpr, msg, act)	if(xpr){ fprintf(stderr, \
	"ERR: %s %d %s() pid:%d %s msg:%s sys:%s\n",__FILE__,__LINE__, __func__ \
	,getpid(), "hit(" #xpr ")", msg, strerror(errno) );act ;}
 #else
 #define ERRact(xpr, msg, act)	if(xpr){ fprintf(stderr, \
	"ERR: %s %d %s() pid:%d %s msg:%s sys:%s\n",__FILE__,__LINE__, "func:c99+" \
	,0 , "hit(" #xpr ")", msg, strerror(errno) );act ;}
 #endif
 #define STOP(xpr, msg)	ERRact(xpr, msg, fputs("STOP\n",stderr);exit(1))
#endif

#define loop(a)		for(int lpcnt=1;lpcnt<=a;lpcnt++)
/*tool end*/

#include "msgp.h"

/*src*/
static int sg_adjmem(sg_t* str, int addsz, int edge);

sg_t* sg_new(void) {
	; STOP( SG_BUFSZ<1, "init bufsz is negative num");
	sg_t* obj=(sg_t*)calloc(sizeof(sg_t), 1);
	; if(!obj){return NULL;}	//STOP(!obj, "calloc() failed");

	obj->ofst = SG_BUFSZ/2;
	char* p = (char*)calloc(sizeof(char), SG_BUFSZ);
	; if(!p){return NULL;}	//STOP(!obj, "calloc() failed");

	obj->p = p + obj->ofst;
	obj->sz=0;
	obj->msz= SG_BUFSZ;
	return obj;
}

int sg_add_impl(sg_t* obj, const char* add, int addsz) {
	; STOP(!obj, "NULL obj.");	//削れば10%早くなるけどセグフォで見えないのは嫌
	if(addsz== -1){addsz=strlen(add);}
	else if(addsz== -2){addsz=strlen(add)+1;}
	else if(addsz<-2){ STOP(addsz, "binsz>=0 or -1:strlen(), -2:strlen()+1, -3...err");}
	
	int pos=0;	//addがsgの内部だったらextendで狂う 保存修正 mallocより多分早い
	int flg=0;
	char* bp = obj->p-obj->ofst;
	if((bp <=add) && (add< bp+obj->msz)){ pos=add- obj->p;flg=1; }
	// -1:add 0:ins memerrはstop?
	if(obj->ofst +obj->sz +addsz+1 >obj->msz ){
		int rc = sg_adjmem(obj, addsz, -1);
		if(rc<0){return -1;}
	}
	if(flg){add=obj->p+pos;}

	memmove(obj->p+obj->sz, add, addsz);
	obj->sz += addsz;
	obj->p[obj->sz]=0;
	return addsz;
}
int sg_addf(sg_t* obj, const char* fmt, ...) {
	; STOP(!obj, "NULL obj.");
	char sbuf[1]={0};
	va_list vl;
	va_start(vl, fmt);
	size_t addsz = vsnprintf(sbuf, sizeof(sbuf), fmt, vl);	//sn系は\0を除く。クソが。
	va_end(vl);
	
	int sz = addsz*1;
	sz = SG_BUFSZ - (sz % SG_BUFSZ) + sz;	//アライメントを揃える
	char* p = malloc(sz);		//内部に自身がいると壊れるので外だし
	; if(!p){return -1;}	//STOP(!p, "realloc() failed");

	va_start(vl, fmt);
	vsnprintf( p, sz, fmt, vl);	// \0
	va_end(vl);
//インライン	40ms >> 35ms 10%程度改善
//	int rtn = sg_add_impl(obj, p, addsz);
	if(obj->ofst +obj->sz +addsz+1 >obj->msz ){
		int rc = sg_adjmem(obj, addsz, -1);
		if(rc<0){return -1;}
	}
	memmove(obj->p+obj->sz, p, addsz);
//	memcpy(obj->p+obj->sz, p, addsz);	cpyもmoveも変わらない 内部で一緒かもしれない
	
	obj->sz += addsz;
	obj->p[obj->sz]=0;

	free(p);
	return addsz;
}

int sg_ins_impl(sg_t* obj, const char* add, int addsz) {
	; STOP(!obj, "NULL obj.");
	if(addsz== -1){addsz=strlen(add);}
	else if(addsz== -2){addsz=strlen(add)+1;}
	else if(addsz<-2){ STOP(addsz, "binsz>=0 or -1:strlen(), -2:strlen()+1, -3...err");}

	int pos=0;
	int flg=0;
	char* bp = obj->p-obj->ofst;
	if((bp <=add) && (add< bp+obj->msz)){ pos=add- obj->p;flg=1; }
	if(obj->ofst < addsz+1){
		int rc = sg_adjmem(obj, addsz, 0);
		if(rc<0){return -1;}
	}
	if(flg){add=obj->p+pos;}

	obj->p -= addsz;
	obj->ofst -= addsz;
	obj->sz += addsz;
	memmove(obj->p, add, addsz);
	return addsz;
}

int sg_insf(sg_t* obj, const char* fmt, ...) {
	;	STOP(!obj, "NULL obj.");
	char sbuf[1]={0};
	va_list vl;
	va_start(vl, fmt);
	//insは\0を付けない
	size_t addsz = vsnprintf(sbuf, sizeof(sbuf), fmt, vl);
	va_end(vl);

	int sz = addsz*1;
	sz = SG_BUFSZ - (sz % SG_BUFSZ) + sz;	//アライメントを揃える
	char* p = malloc(sz);		//内部に自身がいると壊れるので外だし
	; if(!p){return -1;}	//STOP(!p, "realloc() failed");
	va_start(vl, fmt);
	vsnprintf( p, sz, fmt, vl);	// \0
	va_end(vl);

//インライン
//	int rtn = sg_ins_impl(obj, p, addsz);
	if(obj->ofst < addsz+1){
		int rc = sg_adjmem(obj, addsz, 0);
		if(rc<0){return -1;}
	}
	obj->p -= addsz;
	obj->ofst -= addsz;
	obj->sz += addsz;
	memmove(obj->p, p, addsz);
//	memcpy(obj->p, p, addsz);

	free(p);
	return addsz;

//	if(obj->ofst < addsz+1){sg_adjmem(obj, addsz, 0);}
//	
//	int rtn = obj->sz;
//	obj->p -= addsz;
//	obj->ofst -= addsz;
//	obj->sz += addsz;
//
//	va_start(vl, fmt);
//	vsnprintf( obj->p, addsz+1, fmt, vl);	//'\0'で踏み潰されるので戻す
//	va_end(vl);
//	return rtn;
}

void sg_bs(sg_t* obj, int num) {
	;	STOP(!obj, "NULL obj.");
	obj->sz -= num;
	if(obj->sz<0){obj->sz=0;}
	obj->p[obj->sz]=0;
	//ofst変化なし
}
void sg_del(sg_t* obj, int num) {
	;	STOP(!obj, "NULL obj.");
	if(obj->sz <num){ num=obj->sz;}
	obj->p  += num;
	obj->ofst += num;
	obj->sz -= num;
}

void sg_clear(sg_t* obj) {
	if(!obj){return;}
	;	STOP(!obj, "NULL obj.");
	obj->p = obj->p - obj->ofst + obj->msz/2;
	obj->ofst = obj->msz /2;
	obj->sz=0;
	obj->p[0]=0;
}

// corecode
static int sg_adjmem(sg_t* obj, int addsz, int edge){
	//	init: |....@....|
	//	add : |....@@...|
	//	ins : |.===@@...|			realloc					memmove
	//	ext	: |.===@@@@@|  >>  |.==@@@@@|.......| >> |......==@@@@@.......|
	
	// 戦略: dataはセンター付近に陣取って端点で伸ばす
	// 端点でも空きが多いなら中央へmove。使用領域が60%以上(根拠無)ならrealloc.
	// この間数のメリットは高速性と使いやすさでメモリは最大で40%無駄になる
	addsz++;	// \0
	
	//容量up 重い	60%以上でexpand
	if (addsz+1+obj->sz >= SG_TRIGRATIO * obj->msz){
		obj->msz *= 2;
		int reqsz = obj->sz + addsz +1;
		while(obj->msz<reqsz){obj->msz *= 2;}
		char* p = realloc(obj->p - obj->ofst, obj->msz);
		; if(!p){return -1;}	//STOP(!p, "realloc() failed");
		obj->p = p + obj->ofst;
	}
	//中心へ移動 重い どっちでも追加後中央付近になるように調整
	// edgeは0で先頭、-1で尻尾だから自動的に余裕がある状態に調整される
	int n_ofst = (obj->msz - obj->sz - addsz -1)/2 + (!edge)*addsz;
	char* n_p = (obj->p - obj->ofst) + n_ofst;
	
	memmove(n_p, obj->p, obj->sz);
	obj->p = n_p;
	obj->ofst = n_ofst;
	obj->p[obj->sz]=0;
	return 0;
}

/*  sg_free ... define */ 
void sg_free_alt(sg_t* obj) {
	if(!obj){return;}
	free(obj->p - obj->ofst);	obj->p=NULL;	
	obj->ofst=0;
	obj->sz=0;
	obj->msz=0;
	free(obj);
}









/*SH_SMP
#include "stringrow.h"
#include <stdio.h>

int main() {
	sg_t* obj = sg_new();		// ""
	sg_add(obj, "foo");		// "foo"
	char* p = sg_ptr(obj);		// get headptr
	puts(p);					// >> 'foo'
	int sz = sg_sz(obj);		// >> 3
printf("%d\n", sz);

	sg_add(obj, "bar");		// "foobar"
	sg_ins(obj, "baz");		// "bazfoobar"
	sg_ins(obj, "\0abc", 2);	// "(\0)zbazfoobar"	,use only 2byte
	sz = sg_sz(obj);			//	11,	sz is holding binary size
	sg_clear(obj);				// ""
puts(obj->p);
	sg_addf(obj,"%#x",17);		// "0x11"	,fmt same as printf()
puts(obj->p);
	sg_bs(obj, 1);			// "0x1"	,del tail 1byte
printf("%d\n", obj->sz);	// >> len=3

	sg_del(obj,2);			// "1"		,del head 2byte
	puts(obj->p);			// >> "1"
	
	sg_free(obj);
	return 0;
}

//SH_SMPE*/



/*
 change log
 --
2021-06-02  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(rtnval) : omit 2021-03-25 rtnsz change.
	rtn is addsz. follows to standards, printf(), puts() etc. v2.1.0
	
2021-05-04  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(doc) : fix doc, addf/insf allows inner ptr
	
	* stringrow.sh.c(sh) : update to new brp script

2021-03-25  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(addf/insf) : fix self str adding bug, add malloc()

2021-03-25  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(add/ins) : change rtn to predata sz, add ag3==-1/-2, fix doc

2021-03-06  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(add/ins) : allow add inner ptr, flg/pos calc, v2.0.3

2021-03-06  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(all) : change logic,  ins 3000ms >> 6ms. v2.0.2

2021-02-24  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(add/addf/ins/insf) : change rtn from datasz >> addsz
	
	* stringrow.sh.c(pop/drop >> bs/del): change apiname

	* stringrow.sh.c(sg_ptr): add 2nd args, ptr offset.

2021-02-20  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(all) : rewrite. v2.0.0

2020-06-12  Momi-g	<dmy@dmy.dmy>

	* stringrow.sh.c(ERRact) : add #if switch.

*/

