/*
 * garbage collector program copyright (C) 2009 - 2013 H.Niwa 
 */

/*
 * 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 2, 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>

#include <string>
#include <complex>

#include "syserr.h"

#include "bin_node.h"
#include "gc.h"
#include "var.h"
#include "pred.h"
#include "context.h"
#include "builtin.h"
#include "sysmodule.h"
#include "unify.h"
#include "context.h"
#include "gc.h"
#include "module.h"

const char* proc_meminfo = "/proc/meminfo";
int MemInfo(int& free, int& active);

void do_gc(Node* bd);

void GCmark(Node* nd)
{
	
	nd->gcmark();
}

void freeListNode()
{
	if (FreeListNodes == NULL) return;
	
	Node* nd, * ndold;

	for (nd = FreeListNodes; nd != NULL; ) {
		ndold = nd;
		nd = nd->nextnode;
		delete ndold;
	}
	FreeListNodes = NULL;
}

void freeAtomNode()
{
	if (FreeAtomNodes == NULL) return;
	
	Node* nd, * ndold;

	for (nd = FreeAtomNodes; nd != NULL; ) {
		ndold = nd;
		nd = nd->nextnode;
		delete ndold;
	}
	FreeAtomNodes = NULL;
}

void freePredNode()
{
	if (FreePredNodes == NULL) return;
	
	Node* nd, * ndold;

	for (nd = FreePredNodes; nd != NULL; ) {
		ndold = nd;
		nd = nd->nextnode;
		delete ndold;
	}
	FreePredNodes = NULL;
}

void freeVarNode()
{
	if (FreeVarNodes == NULL) return;
	
	Node* nd, * ndold;

	for (nd = FreeVarNodes; nd != NULL; ) {
		ndold = nd;
		nd = nd->nextnode;
		delete ndold;
	}
	FreeVarNodes = NULL;
}

void freeUndefNode()
{
	if (FreeUndefNodes == NULL) return;
	
	Node* nd, * ndold;

	for (nd = FreeUndefNodes; nd != NULL; ) {
		ndold = nd;
		nd = nd->nextnode;
		delete ndold;
	}
	FreeUndefNodes = NULL;
}


void GC()
{
	GC(Nil);
}


struct timeval gctv_last;
struct timeval gctv_long_last;
struct timeval gctv_now;

void GC(Node* bd)
{
	Node*	nd;
	Node*	ndprev;

	extern 	int PROCN;
	
// return;

	gettimeofday(&gctv_now, NULL);
	
	double gclast = gctv_last.tv_sec + (double)gctv_last.tv_usec/1000000.0;
	double gcnow = gctv_now.tv_sec + (double)gctv_now.tv_usec/1000000.0;
	if (gcnow - gclast < 1.0) {
		return;
	} else {
		gctv_last = gctv_now;
	}

	int free, active;
	if (MemInfo(free, active) >= 0) {
		double MemUseRate = (double)active/(double)free;
//fprintf(stderr, "active %d total %d MemUseRate %g\n", active, free, MemUseRate);

		if (MemUseRate < 0.3/PROCN) { 
			if (gctv_now.tv_sec - gctv_long_last.tv_sec > 10) {
				gctv_long_last = gctv_now;
				freePredNode();
				freeVarNode();
				freeUndefNode();
				freeAtomNode();
				freeListNode();
				return;
			} else {
				return;
			}
		} else {
			freeVarNode();
			freeUndefNode();
			if (MemUseRate > 0.6/PROCN) { 
				freePredNode();
				freeAtomNode();
				freeListNode();
			}
		}
	}

	do_gc(bd);
}

void GC_FORCE()
{
	do_gc(Nil);

	freePredNode();
	freeVarNode();
	freeUndefNode();
	freeAtomNode();
	freeListNode();

//	fprintf(stderr, "GC_FORCE %d\n", CountNode());
}

void do_gc(Node* bd)
{
	Node*	nd;
	Node*	ndprev;

	extern Atom AllNodesBody;

	// clear mark
	for (nd = AllNodes->nextnode; nd != NULL; nd = nd->nextnode) {
		nd->ref = 0;
	}

	AllNodesBody.ref = 1;
	AllNodes->ref = 1;
	
	GCmark(AllNodes);
	GCmark(True);
	GCmark(Nil);
	GCmark(__UNDEF__);
	GCmark(ErrGoal);
	GCmark(dlibpathnode);

	GCmark(bd);

	GCmark(Module);

	Context* cxp;
	for (cxp = CXNode; cxp != NULL; cxp = cxp->Next()) {
		GCmark(cxp->inherit);
		GCmark(cxp->env_stack);
		GCmark(cxp->env);
		GCmark(cxp->misc_stack);
		GCmark(cxp->modulename);
		GCmark(cxp->ode);
		GCmark(cxp->integral);
	}

	AllNodesBody.ref = 1;
	AllNodes->ref = 1;

	// free node
	for (ndprev = AllNodes, nd = AllNodes->nextnode; 
			nd != NULL; ) {
				
		if (nd->ref) {
			ndprev = ndprev->nextnode;
			nd = nd->nextnode;
		} else {
			Node* ndold = nd;
			nd = ndprev->nextnode = nd->nextnode;
			switch (ndold->kind()) {
			case LIST:
				ndold->nextnode = FreeListNodes;
				FreeListNodes = ndold;
				break;
			case ATOM:
				ndold->nextnode = FreeAtomNodes;
				FreeAtomNodes = ndold;
				break;
			case PRED:
				ndold->nextnode = FreePredNodes;
				FreePredNodes = ndold;
				break;
			case VAR:
				ndold->nextnode = FreeVarNodes;
				FreeVarNodes = ndold;
				break;
			case UNDEF:
				ndold->nextnode = FreeUndefNodes;
				FreeUndefNodes = ndold;
				break;
			default:
				delete ndold;
				break;
			}
		}
	}
}

int CountNode()
{
	Node*	nd;
	int	n;

	n = 0;
	for (nd = AllNodes; nd != NULL; nd = nd->nextnode) {
		n++;
	}

	return n;
}


#ifndef __MINGW32__
int MemInfo(int& total, int& active)
{
	FILE*	fd;
	char	buf[80];

	total = active = 0;
		
	fd = fopen(proc_meminfo, "r");
	if (fd == NULL) {
		return -1;
	}

	for (;;) {
		if (fgets(buf, 80-1, fd) == NULL) {
			fclose(fd);
			return -1;
		}

		buf[80-1] = 0;

		char* term = strtok(buf, " ");
		if (term == NULL) {
			return -1;
		}
		
		char* valstr = strtok(NULL, " ");
		if (valstr == NULL) {
			return -1;
		}

		long val = strtol(valstr, NULL, 10);

		if (strncmp(term, "MemTotal:", 9) == 0) {
			total = val;
#ifdef __CYGWIN__
		} else if (strncmp(term, "MemFree:", 7) == 0) {
			active = total - val;
			break;
#else /* __CYGWIN__ */
		} else if (strncmp(term, "Active:", 7) == 0) {
			active = val;
			break;
#endif /* __CYGWIN__ */
		}
	}
	
	fclose(fd);

	return 0;
}

#endif /* __MINGW32__ */

