//
//
//  date.cc
//
//      AUTHOR: Russell Lowke
//         T.F: Francis Crick
//  CSCIE-s111: Unit Four, Main - question 2 [worth 15pts],  7/11/2002
//
// Description: A class called DateType that can read the date from
//              the terminal [ readDate() ] in the form of day, month & year,
//              then print the full date [ printDate() ]
//              It can also compare two dates via lessThan(DateType mDate)
//
//       Usage: interface top methods (see method)
//
//      Method: 
//           bool leapYear()          returns true if [int y] is a leap year
//            int NumOfDaysInMonth()  return the number of days in a given month
//           void readDate()          Read the date from terminal year/month/day
//     SeasonType season()            return the season
//           bool lessThan()          Test if class date is < then parameter Date
//           void printDate()         print the date
//              
//   Functions: 
//            int getInt()            reads an integer from the keyboard
//									  and ensures it to be in a given range
//


#include <iostream>
#include <iomanip>
#include <cstdio>

// constants
const int DAYSINWEEK = 7;
enum DayType    {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
enum MonthType  {January, Febuary, March, April, May, June,
                 July, August, September, October, November, December};
enum SeasonType {Fall, Winter, Spring, Summer};

// global function declarations
ostream& operator<< (ostream& os, DayType s);
DayType  operator+  (DayType d, int increment);

// functions

//
//  reads an integer from the keyboard,
//  uses error checking to ensure value is between minValue and maxValue
//
int getInt(char askStr[], int minValue, int maxValue)
{
	int mVar;
	bool inputOK = false;
	
	while (! inputOK) { 
	
		cout << askStr;						// ask query
		cin >> mVar;						// get input
		
		if ( (mVar >= minValue && mVar <= maxValue) )
			inputOK = true;					// input OK, exit loop
		else {
			cout << "Bad input: please enter a value between "
			     << minValue << " and " << maxValue << endl;
			cin.clear();					// clear error
			cin.ignore(80, '\n');			// empty buffer
		}
	}
	
	return mVar;
}


//
//  Overloading + operator for DayType
//  Note: this is not currently used, but I've
//        kept it here as an example
//
DayType operator+ (DayType d, int increment)
{
	d = (DayType) ( (int) d + increment );
	
	while ( (int) d >= DAYSINWEEK) {
		d = (DayType) ( (int) d - DAYSINWEEK );
	}
	
	return d;
}


//
// Overloading << operator to deal with DayType
//
ostream& operator<< (ostream& os, DayType s)
{
	switch (s)
	{
		case Sunday:
			os << "Sunday";
			break;
		case Monday:
			os << "Monday";
			break;
		case Tuesday:
			os << "Tuesday";
			break;
		case Wednesday:
			os << "Wednesday";
			break;
		case Thursday:
			os << "Thursday";
			break;
		case Friday:
			os << "Friday";
			break;
		case Saturday:
			os << "Saturday";
			break;
	}
	return os;
}


//
// Overloading << operator to deal with MonthType
//
ostream& operator<< (ostream& os, MonthType s)
{
	switch (s)
	{
		case January:
			os << "January";
			break;
		case Febuary:
			os << "Febuary";
			break;
		case March:
			os << "March";
			break;
		case April:
			os << "April";
			break;
		case May:
			os << "May";
			break;
		case June:
			os << "June";
			break;
		case July:
			os << "July";
			break;
		case August:
			os << "August";
			break;
		case September:
			os << "September";
			break;
		case October:
			os << "October";
			break;
		case November:
			os << "November";
			break;
		case December:
			os << "December";
			break;
	}
	return os;
}


//
// Overloading << operator to deal with SeasonType
//
ostream& operator<< (ostream& os, SeasonType s)
{
	switch (s)
	{
		case Fall:
			os << "Fall";
			break;
		case Winter:
			os << "Winter";
			break;
		case Spring:
			os << "Spring";
			break;
		case Summer:
			os << "Summer";
			break;
	}
	return os;
}

//
//   DateType class
//
class DateType
{

	private :
		
		  int	month, day, year;				// properties mm/dd/yy
		 bool	leapYear(int y);
		  int	NumOfDaysInMonth(int month, int year);
		 bool	validDate(int m, int d, int y);


      public:
      
    			DateType(); 					// constructor
				~DateType();					// destructor
       	 void	readDate();
       	 void	printDate();
       	 bool	lessThan(DateType mDate);
   SeasonType 	season();
       	
};


//
//  constructor for DateType class
//  
DateType :: DateType ()
{
}


//
//  destructor for DateType class
//  
DateType :: ~DateType()
{
}


//
//  returns true if [int y] is a leap year
//
bool DateType :: leapYear(int y)
{ 
	return ((( y % 4 ==0) && (y % 100 ) != 0)
               ||  ((y % 400 ) == 0 ))  ;
}
         
                 
//
//  return the number of days in a given month
//
int DateType :: NumOfDaysInMonth(int month, int year)
{
	switch ( (MonthType) (month - 1) )
	{ 
		case September:  case April: case June: case November:
			return 30;					// 30 days hath sept, aprl, jun & Nov
		case Febuary:
			if (leapYear( year)) {		// except for feb alone, which has...
				return 29;
			} else {
				return 28;
			}
		default:
			return 31;					// all the rest have 31
	}
}


//
//  Read the date
//  Note: Year and month are read first here,
//        allowing month input to be constrained to correct values
//
void DateType :: readDate()
{
	int lastPossDay;
	char vDayStr[80];					// string to hold valid day text
	
	// constrains to year 1753 through 2100
	year  = getInt("Type a valid year (between 1753 and 2100): ", 1753, 2100);
	
	// constrains to a month 1 through 12
	month = getInt("Type a month number (Jan = 1, Feb = 2, ...): ", 1, 12);
	
	// find number of days in month
	lastPossDay = NumOfDaysInMonth(month, year);
	
	// constrains to number of days in month
	sprintf(vDayStr, "Type a valid day (between 1 and %d): ", lastPossDay);
	day   = getInt(vDayStr, 1, lastPossDay);	
	
}


//
//  return the season
//
SeasonType DateType :: season()
{
	SeasonType mSeason;
	
	switch ( (MonthType) (month - 1) )
	{
		case January:
		case Febuary:
			mSeason = Winter;
			break;
			
		case March:
			if (day <= 20) {				// 20th March
				mSeason = Winter;
			} else {
				mSeason = Spring;
			}
			break;
			
		case April:
		case May:
			mSeason = Spring;
			break;
			
		case June:
			if (day <= 20) {				// 20th June
				mSeason = Spring;
			} else {
				mSeason = Summer;
			}
			break;
			
		case July:
		case August:
			mSeason = Summer;
			break;
			
		case September:
			if (day <= 20) {				// 20th September
				mSeason = Summer;
			} else {
				mSeason = Fall;
			}
			break;
			
		case October:
		case November:
			mSeason = Fall;
			break;
			
		case December:
			if (day <= 20) {				// 20th December
				mSeason = Fall;
			} else {
				mSeason = Winter;
			}
			break;
	}
	
	return mSeason;
	
}

//
//  Test if class date is less then mDate
//
bool DateType :: lessThan(DateType mDate)
{
	if ( year < mDate.year ) {				// is year less?
		return true;
	} else if ( year == mDate.year) {		// is year same?

		if (month < mDate.month ) {			// is month less?
			return true;
		} else if (month == mDate.month) {	// is month same?
		
			if ( day < mDate.day ) {		// is day less?
				return true;
			}
		}
	}
	
	return false;							// must be greater
}

//
//  print the date
//
void DateType :: printDate()
{
	int f;
	int bYear = year;						// take temp versions of year 
	int bMonth = month - 2;					// and a temp shifted month
	
	if (bMonth == 0 || bMonth == -1) {		// for Jan & Feb
		bYear = bYear - 1;					// correct the year
		bMonth = bMonth + 12;				// and shift the month
	}
	
	int yfst2 = bYear / 100;				// first two digits of year
	int ylst2 = bYear % 100;				// last two digits of year
	
	// calc day
	f =        ( (int) (2.6 * bMonth - 0.2) +
	             day + ylst2 +
	             (int) (ylst2 / (float) 4) +
	             (int) (yfst2 / (float) 4) - 2 * yfst2 ) % 7;
	             
	// patch: add 7 to neg values of f
	if (f < 0) {
		f += 7;
	}
	
	cout << (DayType) f << ", " << day << " "
	     << (MonthType) (month - 1) << " " << year << endl;
}


//  
//  main:
// 
void main()
{
	DateType today, otherDay;
	
	today.readDate();			// read first date
	today.printDate();
	cout << "Season of " << today.season() << endl;
	
	otherDay.readDate();		// read second date
	otherDay.printDate();
	cout << "Season of " << otherDay.season() << endl;
	
	if (today.lessThan(otherDay)) {
		cout << "first date is earlier than second" << endl;
	} else {
		cout << "second date is earlier than first" << endl;
	}
	
	exit( 0 );
}