#!/usr/bin/env python
# -*- coding: utf-8

# Required modules
import os
import inspect
import random
import binascii
import bcrypt
from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash
from functools import wraps

# Optionnal modules
import psycopg2
from flask_sqlalchemy import SQLAlchemy

########################################################################
# App settings
########################################################################
app = Flask(__name__)
# Path to static files
app.static_url_path='/static'
# Set debug mode to False for production
app.debug = True
# Various configuration settings belong here (optionnal)
app.config.from_pyfile('config.local.py')
# Generate a new key: head -n 40 /dev/urandom | md5sum | cut -d' ' -f1
app.secret_key = 'ce1d1c9ff0ff388a838b3a1e3207dd27'
# Feel free to use SQLAlchemy (or not)
db = SQLAlchemy(app)


########################################################################
# Sample user database
########################################################################
class Tetawebapp_users(db.Model):
  __tablename__ = 'tetawebapp_users'
  id = db.Column(db.Integer, primary_key=True)
  mail = db.Column(db.Text, nullable=False)
  password = db.Column(db.Text, nullable=False)
  name = db.Column(db.Text, nullable=False)


########################################################################
# Menu and navigation management
########################################################################

def get_menu(page):
  """  The main menu is a list of lists in the followin format:
          [unicode caption,
            {unicode URL endpoint: [unicode route, ...]},
            int 0]
        - The URL end point is the URL where to point to (href)
        - One of the routes MUST match the route called by request
        - The int 0 is used to determine which menu entry is actally called.
          The value MUST be 0."""
  menu = [[u'Home', {u'/': [u'/']}, 0],
          [u'Articles', {u'/articles': [u'/articles', u'/articles/<ID>']}, 0],
          [u'Basics', {u'/basics': [u'/basics']}, 0],
          [u'Inputs', {u'/inputs': [u'/inputs']}, 0],
          [u'Ajax', {u'/ajax': [u'/ajax']}, 0],
          [u'Database', {u'/database': [u'/database']}, 0],
          [u'Todo', {u'/todo': [u'/todo']}, 0],
          ]
  for item in menu:
    for url in item[1]:
      for route in item[1][url]:
        if route == page:
          item[2] = 1
          return menu
  # This should never happen
  return menu

def get_navbar(page, selected):
  """  The horizontal navbar is a list of lists in the followin format:
          [unicode caption, {unicode URL endpoint: [unicode route, ...]}, int 0]
        - The URL end point is the URL where to point to (href)
        - One of the routes MUST match the route called by request
        - The int 0 is used to de """
  navbars = [[u'First article', {u'/articles/1': [u'/articles', u'/articles/<ID>']}, 0, 0],
            [u'Second article', {u'/articles/2': [u'/articles', u'/articles/<ID>']}, 0, 0],
            [u'Third article', {u'/articles/3': [u'/articles', u'/articles/<ID>']}, 0, 0]
            ]
  navbar = []
  for item in navbars:
    for url in item[1]:
      if url == selected:
        item[2] = 1
      for route in item[1][url]:
        if route == page:
          navbar.append(item)
  navbar[len(navbar) - 1][3] = 1
  return navbar

########################################################################
# Session management
########################################################################

def sync_session(request, session):
  """ Synchronize cookies with session """
  for key in request.cookies:
    session[key] = request.cookies[key].encode('utf8')

def sync_cookies(response, session):
  """ Synchronize session with cookies """
  for key in session:
    response.set_cookie(key, value=str(session[key]))

def check_session(func):
  """ Check if the session has required token cookie set.
      If not, redirects to the login page. """
  @wraps(func)
  def check(*args, **kwargs):
    try:
      if session['token'] == request.cookies['token'] and len(session['token']) > 0:
        return func(*args, **kwargs)
      else:
        session['token'] = ''
        response = app.make_response(render_template('login.html', message=''))
        sync_cookies(response, session)
        return response
    except KeyError:
      return render_template('login.html', message='')
  return check

def check_login(login, password):
  """ Puts the login verification code here """
  password = password.encode('utf-8')
  hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
  stored_hash = Tetawebapp_users.query.filter_by(mail=login).with_entities(Tetawebapp_users.password).first()
  if stored_hash:
    if bcrypt.checkpw(password, stored_hash[0].encode('utf-8')):
      return True
  return False

def gen_token():
  """ Generate a random token to be stored in session and cookie """
  token = binascii.hexlify(os.urandom(42))
  return token

########################################################################
# Routes:
# -------
# Except for the index function, the function name MUST have the same
# name than the URL endpoint to make the menu work properly
########################################################################
@app.errorhandler(404)
def page_not_found(e):
  """ 404 not found """
  return render_template('error.html'), 404

@app.route("/login", methods=['GET', 'POST'])
def login():
  login = request.form.get('login')
  password = request.form.get('password')
  if check_login(login, password):
    # Generate and store a token in session
    session['token'] = gen_token()
    # Return user to index page
    page = '/'
    menu = get_menu(page)
    response = app.make_response(render_template('index.html', menu=menu))
    # Push token to cookie
    sync_cookies(response, session)
    return response
  # Credentials are not valid
  response = app.make_response(render_template('login.html', message='Invalid user or password'))
  session['token'] = ''
  sync_cookies(response, session)
  return response

@app.route("/", methods=['GET', 'POST'])
@check_session
def index():
  """ Index page """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('index.html', menu=menu)

@app.route("/articles", methods=['GET', 'POST'])
@check_session
def articles():
  """ Arcticles page """
  page = str(request.url_rule)
  menu = get_menu(page)
  navbar = get_navbar(page, '')
  return render_template('articles.html', menu=menu, navbar=navbar)

@app.route("/articles/<ID>", methods=['GET', 'POST'])
@check_session
def articles_by_id(ID):
  """ Arcticles page """
  page = str(request.url_rule)
  menu = get_menu(page)
  selected = page.replace('<ID>', ID)
  navbar = get_navbar(page, selected)
  return render_template('articles_by_id.html', menu=menu, navbar=navbar, ID=ID)

@app.route("/basics", methods=['GET', 'POST'])
@check_session
def basics():
  """ Basics page """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('basics.html', menu=menu)

@app.route("/inputs", methods=['GET', 'POST'])
@check_session
def inputs():
  """ Show the input collection """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('inputs.html', menu=menu)

@app.route("/ajax", methods=['GET', 'POST'])
@check_session
def ajax():
  """ Propose various AJAX tests """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('ajax.html', menu=menu)

@app.route("/database", methods=['GET', 'POST'])
@check_session
def database():
  """ A blah on using databases """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('database.html', menu=menu)

@app.route("/todo", methods=['GET', 'POST'])
@check_session
def todo():
  """ The famous TODO list """
  page = str(request.url_rule)
  menu = get_menu(page)
  return render_template('todo.html', menu=menu)

########################################################################
# AJAX routes
########################################################################

@app.route("/get_html_from_ajax", methods=['GET', 'POST'])
@check_session
def get_html_from_ajax():
  """ Return HTML code to an AJAX request
      It may generate a 404 http error for testing purpose """
  if int(random.random()*10) % 2:
    # Randomly generate 404 HTTP response
    return render_template('error.html'), 404
  return render_template('ajax_html.html')

@app.route("/get_value_from_ajax", methods=['GET', 'POST'])
@check_session
def get_value_from_ajax():
  """ Return a randomly generated value to an AJAX request
      It may return an error code for testing purpose """
  err_code = 'TETA_ERR'
  RND = int(random.random()*10)
  if RND % 2:
    # Randomly generate error
    return err_code
  return str(RND)

@app.route("/set_value_from_ajax/<value>", methods=['GET', 'POST'])
@check_session
def set_value_from_ajax(value):
  """ Accept a value from an AJAX request
      It may return an error code for testing purpose """
  err_code = 'TETA_ERR'
  if value != 'We Make Porn':
    return 'True'
  return err_code

@app.route("/upload", methods=['POST'])
@check_session
def upload():
  """ Save a file from AJAX request
      Files are saved in UPLOADED_FILES_DEST (see config.local.py) """
  err_code = 'TETA_ERR'
  RND = int(random.random()*10)
  if RND % 2:
    # Randomly generate error
    print err_code
    return err_code
  uploaded_files = []
  if len(request.files) > 0 and request.files['files']:
    uploaded_files = request.files.getlist("files")
  print "Uploaded files:"
  for f in uploaded_files:
    print '  [+] %s [%s]' % (f.filename, f.content_type)
    # Before saving you should:
    # - Secure the filename
    # - Check file size
    # - Check content type
    f.save(os.path.join(app.config['UPLOADED_FILES_DEST'], f.filename))
    f.close()
  return "OK"

########################################################################
# Main
########################################################################
if __name__ == '__main__':
  app.run(host='0.0.0.0')
