
//
// This is example code from Chapter 9.8 "The Date class" of 
// "Programming -- Principles and Practice Using C++" by Bjarne Stroustrup
//

#include "Chrono.h"
#include <vector>
using std::vector;
#include <string>
using std::string;
#include <stdexcept>
using std::runtime_error;

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();
}*/
//------------------------------------------------------------------------------
Date::Date(int yy, Month mm, int dd) : y(yy), m(mm), d(dd) {
	if (!is_date(yy,mm,dd)) {   //is_Valid()) {
		y = 1970;
		d = 1;
		m = Month::Jan;
	}
}
//------------------------------------------------------------------------------

const Date& default_date()
{
    static const Date dd(1970, Date::Month::Jan, 1); // start of computer era
    return dd;
}

//------------------------------------------------------------------------------

Date::Date() //default constructor
    :y(default_date().year()),
     m(default_date().month()),
     d(default_date().day())
{ }

//------------------------------------------------------------------------------
Date::Month int_to_month(int m) {
	if (m > 0 && m < 13) return Date::Month(m);
	else runtime_error("bad month");
}
//------------------------------------------------------------------------------

vector<string> MonthTbl{ "", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };

//------------------------------------------------------------------------------

ostream& operator<<(ostream& osref, Date::Month m) {
	osref << MonthTbl[int(m)];
	return osref;
}

Date::Month operator++(Date::Month& m) {
	m = (m == Date::Month::Dec) ? Date::Month::Jan : Date::Month(int(m) + 1);
	return m;
}
//------------------------------------------------------------------------------
bool isLeapYr(int y) {
	if (y % 400 == 0) return true;
	if (y % 100 == 0) return false;
	if (y % 4 == 0) return true;
	return false;
}
//------------------------------------------------------------------------------

void Date:: add_day(int n) {
	if (n == 0) return;   // base case
	if (m == Month::Dec && d == 31) {  // is it New Year's eve?
		++y;
		d = 1;
		m = Month::Jan;
		return add_day(n - 1);
	}
	switch (int(m)) {
	case 1:
	case 3:
	case 5:
	case 7:
	case 8:
	case 10:
	case 12: {
		if (d == 31) { // is it the end of the month?
			++m;
			d = 1;
			return add_day(n - 1);
			break;
		}
		++d;
		return add_day(n - 1);
		break;
	}
	case 4:
	case 6:
	case 9:
	case 11: {
		if (d == 30) { // is it the end of the month
			++m;
			d = 1;
			return add_day(n - 1);
			break;
		}
		++d;
		return add_day(n - 1);
		break;
	}
	case 2: // February
		if (isLeapYr(y) && d == 29) {
			++m;
			d = 1;
			return add_day(n - 1);
		}
		if (d = 28) {
			++m;
			d = 1;
			return add_day(n - 1);
		}
		++d;
		return add_day(n - 1);
	default:
		return;
	} // end switch
}

//------------------------------------------------------------------------------

void Date::add_month(int n)
{
    // ...where do you go if you Dec 31 and you add 2 months?
	add_day(30);
}

//------------------------------------------------------------------------------

void Date::add_year(int n)
{
    if (m==Month::Feb && d==29 && !isLeapYr(y+n)) { // beware of leap years!
        m = Month::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::Month::Feb:                        // the length of February varies
    days_in_month = (isLeapYr(y))?29:28;
    break;
	case Date::Month::Apr: case Date::Month::Jun: case Date::Month::Sep: case Date::Month::Nov:
    days_in_month = 30;                // the rest have 30 days
    break;
    }

    if (days_in_month<d) return false;

    return true;
} 

//------------------------------------------------------------------------------

/**bool leapyear(int y)
{
    // See exercise ???
    return false;
}*/

//------------------------------------------------------------------------------

bool operator==(const Date& a, const Date& b)
{
    return a.year()==b.year()
        && a.month()==b.month()
        && a.day()==b.day();
}
//------------------------------------------------------------------------------

bool Date::operator==(const Date& a)  {
	return a.year() == year()
		&& a.month() == month()
		&& a.day() == day();
}
//------------------------------------------------------------------------------

bool operator!=(const Date& a, const Date& b) {
    return !(a==b);
}

//------------------------------------------------------------------------------

bool Date::operator!=(const Date& a) {
	return !(a == *this);
}

//------------------------------------------------------------------------------

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)
{
    // ...
    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::Month::Jul, 4); // initialization
    //Chrono::Date d2 = Chrono::next_Sunday(holiday);
    //Chrono::Day  d  = day_of_week(d2);
	cout << "holiday is " << holiday; // << " d2 is " << d2 << '\n';
    //return holiday != d2;
	cin.get();
}
/*catch (Chrono::Date::Invalid&) {
    cerr << "error: Invalid date\n"; 
    return 1;
}
catch (...) {
    cerr << "Oops: unknown exception!\n"; 
    return 2;
}*/

//------------------------------------------------------------------------------
