/**
  * Ben Sowell
  * 6 - 23 - 06
  */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>

struct Bidder
{
	double budget;
	double T;
};

struct Bidder  *bidders;
int *subsetArray;
int numBidders;

/* File to write data to if specified
  on the command line */
FILE *out;
int output;

enum MODE_TYPE  {MEHTA, MAX_BUDGET_LEFT, MIN_BUDGET_LEFT, MAX_BID};
typedef enum MODE_TYPE modeType;
modeType mode;

/**
 * Generates the specified number of bidders with budgets
 * chosen from a uniform random distribution between 
 * minBudget and maxBudget.  Also initializes the subsetArray, 
 * which  will be used to generate a random subset of bidders. 
 * If output is 1, then each budget will be written to the out 
 * file.
 * 
 * @param  num  The number of bidders to generate.
 * @param  minBudget  The smallest possible budget.
 * @param  maxBudget  The largest possible budget.
 */
void generateBidders(int num, double minBudget, double maxBudget)
{
	numBidders = num;

	if(output)
		fprintf(out, "%d\n", numBidders);
   
	int i;
	for(i = 0; i < num; i++)
	{
		bidders[i].budget = minBudget +  (maxBudget - minBudget) * 
				(double) rand() / (double) (RAND_MAX + 1.0);
		bidders[i].T = 0;
		subsetArray[i] = i;

		if(output)
			fprintf(out, "%g\n", bidders[i].budget);
	}
}

/**
 * Generates a random bidder id that is distinct from
 * all previously generated ids. This is done by generating
 * a random subset of integers between 0 and numBidders - 1. 
 *
 * @param  index  The index of the subset element we wish to access.  
 *				   (which is generated on the fly).
 * @return  A random bidder ID. 
 */

int getRandomBidderID(int index)
{	
	int swapNum = (int) (numBidders - index) *
		(double) rand() / (double) (RAND_MAX + 1.0) + index;
	
	int temp = subsetArray[index];
	subsetArray[index] = subsetArray[swapNum];
	subsetArray[swapNum] = temp;
	
	return subsetArray[index];
}

/**
 * Computes the revenue generated by a single query, and updates the
 *  appropriate bidders. If in is NULL, then numBids bidders are randomly 
 * selected each bidding random amount between minBidSize and maxBidSize. 
 * Otherwise, the individual bids are read from in. 
 *
 * @param  num  The number of bids to generate.
 * @param  min  The minimum bid size. 
 * @param  max  The maximum bid size. 
 * @return  The revenue made from this query. 
 */
double getQueryRevenue(FILE *in, int numBids, double minBidSize, double maxBidSize)
{
	int maxBidderID = 0, bidderID = 0;
	double bid = 0.0, maxBid = 0.0, bidTest = 0.0;
	double maxBidTest = (mode == MIN_BUDGET_LEFT) ? 100000 : 0.0;
	double budgetLeft;
	struct Bidder currentBidder;

	if(output) 
		fprintf(out, "%d ", numBids);

	int i;
	for(i = 0; i < numBids; i++)
	{

		/* If in is null, generate the bids at random, otherwise
		   read them from a file */
		if(in == NULL)
		{
			bidderID = getRandomBidderID(i);
			
			bid = (maxBidSize - minBidSize) * 
				(double) rand() / (double) (RAND_MAX + 1.0) + minBidSize;
		}	
		else 
		{
			fscanf(in, "%d %lg", &bidderID, &bid);
		}

		currentBidder = bidders[bidderID];

		if(output)
			fprintf(out, "%d %g ", bidderID, bid);

		switch(mode)
		{

		  	/* MEHTA algorithm */
			case MEHTA:
				bidTest = bid * (1 - exp(currentBidder.T - 1));
				if(bidTest > maxBidTest)
				{
						maxBidTest = bidTest;
						maxBidderID = bidderID;
						maxBid = bid;
				}
				break;

			/* Max Bid algorithm */
			case MAX_BID:
				budgetLeft = currentBidder.budget 
					- currentBidder.budget * currentBidder.T;

				if(bid > maxBid && budgetLeft >= bid)
				{
						maxBid = bid;
						maxBidderID = bidderID;
						maxBidTest = budgetLeft;
				}
				/* Break ties according to the max remaining budget */
				else if(bid == maxBid && budgetLeft > maxBidTest && budgetLeft >= bid)
				{
					maxBidderID = bidderID;
					maxBidTest = budgetLeft;					
				}
				break;

      		/* Max Budget left algorithm */
			case MAX_BUDGET_LEFT:
				budgetLeft = currentBidder.budget 
					- currentBidder.budget * currentBidder.T;

				if(budgetLeft > maxBidTest && budgetLeft >= bid)
				{
					maxBidTest = budgetLeft;
					maxBidderID = bidderID;
					maxBid = bid;
				}
				break;

     		/* Min Budget left algorithm */
			case MIN_BUDGET_LEFT:
				budgetLeft = currentBidder.budget
					- currentBidder.budget * currentBidder.T;

				if(budgetLeft < maxBidTest && budgetLeft >= bid)
				{
					maxBidTest = budgetLeft;
					maxBidderID = bidderID;
					maxBid = bid;
				}
				break;
		}
	}
	if(output)
		fprintf(out, "\n");

	bidders[maxBidderID].T +=  maxBid/bidders[maxBidderID].budget;
	return maxBid;
}

/**
 * Runs AdWords algorithm by using query data from the 
 * specifed file. This file should be in the form:
 *
 * n
 * b1 min1 max1
 * b2 min2 max2
 * b3 min3 max3
 * ...
 * where n is the total number of queries, bi is the number of 
 * bids for the ith query, and mini and maxi are the are the minimum
 * and maximum values for each bid. Each bid is randomly chosen
 * from a uniform random distribution between min and max. 
 *
 * In order for this method to work properly, generateBidders
 * must have been called so that the bidders array is properly 
 * instantiated. 
 *
 * @param  fileName  The file containing the query data. 
 * @return  The revenue from this run. 
 */
double runFromQueryFile(char* fileName)
{
	FILE *f = fopen(fileName, "r");
	int numQueries, num;
	double min, max, revenue = 0.0;

	fscanf(f, "%d\n", &numQueries);

	if(output)
		fprintf(out, "%d\n", numQueries);

	int i;
	for(i = 0; i < numQueries; i++)
	{
		fscanf(f, "%d %lg %lg", &num, &min, &max);
		revenue += getQueryRevenue(NULL, num, min, max);
	}
	return revenue;
}

/**
 * Runs the AdWords algorithm where all data is specified in a file.
 * The file should be of the following form:
 * 
 * numBidders
 * budget1
 * budget2
 * ...
 * numQueries 
 * numBids bidder1 bid1 bidder2 bid2 ...
 * numBids bidder1 bid1 bidder2 bid2 ...
 * ...
 *
 * @param  fileName  The file containing the bidder and query data. 
 * @return  The revenue generated by this run. 
 */
double runFromFile(char* fileName)
{
	FILE *f = fopen(fileName, "r");
	int numQueries, numBids;
	double budget, revenue = 0.0;

	/*Read the number of bidders and allocate
	  the appropriate amount of memory*/
	fscanf(f, "%d\n", &numBidders);
	
	bidders = (struct Bidder*) malloc(sizeof(struct Bidder) * numBidders);

	int i;
	for(i = 0; i < numBidders; i++)
	{
		fscanf(f, "%lg\n", &budget);
		bidders[i].budget = budget;
		bidders[i].T = 0;
	}

 	fscanf(f, "%d\n", &numQueries);
	
	for(i = 0; i < numQueries; i++)
	{
		fscanf(f, "%d ", &numBids);
		revenue += getQueryRevenue(f, numBids, 0, 0);
	}
	
	fclose(f);
	return revenue;
}


/**
 * Runs the AdWords algorithm for numQueries where
 * each query has a number of bids randomly chosen from
 * a uniform distribution between 0 and numBidders. Each 
 * bid is between min and max. 
 *
 * In order for this method to work properly, generateBidders
 * must have been called so that the bidders array is properly 
 * instantiated. 
 *
 * @param  numQueries The number of queries. 
 * @param  min  The minimum bid size. 
 * @param  max  The maximum bid size. 
 */
double runFromRandomQueries(int numQueries, double min, double max)
{
	int num;
	double revenue = 0.0;

	if(output)
		fprintf(out, "%d\n", numQueries);


	int j;
	for(j = 0; j < numQueries; j++)
	{
		num = (int) numBidders * (double) rand() / (double) (RAND_MAX + 1.0);
		revenue += getQueryRevenue(NULL, num, min, max);
	}
 	return revenue;
}


int main(int argc, char *argv[])
{
	srand(time(0));
	double revenue;
	int startIndex = 1;
	
	if(strcmp(argv[startIndex], "mehta") == 0)
		mode = MEHTA;
	else if(strcmp(argv[startIndex], "max_budget_left") == 0)
		mode = MAX_BUDGET_LEFT;
	else if(strcmp(argv[startIndex], "min_budget_left") == 0)
		mode = MIN_BUDGET_LEFT;
	else if(strcmp(argv[startIndex], "max_bid") == 0)
		mode = MAX_BID;
	else
		mode = MEHTA;

	argc--;

	if(argc == 8 || argc == 6)
	{
		output = 1;
		out = fopen(argv[++startIndex], "w");
		argc--;
	}
	else
		output = 0;
	

	/*
	 * The only parameter is a fileName, so we call
	 * runFromFile. 
	 */
	if(argc == 2)
	{
		revenue = runFromFile(argv[++startIndex]);
		free(bidders);
		printf("revenue: %g\n", revenue);
	}
	/*
	 * The user specified, the number of bidders, min and max
	 * budget size, the number of queries, and min and max query
	 * size. We call runFromRandomQueries. 
	 */
	else if(argc == 7)
	{
		int numQueries;
		double minBudget, maxBudget, minBid, maxBid;

		sscanf(argv[++startIndex], "%d", &numBidders);
		sscanf(argv[++startIndex], "%lg", &minBudget);
		sscanf(argv[++startIndex], "%lg", &maxBudget);
		sscanf(argv[++startIndex], "%d", &numQueries);
		sscanf(argv[++startIndex], "%lg", &minBid);
		sscanf(argv[++startIndex], "%lg", &maxBid);

		bidders = (struct Bidder*) malloc(sizeof(struct Bidder) * numBidders);
		subsetArray = (int*) malloc(sizeof(int) * numBidders);


		generateBidders(numBidders, minBudget, maxBudget);
		revenue = runFromRandomQueries(numQueries, minBid, maxBid);
		
		printf("revenue: %g\n", revenue);

		free(bidders);
		free(subsetArray);
				
	}
	/*
	 * The user specifed the number of bids, the min
	 * and max budgets, and a file name, so we call 
	 * runFromQueryFile. 
	 */
	else if(argc == 5)
	{
		double minBudget, maxBudget;

		char* fileName = argv[++startIndex];

		sscanf(argv[++startIndex], "%d", &numBidders);
		sscanf(argv[++startIndex], "%lg", &minBudget);
		sscanf(argv[++startIndex], "%lg", &maxBudget);
			 
		bidders = (struct Bidder*) malloc(sizeof(struct Bidder) * numBidders);
		subsetArray = (int*) malloc(sizeof(int) * numBidders);

		generateBidders(numBidders, minBudget, maxBudget);
		revenue = runFromQueryFile(fileName);

		printf("revenue: %g\n", revenue);
		
		free(bidders);
		free(subsetArray);
	}
	else
	{
		printf("Invalid arguments\n");
	}


	if(output)
			fclose(out);

	return 0;
}
