//--------------------------------------------------------------------------------------
// メモリー確保
// mallocとfreeの対構造をチェックする&一括開放できるようにする
//--------------------------------------------------------------------------------------

#import "malloc2.h"
#import "libs.h"

#ifdef CHECK_MALLOC
#define	m_nslog		NSLog	// 常時表示用
#else
#define	m_nslog(...)
#endif

//--------------------------------------------------------------------------------------

#define MAX_FNAME	32	// ソースファイル名の最大長以上にしておくこと
typedef struct {
	void	*ptr;
	int		cnt;
#ifdef CHECK_MALLOC
	int		lineno;
	char	fname[MAX_FNAME+1];
#endif
} ALLOC_CNT;
#define MAX_ALLOC_CNT	512		// とりあえずこれくらいにしておく;V3.5でMMLバッファーも追加されたので拡大
static ALLOC_CNT allocCnt[MAX_ALLOC_CNT];
static int totalCnt=0; // malloc2()で++,free2では減らされない,freeAll()で=0

//--------------------------------------------------------------------------------------

#ifdef CHECK_MALLOC
static
int getFname(char *fname)
{
	size_t ln=strlen(fname);
	while (fname[--ln]!='/');
	ln++; // '/'の次へ
	// その位置からのファイル名長がMAX_FNAMEを超えるときは、収まる範囲のみにする
	size_t ln2=strlen(&fname[ln]);
	if (ln2>MAX_FNAME) ln=ln2-MAX_FNAME;
	return((int)ln);
}
#endif

static
int searchAdrs(void *ptr)
{
	int i;
	for (i=0;i<totalCnt;i++) {
		if (allocCnt[i].ptr==ptr) return(i);
	}
	// 新規追加対象
	return (-1);
}

//--------------------------------------------------------------------------------------

// 未開放の領域を一括解除する
void freeAll(void)
{
	m_nslog(@"%s:%d",__FUNCTION__,totalCnt);
	int i;
	for (i=0;i<totalCnt;i++) {
#ifdef CHECK_MALLOC
		m_nslog(@"freeAll(%p:%d;%s(%d))",allocCnt[i].ptr,allocCnt[i].cnt,allocCnt[i].fname,allocCnt[i].lineno);
#endif
		if (allocCnt[i].cnt>0) {
#ifdef CHECK_MALLOC
			_free(allocCnt[i].ptr,0,NULL);
#else
			_free(allocCnt[i].ptr);
#endif
		}
	}
	totalCnt=0;
}

#ifdef CHECK_MALLOC
// freeAll()してしまうと情報が消えるので、その前に呼び出すこと
void _dispAllalloc(void)
{
	m_nslog(@"全登録数:%d",totalCnt);
	int i;
	for (i=0;i<totalCnt;i++) {
		m_nslog(@"_free(%p:%d;%s(%d))",allocCnt[i].ptr,allocCnt[i].cnt,allocCnt[i].fname,allocCnt[i].lineno);
	}
}
#endif

//--------------------------------------------------------------------------------------

void *_malloc(size_t size
#ifdef CHECK_MALLOC
			  ,int lineno,char *fname
#endif
			  )
{
	m_nslog(@"%s(%tu/$%tx)",__FUNCTION__,size,size);
	
	void *ptr=malloc(size);
	m_nslog(@"ptr=%p〜%p",ptr,ptr+size-1);
	if (ptr!=NULL) {
#ifdef CHECK_MALLOC
		int ln=getFname(fname);
		m_nslog(@"malloc(%p:%s(%d))",ptr,&fname[ln],lineno);
#endif
		if (totalCnt>=MAX_ALLOC_CNT) {
			m_nslog(@"addCountが溢れた");
			Alert1(@"メモリエラー", @"addCountが溢れた",nil);
			exit(255);
		}
		int i;
#if 0	// X-BASIC'では途中でのfreeはあまり無いため、速度重視で無効にする
//#ifndef CHECK_MALLOC
		// 本番時は、まず空きがないか調べて、あればそこに登録する
		// チェックよりメモリ利用量のほうが優先
		for (i=0;i<totalCnt;i++) {
			if (allocCnt[i].cnt==0) break;
		}
		if (i==totalCnt) totalCnt++; // 空きがないので最後に登録
//#else
		// テスト時は常に最後に登録
#endif
		i=totalCnt++;
//#endif
		
		allocCnt[i].ptr=ptr;
		nslog(@"malloc2[%d]=%p",i,ptr);
#ifdef CHECK_MALLOC
		allocCnt[i].lineno=lineno;
		// ソースファイル名はファイル名＋拡張子部分のみ記録する
		strcpy(allocCnt[i].fname,&fname[ln]);
#endif
		// ptrはmalloc()で確保されたアドレスなので、同じアドレスが出現することはない(なのでmalloc側の重複チェックは省いている)
		// 故に、mallocではcntは1より大きくは成り得ない
		allocCnt[i].cnt++;
	}
	return (ptr);
}

//--------------------------------------------------------------------------------------

void *_realloc1(void *ptr,size_t size
#ifdef CHECK_MALLOC
			  ,int lineno,char *fname
#endif
			  )
{
	nslog(@"%s(%p,%tu)",__FUNCTION__,ptr,size);
	
	if (ptr==NULL) {
		// ptr==NULLのときは新規登録;mallocと同等
		return _malloc(size
#ifdef CHECK_MALLOC
						,lineno,fname
#endif
						);
	}
	
	int i=searchAdrs(ptr);
	if (i==-1) {
		nslog(@"未登録領域をreallocしようとした");
		Alert1(@"メモリエラー", @"未登録領域をreallocしようとした",nil);
	}
#ifdef CHECK_MALLOC
	int ln=getFname(fname);
	m_nslog(@"realloc(%p:%s(%d))",ptr,&fname[ln],lineno);
#endif
	ptr=realloc(ptr,size);
	if (ptr!=NULL) {
		allocCnt[i].cnt++; // reallocした印(回数)
		allocCnt[i].ptr=ptr;
		// realloc側のソース情報は記録しない(とりあえず)
	} else {
		nslog(@"reallocできない");
		Alert1(@"メモリエラー", @"reallocできない",nil);
		exit(252);
	}
	return (ptr);
}

//--------------------------------------------------------------------------------------

void _free(void *ptr
#ifdef CHECK_MALLOC
		   ,int lineno,char *fname
#endif
)
{
	m_nslog(@"%s",__FUNCTION__);
	if (ptr!=NULL) {
#ifdef CHECK_MALLOC
		if (fname!=NULL) {
			int ln=getFname(fname);
			m_nslog(@"free=%p:%s(%d)",ptr,&fname[ln],lineno);
		}
#endif
		int i=searchAdrs(ptr);
		if (i==-1) {
			// 新規のとき
			m_nslog(@"malloc2されてないアドレス");
			Alert1(@"メモリエラー", @"malloc2されてないアドレス",nil);
			exit(254);
		}
		if (allocCnt[i].cnt>0) {
			allocCnt[i].cnt=0;
		} else {
			m_nslog(@"freeされたのに再free");
			//			Alert1(@"メモリエラー", @"freeされたのに再free",nil);
			//			exit(254);
			// 警告は出すが、とりあえず無視させるだけにする
			return;
		}
	} else {
		Alert1(@"メモリエラー", @"NULLをfree",nil);
		m_nslog(@"NULLをfree");
		exit(253);
	}
	free(ptr);
}

//--------------------------------------------------------------------------------------
