import java.util.*;
import java.io.*;

/**
 * @author Ben Sowell
 * @version 6-20-06
 */
class AuctionTester
{
		/**
		 * Class to represent a bidder. Stores
		 * the budget and the value T, which 
		 * is the percentage of the budget that
		 * the bidder has spent.
		 */
		private class Bidder
		{
				public double budget, T;

				public Bidder(double budget)
				{
						this.budget = budget;
				}
				public void update(double amount)
				{
						T += (amount/budget);
				}
		}

		private ArrayList<Bidder> bidders;
		private Random rand;
		private int[] subsetArray;

		public AuctionTester()
		{
				bidders = new ArrayList<Bidder>();
				rand = new Random();
		}

      /**
	   * Generates the specified number of bidders with budgets
	   * chosen from a uniform random distribution between 
	   * minBudget and maxBudget.  Also initializes the subestArray, 
	   * which  will be used to generate a random subset of bidders. 
	   *
	   * @param  num  The number of bidders to generate.
	   * @param  minBudget  The smallest possible budget.
	   * @ param  maxBudget  The largest possible budget.
	   */
		public void generateBidders(int num, double minBudget, double maxBudget)
		{
				double budget;
				subsetArray = new int[num];

				for(int i = 0; i < num; i++)
				{
						budget = rand.nextDouble() * (maxBudget - minBudget)  + minBudget;
						bidders.add(new Bidder(budget));
						subsetArray[i] = i;
				}
		}

     	/**
		 * 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. 
		 */
		public double runFromQueryFile(String fileName)
		{
				int num, swapNum, temp;
				double max, min;
				int maxBidderID;
				double bid, maxBid, bidTest, maxBidTest;
				double revenue = 0.0; 

				try
				{
						Scanner queryScanner = new Scanner(new File(fileName));						
						while(queryScanner.hasNext())
						{
								num = queryScanner.nextInt();
								min = queryScanner.nextDouble();
								max = queryScanner.nextDouble();
								revenue += getQueryRevenue(num, min, max);
						}

						return revenue;
				}
				catch(FileNotFoundException e)
				{
						System.err.println("Could not find query file");
						System.exit(1);
				}
				catch(IOException e)
				{
						System.err.println("IO error");
						System.exit(1);
				}
				
				return -1.0;
		}

		/**
		 * 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. 
		 */
		public double runFromRandomQueries(int numQueries, double min, double max)
		{
				int num;
				double revenue = 0.0; 
			
				for(int j = 0; j < numQueries; j++)
				{
						num = rand.nextInt(bidders.size());
						revenue += getQueryRevenue(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. 
		 */
		public double runFromFile(String fileName)
		{
				try
				{
						Scanner fileScanner = new Scanner(new File(fileName));
						Scanner lineScanner;
						int bidderID, maxBidderID;
						double maxBid, bid, bidTest, maxBidTest;
						double revenue = 0.0;

						//Read info on each of the bidders.
						int numBidders = fileScanner.nextInt();
						for(int i = 0; i < numBidders; i++)
								bidders.add(new Bidder(fileScanner.nextFloat()));

						while(fileScanner.hasNextLine())
						{
								lineScanner = new Scanner(fileScanner.nextLine());

								maxBidTest = 0.0;
								maxBidderID = 0;
								maxBid = 0.0;
				
								while(lineScanner.hasNext())
								{
										bidderID = lineScanner.nextInt();
										bid = lineScanner.nextFloat();

										bidTest = bid * (1 - Math.exp(bidders.get(bidderID).T - 1));

										if(bidTest > maxBidTest)
										{
												maxBidTest = bidTest;
												maxBidderID = bidderID;
												maxBid = bid;
										}
								}

								bidders.get(maxBidderID).update(maxBid);
								revenue += maxBid;

						}

						return revenue;
				}
				catch(FileNotFoundException e)
				{
						System.out.println("Could not find input file");
						System.exit(1);
				}
				catch(IOException e)
				{
						System.out.println("IOException");
						System.exit(1);
				}

				return -1.0;

		}
		
		/**
		 * Randomly generates num bids between min and max and
		 * finds which one maximizes the tradeoff function as specifed
		 * in "AdWords and Generalized On-line Matching" by Mehta et. al. 
		 *
		 * @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. 
		 */
		public double getQueryRevenue(int num, double min, double max)
		{
				int maxBidderID = 0, swapNum, temp;
				double bid = 0.0, maxBid = 0.0, bidTest = 0.0, maxBidTest = 0.0;
				double revenue = 0.0; 

				for(int i = 0; i < num; i++)
				{
						swapNum = rand.nextInt(bidders.size() -  i) + i;
						temp = subsetArray[i];
						subsetArray[i] = subsetArray[swapNum];
						subsetArray[swapNum] = temp;

						//Generate a random bid for this query.
						bid = rand.nextDouble() * (max - min) + min;
										
						bidTest = bid * (1 - Math.exp(
												 bidders.get(subsetArray[i]).T - 1));

						if(bidTest > maxBidTest)
						{
								maxBidTest = bidTest;
								maxBidderID = subsetArray[i];
								maxBid = bid;
						}
				}

				bidders.get(maxBidderID).update(maxBid);
				return maxBid;
		}

		public static void main(String[] args)
		{
				AuctionTester aTest = new AuctionTester();
				double revenue;

				if(args.length == 1)
				{
						revenue = aTest.runFromFile(args[0]);
						System.out.println("revenue: " + revenue);
				}
				else if(args.length == 6)
				{
						int numBidders = Integer.parseInt(args[0]);
						double minBudget = Double.parseDouble(args[1]);
						double maxBudget = Double.parseDouble(args[2]);
						int numQueries = Integer.parseInt(args[3]);
						double minBid = Double.parseDouble(args[4]);
						double maxBid = Double.parseDouble(args[5]);

						aTest.generateBidders(numBidders, minBudget, maxBudget);
						revenue = aTest.runFromRandomQueries(1000, .10, 1.0);
						System.out.println("revenue: " + revenue);
				}
				else if(args.length == 4)
				{
						int numBidders = Integer.parseInt(args[1]);
						double minBudget = Double.parseDouble(args[2]);
						double maxBudget = Double.parseDouble(args[3]);

						aTest.generateBidders(numBidders, minBudget, maxBudget);
						revenue = aTest.runFromQueryFile(args[0]);
						System.out.println("revenue: " + revenue);
				}
				else
				{
						System.out.println("Invalid command line arguments");
				}
		}
}
