#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2010 Örjan Persson

import argcmd

import httplib
import libxml2
import operator
import re
import urllib


PROBE_URL = 'http://www.google.com/'
PROBE_METHOD = 'GET'


def http_request(method, url, body=None, headers={}):
    parts = httplib.urlsplit(url)

    conn_cls = {'http':  httplib.HTTPConnection,
                'https': httplib.HTTPSConnection}.get(parts.scheme)
    if conn_cls is None:
        raise ValueError('Invalid HTTP scheme: %s' % (parts.scheme,))

    conn = conn_cls(*(parts.hostname, parts.port))
    #conn.set_debuglevel(10)

    # retrieve everything after hostname and port
    m = re.match(r'https?://[^:/]+(?::\d+)?(.*)', url)
    path = m.group(1)

    conn.request(method, path, body, headers)

    return conn.getresponse()


def get_query_params(url):
    parts = httplib.urlsplit(url)
    query = parts.query.split('&')

    # XXX there was a function to do this somewhere, right?
    params = {}
    for kv in query:
        k, v = kv.split('=')
        params[urllib.unquote(k)] = urllib.unquote(v)
    return params


def get_wisp(document):
    """Retrieve the WISP inside the HTML Document

    Inside the HTML response, there's a <xml> chunk inside a comment, this
    extracts this section and fixes some brokeness.
    """
    m = re.search(r'<!--\s*(<\?xml.*)-->', document, re.MULTILINE | re.DOTALL)
    xml = m.group(1)

    # the amps in the xml aren't properly escaped
    escaped = re.sub(r'&([^;=]+)=', '&amp;\\1=', xml)

    # return the parsed document (it's supposed to be our internal representation)
    return WISPAccessGatewayParam(escaped)


class WISPAccessGatewayParam(object):
    def __init__(self, xml):
        self.__tree = libxml2.parseDoc(xml)

    def get_param(self, name):
        r = self.__tree.xpathEval('//' + name)
        if not r:
            raise ValueError('No such wisp param: %s' % (name,))
        elif len(r) > 1:
            raise ValueError('Multiple value error: %s' % (name,))
        return r[0].content


def opts_login(parser):
    parser.add_argument('-u', '--username', help='username')
    parser.add_argument('-p', '--password', help='password')
    parser.add_argument('-f', '--force', action='store_true', help='skip some checks')
    parser.add_argument('--insecure', action='store_true', help='continue despite security problems')


def cmd_login(args):
    """Login to chillispot"""
    # TODO make this a bit more generic, following the ACME WISP?
    # http://www.acmewisp.com/
    params = {}

    # probe to see if we need to login
    resp = http_request(PROBE_METHOD, PROBE_URL)
    if not args.force:
        assert resp.status == 302, 'Expected 302 on probe url'

    url = resp.getheader('location')
    if url:
        parts = httplib.urlsplit(url)
        if parts.hostname.find('google') != -1:
            return 1
    elif not args.force:
        assert url is not None, 'unknown location'

    # retrieve the login url from the smartclient xml instead of following
    # the http redirect and doing multiple hops
    body = resp.read()
    wisp = get_wisp(body)
    login_url = wisp.get_param('LoginURL')
    parts = httplib.urlsplit(login_url)
    if not args.insecure:
        assert parts.scheme == 'https', 'Will not send credentials using http'

    # button Login is the key to trigger the stupid cgi script
    body = urllib.urlencode({'button': 'Login',
                             'UserName': args.username,
                             'Password': args.password})

    resp = http_request('POST', login_url, body)

    body = resp.read()
    wisp = get_wisp(body)

    # and one more login request
    # TODO why is the login_url for chillispot hardcoded to use http?
    try:
        login_url = wisp.get_param('LoginResultsURL')
    except ValueError:
        assert resp.status == 200, 'Unknown http status code'
        print 'Logged in as %s.' % (args.username,)
        return

    resp = http_request('GET', login_url)
    body = resp.read()

    url = resp.getheader('location')
    params = get_query_params(url)
    if params.get('res') == 'success':
        left = int(params.get('timeleft', 0))

        # extract days, hours, minutes and seconds
        numbers = []
        factors = [24, 60, 60, 1]
        for i, f in enumerate(factors):
            x = reduce(operator.mul, factors[i:])
            numbers.append(left // x)
            left -= numbers[-1] * x
        days, hours, minutes, seconds = numbers

        t = '%02d:%02d:%02d' % (hours, minutes, seconds)
        if days:
            t = '%d days, %s' % (days, t)
        print 'Logged in as %s. Expires in %s.' % (args.username, t)


def cmd_logout(args):
    """Logout from chillispot"""
    # TODO automatically detect this somehow
    addr = ('192.168.182.1', 3990)
    url = 'http://%s:%d/logoff' % addr

    resp = http_request('GET', url)
    wisp = get_wisp(resp.read())

    message = int(wisp.get_param('MessageType'))
    assert message == 130, 'Invalid message type'
    rc = int(wisp.get_param('ResponseCode'))
    assert rc == 150, 'Invalid response code'
    print 'Logged out.'


if __name__ == '__main__':
    argcmd.main()
