#include "ex14_24.h"
#include <algorithm>

//! constructor taking Size as days
//! the argument must be within (0, 2^32)
Date::Date(Size days)
{
    //! calculate the year
    Size y400 = days / YtoD_400;
    Size y100 = (days - y400 * YtoD_400) / YtoD_100;
    Size y4 = (days - y400 * YtoD_400 - y100 * YtoD_100) / YtoD_4;
    Size y = (days - y400 * YtoD_400 - y100 * YtoD_100 - y4 * YtoD_4) / 365;
    Size d = days - y400 * YtoD_400 - y100 * YtoD_100 - y4 * YtoD_4 - y * 365;
    if(d==0){
        this->year = y400 * 400 + y100 * 100 + y4 * 4 + y;
        this->month = 12;
        this->day = 31;
    }
    else {
    this->year = y400 * 400 + y100 * 100 + y4 * 4 + y + 1;

    //! check if leap and choose the months vector accordingly
    std::vector<Size> currYear =
        isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

    //! calculate day and month using find_if + lambda
    Size D_accumu = 0, M_accumu = 0;
    //! @bug    fixed:  the variabbles above hade been declared inside the
    //! find_if as static
    //!                 which caused the bug. It works fine now after being move
    //!                 outside.

    std::find_if(currYear.cbegin(), currYear.cend(), [&](Size m) {

        D_accumu += m;
        M_accumu++;

        if (d < D_accumu) {
            this->month = M_accumu;
            this->day = d + m - D_accumu;

            return true;
        }
        else
            return false;
    });
 }
}

//! construcotr taking iostream
Date::Date(std::istream& is, std::ostream& os)
{
    is >> day >> month >> year;

    if (is) {
        if (check(*this))
            return;
        else {
            os << "Invalid input! Object is default initialized.";
            *this = Date();
        }
    }
    else {
        os << "Invalid input! Object is default initialized.";
        *this = Date();
    }
}

//! copy constructor
Date::Date(const Date& d) : day(d.day), month(d.month), year(d.year)
{
}

//! move constructor
Date::Date(Date&& d) NOEXCEPT : day(d.day), month(d.month), year(d.year)
{
    std::cout << "copy moving";
}

//! copy operator=
Date& Date::operator=(const Date& d)
{
    this->day = d.day;
    this->month = d.month;
    this->year = d.year;

    return *this;
}

//! move operator=
Date& Date::operator=(Date&& rhs) NOEXCEPT
{
    if (this != &rhs) {
        this->day = rhs.day;
        this->month = rhs.month;
        this->year = rhs.year;
    }
    std::cout << "moving =";

    return *this;
}

//! conver to days
Date::Size Date::toDays() const
{
    Size result = this->day;

    //! check if leap and choose the months vector accordingly
    std::vector<Size> currYear =
        isLeapYear(this->year) ? monthsVec_l : monthsVec_n;

    //! calculate result + days by months
    for (auto it = currYear.cbegin(); it != currYear.cbegin() + this->month - 1;
         ++it)
        result += *it;
        
        auto pre_year = this->year - 1;

    //! calculate result + days by years
    result += (pre_year / 400) * YtoD_400;
    result += (pre_year % 400 / 100) * YtoD_100;
    result += (pre_year % 100 / 4) * YtoD_4;
    result += (pre_year % 4) * YtoD_1;

    return result;
}

//! member operators:   +=  -=

Date& Date::operator+=(Date::Size offset)
{
    *this = Date(this->toDays() + offset);
    return *this;
}

Date& Date::operator-=(Date::Size offset)
{
    if (this->toDays() > offset)
        *this = Date(this->toDays() - offset);
    else
        *this = Date();

    return *this;
}

//! non-member operators:  <<  >>  -   ==  !=  <   <=  >   >=

std::ostream& operator<<(std::ostream& os, const Date& d)
{
    os << d.day << " " << d.month << " " << d.year;
    return os;
}

std::istream& operator>>(std::istream& is, Date& d)
{
    if (is) {
        Date input = Date(is, std::cout);
        if (check(input)) d = input;
    }
    return is;
}

int operator-(const Date& lhs, const Date& rhs)
{
    return lhs.toDays() - rhs.toDays();
}

bool operator==(const Date& lhs, const Date& rhs)
{
    return (lhs.day == rhs.day) && (lhs.month == rhs.month) &&
           (lhs.year == rhs.year);
}

bool operator!=(const Date& lhs, const Date& rhs)
{
    return !(lhs == rhs);
}

bool operator<(const Date& lhs, const Date& rhs)
{
    return lhs.toDays() < rhs.toDays();
}

bool operator<=(const Date& lhs, const Date& rhs)
{
    return (lhs < rhs) || (lhs == rhs);
}

bool operator>(const Date& lhs, const Date& rhs)
{
    return !(lhs <= rhs);
}

bool operator>=(const Date& lhs, const Date& rhs)
{
    return !(lhs < rhs);
}

Date operator-(const Date& lhs, Date::Size rhs)
{ //!  ^^^ rhs must not be larger than 2^32-1
    //! copy lhs
    Date result(lhs);
    result -= rhs;

    return result;
}

Date operator+(const Date& lhs, Date::Size rhs)
{ //!  ^^^ rhs must not be larger than 2^32-1
    //! copy lhs
    Date result(lhs);
    result += rhs;

    return result;
}
