/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#ifndef ecflow_attribute_CronAttr_HPP
#define ecflow_attribute_CronAttr_HPP

#include "ecflow/core/TimeSeries.hpp"

namespace ecf {
class Calendar;
} // namespace ecf

namespace ecf {

// *****************************************************************
// A cron attribute with a single time slot is repeated indefinitely
// ******************************************************************
// Use compiler ,  destructor, assignment, copy constructor,
class CronAttr {
public:
    CronAttr();
    explicit CronAttr(const std::string& time_series);
    CronAttr(const TimeSlot& s, const TimeSlot& f, const TimeSlot& i) : timeSeries_(s, f, i) {}
    explicit CronAttr(const TimeSeries& ts) : timeSeries_(ts) {}
    CronAttr(int h, int m, bool relative = false) : timeSeries_(h, m, relative) {}

    void print(std::string&) const;
    bool operator==(const CronAttr& rhs) const;
    bool operator<(const CronAttr& rhs) const { return timeSeries_ < rhs.timeSeries_; }
    bool structureEquals(const CronAttr& rhs) const;

    void addWeekDays(const std::vector<int>& w);
    void add_last_week_days_of_month(const std::vector<int>& w);
    void addDaysOfMonth(const std::vector<int>& d);
    void add_last_day_of_month() { last_day_of_month_ = true; }
    void addMonths(const std::vector<int>& m);

    void addTimeSeries(const TimeSlot& s, const TimeSlot& f, const TimeSlot& i) { timeSeries_ = TimeSeries(s, f, i); }
    void addTimeSeries(const TimeSeries& ts) { timeSeries_ = ts; }
    void add_time_series(int h, int m, bool relative = false) { timeSeries_ = TimeSeries(h, m, relative); }

    // Once a cron is free its stays free, until re-queue is called
    void calendarChanged(const ecf::Calendar& c); // can set attribute free
    void resetRelativeDuration();

    void reset_only();
    void reset(const ecf::Calendar& c);
    void requeue(const ecf::Calendar& c, bool reset_next_time_slot = true);

    void miss_next_time_slot();
    void setFree(); // ensures that isFree() always returns true
    bool isSetFree() const { return free_; }
    bool isFree(const ecf::Calendar&) const;
    bool checkForRequeue(const ecf::Calendar&) const;
    bool validForHybrid(const ecf::Calendar&) const;
    bool why(const ecf::Calendar&, std::string& theReasonWhy) const;
    bool last_day_of_the_month() const { return last_day_of_month_; }

    // The state_change_no is never reset. Must be incremented if it can affect equality
    // Note: changes in state of timeSeries_, i.e affect the equality operator (used in test)
    //       must be captured. i.e things like relative duration & next_time_slot are
    //       reported by the Why command, & hence need to be synced.
    unsigned int state_change_no() const { return state_change_no_; }

    bool checkInvariants(std::string& errormsg) const;

    // Query:
    const TimeSeries& time() const { return timeSeries_; }
    const TimeSeries& time_series() const { return timeSeries_; }
    bool last_day_of_month() const { return last_day_of_month_; }
    std::vector<int>::const_iterator week_days_begin() const { return weekDays_.begin(); }
    std::vector<int>::const_iterator week_days_end() const { return weekDays_.end(); }
    std::vector<int>::const_iterator last_week_days_of_month_begin() const { return last_week_days_of_month_.begin(); }
    std::vector<int>::const_iterator last_week_days_end_of_month_end() const { return last_week_days_of_month_.end(); }
    std::vector<int>::const_iterator days_of_month_begin() const { return daysOfMonth_.begin(); }
    std::vector<int>::const_iterator days_of_month_end() const { return daysOfMonth_.end(); }
    std::vector<int>::const_iterator months_begin() const { return months_.begin(); }
    std::vector<int>::const_iterator months_end() const { return months_.end(); }

    std::string name() const; // for display/gui
    std::string toString() const;
    std::string dump() const;

    /// parse the line tokens an create a cron attribute. Can throw std::runtime_error
    /// The index parameter allows us to parse:
    /// cron -w 0,1 10:00      // index = 1
    /// -w 0,1 10:00           // index = 0
    /// Expect to parse:
    /// 	cron 23:00                 # run every day at 23:00
    /// 	cron 10:00 20:00 01:00     # run every hour between 10am and 8pm
    ///   cron -w 0,1 10:00          # run every sunday and monday at 10am
    ///   cron -w 0,1L  10:00        # run every sunday and last monday of the month at 10am
    ///   cron -d 10,11,12 12:00     # run 10th, 11th and 12th of each month at noon
    ///   cron -d 10,L     12:00     # run 10th, and last day of the month at noon
    /// 	cron -m 1,2,3 12:00        # run on Jan,Feb and March every day at noon.
    ///   cron -w 0   -m 5,6,7,8 10:00 20:00 01:00 # run every sunday, between May-Aug, every hour between 10am and 8pm
    ///   cron -d 1,L -m 5,6,7,8 10:00 20:00 01:00 # run on first and last day of month, between May-Aug, every hour
    ///   between 10am and 8pm
    static void parse(CronAttr&, const std::vector<std::string>& lineTokens, size_t index, bool parse_state = false);
    static CronAttr create(const std::string& cronString);

private:
    void write(std::string&) const;

private:
    void clearFree(); // resets the free flag
    bool is_day_of_week_day_of_month_and_month_free(const ecf::Calendar&) const;

    bool week_day_matches(int) const;
    bool last_week_day_of_month_matches(const ecf::Calendar&) const;
    bool day_of_month_matches(int, const ecf::Calendar& c) const;
    bool month_matches(int) const;

    boost::gregorian::date next_date(const ecf::Calendar& calendar) const;

    TimeSeries timeSeries_;
    std::vector<int> weekDays_;
    std::vector<int> last_week_days_of_month_;
    std::vector<int> daysOfMonth_;
    std::vector<int> months_;
    unsigned int w_{0};               // NOT implemented, weekday nearest the given day of the month 15W
    unsigned int state_change_no_{0}; // *not* persisted, only used on server side
    bool last_day_of_month_{false};
    bool free_{false}; // persisted for use by why() on client side

    friend class cereal::access;
    template <class Archive>
    void serialize(Archive& ar, std::uint32_t const version);
};

} // namespace ecf

#endif /* ecflow_attribute_CronAttr_HPP */
