//
// This is example code from Chapter 9.8 "The Date class" of 
// "Programming -- Principles and Practice Using C++" by Bjarne Stroustrup
//

#include <iostream>

using namespace std;

// file Chrono.h

namespace Chrono {

	//------------------------------------------------------------------------------

	class Date {
	public:
		enum Month {
			jan = 1, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
		};

		class Invalid { };                 // to throw as exception

		Date(int y, Month m, int d);       // check for valid date and initialize
		Date();                            // default constructor
		// the default copy operations are fine

		// non-modifying operations:
		int   day()   const { return d; }
		Month month() const { return m; }
		int   year()  const { return y; }

		// modifying operations:
		void add_day();
		void add_day(int n);
		void add_month();
		void add_month(int n);
		void add_year(int n);
	private:
		int   y;
		Month m;
		int   d;
	};

	//------------------------------------------------------------------------------

	bool is_date(int y, Date::Month m, int d); // true for valid date

	//------------------------------------------------------------------------------

	bool leapyear(int y);                  // true if y is a leap year

	//------------------------------------------------------------------------------

	bool operator==(const Date& a, const Date& b);
	bool operator!=(const Date& a, const Date& b);

	//------------------------------------------------------------------------------

	ostream& operator<<(ostream& os, const Date& d);
	istream& operator>>(istream& is, Date& dd);

	Date::Month operator++(Date::Month& m)                       // prefix increment operator
	{
		m = (m == Date::Month::dec) ? Date::Month::jan : Date::Month(m + 1);             // "wrap around"
		return m;
	}

	//------------------------------------------------------------------------------

} // Chrono

//------------------------------------------------------------------------------

//The definitions go into Chrono.cpp:

// Chrono.cpp

namespace Chrono {

	// member function definitions:

	//------------------------------------------------------------------------------

	Date::Date(int yy, Month mm, int dd)
		: y(yy), m(mm), d(dd)
	{
		if (!is_date(yy, mm, dd)) throw Invalid();
	}

	//------------------------------------------------------------------------------

	const Date& default_date()
	{
		static const Date dd(2001, Date::jan, 1); // start of 21st century
		return dd;
	}

	//------------------------------------------------------------------------------

	Date::Date()
		:y(default_date().year()),
		m(default_date().month()),
		d(default_date().day())
	{
	}

	//------------------------------------------------------------------------------

	void Date::add_day()
	{
		if (d < 28) { ++d; }
		else if (d == 28 && Date::m == feb && !leapyear(y)) {
			d = 1;
			Date::m = mar;
		}
		else if (d == 28 && Date::m == feb)
			if (leapyear(y)) ++d;
			else {
				d = 1;
				Date::m = mar;
			}
		else if (d == 29 && Date::m == feb) {
			d = 1;
			Date::m = mar;
		}
		else if (27 < d && d < 30) ++d;
		else if (d == 30) {
			if (Date::m == apr || Date::m == jun || Date::m == sep || Date::m == nov) {
				++Date::m;
				d = 1;
			}
			else ++d;
		}
		else {
			if (Date::m == dec) ++y;
			++Date::m;
			d = 1;
		}
	}

	//------------------------------------------------------------------------------

	void Date::add_day(int n) {
		for (int i = 0; i < n; ++i)
			add_day();
	}

	//------------------------------------------------------------------------------

	void Date::add_month() {
		if (Date::m == feb)
			if (leapyear(y))
				add_day(29);
			else add_day(28);
		else if (Date::m == apr || Date::m == jun || Date::m == sep || Date::m == nov)
			add_day(30);
		else if (Date::m = dec) {
			++Date::m;
			++y;
		}
		else add_day(31);
	}

	//------------------------------------------------------------------------------

	void Date::add_month(int n)
	{
		for (int i = 0; i < n; ++i)
			add_month();
	}

	//------------------------------------------------------------------------------

	void Date::add_year(int n)
	{
		if (m == feb && d == 29 && !leapyear(y + n)) { // beware of leap years!
			m = mar;        // use March 1 instead of February 29
			d = 1;
		}
		y += n;
	}

	//------------------------------------------------------------------------------

	// helper functions:

	bool is_date(int y, Date::Month  m, int d)
	{
		// assume that y is valid

		if (d <= 0) return false;            // d must be positive

		int days_in_month = 31;            // most months have 31 days

		switch (m) {
		case Date::feb:                        // the length of February varies
			days_in_month = (leapyear(y)) ? 29 : 28;
			break;
		case Date::apr: case Date::jun: case Date::sep: case Date::nov:
			days_in_month = 30;                // the rest have 30 days
			break;
		}

		if (days_in_month < d) return false;

		return true;
	}

	//------------------------------------------------------------------------------

	bool leapyear(int y)
	{
		if (y % 100 == 0) return false;
		else if (y % 4 == 0) return true;
		return false;
	}

	//------------------------------------------------------------------------------

	bool operator==(const Date& a, const Date& b)
	{
		return a.year() == b.year()
			&& a.month() == b.month()
			&& a.day() == b.day();
	}

	//------------------------------------------------------------------------------

	bool operator!=(const Date& a, const Date& b)
	{
		return !(a == b);
	}

	//------------------------------------------------------------------------------

	ostream& operator<<(ostream& os, const Date& d)
	{
		return os << '(' << d.year()
			<< ',' << d.month()
			<< ',' << d.day()
			<< ')';
	}

	//------------------------------------------------------------------------------

	istream& operator>>(istream& is, Date& dd)
	{
		int y, m, d;
		char ch1, ch2, ch3, ch4;
		is >> ch1 >> y >> ch2 >> m >> ch3 >> d >> ch4;
		if (!is) return is;
		if (ch1 != '(' || ch2 != ',' || ch3 != ',' || ch4 != ')') { // oops: format error
			is.clear(ios_base::failbit);                    // set the fail bit
			return is;
		}
		dd = Date(y, Date::Month(m), d);     // update dd
		return is;
	}

	//------------------------------------------------------------------------------

	enum Day {
		sunday, monday, tuesday, wednesday, thursday, friday, saturday
	};

	//------------------------------------------------------------------------------

	Day day_of_week(const Date& d)
	{
		/* Outline of algo:
		1.  Determine if the year is a leapyear
		2.	Determine the doomsday for that year
		3.	Determine the closest doomsday to your day
		4.	Count the number of days between your day and the nearest doomsday.
		5.	Compute that number mod 7
		6.	Advance the doomsday by that many days to determine the day of the week for your date.

		Example:  Calculate the day of the week for Feb. 24, 2016
		1.	Look up the doomsday for 2016 somehow: Monday
		2.	The closest doomsday is the last day of Feb...and it's a leapyear, so that's 2/29
		3.	
		*/
		return sunday;
	}

	//------------------------------------------------------------------------------

	Date next_Sunday(const Date& d)
	{
		// ...
		return d;
	}

	//------------------------------------------------------------------------------

	Date next_weekday(const Date& d)
	{
		// ...
		return d;
	}

	//------------------------------------------------------------------------------

} // Chrono

//------------------------------------------------------------------------------

int main()
try
{
	Chrono::Date holiday(1978, Chrono::Date::jul, 4); // initialization
	Chrono::Date d2 = Chrono::next_Sunday(holiday);
	Chrono::Day  d = day_of_week(d2);
	cout << "holiday is " << holiday << " d2 is " << d2 << endl;
	for (int i = 0; i < 400; ++i) {
		holiday.add_day();
		cout << "holiday = " << holiday << endl;
		if (i % 30 == 0) cin.get();
	}
	return holiday != d2;
}
catch (Chrono::Date::Invalid&) {
	cerr << "error: Invalid date\n";
	return 1;
}
catch (...) {
	cerr << "Oops: unknown exception!\n";
	return 2;
}

//------------------------------------------------------------------------------