/* Copyright (C) 1995 NetWin World, New Zealand, All rights reserved. */
/* Memory routines which allow compaction   */

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include "mem.h"
#define INDNEWS  TRUE
#ifdef INDNEWS
#include "emsg.h"
#include "lib.h"
#else
#define emsg printf
#define imsg printf
#define dmsg printf
#define FALSE 0
#define TRUE (!FALSE)
#endif

#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_MEM)
#define myfree(x) myfree2(x,M_MEM)

static struct BLOCK *fblock;
static int islocked;

#define FREE_STRINGS TRUE
void p_compress_between(void);
void p_compress3(Block *dst, Block *src);

static int pstr_total,pstr_used,pstr_peak;

#ifndef INDNEWS
main()
{
        int i,j;
        char bf[BFSZ];
        int sz;
        char *s;
        char **p[1000];
        int *m,**mm;
        for (i=0; i<100; i++) {
                sz = ((123456*i) % 100000) + 10;
                p[i] = p_malloc(sz);    
                for (s = *p[i]; s < (*p[i] + sz); s++) *s = i;

                /*p[200] = p_malloc(20); */    /* To cause fragmentation */     
                printf("Allocated %p \n",p[i]);
                if (p[i]==NULL) printf("Returned null\n");
                if (i>10) {
                        printf("call free %d %p %p \n",i-10,p[i-10],*(p[i-10]));
                        p_free(p[i-10]); p[i-10] = NULL;
                }

                for (j=0; j<100; j++) {
                        if (p[j]!=NULL) {
                                sz = ((123456*j) % 100000) + 10;
                                for (s = *p[j]; s < (*p[j] + sz); s++) {
                                        if (*s != j) printf("+++++Datalost\n");
                                }
                        }
                }
                printf("I=%d\n",i);
        }
        p_show();
        for (i=0; i<100; i++) if (p[i]!=NULL) p_free(p[i]);
        p_show();
        printf("Status %s\n",p_status());
        return 0;
}
#endif

void p_lock(char **x)
{
        islocked = TRUE;
}
void p_unlock(char **x)
{
        islocked = FALSE;
}
char *p_show_blockn(int n)
{
        Block *b;
        Piece *p;
        int total=0;
	int i;
	int np=0;
	static char bf[BFSZ];

        for (b=fblock,i=0; (b!=NULL) && (i<n); b=b->next) i++;
	if (b==NULL) return "";
        for (p = b->start; p!=NULL; p = p->next) {
		if (!p->isfree) {total += p->size; np++;}
        }
	lprintf(bf,BFSZ-1,"  Block [%d] Used %8dk of %8dk in %d pieces, free %d %d \015\012",n,total/1000,b->size/1000,np,b->totfree,b->size-total);
	return bf;
}

void p_show(void)
{
        Block *b;
        Piece *p;
        int total=0;

        for (b=fblock; b!=NULL; b=b->next) {
                imsg("==Block size %d biggest %d upto %p \n",b->size,b->biggest,b->upto);
                total += b->size;
                for (p = b->start; p!=NULL; p = p->next) {
                        imsg("Piece %p size %d free %d last %p\n",p,p->size,p->isfree,p->last);
                }
        }
        imsg("Total pmalloced %d \n",total);
}
void p_show_block(Block *b)
{
        Piece *p;
        int total=0;

        imsg("Block size %d biggest %d upto %p \n",b->size,b->biggest,b->upto);
        total += b->size;
        for (p = b->start; p!=NULL; p = p->next) {
                imsg("Piece %p size %d free %d last %p\n",p,p->size,p->isfree,p->last);
        }
}
static int n_last,n_search;
static Block *lastb;
char **p_malloc(int sz)
{
        Block *b,*xb;
        int newsz;
	int isize;
        Piece *p;
	static int done_init=FALSE;
        if (sz==0) {emsg("Error: malloc zero bytes \n"); return NULL;}
        sz += 3;
        sz = sz & 0x5ffffffc;
        pstr_used += sz;
        if (pstr_used>pstr_peak) pstr_peak = pstr_used;
        if (fblock==NULL) {fblock = p_init(200000); lastb = fblock;}

	if (lastb!=NULL) {
		b = lastb;
                if (p_try1(b,&p,sz)) {
			n_last++;
			goto gotit;
		}
	}
	n_search++;
        for (b=fblock; b!=NULL; b=b->next) {
                xb = b;
		if (b->totfree<50000) continue; /* Dont try and squeeze the last bit in*/ 
       		if (b->totfree<sz) continue; /* This one won't fit*/ 
                if (p_try1(b,&p,sz)) goto gotit;
                p_compress1(b);
                if (p_try1(b,&p,sz)) goto gotit;
                p_compress2(b);
                if (p_try1(b,&p,sz)) goto gotit;
        }



/*        b = lastb;
        if (p_try1(b,&p,sz)) goto gotit;
        for (b=fblock; b!=NULL; b=b->next) {
                if (p_try1(b,&p,sz)) goto gotit;
        }

        dmsg("mem: Simple Compress\n");
        for (b=fblock; b!=NULL; b=b->next) {
                p_compress1(b);
                if (p_try1(b,&p,sz)) goto gotit;
        }
        dmsg("mem: Real Compress\n");
        for (b=fblock; b!=NULL; b=b->next) {
                xb = b;
                p_compress2(b);
                if (p_try1(b,&p,sz)) goto gotit;
        }
  */
        newsz = sz + 200000;
        if (newsz > 900000) newsz = 2000000;
	if (newsz < 900000) newsz = 900000;
	isize = atoi(config_get(C_ITEM_MAX));
	if (newsz<isize) newsz = isize+100000;
	if (!done_init) newsz = lib_str_start();
	done_init = TRUE;
	if (newsz<sz) newsz = sz + 100000;
        xb->next = p_init(newsz); b = xb->next;
        /* p_show(); */
        if (p_try1(b,&p,sz)) goto gotit;
        emsg("p_malloc failed\n"); exit(1);
gotit:
        lastb = b;
        return (char **) p;
}

void p_flush(void)
{
        Block *b,*last,*nextb;
	Piece *p,*nextp;
	int total,np;

	last = NULL;
	lastb = NULL;
        if (fblock==NULL) return;

	if (!config_true(C_STRINGS_STATIC)) 
		p_compress_between();
        for (b=fblock; b!=NULL; b=nextb) {
                p_compress1(b);
                p_compress2(b);
		nextb = b->next;
#ifdef FREE_STRINGS
		if (config_true(C_STRINGS_STATIC)) goto skipfree;
		total = 0;  np = 0;
	        for (p = b->start; p!=NULL; p = p->next) {
			if (!p->isfree) {total += p->size; np++;}
	        }

		nextb = b->next;
		if ( (b->totfree == b->size-total) && (b!=fblock) && (np==0) ) {
		 	imsgf("Freeing string block that is empty %p size %d\n",b,b->totfree);
		        pstr_total -= b->totfree;

		        for (p = b->start; p!=NULL; p = nextp) {
				nextp = p->next;
				myfree(p);
		        }
			if (last==NULL) emsg("Fault in block logic\n");
			else last->next = b->next;
			myfree(b->base);
			myfree(b);
		} else {
			last = b;
		}
skipfree:;
#endif
        }
}

Block *p_init(int sz)
{
        Block *b;
        pstr_total += sz;
        b = mymalloc(sizeof(Block));
        if (b==NULL) {emsg("malloc failed in pinit\n"); exit(3); }
	b->totfree = sz;
        b->size = sz;
        b->base = mymalloc(sz);
        if (b->base==NULL) {emsg("malloc failed in pinit base %d\n",sz); exit(3); }
        b->start = p_new(sz,b->base,b,NULL,NULL);
        b->upto = b->start;
        b->biggest = sz;
        b->next = NULL;
        return b;
}
static int n_first,n_notfirst;
int p_try1(Block *b, Piece **p, int sz) /* Try and find a block that is big enough */
{
        if (b->biggest<sz) return FALSE;
        *p = b->upto;
        if ((*p)->isfree) if ((*p)->size>=sz) {
		n_first++;
		return p_got(b,*p,sz);
	}
	n_notfirst++;
        for (*p = b->start; (*p)!=NULL; (*p) = (*p)->next) {
                if ((*p)->isfree) if ((*p)->size>=sz) return p_got(b,*p,sz);    
        }
        return FALSE;
}
int p_got(Block *b, Piece *p, int sz)
{
        p->next = p_split(p, sz);
        p->size = sz;
        p->islocked = FALSE;
        p->isfree = FALSE;
        b->upto = p->next;
        b->biggest = p->next->size; /* Not entirely true but safe enough*/
	b->totfree -= sz;
        return TRUE;
}
Piece *p_new(int sz, void *data, Block *b, Piece *last, Piece *next)
{
        Piece *n = mymalloc(sizeof(Piece));
        n->size = sz;
        n->block = b;
        n->magic = 12345;
        n->isfree = TRUE;
        n->data = data;
        n->last = last;
        n->next = next;
        if (next != NULL) {
                next->last = n;
        }
        return n;
}
Piece *p_split(Piece *p, int sz)
{
        Piece *n = p_new(p->size-sz,p->data+sz,p->block,p,p->next);
        return n;
}
void p_free(char **pp)
{
        Piece *p = (Piece *) pp;

        if (p->magic!=12345) {emsg("Panic in p_free, magic  = %d \n",p->magic); return;}
        if (p->isfree) {emsg("Panic in p_free\n"); return;}
        pstr_used -= p->size;
	p->block->totfree += p->size;

        p->isfree = TRUE;
        for (;p!=NULL;) {
                if (! p->isfree) break;
                if (p->next == NULL) break;
                if (p->next->isfree) p_unsplit(p,"freedelete");
                else break;
        }
        p = (Piece *) pp;
/*      if (p->block->biggest< p->size) p->block->biggest = p->size;*/
}

void p_unsplit(Piece *p, char *text)
{
        Piece *old = p->next;
/*      dmsg("mem: Unsplit (%s) %p - %p   %d %d \n",text,p,p->next,p->isfree,p->next->isfree);*/
        p->size += p->next->size;       
        if (p->block->biggest< p->size) p->block->biggest = p->size;
        p->next = p->next->next;
        if (p->next != NULL) p->next->last = p;
        myfree(old);
        p->block->upto = p;
}
int p_compress1(Block *b)
{
        Piece *p;

        /* Remove duplicate empty cells */      
        for (p = b->start; p!=NULL; p = p->next) {
again:          if (p->isfree) if (p->next!=NULL) if (p->next->isfree) {
                        p_unsplit(p,"compress1");
                        goto again;
                }
                if (p->isfree) if (b->biggest < p->size)  b->biggest = p->size;
        }
        b->upto = b->start;
        return TRUE;
}
Piece *p_find(Block *b, int sz) /* Try and find a block that is big enough */
{
	Piece *p;
        if (b->biggest<sz) {
		return NULL;
	}
        p = b->upto;
        if (p->isfree) if (p->size>=sz) return p;
        for (p = b->start; p!=NULL; p = p->next) {
                if (p->isfree) if (p->size>=sz) return p;
        }
        return NULL;
}

/* Move pieces between blocks to make more space */
void p_compress_between(void)
{
	/* For each block (except first one) if space exists
	in previous block for item, then copy it, and free it*/
	static int tlast;
        Block *b,*last;
	int lastbig;
	if (tlast+600>lib_gmtime()) return;
	tlast = lib_gmtime();
	/*p_show();*/
        if (fblock==NULL) return;
	lastb = NULL;
	last = fblock;
	lastbig = 0;
        for (b=fblock; b!=NULL; b=b->next) {	
again:		if (b->next==NULL) break;
		if (last==NULL) break; 
		lastbig = last->biggest;
		if (b->next!=NULL) p_compress3(last,b->next);
		if (last->biggest==lastbig) {
			last = last->next;
			if (last==b) continue;
			goto again;
		}
	}
	/*p_show();*/

}
void p_move(Piece *dst, Piece *src);
void p_compress3(Block *dst, Block *src)
{
        Piece *p,*found,*pnext;

	if (dst==src) {return;}
	p_compress1(dst);
	p_compress2(dst);
        for (p = src->start; p!=NULL; p = pnext) {
		pnext = p->next;
		if (!p->isfree) {
			found = p_find(dst,p->size);
			if (found!=NULL) p_move(found,p);
		}
	}

}
void p_move(Piece *dst, Piece *src)
{
	/* Swap them over, copy the data, then free src */
	/* and split destination */
	Piece save;

	memmove(dst->data,src->data,src->size);
	
	/* Copy item */
        dst->next = p_split(dst, src->size);
        dst->size = src->size;
        dst->block->upto = dst->next;
        dst->block->biggest = dst->next->size; /* Not entirely true but safe enough*/
	dst->block->totfree -= src->size;
	src->block->totfree += src->size;

	/* Now swap pointers so string pointers won't move. */
	src->isfree = TRUE;
	dst->isfree = FALSE;
	if (dst->last==NULL) dst->block->start = src;
	if (src->last==NULL) src->block->start = dst;
	if (dst->last!=NULL) dst->last->next = src;
	if (dst->next!=NULL) dst->next->last = src;
	if (src->last!=NULL) src->last->next = dst;
	if (src->next!=NULL) src->next->last = dst;
	save = *dst;
	*dst = *src;
	*src = save;
	src->block->upto = src->block->start;	
	dst->block->upto = dst->block->start;	
}

/* Move blocks around to make more space */
int p_compress2(Block *b)
{
        Piece *p,*dst,*pnext;
	
        if (islocked) {dmsg("mem: compress2 can't due to lock\n"); return FALSE;}
        /* Search for free one, then search for next used one and move it */    
        for (p = b->start; p!=NULL; p = p->next) {
                if (p->isfree) {
                        dst = p;
                        for (p=p->next; p!=NULL; p = pnext) {
				pnext = p->next;
                                if (p->isfree) p_unsplit(dst,"compress2"); else break;
                        }
                        if (p!=NULL) { /* Then move this real block to DST*/
                                memmove(dst->data,p->data,p->size);
                                p->data = dst->data;
                                dst->data = p->data+p->size;
                                p->last = dst->last;
                                if (dst->last ==NULL) b->start = p;
                                else dst->last->next = p;
                                if (p->next!=NULL) p->next->last = dst;
                                dst->next = p->next;
                                p->next = dst;
                                dst->last = p;
                        }
                }
                if (p==NULL) break;
        }
        b->upto = b->start;
        return TRUE;
}

/* Just an idea, not used yet. */
void *raw_malloc(int x)
{
        return malloc(x);
}
void raw_free(void *x)
{
        free(x);
}
char *p_status(void)
{
        static char bf[BFSZ];
        lprintf(bf,BFSZ-1,"%dk/%dk/%dk",pstr_used/1000,pstr_peak/1000,pstr_total/1000
		,n_last,n_search,n_first,n_notfirst);
/*        lprintf(bf,BFSZ-1,"%dk/%dk/%dk %d/%d %d/%d",pstr_used/1000,pstr_peak/1000,pstr_total/1000
		,n_last,n_search,n_first,n_notfirst);
		*/
        return bf;
}
