#!/usr/bin/env python
import json
import os.path
import time
import sys
from optparse import OptionParser
from operator import itemgetter

# Start Parser I grabbed from http://pyparsing.wikispaces.com/UnderDevelopment#toc0
from datetime import datetime, timedelta
from pyparsing import *
import calendar
 
# string conversion parse actions
def convertToTimedelta(toks):
    unit = toks.timeunit.lower().rstrip("s")
    td = {
        'week'    : timedelta(7),
        'day'    : timedelta(1),
        'hour'   : timedelta(0,0,0,0,0,1),
        'hr'   : timedelta(0,0,0,0,0,1),
        'minute' : timedelta(0,0,0,0,1),
        'min' : timedelta(0,0,0,0,1),
        'second' : timedelta(0,1),
        'sec' : timedelta(0,1),
        }[unit]
    if toks.qty:
        td *= int(toks.qty)
    x = 0
    if toks.dir == "":
      x = 1
    else:
      x = toks.dir
    if x > 0:
        td *= -x
    else:
        td *= x
    toks["timeOffset"] = td
 
def convertToDay(toks):
    now = datetime.now()
    if "wkdayRef" in toks:
        todaynum = now.weekday()
        daynames = [n.lower() for n in calendar.day_name]
        nameddaynum = daynames.index(toks.wkdayRef.day.lower())
        if toks.wkdayRef.dir > 0:
            daydiff = (nameddaynum + 7 - todaynum) % 7
        else:
            daydiff = -((todaynum + 7 - nameddaynum) % 7)
        toks["absTime"] = datetime(now.year, now.month, now.day)+timedelta(daydiff)
    else:
        name = toks.name.lower()
        toks["absTime"] = {
            "now"       : now,
            "today"     : datetime(now.year, now.month, now.day),
            "yesterday" : datetime(now.year, now.month, now.day)+timedelta(-1),
            "tomorrow"  : datetime(now.year, now.month, now.day)+timedelta(+1),
            }[name]
 
def convertToAbsTime(toks):
    now = datetime.now()
    if "dayRef" in toks:
        day = toks.dayRef.absTime
        day = datetime(day.year, day.month, day.day)
    else:
        day = datetime(now.year, now.month, now.day)
    if "timeOfDay" in toks:
        if isinstance(toks.timeOfDay,basestring):
            timeOfDay = {
                "now"      : timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond),
                "noon"     : timedelta(0,0,0,0,0,12),
                "midnight" : timedelta(),
                }[toks.timeOfDay]
        else:
            hhmmss = toks.timeparts
            if hhmmss.miltime:
                hh,mm = hhmmss.miltime
                ss = 0
            else:            
                hh,mm,ss = (hhmmss.HH % 12), hhmmss.MM, hhmmss.SS
                if not mm: mm = 0
                if not ss: ss = 0
                if toks.timeOfDay.ampm == 'pm':
                    hh += 12
            timeOfDay = timedelta(0, (hh*60+mm)*60+ss, 0)
    else:
        timeOfDay = timedelta(0, (now.hour*60+now.minute)*60+now.second, now.microsecond)
    toks["absTime"] = day + timeOfDay
 
def calculateTime(toks):
    if toks.absTime:
        absTime = toks.absTime
    else:
        absTime = datetime.now()
    if toks.timeOffset:
        absTime += toks.timeOffset
    toks["calculatedTime"] = absTime
 
# grammar definitions
CL = CaselessLiteral
today, tomorrow, yesterday, noon, midnight, now = map( CL,
    "today tomorrow yesterday noon midnight now".split())
plural = lambda s : Combine(CL(s) + Optional(CL("s")))
week, day, hour, hr, minute, min, second, sec = map( plural,
    "week day hour hr minute min second sec".split())
am = CL("am")
pm = CL("pm")
COLON = Suppress(':')
 
# are these actually operators?
in_ = CL("in").setParseAction(replaceWith(1))
from_ = CL("from").setParseAction(replaceWith(1))
before = CL("before").setParseAction(replaceWith(-1))
after = CL("after").setParseAction(replaceWith(1))
ago = CL("ago").setParseAction(replaceWith(-1))
next_ = CL("next").setParseAction(replaceWith(1))
last_ = CL("last").setParseAction(replaceWith(-1))
 
couple = (Optional(CL("a")) + CL("couple") + Optional(CL("of"))).setParseAction(replaceWith(2))
a_qty = CL("a").setParseAction(replaceWith(1))
integer = Word(nums).setParseAction(lambda t:int(t[0]))
int4 = Group(Word(nums,exact=4).setParseAction(lambda t: [int(t[0][:2]),int(t[0][2:])] ))
qty = integer | couple | a_qty
dayName = oneOf( list(calendar.day_name) + [x.lower() for x in calendar.day_name] )
 
dayOffset = (qty("qty") + (week | day)("timeunit"))
dayFwdBack = (from_ + now.suppress() | ago)("dir")
weekdayRef = (Optional(next_ | last_, -1 )("dir") + dayName("day"))
dayRef = Optional( (dayOffset + (before | after | from_)("dir") ).setParseAction(convertToTimedelta) ) + \
            ((yesterday | today | tomorrow)("name")|
             weekdayRef("wkdayRef")).setParseAction(convertToDay)
todayRef = (dayOffset + dayFwdBack).setParseAction(convertToTimedelta) | \
            (in_("dir") + qty("qty") + day("timeunit")).setParseAction(convertToTimedelta)
 
dayTimeSpec = dayRef | todayRef
dayTimeSpec.setParseAction(calculateTime)
 
hourMinuteOrSecond = (hour | hr | minute | min | second | sec)
 
timespec = Group(int4("miltime") |
                 integer("HH") + 
                 Optional(COLON + integer("MM")) + 
                 Optional(COLON + integer("SS")) + (am | pm)("ampm")
                 )
absTimeSpec = ((noon | midnight | now | timespec("timeparts"))("timeOfDay") + 
                Optional(dayRef)("dayRef"))
absTimeSpec.setParseAction(convertToAbsTime,calculateTime)
 
relTimeSpec = qty("qty") + hourMinuteOrSecond("timeunit") + \
                (from_ | before | after)("dir") + \
                absTimeSpec("absTime") | \
              qty("qty") + hourMinuteOrSecond("timeunit") + ago("dir") | \
              in_ + qty("qty") + hourMinuteOrSecond("timeunit") | \
              qty("qty") + hourMinuteOrSecond("timeunit") 
relTimeSpec.setParseAction(convertToTimedelta,calculateTime)
 
nlTimeExpression = (absTimeSpec | dayTimeSpec | relTimeSpec)

# End parser

def db_append(data):
  db_file = open(os.path.expanduser("~/.ptwdb"), "r")
  db = json.load(db_file)
  db_file.close()
  if not timestamp_and_text_exists(data["timestamp"], data["text"], db):
    db.append(data)
  db_file = open(os.path.expanduser("~/.ptwdb"), "w")
  json.dump(db, db_file, indent=2)
  db_file.close()

def db_new():
  db_file = open(os.path.expanduser("~/.ptwdb"), "w")
  json.dump([], db_file)
  db_file.close()

def db_get(count=None, since=None):
  if count is None and since is None:
    count = 10
  if count is None and since is not None:
    count = 500000
  out = []
  db_file = open(os.path.expanduser("~/.ptwdb"), "r")
  db = json.load(db_file)
  db_file.close()
  db.sort(key=itemgetter(u"timestamp"))
  while count != 0:
    if len(db) == 0:
      break
    x = db.pop()
    if since is not None and x["timestamp"] < since:
      break
    out.append(x)
    count -= 1
  return out

def timestamp_and_text_exists(timestamp, text, db):
  for x in db:
    if x["timestamp"] == timestamp:
      if text.startswith(x["text"]):
        return True
  return False


def format(l):
  out = []
  for x in l:
    timestr = time.strftime("%a %b %d %Y @ %I:%M%p",
                            time.localtime(x["timestamp"]))
    out.append("%s: %s" % (timestr, x["text"]))

  return out


parser = OptionParser()
parser.add_option("--host", dest="hostname", help="Hostname from post",
                  metavar="HOSTNAME", default="localhost")

parser.add_option("--client", dest="client", help="Client for post",
                  default="Command Line", metavar="CLIENT")
parser.add_option("-n", dest="num", help="Number of posts to show",
                  type="int", default=None, metavar="N")
parser.add_option("--source", dest="import_source", help="Source of git imports",
                  type="str", default="", metavar="STR")
parser.add_option("-s", "--since", dest="since", help="Number of posts to show",
                  default=None, metavar="SINCE")
parser.add_option("-v", "--verbose", dest="verbose", help="Debug output",
                  default=False, action="store_true")
(options, args) = parser.parse_args()
if len(args) < 1:
  parser.print_help()
  exit()
command = args[0]
text = " ".join(args[1:])
if command == "post":
  if len(text) < 1:
    parser.print_help()
    exit()
  data = {"timestamp" : time.time(), "text" : text, "client" : options.client,
          "hostname" : options.hostname  }
  if not os.path.exists(os.path.expanduser("~/.ptwdb")):
    db_new()
  db_append(data)
elif command == "get":
  if options.since is not None:
    p = nlTimeExpression.parseString(options.since)
    if "calculatedTime" in p:
      options.since = time.mktime(p.calculatedTime.timetuple())
      if options.verbose:
        print p.calculatedTime.isoformat()
    else:
      options.since = None
  for x in format(db_get(options.num, options.since)):
    print x
elif command == "since":
  if len(args) < 2:
    parser.print_help()
    exit()
  p = nlTimeExpression.parseString(text)
  if "calculatedTime" in p:
    options.since = time.mktime(p.calculatedTime.timetuple())
    if options.verbose:
      print p.calculatedTime.isoformat()
  else:
    options.since = None
  for x in format(db_get(options.num, options.since)):
    print x
elif command == "import":
  for line in sys.stdin:
    fields = line.strip().split("\t")
    ts = int(fields[0])
    text = fields[1]
    if options.import_source != "":
      text = "%s: %s" % (options.import_source, text)
    data = {"timestamp" : ts, "text" : text, "client" : options.client,
            "hostname" : options.hostname }
    if not os.path.exists(os.path.expanduser("~/.ptwdb")):
      db_new()
    db_append(data)
else:
  print "Invalid Command"
  parser.print_help()
  exit()
