/*
City.cpp

This file implements the City class definition in City.h. Its general purpose is
to store and access the data associated with a virtual representaiton of a City
in a simulation. A one-dimensional array of struct GridLocation (see City.h) is
dynamically alocated by the constructor to simulate a two-dimensional array in
representation of a rectangular city.
*/


#include "City.h"

using namespace std;


// Constructor
City::City(char code, int numRow, int numCol){
	int i;
	
	//set the class variables based on the values passed in
	cityCode = code;
	numRows = numRow;
	numColumns = numCol;
	currentDay = 0;
	daysContagious = 0;
	
	//dynamically allocate an array of GridLocations large enough to represent a (numRows x numColums)-sized city
	grid = new GridLocation[numRows*numColumns];
	
	srand( (unsigned)time(0) );	//seed random number generator based on time of instantiation
	
	//initialize all values in the city grid as appropriate
	for(i=0; i<numRows*numColumns; i++){
		grid[i].infected = false;
		grid[i].vaccinated = false;
		grid[i].day = -1;
		grid[i].infectionDay = 0;
		grid[i].vaccinationDay = 0;
		grid[i].highRisk = false;
	}
}

// Default/empty constructor
City::City(){
	cityCode = '0';
	numRows = 0;
	numColumns = 0;
}

// Destructor
City::~City(){
	delete [] grid;
}

//////////////////
// Accessor methods (const)

char City::GetCityCode() const{
	return cityCode;
}

int City::GetRows() const{
	return numRows;
}
int City::GetColumns() const{
	return numColumns;
}
int City::GetDayNumber() const{
	return currentDay;
}


// IsUnaffected basically tells us whether or not a location is infectable
bool City::IsUnaffected(int r, int c) const{
	//if neither infected nor vaccinated, the true
	if(!grid[r*numColumns + c].infected && !grid[r*numColumns + c].vaccinated){
		return true;
	}
	//if vaccinated, but vaccine not effective yet, then true
	else if(currentDay < grid[r*numColumns + c].day + daysTillVaccineEffective && grid[r*numColumns + c].vaccinated){
		return true;
	}else{
		return false;
	}
}

// A location must be vaccinated, and the vaccine must be effective for this to be true. More accurately, this function would be "IsEffectivelyVaccinated"
bool City::IsVaccinated(int r, int c) const{
	if(currentDay > (grid[r*numColumns + c].day + daysTillVaccineEffective) && grid[r*numColumns + c].vaccinated){
		return true;
	}else{
		return false;
	}
}

bool City::IsInfected(int r, int c) const{
	return grid[r*numColumns + c].infected;
}

bool City::IsContagious(int r , int c) const{
	if(grid[r*numColumns + c].infected && currentDay < (grid[r*numColumns + c].day + daysContagious)){
		return true;
	}else{
		return false;
	}
}

double City::GetVaccineIneffectiveness() const{
	return vaccineIneffectiveness;
}

bool City::IsHighRisk(int r, int c) const{
	return grid[r*numColumns + c].highRisk;
}

/* Checks if any of the 8 potential locations directly adjacent to grid[r,c] are
   infected. It provides special cases for each of the grid edges and corners.
*/
bool City::adjacentInfectionCheck(int r, int c) const{
		//top edge
		if(r == 0){
			//top left corner
			if(c == 0){
				if(	IsInfected(1,1) ||
					IsInfected(0,1)	||
					IsInfected(1,0)){
						return true;
				}
			}
			//top right corner
			else if(c == numColumns){
				if(	IsInfected(1,numColumns) ||
					IsInfected(1,numColumns-1) ||
					IsInfected(0,numColumns-1)){
						return true;
					}
			}
			//rest of the top edge
			else{
				if(	IsInfected(r,c-1) ||
					IsInfected(r+1,c-1) ||
					IsInfected(r+1, c) ||
					IsInfected(r+1 ,c+1) ||
					IsInfected(r, c+1)){
						return true;
				}
			}
		}
		//bottom edge
		else if(r == numRows){
			//bottom left corner
			if(c == 0){
				if(	IsInfected(numRows-1 ,0) ||
					IsInfected(numRows-1 ,1) ||
					IsInfected(numRows, 1)){
						return true;
					}
			}
			//bottom right corner
			else if(c == numColumns){
				if(	IsInfected(numRows-1,numColumns) ||
					IsInfected(numRows-1,numColumns-1) ||
					IsInfected(numRows,numColumns-1)){
						return true;
					}
			}
			//rest of bottom edge
			else{
				if(	IsInfected(r,c-1) ||
					IsInfected(r-1,c-1) ||
					IsInfected(r-1, c) ||
					IsInfected(r-1 ,c+1) ||
					IsInfected(r, c+1)){
						return true;
					}
			}
		}
		//rest of left edge (excluding corners)
		else if(c == 0){
			if(	IsInfected(r,c+1) ||
				IsInfected(r-1,c+1) ||
				IsInfected(r+1, c+1) ||
				IsInfected(r-1 ,c) ||
				IsInfected(r+1, c)){
					return true;
				}
		}
		//rest of right edge (excluding corners)
		else if(c == numColumns){
			if(	IsInfected(r,c-1) ||
				IsInfected(r-1,c-1) ||
				IsInfected(r+1, c-1) ||
				IsInfected(r-1 ,c) ||
				IsInfected(r+1, c)){
					return true;
				}
		}
		//non-edge/corner
		else{
			if(	IsInfected(r,c-1) ||
				IsInfected(r-1,c-1) ||
				IsInfected(r-1, c) ||
				IsInfected(r-1 ,c+1) ||
				IsInfected(r, c+1) ||
				IsInfected(r+1,c-1) ||
				IsInfected(r+1, c) ||
				IsInfected(r+1 ,c+1)){
					return true;
				}
		}
	return false;
}

//////////////////////
// Statistics collection and summary methods (const):
// Generally, these march through eveyr location in the city grid, and increment
// a counter if the condition in question is met


long City::NumberInfected() const{
	int total = 0;
	int i;
		
	for(i=0; i<numRows*numColumns; i++){
		if(grid[i].infected){
			total++;
		}
	}
	return total;
}

long City::NumberVaccinated() const{
	int total = 0;
	int i;
		
	for(i=0; i<numRows*numColumns; i++){
		if(grid[i].vaccinated){
			total++;
		}
	}
	return total;
}

long City::NumberVaccinatedAndInfected() const{
	int total = 0;
	int i;
	
	for(i=0; i<numRows*numColumns; i++){
		if(grid[i].vaccinated && grid[i].infected){
			total++;
		}
	}
	return total;
}

////////////////
// Print and display methods

void City::PrintDay(int dayToPrint) const{
	int i;
	cout << "City Code: \t" << cityCode << endl;
	cout << "Number of Rows: \t" << numRows << endl;
	cout << "Number of Columns: \t" << numColumns << endl;
	cout << "High Risk is denoted by capital letters" << endl;
	cout << "Infected = I|i \t Vaccinated = V|v "<<endl <<"Vaccinated and Infected = O|o \t Unaffected = U|u"<< endl;
	cout << "Grid layout: " << endl; 
	for(i=0; i< numRows*numColumns; i++){
		if(i % numColumns == 0){
			cout << endl << i/numColumns << ": \t" ;
		}
		if(grid[i].infected && grid[i].vaccinated && grid[i].highRisk && dayToPrint >= grid[i].infectionDay && dayToPrint >= grid[i].vaccinationDay){
			cout << "O ";
		}else if(!grid[i].infected && !grid[i].vaccinated && grid[i].highRisk){
			cout << "U ";
		}else if(grid[i].vaccinated && grid[i].highRisk && dayToPrint >= grid[i].vaccinationDay ){
			cout << "V ";
		}else if(grid[i].infected && grid[i].highRisk && dayToPrint >= grid[i].infectionDay){
			cout << "I ";
		}else if(grid[i].infected && grid[i].vaccinated && !grid[i].highRisk && dayToPrint >= grid[i].infectionDay && dayToPrint >= grid[i].vaccinationDay){
			cout << "o ";
		}else if(!grid[i].infected && !grid[i].vaccinated && !grid[i].highRisk){
			cout << "u ";
		}else if(grid[i].vaccinated && !grid[i].highRisk && dayToPrint >= grid[i].vaccinationDay){
			cout << "v ";
		}else if(grid[i].infected && !grid[i].highRisk && dayToPrint >= grid[i].infectionDay){
			cout << "i ";
		}else if(grid[i].infected && dayToPrint <= grid[i].infectionDay  && !grid[i].highRisk){
			cout << "u ";
		}else if(grid[i].vaccinated && dayToPrint <= grid[i].vaccinationDay  && !grid[i].highRisk){
			cout << "u ";
		}else if(grid[i].highRisk){
			cout << "U ";
		}else{
			cout << endl << "Error: " << i << endl;
			cout << grid[i].infected << " " << grid[i].infectionDay<< " " << grid[i].vaccinated << " "<< grid[i].vaccinationDay << " " << grid[i].highRisk << endl;
		}
	}
	cout << endl << endl; 
}
void City::Print() const{
	int i;
	cout << "City Code: \t" << cityCode << endl;
	cout << "Number of Rows: \t" << numRows << endl;
	cout << "Number of Columns: \t" << numColumns << endl;
	cout << "High Risk is denoted by capital letters" << endl;
	cout << "Infected = I|i \t Vaccinated = V|v "<<endl <<"Vaccinated and Infected = O|o \t Unaffected = U|u"<< endl;
	cout << "Grid layout: " << endl; 
	for(i=0; i< numRows*numColumns; i++){
		if(i % numColumns == 0){
			cout << endl << i/numColumns << ": \t" ;
		}
		if(grid[i].infected && grid[i].vaccinated && grid[i].highRisk){
			cout << "O ";
		}else if(!grid[i].infected && !grid[i].vaccinated && grid[i].highRisk){
			cout << "U ";
		}else if(grid[i].vaccinated && grid[i].highRisk){
			cout << "V ";
		}else if(grid[i].infected && grid[i].highRisk){
			cout << "I ";
		}else if(grid[i].infected && grid[i].vaccinated && !grid[i].highRisk){
			cout << "o ";
		}else if(!grid[i].infected && !grid[i].vaccinated && !grid[i].highRisk){
			cout << "u ";
		}else if(grid[i].vaccinated && !grid[i].highRisk){
			cout << "v ";
		}else if(grid[i].infected && !grid[i].highRisk){
			cout << "i ";
		}else{
			cout << endl << "Error: " << i << endl;
		}
	}
	cout << endl << endl; 
}

void City::StreamPrint(ostream &out) const{
	int r, c;
	for(r = 0; r < numRows; r++){
		for(c = 0; c < numColumns; c++){
			if(grid[r*numColumns + c].vaccinated && grid[r*numColumns + c].infected && grid[r*numColumns + c].highRisk){
				out<< cityCode << " " << r << " " << c << " V "<< grid[r*numColumns + c].vaccinationDay << " S "<<grid[r*numColumns + c].infectionDay << " H"<<'\n'; 
			}else if(grid[r*numColumns + c].vaccinated && grid[r*numColumns + c].highRisk){
				out<< cityCode << " " << r << " " << c << " V "<< grid[r*numColumns + c].vaccinationDay<< " H" << '\n';
			}else if(grid[r*numColumns + c].infected && grid[r*numColumns + c].highRisk){
				out<< cityCode << " " << r << " " << c << " S "<<grid[r*numColumns + c].infectionDay << " H" << '\n';
			}else if(grid[r*numColumns + c].highRisk){
				out<< cityCode << " " << r << " " << c << " H"<<'\n';
			}else if(grid[r*numColumns + c].vaccinated && grid[r*numColumns + c].infected){
				out<< cityCode << " " << r << " " << c << " V "<< grid[r*numColumns + c].vaccinationDay << " S "<<grid[r*numColumns + c].infectionDay <<'\n'; 
			}else if(grid[r*numColumns + c].vaccinated){
				out<< cityCode << " " << r << " " << c << " V "<< grid[r*numColumns + c].vaccinationDay<< '\n';
			}else if(grid[r*numColumns + c].infected){
				out<< cityCode << " " << r << " " << c << " S "<<grid[r*numColumns + c].infectionDay<< '\n';
			}
		}
	}
}

ostream & operator<<(ostream & out, const City & cityToPrint) {
	cityToPrint.StreamPrint(out);
	return out;
}

////////////////////
// Modifier methods


/* The following two methods do not perform any error checking, but simply set
   a grid location to vaccinated or infected, and sets the related fields to the
   current day (of the simulation) for purposes of checking if a location is
   contagious or if the vaccine is effective yet.
*/
bool City::VaccinateLocation(int r, int c){
	grid[r*numColumns + c].vaccinated = true;
	grid[r*numColumns + c].day = currentDay;
	grid[r*numColumns + c].vaccinationDay = currentDay;
	return true;
}

bool City::InfectLocation(int r, int c){
	grid[r*numColumns + c].infected = true;
	grid[r*numColumns + c].day = currentDay;
	grid[r*numColumns + c].infectionDay = currentDay;
	return true;
}

/* Originally designed to perform some internal changes to set "just infected"
   locations to the regular definition of infected, FixGrid now just sets
   currentDay. It was left in the code for legacy compatability.
*/
void City::FixGrid(int newDay){
	currentDay = newDay;
}

void City::SetDaysContagious(int numberOfDays){
	daysContagious = numberOfDays;
}

void City::SetDaysUntilVaccineEffective(int days){
	daysTillVaccineEffective = days;
}

void City::SetVaccineIneffectiveness(double probability){
	vaccineIneffectiveness = probability;
}

void City::SetHighRisk(int r,int c){
	grid[r*numColumns + c].highRisk = true;
}
