/* trade.c. This program allows users to interact
	with the WWWWS system. Set these #defines
	appropriately before compiling. */

#define DATA_PATH "/CHANGE/THIS/PATH/wwwws"
#define PROGRAM_URL "/CHANGE/THIS/URL/cgi-bin/trade"

#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "parse.h"
#include "cgic.h"
#include "gd.h"
#include "chart.h"

#define PATH_SPACE 256
#define STRING_SPACE 256
#define HISTORY_SIZE 30

int getFunds(float *fundsP);

int setFunds(float funds);

int getStockPrice(char *sym, float *price);

int getStockHistory(char *sym, float *history, int *historySizeP);

int getStockShares(char *sym, int *sharesP);

int setStockShares(char *sym, int shares);

int Trade();
int History(char *symbol);
int HistoryChart(char *symbol);
int Transactions();
int Portfolio();
int Newspaper();

char *accountName;

int cgiMain() {
	time_t now;
	time(&now);
	srand((int) now);
	accountName = cgiRemoteUser;
	if (!strcmp(accountName, "")) {
		/* This shouldn't happen! But it will happen
			if the program is not installed according
			to the instructions, or if the server
			has bugs regarding user authentication. */
		fprintf(cgiOut, "Pragma: no-cache\n");
		cgiHeaderContentType("text/html");
		fprintf(cgiOut, "<html>\n");
		fprintf(cgiOut, 
			"<head><title>User Not Authenticated</title></head>\n");
		fprintf(cgiOut, "<body><h1>User Not Authenticated</h1>\n");
		fprintf(cgiOut, "The trade program was not installed in\n");
		fprintf(cgiOut, "a password-authenticated directory, or\n");
		fprintf(cgiOut, "there is a bug in the web server.\n");
		fprintf(cgiOut, "Please report this problem to the\n");
		fprintf(cgiOut, "site administrator.\n");
		fprintf(cgiOut, "</body></html>\n");
		return 1;
	}
	if (!strcmp(cgiPathInfo, "/trade")) {
		return Trade();
	} else if (!strncmp(cgiPathInfo, "/history/", 9)) {
		return History(cgiPathInfo + 9);
	} else if (!strncmp(cgiPathInfo, "/historychart/", 14)) {
		/* Sigh, remove the image.gif thing at the end */
		char *s = (char *) malloc(strlen(cgiPathInfo + 14) + 1);
		char *im;
		int result;
		strcpy(s, cgiPathInfo + 14);
		im = strstr(s, "/image");
		if (im) {
			*im = '\0';
		}		
		result = HistoryChart(s);
		free(s);
	} else if (!strcmp(cgiPathInfo, "/transactions")) {	
		return Transactions();
	} else if (!strcmp(cgiPathInfo, "/newspaper")) {
		return Newspaper();
	} else {
		return Portfolio();
	}
}

void DatabaseMissing();

char *directions[] = {
	"buy",
	"sell"
};

void StartTransaction();

void EndTransaction();

void TransactionLog(char *text);

void stockBuy(char *symbol, int shares);

void stockSell(char *symbol, int shares);

int Trade() {
	FILE *in;
	char symbol[STRING_SPACE], priceString[STRING_SPACE], attr[STRING_SPACE];
	char s[PATH_SPACE];
	int shares;
	float funds;
	SetFieldSeparator(' ');
	sprintf(s, "%s/database", DATA_PATH);
	in = fopen(s, "r");
	if (!in) {
		DatabaseMissing();
		return 0;
	}
	StartTransaction();
	if (cgiFormString("newbuy symbol", symbol, STRING_SPACE) 
		!= cgiFormNotFound) {
		cgiFormInteger("newbuy shares", &shares, 0);
		if (shares) {
			stockBuy(symbol, shares);
		}	
	}
	do {
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		sprintf(attr, "%s shares", symbol);
		cgiFormInteger(attr, &shares, 0);
		if (shares > 0) {	
			int direction;
			sprintf(attr, "%s direction", symbol);
			cgiFormRadio(attr, directions, 2, &direction, -1);
			if (direction == -1) {
				/* Not a complete submission. 
					Don't guess! There's serious
					play money at stake here. */
				char s[STRING_SPACE];
				sprintf(s, 
					"Please specify buy or sell for %s. No transaction performed.", symbol);
				TransactionLog(s);
				EndTransaction();
				return 0;
			}
			if (direction == 0) {
				stockBuy(symbol, shares);
			} else {
				stockSell(symbol, shares);
			}
		} else if (shares == 0) {
			/* OK, do nothing */
		} else if (shares < 0) {
			char s[STRING_SPACE];
			sprintf(s, "%s: negative numbers of shares are not accepted for trades. Enter a positive number and select buy or sell.", symbol);
			TransactionLog(s);
		}				
	} while (NextRecord(in));
	fclose(in);
	EndTransaction();
	return Portfolio();
}	

void stockBuy(char *symbol, int shares) {
	/* A purchase. Can we afford it? */	
	float purchasePrice;	
	float price;
	float funds;
	if (!getFunds(&funds)) {
		char s[STRING_SPACE];
		sprintf(s, 
			"Unable to access your account. Please contact the site administrator.");
		TransactionLog(s);
		return;
	}
	if (!getStockPrice(symbol, &price)) {
		char s[STRING_SPACE];
		sprintf(s, 
			"Stock symbol %s is not in the database.",
			symbol);
		TransactionLog(s);
		return;
	}
	purchasePrice = shares * price;
	if (purchasePrice > funds) {
		/* No */
		char s[STRING_SPACE];
		sprintf(s, 
			"Insufficient funds to purchase %d shares of %s!", 
			shares, symbol);
		TransactionLog(s);
	} else {
		int currentShares;
		char s[STRING_SPACE];
		getStockShares(symbol, &currentShares);
		/* An ideal universe with no commissions! */
		currentShares += shares;	
		funds -= purchasePrice;
		setStockShares(symbol, currentShares);
		
		sprintf(s, 
			"Purchased %d shares of %s at %.3f.", shares, 
			symbol, price);
		TransactionLog(s);
	}
	setFunds(funds);
}

void stockSell(char *symbol, int shares) {
	/* A sale. Do we have that many shares? */
	float salePrice;	
	int currentShares;
	float price;
	float funds;
	if (!getFunds(&funds)) {
		char s[STRING_SPACE];
		sprintf(s, 
			"Unable to access your account. Please contact the site administrator.");
		TransactionLog(s);
		return;
	}	
	if (!getStockPrice(symbol, &price)) {
		char s[STRING_SPACE];
		sprintf(s, 
			"Stock symbol %s is not in the database.",
			symbol);
		TransactionLog(s);
		return;
	}	
	salePrice = shares * price;
	getStockShares(symbol, &currentShares);
	if (currentShares < shares) {
		/* No */
		char s[STRING_SPACE];
		sprintf(s, 
			"Can't sell %d shares of %s: you only have %d shares!",
			 shares, symbol, currentShares);
		TransactionLog(s);
	} else {
		char s[STRING_SPACE];
		/* An ideal universe with no commissions! */
		currentShares -= shares;	
		funds += salePrice;
		setStockShares(symbol, currentShares);
		sprintf(s, 
			"Sold %d shares of %s at %.3f.", shares, symbol, price);
		TransactionLog(s);
	}
	setFunds(funds);
}

void DatabaseMissing() {
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("text/html");
	fprintf(cgiOut, "<html><head><title>Database Missing</title></head>\n");
	fprintf(cgiOut, "<body><h1>Database Missing</h1>\n");
	fprintf(cgiOut, "Please contact the site administrator.\n");
	fprintf(cgiOut, "</body></html>\n");
}

FILE *currentOut;
FILE *permanentOut;

void StartTransaction() {
	char s[PATH_SPACE];
	sprintf(s, "%s/%s.cur", DATA_PATH, accountName);
	currentOut = fopen(s, "w");
	sprintf(s, "%s/%s.log", DATA_PATH, accountName);
	permanentOut = fopen(s, "a");
}

void EndTransaction() {
	fprintf(currentOut, "End of transaction.<p>\n");
	fclose(currentOut);
	fclose(permanentOut);
}

void TransactionLog(char *text) {
	time_t now;
	time(&now);
	fprintf(currentOut, "%s<p>\n", text);
	fprintf(permanentOut, "%s Time: %s<p>", text, ctime(&now));
}

int History(char *symbol) 
{
	float history[HISTORY_SIZE];
	int size;
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("text/html");
	fprintf(cgiOut, "<html>\n");		
	fprintf(cgiOut, "<head><title>Price History: %s</title></head>\n",
		symbol);
	fprintf(cgiOut, "<body><h1>Price History: %s</h1>\n", symbol);
	if (!getStockHistory(symbol, history, &size)) {
		fprintf(cgiOut, 
			"<h2>There is no stock with that ticker symbol.</h2>\n");
	} else {
		/* Add a random component to the URL in order to
			prevent web browsers from caching the chart
			when it is out of date. */
		fprintf(cgiOut,
			"<img src=\"%s/historychart/%s/image%d.gif\"><p>\n",
			PROGRAM_URL, symbol, abs((rand() >> 8) % 1000));
	}
	fprintf(cgiOut,
		"Prices for the past 30 days are shown.\n");
	fprintf(cgiOut,
		"<hr><a href=\"%s/portfolio\">Portfolio</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut,
		"<a href=\"%s/newspaper\">Newspaper</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut,
		"<a href=\"%s/transactions\">Past Transactions</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut, "</body></html>\n");
	return 0;
}

int HistoryChart(char *symbol) 
{
	float history[HISTORY_SIZE];
	char *labels[HISTORY_SIZE];
	int size;
	int i;
	gdImagePtr im;
	im = gdImageCreate(400, 200);
	getStockHistory(symbol, history, &size);	
	for (i = 0; (i < size); i++) {
		labels[i] = (char *) malloc(20);
		sprintf(labels[i], "%d", size - i - 1);
	}
	chartDraw(im, chartBar | chartCross | chartLine,
		labels, "Stock Price", history, size); 
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("image/gif");
	gdImageGif(im, cgiOut);
	gdImageDestroy(im);
}

int Transactions() {
	int ch;
	FILE *in;
	char s[PATH_SPACE];
	sprintf(s, "%s/%s.log", DATA_PATH, accountName);
	in = fopen(s, "r");
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("text/html");
	fprintf(cgiOut, "<html>\n");		
	fprintf(cgiOut, "<head><title>Past Transactions</title></head>\n");
	fprintf(cgiOut, "<body><h1>Past Transactions</h1>\n");
	if (!in) {
		fprintf(cgiOut, "<h2>No transactions to date.</h2>\n");
	} else {
		while(1) {
			ch = getc(in);
			if (ch == EOF) {
				break;
			}
			putc(ch, cgiOut);
		}
		fclose(in);
	}
	fprintf(cgiOut,
		"<hr><a href=\"%s/portfolio\">Portfolio</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut,
		"<a href=\"%s/newspaper\">Newspaper</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut, "</body></html>\n");
	return 0;
}

int Newspaper() {
	FILE *in;
	int found = 0;
	char s[PATH_SPACE];
	float history[HISTORY_SIZE];
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("text/html");
	fprintf(cgiOut, "<html>\n");		
	fprintf(cgiOut, "<head><title>Financial News</title></head>\n");
	fprintf(cgiOut, "<body><h1>Financial News</h1>\n");	
	sprintf(s, "%s/database", DATA_PATH);
	in = fopen(s, "r");
	if (!in) {
		return 0;
	}
	SetFieldSeparator(' ');
	do {
		char symbol[STRING_SPACE];
		char priceString[STRING_SPACE];		
		int historyPos = 0;
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		while (GetField(in, priceString, STRING_SPACE)) {
			history[historyPos++] = atof(priceString);
		}	
		if (historyPos) {
			fprintf(cgiOut, "%s: trading at %.3f", 
				symbol, history[historyPos-1]);
			if (historyPos >= 2) {
				float move = history[historyPos-1] -
					history[historyPos-2];
				if (move == 0.0) {
					fprintf(cgiOut, " unchanged");
				} else if (move > 0.0) {
					fprintf(cgiOut, " up %.3f", move);
				} else {
					fprintf(cgiOut, " down %.3f", move);
				}
			}
			fprintf(cgiOut, "<a href=\"%s/history/%s\">\n",
				PROGRAM_URL, symbol);
			fprintf(cgiOut, "See History</a><p>\n");
		}	
	} while (NextRecord(in));
	fclose(in);
	fprintf(cgiOut,
		"<hr><a href=\"%s/portfolio\">Portfolio</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut,
		"<a href=\"%s/transactions\">Past Transactions</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut, "</body></html>\n");
	return 0;
}

int Portfolio() {
	float funds;
	float price;
	FILE *in;
	int shares;
	float total = 0.0;
	char s[PATH_SPACE];
	char fundsString[STRING_SPACE];
	fprintf(cgiOut, "Pragma: no-cache\n");
	cgiHeaderContentType("text/html");
	fprintf(cgiOut, "<html>\n");		
	fprintf(cgiOut, "<head><title>Portfolio</title></head>\n");
	fprintf(cgiOut, "<body><h1>Portfolio</h1>\n");	
	sprintf(s, "%s/%s.cur", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (in) {
		int ch;
		fprintf(cgiOut, "<h2>Transaction Report</h2>\n");
		while(1) {
			ch = getc(in);
			if (ch == EOF) {
				break;
			}
			putc(ch, cgiOut);
		}	
		fclose(in);
		unlink(s);
	}
	
	sprintf(s, "%s/%s.dat", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (!in) {
		/* Bad, bad news */
		fprintf(cgiOut, "<h2>No database record available</h2>\n");
		fprintf(cgiOut, "Your database entry is not available.\n");
		fprintf(cgiOut, "Please contact the site administrator.\n");
		fprintf(cgiOut, "</body></html>\n");
		return 0;
	}
	SetFieldSeparator(' ');
	GetField(in, fundsString, STRING_SPACE);
	funds = atof(fundsString);
	fprintf(cgiOut, "<body><h3>Current Holdings</h3>\n");
	fprintf(cgiOut, "<body><h3>Funds Available: %.3f</h3>\n", funds);	
	fprintf(cgiOut, "<form method=POST action=\"%s/trade\">\n", 
		PROGRAM_URL);
	fprintf(cgiOut, "<table border=1>\n");
	fprintf(cgiOut, "<tr><th>Stock Ticker</th>\n");
	fprintf(cgiOut, "<th>Shares Held</th>\n");
	fprintf(cgiOut, "<th>Current Price</th>\n");
	fprintf(cgiOut, "<th>Total Value</th>\n");
	fprintf(cgiOut, "<th colspan=3>Shares to Trade</th></tr>\n");
	if (!NextRecord(in)) {
		fprintf(cgiOut, 
			"<tr><th colspan=7>No current holdings.</th></tr>\n");
	} else {
		do {
			char symbol[STRING_SPACE];
			char sharesString[STRING_SPACE];		
			if (!GetField(in, symbol, STRING_SPACE)) {
				break;
			}
			if (!GetField(in, sharesString, STRING_SPACE)) {
				break;
			}
			shares = atoi(sharesString);
			getStockPrice(symbol, &price);
			fprintf(cgiOut, "<tr><td>%s</td><td>%d</td><td>%.3f</td>",
				symbol, shares, price);
			fprintf(cgiOut, "<td>%.3f</td>\n", shares * price);
			total += shares * price;
			fprintf(cgiOut, "<td>\n");
			fprintf(cgiOut,
				"<input type=text name=\"%s shares\" value=\"0\">\n",
				symbol);
			fprintf(cgiOut, "</td>\n");
			fprintf(cgiOut, 
				"<td><input type=radio value=buy name=\"%s direction\" checked>buy</td>\n",
				symbol);
			fprintf(cgiOut, 
				"<td><input type=radio value=sell name=\"%s direction\">sell</td>\n",
				symbol);
			fprintf(cgiOut, "</td></tr>\n");
		} while (NextRecord(in));
	}
	fclose(in);
	fprintf(cgiOut, "<tr><th>Total Value</th><td colspan=2></td>\n");
	fprintf(cgiOut, "<td>%.3f</td><td colspan=3></td></tr>\n", total);
	fprintf(cgiOut, "</table>\n");
	fprintf(cgiOut, "Acquire new stock: \n");
	fprintf(cgiOut, "<input type=text name=\"newbuy symbol\">\n");
	fprintf(cgiOut, 
		"Shares: <input type=text name=\"newbuy shares\"><p>\n");
	fprintf(cgiOut, "<input type=submit value=\"Submit New Transactions\">\n");
	fprintf(cgiOut, "</form><p>\n");
	fprintf(cgiOut,
		"<a href=\"%s/newspaper\">Newspaper</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut,
		"<a href=\"%s/transactions\">Past Transactions</a> \n",
		PROGRAM_URL);
	fprintf(cgiOut, "</body></html>\n");
	return 0;
}

int getStockPrice(char *sym, float *priceP) {
	FILE *in;
	int found = 0;
	char s[PATH_SPACE];
	sprintf(s, "%s/database", DATA_PATH);
	in = fopen(s, "r");
	if (!in) {
		return 0;
	}
	SetFieldSeparator(' ');
	do {
		char symbol[STRING_SPACE];
		char priceString[STRING_SPACE];		
		float price;
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		if (strcmp(symbol, sym)) {
			/* Not the right record */
			continue;
		}
		while (GetField(in, priceString, STRING_SPACE)) {
			price = atof(priceString);
			found = 1;
		}	
		if (found) {
			*priceP = price;
			break;
		}
	} while (NextRecord(in));
	fclose(in);
	return found;
}

int getStockHistory(char *sym, float *history, int *historySizeP) {
	FILE *in;
	int found = 0;
	int historyPos = 0;
	char s[PATH_SPACE];
	sprintf(s, "%s/database", DATA_PATH);
	in = fopen(s, "r");
	if (!in) {
		return 0;
	}
	SetFieldSeparator(' ');
	do {
		char symbol[STRING_SPACE];
		char priceString[STRING_SPACE];		
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		if (strcmp(symbol, sym)) {
			/* Not the right record */
			continue;
		}
		found = 1;
		while (GetField(in, priceString, STRING_SPACE)) {
			history[historyPos++] = atof(priceString);
		}	
		break;
	} while (NextRecord(in));
	fclose(in);
	*historySizeP = historyPos;
	return found;
}

int getFunds(float *fundsP) {
	FILE *in;
	char s[PATH_SPACE];
	char fundsString[STRING_SPACE];
	sprintf(s, "%s/%s.dat", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (!in) {
		*fundsP = 0;
		return 0;
	}
	SetFieldSeparator(' ');
	if (!GetField(in, fundsString, STRING_SPACE)) {
		fclose(in);
		*fundsP = 0;
		return 0;	
	}
	*fundsP = atof(fundsString);
	fclose(in);
	return 1;
}

int setFunds(float newFunds) {
	FILE *in, *out;
	int found = 0;
	float funds;
	char s[PATH_SPACE], snew[PATH_SPACE];
	char fundsString[STRING_SPACE];		
	sprintf(s, "%s/%s.dat", DATA_PATH, accountName);
	sprintf(snew, "%s/%s.new", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (!in) {
		return 0;
	}
	out = fopen(snew, "w");
	if (!out) {
		fclose(in);
		return 0;
	}
	SetFieldSeparator(' ');
	if (!GetField(in, fundsString, STRING_SPACE)) {
		fclose(in);
		return 0;
	}
	funds = atof(fundsString);	
	fprintf(out, "%.3f\n", newFunds);
	while (NextRecord(in)) {
		char symbol[STRING_SPACE];
		char sharesString[STRING_SPACE];		
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		if (!GetField(in, sharesString, STRING_SPACE)) {
			break;
		}
		fprintf(out, "%s %s\n", symbol, sharesString);
	} 
	fclose(in);
	fclose(out);
	rename(snew, s);
	return 1;
}

int getStockShares(char *sym, int *sharesP) {
	FILE *in;
	int found = 0;
	int shares;
	char s[PATH_SPACE];
	sprintf(s, "%s/%s.dat", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (!in) {
		*sharesP = 0;
		return 0;
	}
	SetFieldSeparator(' ');
	/* Skip over the first record, which contains this customer's funds */
	*sharesP = 0;
	while (NextRecord(in)) {
		char symbol[STRING_SPACE];
		char sharesString[STRING_SPACE];		
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		if (strcmp(symbol, sym)) {
			/* Not the right record */
			continue;
		}
		found = 1;
		if (!GetField(in, sharesString, STRING_SPACE)) {
			break;
		}
		shares = atoi(sharesString);
		break;
	} 
	fclose(in);
	if (!found) {
		*sharesP = 0;
	}
	*sharesP = shares;
	return found;
}

int setStockShares(char *sym, int shares) {
	FILE *in, *out;
	int found = 0;
	float funds;
	char s[PATH_SPACE], snew[PATH_SPACE];
	char fundsString[STRING_SPACE];		
	sprintf(s, "%s/%s.dat", DATA_PATH, accountName);
	sprintf(snew, "%s/%s.new", DATA_PATH, accountName);
	in = fopen(s, "r");
	if (!in) {
		return 0;
	}
	out = fopen(snew, "w");
	if (!out) {
		fclose(in);
		return 0;
	}
	SetFieldSeparator(' ');
	/* Handle the first record, which contains this customer's funds */
	if (!GetField(in, fundsString, STRING_SPACE)) {
		fclose(in);
		return 0;
	}
	funds = atof(fundsString);	
	fprintf(out, "%.3f\n", funds);
	while (NextRecord(in)) {
		char symbol[STRING_SPACE];
		char sharesString[STRING_SPACE];		
		if (!GetField(in, symbol, STRING_SPACE)) {
			break;
		}
		if (!GetField(in, sharesString, STRING_SPACE)) {
			break;
		}
		if (strcmp(symbol, sym)) {
			/* Not the right record */
			fprintf(out, "%s %s\n", symbol, sharesString);
			continue;
		} else {
			fprintf(out, "%s %d\n", sym, shares);
			found = 1;
		}
	}
	fclose(in);
	if (!found) {
		fprintf(out, "%s %d\n", sym, shares);
	}
	fclose(out);
	rename(snew, s);
	return 1;
}
