// NETask.java
//
// coding: Vytautas Leonavicius email: cpp@maxi.lt
// some ideas taken from (NashModel1.java): unknown student work (thank You!)
// theory: Tomas Motuzas, Tomas Kikalas
// Last update 2002 11 14  16.52

package lt.ktu.gmj.tasks;

import lt.ktu.gmj.*;
import lt.ktu.gmj.propertySheet.*;
import java.util.*; 

/////////////////////////////////////////////////////////

//******************************
// NashMarket model 1 impl.*****
//******************************
class NashMarket                                                                
{                                                                               
// member vars

    // Model parameters. Hard change them or implement property provider field to customize them from applet                                                                  
    private final int N_CLIENTS = 1000;			// clients to share between servers                                         
    private final int N_SERVERS = 3;                    // nr of servers                        
    private final double D_AVG_CLIENT_COME = 10.0;      // average client comming time                        
    private final double D_MAX_CLIENT_CAN_PAY = 10.0;   // max client can pay                        
    private double dCurrentTime = 0.0;			// current time variable.
    private NEDomain domain = null;                     // domain reference                     
			                                                                                
    public NashServer nsvServers[];                     // nash servers vector                        
	
    /*
     * Constructor
     */		                                                                                    
    public NashMarket(NEDomain domain)                                                         
    {   
	// set domain reference                                      
	this.domain = domain;
	// create servers vector
	this.nsvServers = new NashServer[this.N_SERVERS]; 
	// init                                 
        for(int iServer = 0; iServer < this.N_SERVERS; iServer++)
	    this.nsvServers[iServer] = new NashServer();
    }
    
    /*
     * Helper method. Sets server parameters accourding to domain
     */ 
    private void SetServerParams()
    {
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++){
    	    //
	    // Set server parameters:
	    //                                 
    	    this.nsvServers[iServer].dMinPrice = domain.min[iServer * 2];
	    this.nsvServers[iServer].dMinCapacity = domain.min[iServer * 2 + 1];
	    this.nsvServers[iServer].dMaxPrice = domain.max[iServer * 2];
	    this.nsvServers[iServer].dMaxCapacity = domain.max[iServer * 2 + 1];
	}	
    }
    /*                                                                           
     * Calculates Nash Equilibrum for specified Point object
     */						                                                                                
    public double CalculateEquilibrum(Point pt)                                 
    {
	//
	// MATHS: NE = abs[U1sb(xSelfBest, ySelfBest, x2, y2, x3, y3) - 
	//		U1(x1, y1, x2, y2, x3, y3)] + abs[U2sb(...) - U2(...)] + 
	//		abs[U3sb(...) - U3(...)]:
	//		where: UXsb - each server`s max profit. if he brakes the contract,
	//		UX - each servers contract-based profit.
			
	//
	// Calculate each server`s profit with GMJ passed parameters.
	RunMarket(pt);
	
	// save each server`s profit:
	double[] dvEachServerUnbrokenProfit = new double[this.N_SERVERS];
	double[] dvEachServerBrokenProfit = new double[this.N_SERVERS];
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++)
	    dvEachServerUnbrokenProfit[iServer] = dvEachServerBrokenProfit[iServer] = 
	    this.nsvServers[iServer].GetProfit(this.N_CLIENTS, this.D_AVG_CLIENT_COME);

	// now, break the contract. Search for max profits.
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++){
	    
	    // iterate through prices (maths: ySelfBest[iServer])
	    for(double dCurrPrice = this.nsvServers[iServer].dMinPrice;
		dCurrPrice <= this.nsvServers[iServer].dMaxPrice;
		dCurrPrice += this.nsvServers[iServer].GetPriceStep()){
		
		// iterate through capacities (maths: xSelfBest[iServer])
		for(double dCurrCap = this.nsvServers[iServer].dMinCapacity;
		    dCurrCap <= this.nsvServers[iServer].dMaxCapacity;
		    dCurrCap += this.nsvServers[iServer].GetCapacityStep()){
		    
		    // Create new market state:
		    Point ptNewPoint = new Point(pt);
		    ptNewPoint.x[iServer * 2] = dCurrPrice;
		    ptNewPoint.x[iServer * 2 + 1] = dCurrCap;
		    
		    // look what happens:
		    RunMarket(ptNewPoint);
		    
		    // save profit if this state profit exceeds previous profit(s)
		    if(this.nsvServers[iServer].GetProfit(this.N_CLIENTS, this.D_AVG_CLIENT_COME) > dvEachServerBrokenProfit[iServer])
			dvEachServerBrokenProfit[iServer] = this.nsvServers[iServer].GetProfit(this.N_CLIENTS, this.D_AVG_CLIENT_COME);
		} // dCurrCap
	    } // dCurrPrice
	} // iServer


	/*
	// debug
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++)
	    System.out.println(iServer + "th sever`s unbr profit " + dvEachServerUnbrokenProfit[iServer] +
	    " broken profit " + dvEachServerBrokenProfit[iServer]);
	*/
	
	// Calculate difference:
	double dDifference = 0.0;
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++)
	    dDifference += Math.abs(dvEachServerBrokenProfit[iServer] - dvEachServerUnbrokenProfit[iServer]);
        
	return dDifference;// 	return 0.0;                                                           
    }                                                                           
	
    /*
     * Helper method. Restarts market and sets servers parameters to passed by GMJ
     */
    private void RestartMarket(Point pt)
    {
    	// set domain variables                      
	SetServerParams();
	// restart time
	this.dCurrentTime = 0.0;
                                                 
        for(int iServer = 0; iServer < this.N_SERVERS; iServer++){
	    // restart servers
	    this.nsvServers[iServer].RestartServer();         
	    // set GMJ passed variables      
    	    this.nsvServers[iServer].SetServicePrice(pt.x[iServer * 2]);        
    	    this.nsvServers[iServer].SetServerCapacity(pt.x[iServer * 2 + 1]);  
	}                                                                       	
    }
    
    /*
     * Helper method. Restarts market and shares N_CLIENTS between servers
     */	                                                                                    
    private void RunMarket(Point pt)                                             
    {   
	// Restart market
	RestartMarket(pt);                                                  
    	
	// share N_CLIENTS between servers and going away				                                                                                    
        for(int iClient = 0; iClient < this.N_CLIENTS; iClient++){              
	    // calculate current time.                                          
    	    this.dCurrentTime += (-1.0 /                                        
    	        this.D_AVG_CLIENT_COME) * Math.log(1.0 - Math.random());

    	    // Serve clients                                                    
    	    for(int iServer = 0; iServer < this.N_SERVERS; iServer++)           
        	this.nsvServers[iServer].ServeClient(this.dCurrentTime);        

            // Choose best server
	    int iBestSrv = ClientChooseBestServer();
	    if(iBestSrv == -1) continue; // Client goes away                    
		                                                                                
    	    // Chosen server accepts client                                     
	    this.nsvServers[iBestSrv].AcceptClient(this.dCurrentTime);    
	}
	/*
	// debug
	for(int iServer = 0; iServer < this.N_SERVERS; iServer++){
	    System.out.println(iServer + " th server: cl. in queue: " +
		this.nsvServers[iServer].GetClientsInQueue() + " clients served " +
		this.nsvServers[iServer].GetClientsServed());
	    System.out.println(iServer + " th server service price: " +
		this.nsvServers[iServer].GetInitialServicePrice() + 
		" total price: " + this.nsvServers[iServer].GetTotalServicePrice());
	}*/	                                                              
    }                                                                           
	
    /*
     * Helper method. Client choose best server or goes away
     */ 						                                                                                    
    private int ClientChooseBestServer()                                         
    {                                                                           
        // -1 indicates what client goes away.                                  
	int iBest = 0;                                                          
	double dTotalPrice = this.nsvServers[0].GetTotalServicePrice();         
										            // look for minimal total price                                         
	for(int iServer = 1; iServer < this.N_SERVERS; iServer++){              
            if(dTotalPrice > this.nsvServers[iServer].GetTotalServicePrice()){  
                dTotalPrice = this.nsvServers[iServer].GetTotalServicePrice();
                iBest = iServer;                                                
            }                                                                   
        }	
	                                                                                
	if(this.D_MAX_CLIENT_CAN_PAY < dTotalPrice) return -1; // Go away       
	                                                                    
        //                                                                      
        // TODO Implement random server selection. But im not sure it`s really needed                               
	//                                                                      
					                                                                                
	return iBest;                                                           
    }                                                                                                                                       
}; 
//**********************************	
// NashServer model 1 impl.*********
//**********************************

class NashServer                                                         
{                                                                                   
    private int nClientsServed = 0;       // how many clients were served                                       
	                                                                                
    private double dServicePrice = 0.0;   // service price                                       
    private double dServerCapacity = 1.0; // server capacity        
    
    // during market run, current time increases. Theese two vectors
    // are representing queue                               
    private Vector vecTimesClientsWait = new Vector(0, 10);
    private Vector vecTimesClientsGoAway = new Vector(0, 10);                
    // change theese
    public int nPricesCnt = 10;
    public int nCapacitiesCnt = 10;
    
    // domain variables
    public double dMinPrice = 0.0;
    public double dMinCapacity = 0.0;
    public double dMaxPrice = 10.0;
    public double dMaxCapacity = 10.0;
                               
    public void RestartServer()
    {
	this.nClientsServed = 0;
	// reset queue.
	this.vecTimesClientsWait = new Vector(0, 10);
	this.vecTimesClientsGoAway = new Vector(0, 10);
    }
    			                                                                                
    public void ServeClient(double dCurrentTime)                                
    {   
        if(this.vecTimesClientsWait.size() == 0) return;                                   
	
	 // serve client if he should be
    	if(dCurrentTime >= 
	    (((Double)this.vecTimesClientsGoAway.elementAt(0)).doubleValue())){               
	    this.nClientsServed++;
	    // remove client from queue:
	    this.vecTimesClientsWait.removeElementAt(0); 
	    this.vecTimesClientsGoAway.removeElementAt(0);                                             
    	}                                                                       
    }
  
    public void AcceptClient(double dCurrentTime)                               
    { 
	// generate this clients serve time
        double dTimeToWait = (-1.0 /                                              
	    this.dServerCapacity) * Math.log(1.0 - Math.random());             
	
	//this.vecTimesClientsWait.addElement(new Double(dTimeToWait));
	// calculate total time to wait
	double dTotalTimeToWait = dTimeToWait;
	if(this.vecTimesClientsWait.size() != 0){
	    // if there are clients in queue, calculate,
	    // then they`ll be served:
	    for(int iClient = 0; iClient < this.vecTimesClientsWait.size(); iClient++)
		dTotalTimeToWait += ((Double)this.vecTimesClientsWait.elementAt(iClient)).doubleValue();
	    /*
	    //TODO: imagine what some time is ellapsed after first client in queue stand first.
	    //	 this should be taken in account.
	    // substract by time ellapsed:
	    double dEllapsed = 0.0;
	    if(((Double)this.vecTimesClientGoAway.elementAt(0)).doubleValue() < dCurrentTime)
		dEllapsed
	    //dTotalTimeToWait -= (dCurrentTime - ((Double)this.vecTimesOfCome.elementAt(0)).doubleValue());
	    */
	}    
	
	// save this client`s time of quit (this is the time he should wait to be served) 
	this.vecTimesClientsGoAway.addElement(new Double(dTotalTimeToWait + dCurrentTime));
	// save this client`s time to wait
	this.vecTimesClientsWait.addElement(new Double(dTimeToWait));

	/* Debug	
	for(int iClient = 0; iClient < this.vecTimesClientsWait.size(); iClient++){
	    System.out.println(iClient + " th client wait time " + 
	    ((Double)this.vecTimesClientsWait.elementAt(iClient)).doubleValue() + " time of quit " +
	    ((Double)this.vecTimesClientsGoAway.elementAt(iClient)).doubleValue());        
	}	*/
    }                                                                           
						                                                                                
    public double GetTotalServicePrice()                                        
    {                                                                           
        return this.dServicePrice + (this.vecTimesClientsWait.size() / 
	    this.dServerCapacity);                                                                              
    }
    
    public int GetClientsInQueue(){return this.vecTimesClientsWait.size();}   
    public int GetClientsServed(){return this.nClientsServed;} 
    public double GetInitialServicePrice(){return this.dServicePrice;}                                                                       
    
    public double GetProfit(int nClientsTotal, double dAvgClientCome)
    {
	return (((double)this.nClientsServed) / 
	    ((double)nClientsTotal / dAvgClientCome)) * this.dServicePrice - this.dServerCapacity;
    }
    	
    public double GetCapacityStep(){return (this.dMaxCapacity - this.dMinCapacity) / (double)this.nCapacitiesCnt;}
    public double GetPriceStep(){return (this.dMaxPrice - this.dMinPrice) / (double)this.nPricesCnt;}
    
    public void SetServerCapacity(double dNewCapacity){this.dServerCapacity = dNewCapacity;}                                                                                                                               
    public void SetServicePrice(double dNewPrice){this.dServicePrice = dNewPrice;}                                                                           
};    

/////////////////////////////////////////////////////
class NEDomain extends DomainWithConstraint 
{
    //
    // Custom parameters
    //
    //    public 
    static final String []dimensions={
	"1st Price",
        "1st Server Capacity",
	"2nd Price",
        "2nd Server Capacity",
	"3rd Price",
        "3rd Server Capacity"
    };

    public String[] dimensions()
    {return dimensions;}

    NEDomain()
    {
     // set default parameters
     // odd: 	price 
     // even: 	capacity
	min[0] = min[1] = min[2] = min[3] = min[4] = min[5] = 1.0;
	max[0] = max[1] = max[2] = max[3] = max[4] = max[5] = 10.0;

	defaultPoint.x[0] = defaultPoint.x[1] = defaultPoint.x[2] =
	    defaultPoint.x[3] = defaultPoint.x[4] = defaultPoint.x[5] = 
	    ((min[0] + max[0]) / 2.0);
    }
};

public class NETask extends AbstractTask implements TaskWithAnalyzers 
{
    private NEDomain domain=new NEDomain();
    private NashMarket nmMarket;

    public NETask()
    {
	nmMarket = new NashMarket(domain);
    }
    
    public void customize (PropertyManager manager)
    {
    }	

     public Domain domain() {return domain;}


     public double f(Point pt)
     {
	return this.nmMarket.CalculateEquilibrum(pt);;
     }

     public Class[] analyzers () throws ClassNotFoundException
     {
	Class spectrumClass=Class.forName("lt.ktu.gmj.analysis.Spectrum");
        return new Class[]{ spectrumClass };
     }
};

