#!/usr/bin/env python
#
# Python skripta, ki s pomocjo tabua-java in pdfji na spletni strani lpp
# (http://www.lpp.si/sites/default/files/lpp_vozniredi/iskalnik/index.php)
# preparsa case in podatke o postaji (slednje preko OSM) in jih shrani v
# mongodb (bazo imenovano buslines).
#
# (!) Skripta ignorira morebitne opombe ob casih (recimo 55B ali 20L ali 34Ga
# (!) se pretvori v 55, 18, 34)
#
# Requirements:
#   * tabula-java ... https://github.com/tabulapdf/tabula-java/releases
#   * database on 'mongodb://localhost:27017'
#
# @author David Rubin

import sys
import subprocess
import csv
import re
import pymongo
import requests
import pytz
import datetime
import overpy
from io import StringIO
from collections import defaultdict
from pathlib import Path

# Get tabula jar: https://github.com/tabulapdf/tabula-java/releases
tabula_path = '/home/david/Projects/tabula-java.jar'
osm_api = 'https://nominatim.openstreetmap.org/search/{}?format=json&addressdetails=1&limit=1&type=bus_stop'



def pdf_to_csv(cmd):
    """
    Vrne csv reader za podan tabula ukaz
    """
    # Pozeni ukaz in si shrani izhod
    header_out = subprocess.run(cmd, stdout=subprocess.PIPE).stdout.decode('cp1252')
    # Izhod spremeni v iterable
    header_f = StringIO(header_out)
    # Vrni CSV reader nad podanim izhodom
    return csv.reader(header_f, delimiter=',') # Preberi CSV, ki ga zgorni skript ustvari


def parse_pdf_head(r1, r2):
    """
    Vrne mongodb kompatibilna modela za avtobus in postajo

    l1 je vedno oblike:
    'Odhod iz postajalisca:', '<stevilka postaje>', '<ime postaje>'

    l2 variira (nevem zakaj):
    '', 'v smeri:', ''
    '<linija>', '', '<koncna postaja>'

    ali
    '<linija>', 'v smeri:', '<koncna postaja>'
    """
    bus = {}
    station = {}

    # Bi vedno morala biti le ena vrstica!
    for line in r1:
        station['stationName'] = line[2]
        station['_id'] = int(line[1])
    
    for line in r2:
        bus['busLine'] = line[0]
        bus['busDirection'] = line[2]
    bus['_id'] = bus['busLine'] + '_' + bus['busDirection'][0]

    return bus, station


def parse_pdf_schedule(r):
    """
    Vrne 3 objekte, ki vsebujejo ure in minute za prihode avtobus na postajo glede na dan
    """
    work = {}
    sat = {}
    sun = {}
    for row in r:
        if row[0] == '':
            # Preskoci vrstico, ki ima prazno uro
            continue
        hour = "".join(re.findall(r'\d', row[0]))
        if hour == '':
            continue
        # Delavnik
        work[hour] = parse_minutes_from_row(row[1])
        # Sobota
        sat[hour] = parse_minutes_from_row(row[2])
        # Nedelja in prazniki
        sun[hour] = parse_minutes_from_row(row[3])
    return work, sat, sun


def parse_minutes_from_row(raw_minutes):
    """
    Pretvori grobo obliko seznama v dict
    """
    return re.findall(r'\d{2}', raw_minutes)


def construct_mongo_arrivals(raw_times, day_type, station_id, line_id):
    """
    Ustvari mongodb objekt za vozni red
    """
    arrivals = {}
    arrivals['dayType'] = day_type
    arrivals['station'] = station_id
    arrivals['bus'] = line_id
    arrivals['arrivalTimes'] = []
    for k, v in raw_times.items():
        if len(v) == 0:
            continue
        for mins in v:
            arrivals['arrivalTimes'].append(datetime.datetime(datetime.MINYEAR,
                3, 3, int(k), int(mins), tzinfo=pytz.timezone('CET')))
    return arrivals

def get_station_locations(db):
    """
    Najde lokacijo za vse postaje v Ljubljani s pomocjo overpass
    """

    # Query je izdelana s pomocjo overpass-turbo.eu
    queryString = """
        /*
        This has been generated by the overpass-turbo wizard.
        The original search was:
        “highway=bus_stop in Ljubljana”
        */
        [out:json][timeout:25];
        // fetch area “Ljubljana” to search in
        area(3601675898)->.searchArea;
        // gather results
        (
        // query part for: “highway=bus_stop”
        node["highway"="bus_stop"](area.searchArea);
        way["highway"="bus_stop"](area.searchArea);
        relation["highway"="bus_stop"](area.searchArea);
        );
        // print results
        out body;
        >;
        out skel qt;
    """
    api = overpy.Overpass()
    result = api.query(queryString)
    
    for s in result.nodes:
        lat = float(s.lat)
        lon = float(s.lon)
        try:
            stationID = int(s.tags['name'].split('~')[0].strip())
            stationName = s.tags['name'].split('~')[1].strip()
            db.lpp_stations.insert_one({'_id': stationID, 'location': [lon, lat], 'stationName': stationName})
        except:
            # Postaja se je pojavila 2x??
            continue


def main(args, db):
    global tabula_path
    # Preberi ime datoteke, katero bomo parsali v array (in kasneje shranili v MongoDB)
    if len(args) < 1:
        print("Please include a LPP PDF file as the first argument.")
        sys.exit(1)
    lpp_pdf = args[0]

    # Preberi pot do tabula-java.jar, ce zgoraj podana datoteka ne obstaja
    if len(args) < 2 and not Path(tabula_path).is_file():
        print("Please include the path to the tabula-java.jar as the second argument.")
        sys.exit(1)
    elif len(args) == 2:
        tabula_path = args[1]
    
   


    # Ukaza, ki iz datoteke poparsata dolocen del (prvi je header, drugi pa vozni red)
    cmd_head1 = ['java', '-jar', tabula_path, '-a', '21.197,206.019,39.047,574.175', '-p', '1', lpp_pdf]
    cmd_head2 = ['java', '-jar', tabula_path, '-a', '36.072,156.188,79.953,572.688', '-p', '1', lpp_pdf]
    cmd_sched = ['java', '-jar', tabula_path, '--lattice', '-a', '115.653,125.322,579.753,576.034', '-p', '1', lpp_pdf]

    # Preberi podatke iz glave
    hr1 = pdf_to_csv(cmd_head1)
    hr2 = pdf_to_csv(cmd_head2)
    line, station = parse_pdf_head(hr1, hr2)

    # Ce ime postaje ni nastavljeno preklici delovanje, saj ne moremo dobiti lokacije
    if station['stationName'] == '':
        print('Station has no name on pdf', lpp_pdf)
        return
    # Preberi podatke o delavniku iz voznega reda
    sr = pdf_to_csv(cmd_sched)
    # ride_* je dict, pri katerem so kljuci ure dneva (00-23), vrednosti pa seznam minut
    # ko avtobus pripelje
    ride_work, ride_sat, ride_sun = parse_pdf_schedule(sr)

   
    # Create the bus (if not exists)
    db.lpp_buses.update_one({'_id': line['_id']}, {'$set': {'_id': line['_id'],
        'busDirection': line['busDirection'], 'busLine': line['busLine']}}, upsert=True)

    # Construct the lpp_arrival objects
    try:
        arrivals = [construct_mongo_arrivals(ride_work, 'Delavnik', station['_id'], line['_id']),
            construct_mongo_arrivals(ride_sat, 'Sobota', station['_id'], line['_id']),
            construct_mongo_arrivals(ride_sun, 'Nedelja in prazniki', station['_id'], line['_id'])]
    except ValueError as err:
        print(err)
        print('On pdf: ', lpp_pdf)
        return
    # Store the arrivals in to the db
    db.lpp_arrivals.insert_many(arrivals)

if __name__ == '__main__':
    # Connect to the db
    client = pymongo.MongoClient('mongodb://localhost:27017')
    db = client.buslines

    # Ustvari vse postaje na omocju Ljubljane v MongoDB
    print('Loading all stations from overpass', end="\r")
    get_station_locations(db)
    print('{}Stations loaded'.format('\x1b[2K'))
    main(sys.argv[1:], db)