"""
flightCal -- make hCalendar from text flight info

:Author: `Dan Connolly`_
:Version: $Revision: 1.15 $ of $Date: 2006/04/21 07:29:00 $
:Copyright: `W3C Open Source License`_ Share and enjoy.

.. _Dan Connolly: http://www.w3.org/People/Connolly/
.. _W3C Open Source License: http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231


Note: we use str.rsplit() which is new in python2.4

Usage
-----

Run a la::

   python2.4 title <flightinfo.txt >flightinfo.html


where flightinfo.txt is a text flight itinerary
in a format produced, I think, by Sabre_.

.. _Sabre: http://en.wikipedia.org/wiki/Sabre_%28computer_system%29


Testing and Colophon
--------------------

The examples in the docstrings below are executable doctest_ unit
tests.  Check them a la::

  python flightCal.py --test

.. _doctest: http://www.python.org/doc/lib/module-doctest.html

This module is documented in rst_ format for use with epydoc_.

.. _epydoc: http://epydoc.sourceforge.net/
.. _rst: http://docutils.sourceforge.net/docs/user/rst/quickstart.html

"""

import re
from xml.sax.saxutils import escape as xmldata

import aptdata # from http://dev.w3.org/cvsweb/2001/palmagent/

__version__ = "$Id: flightCal.py,v 1.15 2006/04/21 07:29:00 connolly Exp $"
__docformat__ = 'restructuredtext en'

TestData = """
 25 FEB 06  -  SATURDAY
    AIR   AMERICAN AIRLINES    FLT:1557   ECONOMY
          LV KANSAS CITY INTL             512P           EQP: MD-80
          DEPART: TERMINAL BUILDING C                    01HR 33MIN
          AR CHICAGO OHARE                645P           NON-STOP
          ARRIVE: TERMINAL 3                             REF: JKLWXS
          CONNOLLY/DANIEL   SEAT-26F   AA-XDW5282
    AIR   AMERICAN AIRLINES    FLT:98     ECONOMY        MULTI MEALS
          LV CHICAGO OHARE                1025P          EQP: BOEING 777
          DEPART: TERMINAL 3                             07HR 35MIN

 26 FEB 06  -  SUNDAY
          AR LONDON HEATHROW              1200N          NON-STOP
          ARRIVE: TERMINAL 3                             REF: JKLWXS
          CONNOLLY/DANIEL   SEAT-34J   AA-XDW5282
    AIR   AMERICAN AIRLINES    FLT:6580   ECONOMY        MEALS
          OPERATED BY BRITISH AIRWAYS
          LV LONDON HEATHROW              415P           EQP: BOEING 757
          DEPART: TERMINAL 1                             02HR 00MIN
          AR NICE                         715P           NON-STOP
          ARRIVE: AEROGARE 1                             REF: JKLWXS
"""


class Template(object):
    def __init__(self, title, events):
        self.title = title
        self.events = events
        
    def generate(self):
        yield DocTop % {'title': xmldata(self.title)}
        for e in self.events:
            yield "<tr class='vevent'>\n"
            e['url_x'] = xmldata(e['url']) # a bit of a kludge...
            yield DateCell % e
            if e['ARLV'] == 'LV':
                yield DepartCell % e
            else:
                if not e.has_key('SEAT'): e['SEAT'] = '?'
                yield ArriveCell % e
            if e.has_key('location_vcard'):
                where = e['location_vcard']
                where['geo_lat'] = where['geo']['latitude'] # kludge again...
                where['geo_lon'] = where['geo']['longitude']
                where['extended-address'] = where.get('adr', {}).get('extended-address', '')
                yield GeoCell % where
            else:
                yield LocationCell % e
            yield "</tr>\n"
        yield DocBottom % self

DocTop = u"""<?xml version="1.0" encoding="utf-8"?><!--*- nxml -*-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head profile="http://www.w3.org/2003/g/data-view
		 http://purl.org/NET/erdf/profile">
  <link rel="transformation" href="http://www.w3.org/2002/12/cal/glean-hcal.xsl"/>
<title>%(title)s</title>\
</head>
<body>
<h1>%(title)s</h1>
<table border='1'>
<tr><th>Date</th><th>Summary</th><th>Location</th></tr>
"""

DateCell = u"""
<td>%(date)s</td>
"""

DepartCell = u"""
<td class='description'>
 <span class='summary'>
  <a class='url' href='%(url_x)s'>%(carrier)s FLT:%(FLT)d</a><br />
  %(ARLV)s</span> %(day)s
  <abbr class='dtstart' title='%(dtstart)s'>%(time)s</abbr>
  <br />%(EQP)s
</td>
"""

ArriveCell = u"""
<td class='description'>
 <span class='summary'>
  <a class='url' href='%(url_x)s'>%(carrier)s FLT:%(FLT)d</a><br />
  %(ARLV)s</span> %(day)s
  <abbr class='dtstart' title='%(dtstart)s'>%(time)s</abbr>
  <br />SEAT: %(SEAT)s
</td>
"""


GeoCell = u"""
<td class='location vcard'>
 <a href='%(url)s' class='url org fn'>%(org)s
  <abbr class='geo' title='%(geo_lat)s;%(geo_lon)s'>
    (<span class='nickname'>%(nickname)s</span>)
  </abbr>
 </a>
 <div class='adr'>
   <span class='extended-address'>%(extended-address)s</span>
 </div>
</td>
"""

LocationCell = u"""
<td class='location'>%(location)s</td>
"""

DocBottom = u"""
</table>
<address>generated using flightCal.py<br />
See <a href='Makefile'>Makefile</a> and 
<a href='./'>other nearby files</a> for details.</address>
</body>
</html>
"""



def progress(*args):
    import sys
    for a in args:
        sys.stderr.write('%s ' % a)
    sys.stderr.write("\n")

    
def flights(lines, web=None):
    overnight = {}
    for date, day, daylines in splitDays(lines):
        for ev in splitEvents(date, day, daylines, overnight):
            fill(ev, web)
            yield ev
        overnight = ev

def fill(ev, web=None):
    ev['dtstart'] = "%sT%s" % (ev['date'], stdTime(ev['time']))
    ev['url'] = fltlink(ev['carrier'], ev['FLT'])

    airport = {'org': ev['location'], 'fn': ev['location']}
    iata_nick(airport)
    if web and airport.has_key('nickname'):
        aptdata.airportCard(web, airport['nickname'], airport)
        progress("looked up airport:", airport)
    if airport.has_key('geo'):
        where = airport
        txt = ev.get('DEPART', None) or ev.get('ARRIVE', None) or None
        if txt:
            where = {'adr': {}} # new, more specialized location
            where.update(airport)
            # would be nice to fill in country-name from wikipedia too
            where['adr']['extended-address'] = txt
        ev['location_vcard'] = where
    # hmm... we throw away url if geo not found. maybe keep it?


def iata_nick(card):
    """Set nickname to the iata code
    if the org field of a card is a known airport name
    """
    org = card['org']
    for iata, n in aptdata.Airports:
        if org == n:
            card['nickname'] = iata

def locationCell(w, loc):
    for iata, n in aptdata.Airports:
        if loc == n:
            where = aptdata.airportCard(iata)
            where['geo_lat'] = where['geo']['latitude']
            where['geo_lon'] = where['geo']['longitude']
            w("  <td class='location vcard'><a href='%(url)s'>%(org)s <abbr class='geo' title='%(geo_lat)s,%(geo_lon)s'>(%(nickname)s)</abbr></a></td>\n" % where)
            return
    else:
        w("  <td class='location'>%s</td>\n" % \
          (loc,))

def stdDate(ddmmyy):
    """
    >>> stdDate("04 MAR 06")
    '2006-03-04'
    """
    day, month, year = ddmmyy.split()
    year = 2000 + int(year)
    month = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
             'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'].index(month) + 1
    day = int(day)
    return "%04d-%02d-%02d" % (year, month, day)
    
def stdTime(hhmm):
    """
    >>> stdTime("423P")
    '16:23:00'
    """
    min = int(hhmm[-3:-1])
    hr = int(hhmm[:-3])
    if hhmm.endswith("P"): hr += 12
    sec = 0
    tz = '' # local time
    return "%02d:%02d:%02d%s" % (hr, min, sec, tz)
    
def whenElt(ddmmyy, hhmm, elt='abbr', prop='dtstart'):
    """
    >>> whenElt("04 MAR 06", "423P")
    "<abbr class='dtstart' title='2006-03-04T16:23:00'>423P</abbr>"

    note: we assume post-y2k dates
    and local time
    """

    day, month, year = ddmmyy.split()
    year = 2000 + int(year)
    month = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
             'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'].index(month) + 1
    day = int(day)
    min = int(hhmm[-3:-1])
    hr = int(hhmm[:-3])
    if hhmm.endswith("P"): hr += 12
    sec = 0
    tz = '' # local time
    return "<%s class='%s' title='%04d-%02d-%02dT%02d:%02d:%02d%s'>%s</%s>" % \
               (elt, prop, year, month, day, hr, min, sec, tz, hhmm, elt)


def fltlink(carrier, num):
    """

    >>> fltlink('AMERICAN AIRLINES', 1557)
    'https://bwi.flightview.com/fvSabreVT/fvCPL.exe?vthost=1W&acid=1557&qtype=htm&AL=AA&Find1=Track+Flight'
    
    """

    airlines = {'AMERICAN AIRLINES': 'AA',

                # http://en.wikipedia.org/wiki/Midwest_Airlines
                'MIDWEST AIRLINES': 'YX'
                }
    airlineCode = airlines[carrier]
    action = 'https://bwi.flightview.com/fvSabreVT/fvCPL.exe'
    params = {'AL': airlineCode,
              'acid': str(num),
              'qtype': 'htm',
              'Find1': 'Track+Flight',
              'vthost': '1W'
              }
    q = '&'.join(['%s=%s' % (n, v) for n, v in params.iteritems()])
    return "%s?%s" % (action, q)


def splitDays(lines):
    r""" split lines in to (yyyy_mm_dd, day, daylines) tuples

    >>> len(list(splitDays(TestData.split("\n"))))
    2

    >>> len(splitDays(TestData.split("\n")).next()[2])
    10
    
    >>> splitDays(TestData.split("\n")).next()[0]
    '2006-02-25'
    
    >>> i = splitDays(TestData.split("\n")); dummy=i.next(); i.next()[0]
    '2006-02-26'
    
    
    """

    date, day, daylines = None, None, []
    
    for ln in lines:
        m = re.search("(\d\d \w\w\w \d\d)  -  ([A-Z]+)$", ln)
        if m:
            if daylines:
                yield (date, day, daylines)
                daylines = []
            date, day = stdDate(m.group(1)), m.group(2)
        elif date:
            daylines.append(ln)
    
    if daylines:
        yield (date, day, daylines)


def splitEvents(date, day, lines, flt={}):
    r""" split daylines into AR/LV events

    >>> days = splitDays(TestData.split("\n")); \
    ... date, day, lines = days.next(); \
    ... events = splitEvents(date, day, lines); \
    ... e=events.next(); (e['carrier'], e['FLT'], e['note'], e['dur'])
    ('AMERICAN AIRLINES', 1557, 'ECONOMY', '01HR 33MIN')

    >>> days = splitDays(TestData.split("\n")); \
    ... date, day, lines = days.next(); \
    ... events = splitEvents(date, day, lines); \
    ... d=events.next(),events.next(); e=events.next();e['FLT']
    98

    """


    lines = iter(lines)

    while 1:
        try:
            ln = lines.next()
        except StopIteration:
            return
        ln = ln.strip()
        
        if ln.startswith("AIR"):
            flt = flightLine(ln)
            flt['date'] = date
            flt['day'] = day
        elif ln.startswith("LV"):
            e = dict([(k, flt[k]) for k in
                      ('carrier', 'FLT', 'note', 'date', 'day')])
            e.update(lvLine(ln))
            depline = lines.next()
            e['dur'] = depline.strip()[-10:]
            e['DEPART'] = depline[depline.index(':')+1:-11].strip()
            e['ARLV'] = 'LV'
            yield e
        elif ln.startswith("AR"):
            e = dict([(k, flt[k]) for k in
                      ('carrier', 'FLT', 'note', 'date', 'day')])
            e.update(arLine(ln))
            txt, e['REF'] = lines.next().strip().split("REF: ")
            e['ARRIVE'] = txt.strip().split("ARRIVE: ")[1]
            txt = lines.next()
            if txt.find('SEAT-') >= 0:
                e['SEAT'] = txt.split('SEAT-')[1].split(' ', 1)[0]
            e['note'] = txt + e['note']
            e['ARLV'] = 'AR'
            yield e
            
                     
def flightLine(ln):
    """
    >>> flightLine("AIR   AMERICAN AIRLINES    FLT:1557   ECONOMY") == \\
    ... {'carrier': 'AMERICAN AIRLINES', 'FLT': 1557, 'note': 'ECONOMY'}
    True
    
    >>> flightLine("AIR   AMERICAN AIRLINES    FLT:98     ECONOMY        MULTI MEALS") == \\
    ... {'carrier': 'AMERICAN AIRLINES', 'FLT': 98, 'note': 'ECONOMY        MULTI MEALS'}
    True
    
    """
    
    i = ln[4:].index("FLT:")
    carrier = ln[4:i].strip()
    flt = ln[i:].split()[0]
    notes = ln[i+4+len(flt):].strip()
    flt = int(flt.split(':')[1])
    return {'carrier': carrier, 'FLT': flt, 'note': notes}


def lvLine(ln):
    r"""
    >>> lvLine("LV KANSAS CITY INTL             512P           EQP: MD-80") ==\
    ... {'location': 'KANSAS CITY INTL', 'time': '512P', 'EQP': 'MD-80'}
    True
    """

    i = ln.index("EQP:")
    eqp = ln[i+5:]
    apt, when = ln[3:i].strip().rsplit(None, 1)
    return {'location': apt, 'time': when, 'EQP': eqp}


def arLine(ln):
    """
    >>> arLine("AR CHICAGO OHARE                645P           NON-STOP") == \\
    ... {'location': 'CHICAGO OHARE', 'time': '645P', 'note': 'NON-STOP'}
    True
    """
    
    apt, when, cls = ln[3:].rsplit(None, 2)
    return {'location': apt, 'time': when, 'note': cls}


def _test():
    import doctest
    doctest.testmod()

if __name__ == '__main__':
    import sys
    if '--test' in sys.argv:
        _test()
    else:
        events = flights(sys.stdin, aptdata.WebCache("wikipedia-cache").get)
        t = Template(sys.argv[1], events)
        for s in t.generate():
            sys.stdout.write(s.encode('utf-8'))
