//
//  File:   hzCron.h
//
//  Legal Notice: This file is part of the HadronZoo C++ Class Library.
//
//  Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
//  The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
//  Software Foundation, either version 3 of the License, or any later version.
//
//  The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
//  A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
//
//  The hzCron class serves as an extension to the HadronZoo Date classes for mainstream scheduling purposes. It applies rules to schedule periodic tasks in 'real world' dates such
//  as the last Sunday of the month. Once initialized a hzCron instance can test if the associated task would run on the supplied date and can be wound back and forth to establish
//  past and future dates for the task.
//
//  Initialization is both a matter of defining the dates upon which the task can run and the time(s) the task will run at on those dates. To enable schedules that differ according
//  to day of week and official holidays etc, multiple hzCron instances can be used. Where multiple sets of date and time rules apply, the effect of the rules are superimposed. For
//  example, if you want a task such as a system backup, to run midnight every day plus every hour during work time Monday to Friday plus four times on Saturday and bank holidays
//  and twice on Sunday - you would perameterize as follows:-
//
//  <datetime:rule period="Every Day">
//      <runtime absolute="00:00:00">
//  </datetime:rule>
//  <datetime:rule period="Monday - Friday">
//      <datetime:execpt>Bank Holidays</datetime:execpt>
//      <runtime freq="Every hour" start="08:30:00" stop="18:30:00"/>
//  </datetime:rule>
//  <datetime:rule period="Every Saturday">
//      <datetime:execpt>25:12<</datetime:execpt>
//      <runtime freq="10800" start="11:30:00" stop="20:30:00"/>
//  </datetime:rule>
//  <datetime:rule period="Every Sunday">
//      <datetime:execpt>25:12<</datetime:execpt>
//      <runtime time="11:30:00"/>
//      <runtime time="17:30:00"/>
//  </datetime:rule>
//
//  Dates are specified by a hzPeriodicity enum and where applicable, a hzMonthrule enum - together with an optional set of exceptions. The associated times may be absolute times 
//  (eg 06.30.00) or a frequency (eg every hour) together with a start and stop time.
//
#ifndef hzCron_h
#define hzCron_h
#include "hzDate.h"
/*
**  Public Holidays
*/
extern  const uint32_t  HZ_PUBHOL_NEWYEAR ;         //  Always Jan 1st
extern  const uint32_t  HZ_PUBHOL_GOOD_FRIDAY ;     //  Friday before Easter
extern  const uint32_t  HZ_PUBHOL_EASTER_SUNDAY ;   //  Easter Sunday
extern  const uint32_t  HZ_PUBHOL_EASTERMONDAY ;    //  Monday before Easter
extern  const uint32_t  HZ_PUBHOL_MAYDAY ;          //  First Monday in May
extern  const uint32_t  HZ_PUBHOL_SPRING ;          //  Last Monday in May
extern  const uint32_t  HZ_PUBHOL_SUMMER ;          //  Last Monday in August
extern  const uint32_t  HZ_PUBHOL_XMAS ;            //  Always Dec 25th
extern  const uint32_t  HZ_PUBHOL_BOXING ;          //  Always Dec 25th
extern  const uint32_t  HZ_PUBHOL_LUE_NEWYEAR ;     //  First working day of the year
extern  const uint32_t  HZ_PUBHOL_LUE_XMAS ;        //  Either Dec 25th or first working day after that date
extern  const uint32_t  HZ_PUBHOL_LUE_BOXING ;      //  Either Dec 26th or first working day after xmas day in lieu
extern  const uint32_t  HZ_PUBHOL_ALLUK ;           //  All UK public holidays
/*
**  Enum definitions
*/
enum    hzPeriodicity
{
    //  Category:   Scheduling
    //
    //  Used to define the periodicity of a periodic task in standard calenda terms. Note that where the periodicity is a month or more, further qualification with a hzMonthrule is
    //  required.
    HZPERIOD_NEVER,         //  Never runs
    //  Not dependent on monthrules or reference date
    HZPERIOD_DAY,           //  Every day
    HZPERIOD_MONSAT,        //  Mon - Sat
    HZPERIOD_WEEKDAY,       //  Weekdays only
    HZPERIOD_EMON,          //  Every Monday
    HZPERIOD_ETUE,          //  Every Tuesday
    HZPERIOD_EWED,          //  Every Wednesday
    HZPERIOD_ETHR,          //  Every Thursday
    HZPERIOD_EFRI,          //  Every Friday
    HZPERIOD_ESAT,          //  Every Saturday
    HZPERIOD_ESUN,          //  Every Sunday
    //  Need reference date to detemine which week is valid
    HZPERIOD_ALT_MON,       //  Every other Monday
    HZPERIOD_ALT_TUE,       //  Every other Tuesday
    HZPERIOD_ALT_WED,       //  Every other Wednesday
    HZPERIOD_ALT_THR,       //  Every other Thursday
    HZPERIOD_ALT_FRI,       //  Every other Friday
    HZPERIOD_ALT_SAT,       //  Every other Saturday
    HZPERIOD_ALT_SUN,       //  Every other Sunday
    //  Dependent on monthrules to determine day of month to run
    HZPERIOD_MONTH,         //  Monthly
    HZPERIOD_MONTH1,        //  Bi-Monthly (odd)
    HZPERIOD_MONTH2,        //  Bi-Monthly (even)
    HZPERIOD_QTR1,          //  Quarterly (1)
    HZPERIOD_QTR2,          //  Quarterly (2)
    HZPERIOD_QTR3,          //  Quarterly (3)
    HZPERIOD_HYEAR1,        //  Half-Yearly (1)
    HZPERIOD_HYEAR2,        //  Half-Yearly (2)
    HZPERIOD_HYEAR3,        //  Half-Yearly (3)
    HZPERIOD_HYEAR4,        //  Half-Yearly (4)
    HZPERIOD_HYEAR5,        //  Half-Yearly (5)
    HZPERIOD_HYEAR6,        //  Half-Yearly (6)
    HZPERIOD_YEAR1,         //  Yearly (Jan)
    HZPERIOD_YEAR2,         //  Yearly (Feb)
    HZPERIOD_YEAR3,         //  Yearly (Mar)
    HZPERIOD_YEAR4,         //  Yearly (Apr)
    HZPERIOD_YEAR5,         //  Yearly (May)
    HZPERIOD_YEAR6,         //  Yearly (Jun)
    HZPERIOD_YEAR7,         //  Yearly (Jul)
    HZPERIOD_YEAR8,         //  Yearly (Aug)
    HZPERIOD_YEAR9,         //  Yearly (Sep)
    HZPERIOD_YEAR10,        //  Yearly (Oct)
    HZPERIOD_YEAR11,        //  Yearly (Nov)
    HZPERIOD_YEAR12,        //  Yearly (Dec)
    HZPERIOD_RANDOM,        //  Completly Random
    HZPERIOD_INVALID        //  Invalid
} ;
enum    hzMonthrule
{
    //  Category:   Scheduling
    //
    //  Used to specify which day in a month, the task should run
    HZMONTHRULE_NULL,               //  Not applicable (default)
    HZMONTHRULE_ERA_DERIVE,         //  From Start Date
    HZMONTHRULE_FIRST_DAY,          //  First of month
    HZMONTHRULE_FIRST_WEEK_DAY,     //  First of month
    HZMONTHRULE_FIRST_WORK_DAY,     //  First of month
    HZMONTHRULE_LAST_DAY,           //  Last of month
    HZMONTHRULE_LAST_WEEK_DAY,      //  Last of month
    HZMONTHRULE_LAST_WORK_DAY,      //  Last of month
    HZMONTHRULE_1ST_MON,            //  1st Monday
    HZMONTHRULE_1ST_TUE,            //  1st Tuesday
    HZMONTHRULE_1ST_WED,            //  1st Wednesday
    HZMONTHRULE_1ST_THR,            //  1st Thursday
    HZMONTHRULE_1ST_FRI,            //  1st Friday
    HZMONTHRULE_1ST_SAT,            //  1st Saturday
    HZMONTHRULE_1ST_SUN,            //  1st Sunday
    HZMONTHRULE_2ND_MON,            //  2nd Monday
    HZMONTHRULE_2ND_TUE,            //  2nd Tuesday
    HZMONTHRULE_2ND_WED,            //  2nd Wednesday
    HZMONTHRULE_2ND_THR,            //  2nd Thursday
    HZMONTHRULE_2ND_FRI,            //  2nd Friday
    HZMONTHRULE_2ND_SAT,            //  2nd Saturday
    HZMONTHRULE_2ND_SUN,            //  2nd Sunday
    HZMONTHRULE_3RD_MON,            //  3rd Monday
    HZMONTHRULE_3RD_TUE,            //  3rd Tuesday
    HZMONTHRULE_3RD_WED,            //  3rd Wednesday
    HZMONTHRULE_3RD_THR,            //  3rd Thursday
    HZMONTHRULE_3RD_FRI,            //  3rd Friday
    HZMONTHRULE_3RD_SAT,            //  3rd Saturday
    HZMONTHRULE_3RD_SUN,            //  3rd Sunday
    HZMONTHRULE_4TH_MON,            //  4th Monday
    HZMONTHRULE_4TH_TUE,            //  4th Tuesday
    HZMONTHRULE_4TH_WED,            //  4th Wednesday
    HZMONTHRULE_4TH_THR,            //  4th Thursday
    HZMONTHRULE_4TH_FRI,            //  4th Friday
    HZMONTHRULE_4TH_SAT,            //  4th Saturday
    HZMONTHRULE_4TH_SUN,            //  4th Sunday
    HZMONTHRULE_LAST_MON,           //  Last Monday
    HZMONTHRULE_LAST_TUE,           //  Last Tuesday
    HZMONTHRULE_LAST_WED,           //  Last Wednesday
    HZMONTHRULE_LAST_THR,           //  Last Thursday
    HZMONTHRULE_LAST_FRI,           //  Last Friday
    HZMONTHRULE_LAST_SAT,           //  Last Saturday
    HZMONTHRULE_LAST_SUN,           //  Last Sunday
    HZMONTHRULE_INVALID             //  Invalid
} ;
/*
**  Non-member prototypes of functions to convert periodicities/monthrules to/from strings
*/
hzPeriodicity   Str2Periodicity (const hzString& P) ;
hzMonthrule     Str2Monthrule   (const hzString& R) ;
const char*     Periodicity2Str (const hzPeriodicity P) ;
const char*     Monthrule2Str   (const hzMonthrule mrule) ;
/*
**  Class definitions
*/
class   hzCron
{
    //  Category:   Scheduling
    //
    //  The hzCron class controls when a task may run. There is both a 'run day' component which determines what days a task can be run (e.g. Weekdays only) and
    //  a time component (e.g. every hour from 6.30am to 6.30pm inclusive). This aims to cope with calenda intervals more in keeping with 'real life' scheduling
    //  requirements as well as fixed intervals such as every five minutes or every six hours.
    //
    //  The run day component is implimented as an enumerated periodicity (hzPeriodicity), which if daily or weekly stands on its own. For 14 day periodicities,
    //  specified as 'every other' of whatever day, an 'Era start' date is also needed in order to determine if the current week applies. Periodicities that are
    //  Monthly or longer require an enumerated monthrule.
    //
    //  Specifying run days by the periodicity on its own or in conjunction with either an era start date or a month rule, does not cater for public holidays or
    //  any other days that might be deemed an exception. This is currently dealt with by a 32 bit flag which selects which public holidays are to be observed.
    //  This approach has been depricated and awaits replacement. The intended new approach will keep the per hzCron instance flag but will test this against a
    //  map of dates to flag values. If the current day is in the map and the flags clash the task will not be queued up for that day. Populating the map will
    //  be the responsibility of the application. Note also that there is currently no means to specify what if anything should happen in lieu of excluded run
    //  days.
    hzString        m_error ;       //  Error reports
    hzSDate         m_Era ;         //  First valid date in series
    hzTime          m_Wake ;        //  First time timer will trigger on the first valid date
    hzPeriodicity   m_Period ;      //  Periodicity (frequency)
    hzMonthrule     m_Rule ;        //  When to run in month (for month based events)
    uint32_t        m_ItemOffset ;  //  Start value for items (for the era; eg issue=1000 starts on Jan 1st, 1875)
    uint32_t        m_exclhols ;    //  Exclude [holiday] flags
    uchar           m_frMth ;       //  Exclude from month/day
    uchar           m_frDay ;       //  Exclude from month/day
    uchar           m_toMth ;       //  Exclude to month/day
    uchar           m_toDay ;       //  Exclude to month/day
    bool            m_bActive ;     //  Set by validation
public:
    hzCron  (void)
    {
        m_Period = HZPERIOD_NEVER ;
        m_Rule = HZMONTHRULE_NULL ;
        m_ItemOffset = 0 ;
        m_exclhols = 0 ;
        m_frMth = 0 ;
        m_frDay = 0 ;
        m_toMth = 0 ;
        m_toDay = 0 ;
        m_bActive = false ;
    }
    ~hzCron (void)
    {
    }
    //  Set functions
    void    SetEra      (const hzSDate& era)                    { m_bActive = false ; m_Era = era ; }
    void    SetEra      (const hzSDate& era, uint32_t nOset)    { m_bActive = false ; m_Era = era ; m_ItemOffset = nOset ; }
    void    SetPeriod   (const hzPeriodicity period)            { m_bActive = false ; m_Period = period ; }
    void    SetRule     (const hzMonthrule mrule)               { m_bActive = false ; m_Rule = mrule ; }
    hzEcode SetEraDate  (uint32_t Y, uint32_t M, uint32_t D)    { m_bActive = false ; return m_Era.SetDate(Y, M, D) ; }
    hzEcode SetEraDate  (const hzString& dstr)                  { m_bActive = false ; return m_Era.SetDate(*dstr) ; }
    hzEcode SetEraTime  (uint32_t h, uint32_t m, uint32_t s)    { m_bActive = false ; return m_Wake.SetTime(h, m, s) ; }
    hzEcode SetEraTime  (const hzString& dstr)                  { m_bActive = false ; return m_Wake.SetTime(*dstr) ; }
    void    SetPeriod   (const hzString& pstr)      { m_bActive = false ; m_Period = Str2Periodicity(pstr) ; }
    void    SetRule     (const hzString& rstr)      { m_bActive = false ; m_Rule = Str2Monthrule(rstr) ; }
    //  Get functions
    const hzSDate       Era     (void) const    { return m_Era ; }
    const hzPeriodicity Period  (void) const    { return m_Period ; }
    const hzMonthrule   Rule    (void) const    { return m_Rule ; }
    const hzTime        Time    (void) const    { return m_Wake ; }
    hzString&   Error   (void)  { return m_error ; }
    //  Initialize in one go
    hzEcode Initialize  (hzSDate& start, hzPeriodicity P, hzMonthrule mrule) ;
    void    Exclude     (uint32_t phFlags) ;
    void    Exclude     (uint32_t month, uint32_t day) ;
    void    Exclude     (uint32_t fromMonth, uint32_t fromDay, uint32_t toMonth, uint32_t toDay) ;
    hzEcode Exclude     (const hzString& arg) ;
    hzEcode Exclude     (const hzString& from, const hzString& to) ;
    //  Initialize after setting values individually
    hzEcode Validate    (void) ;
    //  Test if a date meets periodicity and monthrules
    hzEcode TestDate    (hzSDate& d) ;
    hzEcode TestDate    (hzXDate& d) ;
    hzEcode Advance     (hzXDate& date, uint32_t factor = 1) ;
    hzEcode Advance     (hzSDate& date, uint32_t factor = 1) ;
    hzEcode Retard      (hzXDate& date, uint32_t factor = 1) ;
    hzEcode Retard      (hzSDate& date, uint32_t factor = 1) ;
    bool    operator==  (hzCron& op)    { return m_Era == op.m_Era && m_Period == op.m_Period && m_Rule == op.m_Rule ? true : false ; }
    bool    operator!=  (hzCron& op)    { return m_Era == op.m_Era && m_Period == op.m_Period && m_Rule == op.m_Rule ? false : true ; }
} ;
/*
**  Prototypes
*/
uint32_t    IsHoliday   (hzSDate& date, uint32_t holFlags = 0) ;
uint32_t    IsHoliday   (hzXDate& date, uint32_t holFlags = 0) ;
#endif  //  hzCron_h