#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: scheduleparser.py 12290 2011-02-10 14:02:38Z pstorz $
import re
import operator

import datetime,time

# parser for schedules 
# 1.: downcase string
# 2.: find strings: jan-dec, w00-w53, mon-sun, 1-31, 1st-5th| first-fifth, overrides:

# Keywords (from dird/run_conf.c

# TODO: put this in central c2xxx file

keyword_definition_string = """
static struct s_keyw keyw[] = {
  {NT_("on"),         s_none,    0},
  {NT_("at"),         s_at,      0},

  {NT_("sun"),        s_wday,    0},
  {NT_("mon"),        s_wday,    1},
  {NT_("tue"),        s_wday,    2},
  {NT_("wed"),        s_wday,    3},
  {NT_("thu"),        s_wday,    4},
  {NT_("fri"),        s_wday,    5},
  {NT_("sat"),        s_wday,    6},
  {NT_("jan"),        s_month,   0},
  {NT_("feb"),        s_month,   1},
  {NT_("mar"),        s_month,   2},
  {NT_("apr"),        s_month,   3},
  {NT_("may"),        s_month,   4},
  {NT_("jun"),        s_month,   5},
  {NT_("jul"),        s_month,   6},
  {NT_("aug"),        s_month,   7},
  {NT_("sep"),        s_month,   8},
  {NT_("oct"),        s_month,   9},
  {NT_("nov"),        s_month,  10},
  {NT_("dec"),        s_month,  11},

  {NT_("sunday"),     s_wday,    0},
  {NT_("monday"),     s_wday,    1},
  {NT_("tuesday"),    s_wday,    2},
  {NT_("wednesday"),  s_wday,    3},
  {NT_("thursday"),   s_wday,    4},
  {NT_("friday"),     s_wday,    5},
  {NT_("saturday"),   s_wday,    6},
  {NT_("january"),    s_month,   0},
  {NT_("february"),   s_month,   1},
  {NT_("march"),      s_month,   2},
  {NT_("april"),      s_month,   3},
  {NT_("june"),       s_month,   5},
  {NT_("july"),       s_month,   6},
  {NT_("august"),     s_month,   7},
  {NT_("september"),  s_month,   8},
  {NT_("october"),    s_month,   9},
  {NT_("november"),   s_month,  10},
  {NT_("december"),   s_month,  11},

  {NT_("daily"),      s_daily,   0},
  {NT_("weekly"),     s_weekly,  0},
  {NT_("monthly"),    s_monthly, 0},
  {NT_("hourly"),     s_hourly,  0},

  {NT_("1st"),        s_wom,     0},
  {NT_("2nd"),        s_wom,     1},
  {NT_("3rd"),        s_wom,     2},
  {NT_("4th"),        s_wom,     3},
  {NT_("5th"),        s_wom,     4},

  {NT_("first"),      s_wom,     0},
  {NT_("second"),     s_wom,     1},
  {NT_("third"),      s_wom,     2},
  {NT_("fourth"),     s_wom,     3},
  {NT_("fifth"),      s_wom,     4},
  {NULL,         s_none,    0}
};
"""
#RXP_KEYWORDDEF  = re.compile('^\s+\{NT_\("(?P<keyword>[\w|\d]+"\)).*')
RXP_KEYWORDDEF  = re.compile('\s+{NT_\("(.*)"\),.*s_(\w+),.*(\d+)},\n')
#RXP_KEYWORDDEF  = re.compile('.*')
RXP_OVERRIDE = re.compile('(?P<name>\S+\s*)=(?P<value>\s*\S+)')
#RXP_OVERRIDE = re.compile('(?P<name>\w+)\s*=\s*(?P<value>\w+)')


on= {}
at = {}
wday = {}
month = {}
daily = {}
weekly = {}
monthly = {}
hourly = {}
wom = {}

mday = {}
for i in range(31):
  mday[str(i+1)] = i+1 
  
woy = {}
for i in range(54):
  index = "w%02d" % (i)  
  woy[index] = i


keywords = {}
keywords['on'] = on
keywords['at'] = at
keywords['wday'] = wday
keywords['month'] = month
keywords['daily'] = daily
keywords['weekly'] = weekly
keywords['hourly'] = hourly
keywords['monthly'] = monthly
keywords['wom'] = wom
keywords['mday'] = mday
keywords['woy'] = woy

#for kwdef in RXP_KEYWORDDEF.finditer(keyword_definition_string):
  #print kwdef.group(1), kwdef.group(2),kwdef.group(3)
  #if kwdef.group(2) == 'none': 
  #  continue
#  evalstring = "%s['%s']=%s" % (kwdef.group(2), kwdef.group(1),kwdef.group(3))
#  print evalstring

on['on']=0  
at['at']=0
wday['sun']=0
wday['mon']=1
wday['tue']=2
wday['wed']=3
wday['thu']=4
wday['fri']=5
wday['sat']=6
month['jan']=0
month['feb']=1
month['mar']=2
month['apr']=3
month['may']=4
month['jun']=5
month['jul']=6
month['aug']=7
month['sep']=8
month['oct']=9
month['nov']=10
month['dec']=11
wday['sunday']=0
wday['monday']=1
wday['tuesday']=2
wday['wednesday']=3
wday['thursday']=4
wday['friday']=5
wday['saturday']=6
month['january']=0
month['february']=1
month['march']=2
month['april']=3
month['june']=5
month['july']=6
month['august']=7
month['september']=8
month['october']=9
month['november']=10
month['december']=11
daily['daily']=0
weekly['weekly']=0
monthly['monthly']=0
hourly['hourly']=0
wom['1st']=0
wom['2nd']=1
wom['3rd']=2
wom['4th']=3
wom['5th']=4
wom['first']=0
wom['second']=1
wom['third']=2
wom['fourth']=3
wom['fifth']=4


scheduleKeywords = ('at', 'on', 'wday', 'month', 'wom', 'mday', 'woy',
                    'hourly','daily','weekly','monthly')

periodKeywords = ('hourly','daily','weekly','monthly')

weekdays = ('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat')
ordinals = ('1st', '2nd', '3rd', '4th', '5th')
months = ('jan', 'feb', 'mar', 'apr', 'may', 'jun', 
          'jul', 'aug', 'sep', 'oct', 'nov', 'dec')

overrides = ('level','pool','storage',
             'spoolsize','spooldata','messages',
             'fullpool','incrementalpool','differentialpool',
             'writepartafterjob', 'priority')

bool_overrides = ('spooldata','writepartafterjob')





class RunEntry:
  def __init__(self,inputString=""):
    self.inputString = inputString
    self.sched = {}
    self.hour = 0
    self.minute = 0
    self.value = ''
    self.runTimes = []
    for kw in scheduleKeywords:
      self.sched[str(kw)] = set([])
      
    self.overrides = {}
    for ov in overrides:
      self.overrides[ov] = None
    
    #for pk in periodKeywords:
    #  self.sched[pk] = True
    
    if type(self.inputString).__name__ == 'str' and len(self.inputString) > 0:
      self.parseString()
    elif self.inputString == None:
      self.value=None
    else:
      self.sched['at'] = set(['0'])
      self.sched['on'] = set(['0'])
    # disabled for profiling
    #self.calcStarttimes()
  
  def setValue(self,string):
    if type(string).__name__ == 'str' or  type(string).__name__ == 'unicode':
      self.inputString = string
      if len(self.inputString) > 0:
        self.parseString( )
    elif string is None:
      self.value = None
    else:
      print("setValue called with wrong param type:%s") % type(string)
      self.inputString = ''
      
    #self.calcStarttimes()
    

  def runsAtDateTime(self,dt):
    '''
    check if this runentry will run at given datetime
    '''
    wom = (dt.day-1)/7
    (year,weeknumber,weekday) = dt.isocalendar()
    #tm_wom(dt.day,(dt.weekday()+8)%7)
    #print wom
    
    #if runentry.minute != dt.minute:
    #print "minute yes: %s == %s" % (runentry.minute, dt.minute)
    #else:
    #print "minute no: %s != %s" % (runentry.minute, dt.minute)      
    #  return False
    #print len(runentry.sched['hourly'])
    if self.hour != dt.hour and len(self.sched['hourly']) == 0:
      #print "hour yes: %s == %s" % (runentry.hour, dt.hour)
      #else:
      #print "hour no: %s != %s" % (runentry.hour, dt.hour)      
      return False
    
    #if len(runentry.sched['wom']) > 0:
    if ( len(self.sched['wom']) != 0 
       and wom not in self.sched['wom']):
      #print "WOM_YES %s %s" % (wom,runentry.sched['wom'])
      #else:
      #print "WOM_NO %s %s" % (wom,runentry.sched['wom'])
      return False
      
    if (len(self.sched['woy']) != 0 
      and weeknumber not in self.sched['woy']):
      # print "WOY_YES %s %s" % (weeknumber,runentry.sched['woy'])
      #else:
      #print "WOY_NO %s %s" % (weeknumber,runentry.sched['woy'])
      return False
      #else:
      #  print "WOY_YES %s %s" % (weeknumber,runentry.sched['woy'])
      
    if (len(self.sched['mday']) != 0 
      and dt.day not in self.sched['mday']):
      #print "MDAY_YES %s %s" % (dt.day,runentry.sched['mday'])
      #else:
      #print "MDAY_NO %s %s" % (dt.day,runentry.sched['mday'])   
      return False
    
    if (len(self.sched['month']) != 0  # month is configured
      and dt.month-1 not in self.sched['month'] # actual month is not given
      ):
      #print "MONTH_YES %s %s" % (dt.month,runentry.sched['month'])
      #else:
      #print "MONTH_NO %s %s" % (dt.month,runentry.sched['month'])     
      return False
  
    if (len(self.sched['wday']) != 0 
      and (dt.weekday()+1)%7 not in self.sched['wday']):
      #print "WEEKDAY_YES %s %s" % (dt.weekday(),runentry.sched['wday'])
      #else:
      #print "WEEKDAY_NO %s %s" % ((dt.weekday()+1)%7,runentry.sched['wday'])     
      return False
    
    return True
    
    
  def calcStarttimes(self):
    ''' 
    calculates the starting times 
    of this run entry for the next year 
    '''
    self.runTimes=[]
    timestart= (int(time.time())/60*60)
    timeend = timestart + 86400*366 #TODO: echte Zeitdifferenze in 1 Jahr nehmen
    
    ts = timestart
    while ts < timeend:
      ts += 3600 # check every hour
      #ts += 86400
      #print datetime.datetime.fromtimestamp(ts)#,  runsAtDateTime(datetime.datetime.fromtimestamp(ts))
      dt = datetime.datetime.fromtimestamp(ts)
      if self.runsAtDateTime(dt):
        dt = dt.replace(minute=self.minute)
        #print dt, dt.weekday()
        self.runTimes.append(dt)
        #print dt
    
   
  def findRanges(self,list):
    if len(list) == 0:
      return set()
    ranges = []
    #inrange = False
    rangeentry = [None,None]
    for number in sorted(list):
      #print number,
      if rangeentry[1] != number - 1:
        if rangeentry != [None,None]: # do not append initial rageentry
          ranges.append(rangeentry)
        rangeentry=[number,number] # start new rangeentry
        #print "startet new rangeentry:" + str(rangeentry)
      else:
        rangeentry[1]=number  # inside range
      
    ranges.append(rangeentry) # append last range   
    return ranges
     
  def __str__(self):
    s = '' # outputstring
    # print overrides
    for ov,val in self.overrides.iteritems():
      if val != None:
        s += "%s=%s " %(ov,val)

        
    if len(self.sched['on']):
      s += 'on '
    
    # month
    outlist = []
    for m in self.findRanges(self.sched['month']):
      if m[0]==m[1]:              # range start == range stop
        outlist.append(months[m[0]])
      else:
        outlist.append("%s-%s" % (months[m[0]],months[m[1]]) )
    s += ','.join(outlist)
    s += ' '
    
    # monthday
    outlist = []
    for m in self.findRanges(self.sched['mday']):
      if m[0]==m[1]:              # range start == range stop
        outlist.append(str(m[0]))
      else:
        outlist.append("%s-%s" % (str(m[0]),str(m[1])) )
      #outlist.append(str(m))
    s += ','.join(outlist)
    s += ' '
     
    # weekofmonth
    outlist = []
    for m in self.findRanges(self.sched['wom']):
      if m[0]==m[1]:              # range start == range stop
        outlist.append(ordinals[m[0]])
      else:
        outlist.append("%s-%s" % (ordinals[m[0]],ordinals[m[1]]) )
      #outlist.append(ordinals[m])
    s += ','.join(outlist)    
    s += ' '
    
    # weekday
    outlist = []
    for m in self.findRanges((self.sched['wday'])):
      if m[0]==m[1]:              # range start == range stop
        outlist.append(weekdays[m[0]])
      else:
        outlist.append("%s-%s" % (weekdays[m[0]],weekdays[m[1]]) )
      
      #outlist.append(weekdays[m])
    s += ','.join(outlist)    
    s += ' '
    
    # yearweek
    outlist = []
    for m in self.findRanges((self.sched['woy'])):
      if m[0]==m[1]:              # range start == range stop
        outlist.append("w%02d" % (m[0]))
      else:
        outlist.append("%s-%s" % ("w%02d" % (m[0]),"w%02d" % (m[1])) )
      #outlist.append("w%02d" % (m))
    s += ','.join(outlist)    
    s += ' '
   
    # hourly daily weekly monthly
    for kw in periodKeywords:
      if self.sched[kw]: 
        s += kw + ' '
    # at keyword
    if len(self.sched ['at']):
      s += 'at '
    # here TIME  
    if self.value is not None:
      s += "%02d:%02d" %(self.hour, self.minute)  
    return s
  
  def parseString(self):
    # extract overrides
    if len(self.inputString) == 0:
      print "inputString has zero length!"
      return
    #print  "parsestring called with string>" + self.inputString +"<" 
    for ov in RXP_OVERRIDE.finditer(self.inputString):
      #print "Found override:" + ov.group('name') +'='+ ov.group('value')
      #print "stripped override:" + ov.group('name').strip().lower() +'='+ ov.group('value').strip()
      self.overrides[ov.group('name').strip().lower()] = ov.group('value').strip()
      tmpstring = self.inputString.replace(ov.group('name')+'='+ov.group('value'),'') # remove found values from string
      self.inputString = tmpstring
      
    self.inputString = self.inputString.lower()
    #print self.inputString

    # extract time
    timeindex = self.inputString.rfind(':')
    time = self.inputString[timeindex-2:timeindex+3]
    self.minute = int(time[-2:])
    self.hour = int(time[:2])
    #print time
    tmpstring = self.inputString.replace(time,'') # remove time from string
    self.inputString = tmpstring
    #print self.inputString

    rng = {}  # range for keywordclasses   
    
    #for keywordclass, keywords in sorted(keywords.iteritems(), reverse = True):
    #for keywordclass, kws in keywords.iteritems(): # this is not possible, we need the right order
    for keywordclass in ['hourly','daily','weekly','monthly', # first, may collide with mon(day) 
                     'woy', # may collide with mday 
                     'wday',# may collide with mday (1)st
                    'at', 'on',  'month', 'wom', 'mday'
                    ]:
      kws = keywords[keywordclass]
      rng['start'] = None
      rng['end'] = None         
      #print "-----"+ keywordclass, kws
      # for keyword,value in sorted(kws.iteritems()):
  
      for keyword,value in sorted(kws.items(), key = operator.itemgetter(1), reverse = True):

        # check for range 
        rangestart = self.inputString.find(keyword + '-')
        if rangestart != -1:
          #print "rangestart found:" + keyword + ": -> " + str(value)
          rng['start'] = value
        
        endrange = self.inputString.find('-' + keyword)
        if endrange != -1:
          #print "rangeend found:" + keyword + ": -> " + str(value)
          rng['end'] = value
        #print keyword,value
        
        kwindex = self.inputString.find(keyword)

        if  kwindex != -1:
          # cut out found string, so it can not match next keywords....
          tmpstring = self.inputString.replace(keyword,'')
          self.inputString = tmpstring
          #print self.inputString
          if ( (rng['start'] is None) or (rng['end'] is None) ):  
            #print "found " + keywordclass + ": -> " + str(value)
            #print self.sched[keywordclass]
            self.sched[keywordclass].add(value)
          
            #print self.inputString
            
      # do we have a range?
      if (rng['start'] is not None) and (rng['end'] is not None):
        for i in range(rng['start'],rng['end']):
          #print "setting %d because of range" % i
          self.sched[keywordclass].add(i)  
          
            
    
# 1.: downcase string
# 2.: find strings: jan-dec, w00-w53, mon-sun, 1-31, 1st-5th| first-fifth, overrides:
if __name__ == "__main__":
#  inputstring = 'on Jul,Aug,Oct,sep 1,2,3,4,10,16 first,2nd,3rd,fifth Tue,Wed w23,w26,w28,w37 monthly at 03:00'
#  print inputstring
#  schedule = Schedule(inputstring)
#  print schedule
  
  inputstring = 'Level = Full Pool=FileStoragePool Storage=FileStorage on sat at 23:10'
#  print inputstring
#  schedule = Schedule(inputstring)
#  print schedule
      
  #inputstring = 'Level =  Full hourly SpoolData=yes Pool=Export-Server-Pool Storage=TandbergT40 w01-w19 1-12 jan-nov 1st-3rd mon-sat at 0:01'
  print inputstring
  schedule = RunEntry(inputstring)
  #print schedule, schedule.value,
  print len(schedule.runTimes), schedule.runTimes
  
  #dt = datetime.datetime.now()
  #print schedule.runsAtDateTime(dt)#,schedule.runTimes
  
  #schedule = RunEntry(None)
  #print schedule, schedule.value
  


