#!/usr/bin/python

import os
import time
import sys
import cgi
from simpletal import simpleTAL, simpleTALES, simpleTALUtils
import unicodedata
import urllib
import psycopg2
import psycopg2.extras

debug = True
prefix = "/rest/registry" # NO slash at the end!
db = "dummyregistry"
db_user = "rester"
db_encoding = "UTF-8"
web_encoding = "UTF-8"
max_words = 50

if debug:
    import cgitb; cgitb.enable()

class NotFound(Exception):
    pass

html_page = """<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <meta name="robots" content="noindex,nofollow"/>
     <link rel="stylesheet" type="text/css" href="/css/registry.css" media="screen"/>
     <title tal:content="title">The title</title>
  </head>
  <body>
      <h1 tal:content="title">The title</h1>
      <div tal:content="structure content">The content</div>
      <p><a href="%s/">Home</a></p>
  </body>
</html>
""" % prefix

home_blurb = """
<div>
<p>This is just an experimental Web site. Do not ask questions about it, it is just to experiment.</p>
<p>To get <a href="?getall=yes">all the registry</a>.</p>
<p>To register a new word: </p><phrase tal:replace="structure reg_form"/>
</div>
"""

one_word_blurb = """
<div>
<h2 tal:content="word">The word</h2>
<p>Registered by <span class="ipaddress" tal:content="origin"/><span tal:condition="useragent"> (HTTP client <span class="useragent" tal:content="useragent"/>)</span><span tal:condition="created"> on <phrase tal:replace="created"/></span>.</p>
<p tal:condition="comments">Comments: <phrase tal:replace="comments"/>.</p>
<ol>
<li tal:repeat="char dword"><phrase tal:replace="char"/></li>
</ol>
</div>
"""

all_words_blurb = """
<div>
<h2>All the registry</h2>
<table id="registry">
<tr><th>Word</th><th>Origin</th><th>Creation</th></tr>
<tr tal:repeat="word allwords"><td><a tal:attributes="href word/word" tal:content="word/word"/></td><td tal:content="word/origin"/><td tal:content="word/created"/><td><!-- Yes, I know, it is not RESTian at all... TODO --><a tal:attributes="href string:${word/word}?delete=yes">Delete it</a></td></tr>
</table>
</div>
"""
# Note: action="DELETE" is completely ignored by the browsers (and not valid in HTML)

reg_form_blurb = """
<form method="POST">
<p>Type a word: <input type="text" name="word" /></p>
<!-- The syntax of the <textarea> element (no content, but a start and an end tag is because of a parsing bug in Firefox 1 -->
<p>Type (optional) comments:<br/> <textarea cols="40" rows="20" name="comments"></textarea></p>
<p><input type="submit" name="register" value="Register it" /></p>
</form>
"""

def headers(status = "200 OK"):
    return """Status: %s
Content-type: text/html
X-Script: registry running with Python %s
""" % (status,  sys.version.split()[0])

def response(title="No title", content=None):
    result = simpleTALUtils.FastStringOutput()
    context.addGlobal("title", title)
    context.addGlobal("content", content)
    template.expand (context, result, outputEncoding=web_encoding)
    return result.getvalue()

def dump_word(word):
    result = []
    for char in word:
        ochar = ("U+%06x" % ord(char)).upper()
        result.append("%s %s (%s)" % (ochar,
                                      unicodedata.name(char,
                                                       u"Unknown character"),
                                      unicodedata.category(char)))
    return result

def delete(word):
    cursor.execute("DELETE FROM Words WHERE word = %s", (word, ))
    if cursor.rowcount == 0:
        raise NotFound
    cursor.execute("COMMIT")

# TODO: test URL encoding
# ProgrammingError: invalid byte sequence for encoding "UTF8": 0xe92020
# HINT:  This error can also happen if the byte sequence does not match the encoding expected by the server, which is controlled by "client_encoding".
connection = psycopg2.connect("dbname=%s user=%s" % (db, db_user))
connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
cursor = connection.cursor()
cursor.execute("SET client_encoding TO %s;", (web_encoding, ))
template = simpleTAL.compileXMLTemplate(html_page)
word_template = simpleTAL.compileXMLTemplate(one_word_blurb)
all_words_template = simpleTAL.compileXMLTemplate(all_words_blurb)
reg_form_template = simpleTAL.compileXMLTemplate(reg_form_blurb)
home_template = simpleTAL.compileXMLTemplate(home_blurb)
context = simpleTALES.Context()
method = os.environ['REQUEST_METHOD']
request = urllib.unquote(os.environ['REQUEST_URI'])
request = unicode(request, web_encoding)
request = request.replace(prefix, "", 1)
# TODO: if the request is empty (not even a slash), instanciation of relative URL won't work
# TODO: slashes in the word can make a lot of confusion afterwards...
# TODO: use urlparse instead! (http://docs.python.org/lib/module-urlparse.html)
if len(request) > 0 and request[0] == "/":
    request = request[1:]
if len(request) > 0 and request[-1] == "?": # Some Web browsers do it
    request = request[0:-1]
first_question = request.find("?")
if first_question != -1:
    form = cgi.FieldStorage(request[first_question+1:])
    request = request[:first_question]
else:
    form = cgi.FieldStorage()
if method == "GET":
    if request == "":
        getall = form.getfirst("getall", "").lower()
        if getall and (getall == "yes" or getall == "y"):
            words = []
            cursor.execute("""
                  SELECT word,origin,created,updated,comments FROM Words
                          ORDER BY updated DESC LIMIT %s
              """, (max_words, ))
            for tuple in cursor.fetchall():
                words.append({'word': unicode(tuple[0], db_encoding), 'origin': tuple[1],
                              'created': tuple[2], 'updated': tuple[3], 'comments': tuple[4]})
            context.addGlobal("allwords", words)
            print headers()
            print response(title="All the registry", content=all_words_template)
        else:
            print headers("")
            context.addGlobal("reg_form", reg_form_template)
            print response("Home page", content=home_template)
    else:
        delete_it = form.getfirst("delete", "").lower()
        # Note: unfortunately, this seems the only way to make the delete method available
        # to ordinary Web browsers.
        if delete_it and (delete_it == "yes" or delete_it == "y"):
            try:
                delete(request)
                print headers()
                print response("%s deleted" % request)
            except NotFound:
                print headers("404 Not found")
                print response("%s not found" % request)
        else:
            cursor.execute("""
               SELECT word,origin,created,updated,comments,useragent FROM Words WHERE word = %s
                  """, (request, ))
            if cursor.rowcount == 0:
                print headers("404 Not found")
                print response("%s not found" % request)
            else:
                for mytuple in cursor.fetchall():
                        word = unicode(mytuple[0], db_encoding)
                        origin = mytuple[1]
                        created = mytuple[2]
                        updated = mytuple[3]
                        comments = mytuple[4]
                        useragent = mytuple[5]
                        context.addGlobal("word", word)
                        context.addGlobal("dword", dump_word(word))
                        context.addGlobal("origin", origin)
                        # TODO: better presentation of dates, without the seconds
                        context.addGlobal("created", created)
                        context.addGlobal("updated", updated)
                        context.addGlobal("comments", comments)
                        context.addGlobal("useragent", useragent)
                print headers()
                print response("Get %s" % request, content=word_template) 
elif method == "PUT":
    if request == "":
        print headers("403 Forbidden")
        print response("Replacing the whole registry is not authorized")
    else:
        cursor.execute("SELECT id FROM Words WHERE word = %s", (request, ))
        if cursor.rowcount != 0:
            print headers("403 Forbidden")
            # TODO: update origin and useragent?
            print response("Replacing an already registering word is not allowed",
                           content="%s already exists" % request)
        else:
            cursor.execute("INSERT INTO Words (word, origin, useragent) VALUES (%s, %s, %s)",
                           (request, os.environ["REMOTE_ADDR"], os.environ["HTTP_USER_AGENT"]))
            cursor.execute("COMMIT")
            # TODO: add the body in comments
            print headers()
            print response("Put %s: success." % request)
elif method == "POST":
    if request == "":
        word = unicode(form.getfirst("word", ""), web_encoding)
        if not word:
            print headers("406 No word in the form")
            print response("You must indicate a \"word\" parameter in the form")
        else:
            cursor.execute("SELECT id FROM Words WHERE word = %s", (word, ))
            if cursor.rowcount != 0:
                print headers("403 Forbidden")
                # TODO: update origin and useragent?
                print response("Replacing an already registering word is not allowed",
                               content="%s already exists" % word)
            else:
                # TODO: it duplicates what's already in PUT!
                comments = unicode(form.getfirst("comments", ""), web_encoding)
                if comments.strip() == "":
                    comments = None
                client = os.environ["REMOTE_ADDR"]
                cursor.execute("""
                            INSERT INTO Words (word, origin, useragent, comments)
                                     VALUES (%s, %s, %s, %s)""",
                               (word, client,
                                os.environ["HTTP_USER_AGENT"], comments))
                cursor.execute("COMMIT")
                # TODO: add the body in comments
                context.addGlobal("origin", client)
                context.addGlobal("word", word)
                context.addGlobal("dword", dump_word(word))
                print headers()
                print response("Post \"%s\" to the registry: success" % word,
                               content=word_template)
    else:
        print headers("501 Not yet implemented")
        print response("Updating a single word is not yet implemented")        
elif method == "DELETE":
    # TODO: a bit of security, for instance check that the origin is the same?
    try:
        delete(request)
        print headers()
        print response("%s deleted" % request)
    except NotFound:
        print headers("404 Not found")
        print response("%s not found" % request)
else: # Unknown method
    print headers("400 unknown method")
    print response("Unknown method %s" % method)
cursor.close()
connection.close()
