# -*- encoding: utf-8 -*-
from flask import Flask, render_template, flash, url_for, redirect, request
from database import db
from sqlalchemy.exc import OperationalError, IntegrityError
import os
from forms import *
import requests
from filters import register_filters
from googleapi import get_distance, DistanceServiceError
from rest import rest_get_all, rest_get_id, rest_delete_id, rest_post, rest_put
from requests.exceptions import HTTPError
import flask.ext.restless
from collections import namedtuple
from dateutil import parser

app = Flask(__name__)
app.config.from_object('wsgi.config')

db.init_app(app)
register_filters(app)

CAR_FIELDS=["id", "manufacturer", "typ", "platenumber", "color", "consumption", "price", "seats", "station"]
KUNDE_FIELDS = [ "id", "name", "street", "plz", "city", "country", "leihen"]
STATION_FIELDS = [ "id", "name", "street", "plz", "city", "country"]
LEIHE_FIELDS = ["id", "kunde_id", "car_id", "von", "bis", "returned", "station_abhol_id", "station_return_id"]

class Car(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
    def __repr__(self):
        return "%s %s %s" % (self.manufacturer, self.typ, self.platenumber)

class Station(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    @property
    def adresse(self):
        return "%s, %s %s, %s" % (self.street, self.plz, self.city, self.country)

class Kunde(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    @property
    def adresse(self):
        return "%s, %s %s, %s" % (self.street, self.plz, self.city, self.country)

class Leihe(object):
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)
        if 'kunde_id' in self.__dict__.keys():
            try:
                self.kunde = rest_get_id("kunden", self.kunde_id)["name"]
            except KeyError:
                self.kunde = 'None'
        if 'von' in self.__dict__.keys():
            self.von = parser.parse(self.von)
        if 'bis' in self.__dict__.keys():
            self.bis = parser.parse(self.bis)
        if 'car_id' in self.__dict__.keys():
            car = rest_get_id("cars", self.car_id)
            car = Car(**car)
            self.car = car.__repr__()
        if hasattr(self, 'station_return_id'):
            self.station_return = rest_get_id("stations", self.station_return_id)["name"]
        if hasattr(self, 'station_abhol_id'):
            self.station_abhol = rest_get_id("stations", self.station_abhol_id)["name"]

def tidy_objects(objects, fields):
    objects.sort(key=lambda x: x["id"])
    for o in objects:
        for key in o.keys():
            if key not in fields:
                del(o[key])
                continue
            if isinstance(o[key], dict):
                if "name" in o[key].keys():
                    o[key] = o[key]["name"]
                elif "id" in o[key].keys():
                    o[key] = o[key]["id"]
            elif isinstance(o[key], list):
                del(o[key])
    return objects


COUNTRIES = [ (u'Österreich', u'AT', u'EUR'), (u"Ungarn", u"HU", u"HUF" ), (u"Schweiz", u"CH", u"CHF")]

@app.before_first_request
def fill_countries():
    """
    Die Liste von Ländern wird über 2 Webservices geholt. Das erste hlt von www.oorsprong.org eine Liste mit allen Ländern.
    Dabei sind diese als JSON verfügbar und werden nach Kontinente aufgeteilt.

    Wir nehmen nur die europäischen Länder und verwendet das zweite Webservice - wieder Json - um aus den ISO-Codes die
    deutsche Bezeichnung zu erhalten.

    Da beide Services von Privatpersonen zur Verfügung gestellt werden, möchte ich deren Server nicht überlasten und die
    Services werden nur genutzt, wenn die app nicht im Debugmode läuft.
    """
    global COUNTRIES
    if app.debug:
        try:
            response = requests.get('https://raw.githubusercontent.com/umpirsky/country-list/master/country/cldr/de_AT/country.json')
            dict_iso_name = response.json()
            response = requests.get('https://raw.githubusercontent.com/mledoze/countries/master/countries.json')
            _countries = [(c[u"name"], dict_iso_name.get(c[u'cca2'], ''), c[u"currency"][0]) for c in response.json() if c[u"region"] == u"Europe"]
            COUNTRIES = sorted(_countries, key = lambda x: x[1])
            flash('Countrycode loaded')
        except Exception:
            flash('Countrycodes konnten nicht geladen werden - falling back to fallback', category='warning')
    else:
        flash('Not loading Countrycodes in Debugmode')

@app.route("/")
def index():
    return render_template('index.html')

@app.route("/create_db")
def create_db():
    """
    Call :py:meth:`models.initialize_database` and recreate the database
    """
    d = {}
    try:
        flash("Database Queryied")
        d["cars"] = tidy_objects(rest_get_all("cars"), CAR_FIELDS)
        d["stations"] = tidy_objects(rest_get_all("stations"), STATION_FIELDS)
        d["kunden"] = tidy_objects(rest_get_all("kunden"), KUNDE_FIELDS)
        d["leihen"] = rest_get_all("leihen")
        d["car_fields"] = CAR_FIELDS
        d["stations_fields"] = STATION_FIELDS
        d["kunden_fields"] = KUNDE_FIELDS
        d["leihen_fields"] = LEIHE_FIELDS
    except OperationalError, e:
        flash(str(e), category='error')
    return render_template("create_db.html", **d)

@app.route("/cars/")
def cars_list():
    """
    List all Cars
    """
    cars = rest_get_all("cars")
    cars = tidy_objects(cars, CAR_FIELDS)
    return render_template("cars_list.html", cars=cars, fields=CAR_FIELDS)

@app.route("/kunden/")
def kunden_list():
    """
    List all Kunden
    """
    kunden = rest_get_all("kunden")
    kunden = tidy_objects(kunden, KUNDE_FIELDS)
    return render_template("kunden_list.html", kunden=kunden)

@app.route("/kunde/", methods=['POST', 'GET'], endpoint='kunden_new')
@app.route("/kunde/<int:kunde_id>/", methods=['POST', 'GET'], endpoint='kunden_edit')
def kunden_edit(kunde_id=None):
    """
    Legt entweder einen neuen Kunden an oder editiert einen bestehenden. Wie immer gibt es das Problem, das create und
    update sehr ähnlich sind - hier habe ich beides auf einmale implementiert.

    Mgl kann mit Classbased Views strukturierter vorgegangen werden
    """
    leihen = []
    kunde = None

    if kunde_id:
        try:
            kunde_dict = rest_get_id("kunden", kunde_id)
            kunde = Kunde(**kunde_dict)
            leihen = rest_get_all("leihen")
            leihen = [Leihe(**d) for d in leihen if d["kunde_id"] == kunde_id]
            form = KundenForm(request.form, kunde)
        except HTTPError, e:
            return errorpage(e)
    else:
        form = KundenForm()

    laendernamen = [c[1] for c in COUNTRIES]
    if kunde_id:
        if kunde.country in laendernamen:
            del(laendernamen[laendernamen.index(kunde.country)])
            laendernamen.insert(0, kunde.country)
    choices = zip(laendernamen, laendernamen)
    form.country.choices = zip(laendernamen, laendernamen)

    if form.validate_on_submit():
        try:
            if not kunde_id:        # Delete possible id-Field for new Kunden
                del(form['id'])
                rest_post("kunden", form.data)
                flash(("Kunde %s wurde neu angelegt" % form.data["name"]))
            else:
                rest_put("kunden", kunde_id, form.data)
                flash(("Kunde %s wurde gespeichert" % form.data["name"]))
        except HTTPError, e:
                return errorpage(e)
        return redirect(url_for('kunden_list'))
    return render_template("kunden_edit.html", form=form, kunde=kunde, leihen=leihen, leihen_fields=LEIHE_FIELDS)

@app.route("/kunde/<int:kunde_id>/delete/")
def kunden_delete(kunde_id):
    """
    Löschen eines Kunden - aber nur wenn er keine offenen Leihen gibt.
    """
    kunde = Kunde(**rest_get_id("kunden", kunde_id))
    name = kunde.name
    leihen_not_beendet = len([leihe["returned"] for leihe in kunde.leihen if not leihe["returned"]])
    if leihen_not_beendet>0:
        flash(u'%s hat noch offene Leihen und kann nicht gelöscht werden.' % name, category='warning')
        return redirect(url_for('kunden_edit', kunde_id=kunde_id))
    try:
        rest_delete_id("kunden", kunde_id)
    except HTTPError, e:
        return errorpage(e)
    flash(u'%s aus Kundendatei gelöscht' % name)
    return redirect(url_for('kunden_list'))

@app.route("/leihen/")
def leihen_list():
    leihen = rest_get_all("leihen")
    leihen = [Leihe(**d) for d in leihen]
    leihen.sort(key=lambda x: x.id)
    return render_template("leihen_list.html", leihen=leihen)

@app.route("/leihe/", methods=["POST", "GET"], endpoint='leihen_new')
@app.route("/leihe/<int:leihe_id>/", methods=["POST", "GET"], endpoint='leihen_edit')
def leihen_edit(leihe_id=None):
    """
    Create und update for Leihen.
    Wertet auf die URL-Query Parameter kunde_id und station_id aus, die die entsprechenden Felder voreinstellen.
    """
    leihe = None
    try:
        if leihe_id:
            leihe = Leihe(**rest_get_id("leihen", leihe_id))
            form = LeiheForm(request.form, leihe)
        else:
            form = LeiheForm()
        form.kunde_id.choices = [(kunde["id"], kunde["name"]) for kunde in rest_get_all("kunden")]
        form.car_id.choices = [(car["id"], car["platenumber"]) for car in rest_get_all("cars")]
        form.station_abhol_id.choices = [(station["id"], station["name"]) for station in rest_get_all("stations")]
        form.station_return_id.choices = [(station["id"], station["name"]) for station in rest_get_all("stations")]
        if u'kunde_id' in request.args:
            form.kunde_id.data = int(request.args[u'kunde_id'])
        if u'station_id' in request.args:
            form.station_abhol_id.data = int(request.args[u'station_id'])
            form.station_return_id.data = int(request.args[u'station_id'])
        if form.validate_on_submit():
            kunde = Kunde(**rest_get_id("kunden", form.data["kunde_id"]))
            data = form.data
            data["von"] = form.data["von"].isoformat()
            data["bis"] = form.data["bis"].isoformat()
            if not leihe_id:
                del(data["id"])
                rest_post("leihen", data)
                flash(u"Leihe für %s wurde neu angelegt" % kunde.name, category="success")
            else:
                rest_put("leihen", leihe_id, data)
                flash(u"Leihe für %s wurde neu geändert" % kunde.name, category="success")
            return redirect(url_for('leihen_list'))
    except HTTPError, e:
        return errorpage(e)
    return render_template("leihe_edit.html", form=form, leihe=leihe)

@app.route("/kunde/<int:kunde_id>/neueleihe")
def leihe_neu1(kunde_id):
    """
    Klickt man bei der Kunden-Detail View auf "Neue Leihe" werden in dieser View anhand der Adresse des Kunden
    die Distanzen zu unseren Verleihstationen berechnet und als Tabelle angezeigt.
    """
    try:
        kunde = Kunde(**rest_get_id("kunden", kunde_id))
        stationen = [Station(**d) for d in rest_get_all("stations")]
        cachehits = 0
        for station in stationen:
            try:
                station.distance, cachehit = get_distance(kunde.adresse, station.adresse)
            except DistanceServiceError:
                station.distance = {'distance' : { 'text' : 'Unavailable', 'value' : -1}, 'duration' : { 'text' : "bis ans Ende der Zeit", 'value' : -1}}
                cachehit = False
            if cachehit:
                cachehits += 1
        stationen.sort(key=lambda x: x.distance['duration']['value'])
        flash("Cachehits bei der Distanzberechnung: %d" % cachehits)
        return render_template("neue_leihe_kunde1.html", kunde=kunde, stationen=stationen)
    except HTTPError, e:
        return errorpage(e)

@app.errorhandler(404)
def errorpage(e=None):
    status = 404
    content = "Page not found"
    message = "Page not Found"
    if e:
        if hasattr(e, "response"):
            try:
                status = e.response.status_code
                content = e.response.content
            except Exception:
                pass
        if hasattr(e, "message"):
            message = e.message
    return render_template('errorpage.html', status=status, content=content, message=message), status

if __name__ == "__main__":
    app.run()


