#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <libgen.h>
#include <math.h>
#include <mach/mach.h>
#include <sys/sysctl.h>
#include <string.h>

/* total memory is pages (free+active+inactive+wired)
 * "used" memory is pages (active+inactive+wired) - inaccurate but good enough...
 * "free" memory is pages free, unsurprisingly.
 * multiply by the size of a page to get the number in bytes
 */

unsigned int pageSize;
mach_port_t hostPort;
char bn[BUFSIZ],kflag=0,bflag=0,mflag=0,pflag=0,vflag=0;
useconds_t delay=0;
double inDelay=0.0;
extern int errno;

void displayOutput(void);
void displayBanner(void);
void usage(void);
void version(void);

int main (int argc, const char * argv[])
{
	char oc;
	pageSize=4096; //seems to be the default page size - see e.g. vm_stat(1)
	strncpy(bn,basename(argv[0]),BUFSIZ);
	// parse argv in here....
	while((oc=getopt(argc,argv,"bkmps:vV"))!=-1)
	{
		switch(oc)
		{
			case 'b':
				bflag=1;
				break;
			case 'k':
				kflag=1;
				break;
			case 'm':
				mflag=1;
				break;
			case 'p':
				pflag=1;
				break;
			case 's':
				if((sscanf(optarg,"%lf",&inDelay))==0)
				{
					usage();
					exit(EXIT_FAILURE);
				}
				break;
			case 'v':
				vflag=1;
				break;
			case 'V':
				version();
				exit(EXIT_SUCCESS);
				break;
			case '?':
			default:
				usage();
				exit(EXIT_FAILURE);
		}
	}
	if ((bflag && kflag) || (bflag && mflag) || (kflag && mflag) || (bflag && pflag) || (kflag && pflag) || (mflag && pflag))
	{
		usage();
		exit(EXIT_FAILURE);
	}
	if(bflag==0 && kflag==0 && mflag==0 && pflag==0) kflag=1;
	// .....
	hostPort=mach_host_self(); // connect to our own mach port, I think.
	// check the page size
	if(host_page_size(hostPort,&pageSize)!=KERN_SUCCESS)
	{
		fprintf(stderr,"%s: could not get page size: using default 4096\n",bn);
		pageSize=4096;
	}
	
	displayBanner();
	
	if(inDelay==0.0)
	{
		displayOutput();
	}
	else
	{
		delay=(useconds_t)floor(inDelay*1000000.0);
		while(1)
		{
			displayOutput();
			usleep(delay);
			if(vflag)
			{
				printf("\n");
				displayBanner();
			}
		}
	}

	exit(EXIT_SUCCESS);
}

void displayBanner(void)
{
	printf("%16s%12s%12s\n","total","used","free");
	return;
}

void displayOutput(void)
{
	unsigned int count=HOST_VM_INFO_COUNT;
	unsigned int totalMemory,usedMemory,freeMemory,denominator;
	vm_statistics_data_t vms; //eww...VMS
	struct xsw_usage xsu;
	size_t xsusiz=sizeof(xsu);
	int mib[2]={CTL_VM,VM_SWAPUSAGE};
	
	if(bflag) denominator=1;
	if(kflag) denominator=1024;
	if(mflag) denominator=1024*1024;
	if(pflag) denominator=pageSize; //how stupid is that?
	
	if(host_statistics(hostPort, HOST_VM_INFO, (host_info_t)&vms,&count) != KERN_SUCCESS)
	{
		fprintf(stderr,"%s: could not get VM stats: aborting\n",bn);
		exit(EXIT_FAILURE);
	}
	
	usedMemory=(vms.active_count+vms.inactive_count+vms.wire_count)*pageSize;
	freeMemory=vms.free_count*pageSize;
	totalMemory=usedMemory+freeMemory;
	
	printf("Mem:%12u%12u%12u\n",totalMemory/denominator,usedMemory/denominator,freeMemory/denominator);

	errno=0;
	if(sysctl(mib,2,&xsu,&xsusiz,NULL,0)==-1)
	{
		fprintf(stderr,"%s: could not get swap usage: %s\n",bn,strerror(errno));
		exit(EXIT_FAILURE);
	}
	printf("Swap:%11llu%12llu%12llu\n",xsu.xsu_total/denominator,xsu.xsu_used/denominator,xsu.xsu_avail/denominator);
	
	if(vflag)
	{
		printf("pageins: %7u\tpageouts:%7u\n",vms.pageins,vms.pageouts);
	}
	return;
}

void usage(void)
{
	printf("%s [-b|-k|-m|-p] [-s delay] [-vV]\n\n",bn);
	printf("display free memory on the system.\n");
	printf("-b\tdisplay output in bytes\n");
	printf("-k\tdisplay output in kilobytes\n");
	printf("-m\tdisplay output in megabytes\n");
	printf("-p\tdisplay output in #pages\n");
	printf("-s delay\tprint output every delay seconds\n");
	printf("-v\tinclude pagein/pageout statistics\n");
	printf("-V\tprint version information\n");
}

void version(void)
{
	printf("Darwin free version 1.0\n");
	printf("By Graham J. Lee\n");
}