# @GPL_HEADER_START
# Use of the RAVE and related source code is subject to the terms
# of the following licenses:
# 
# GNU Public License (GPL) Rights pursuant to Version 2, June 1991
# Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013
# 
# NO WARRANTY
# 
# ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER
# PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY
# PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN
# "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
# KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT
# LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE,
# MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE
# OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT,
# SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY
# TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF
# WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES.
# LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF
# CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON
# CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE
# DELIVERABLES UNDER THIS LICENSE.
# 
# Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie
# Mellon University, its trustees, officers, employees, and agents from
# all claims or demands made against them (and any related losses,
# expenses, or attorney's fees) arising out of, or relating to Licensee's
# and/or its sub licensees' negligent use or willful misuse of or
# negligent conduct or willful misconduct regarding the Software,
# facilities, or other rights or assistance granted by Carnegie Mellon
# University under this License, including, but not limited to, any
# claims of product liability, personal injury, death, damage to
# property, or violation of any laws or regulations.
# 
# Carnegie Mellon University Software Engineering Institute authored
# documents are sponsored by the U.S. Department of Defense under
# Contract F19628-00-C-0003. Carnegie Mellon University retains
# copyrights in all material produced under this contract. The U.S.
# Government retains a non-exclusive, royalty-free license to publish or
# reproduce these documents, or allow others to do so, for U.S.
# Government purposes only pursuant to the copyright license under the
# contract clause at 252.227.7013.
# 
# @GPL_HEADER_END
import time
import codecs
from types import NoneType
from httplib import HTTPConnection
try:
    from httplib import HTTPSConnection
    https_supported = True
except:
    pass

from urllib import urlencode
from urlparse import urlsplit
import xml.dom.minidom as minidom
from rave.util import json
import socket

from rave.util import json

__VERSION__ = '1.1'

# Namespace URI for RAVE elements
ns_rave = 'http://www.cert.org/schema/rave'

# Codec for decoding JSON data
utf8 = codecs.getwriter("utf8")

class RaveCommunicationError(Exception):
    "Problem talking to RAVE"
    def __init__(self, code, msg=None):
        Exception.__init__(self)
        self.code = code
        if msg:
            self.msg = msg
        else:
            self.msg = "General failure"
    def __str__(self):
        return "RaveCommunicationError(%d, %s)" % (self.code, self.msg)

class RaveTimeoutError(Exception):
    """
    Timeout while fetching visualization from storage (NOT while
    talking to raved).
    """
    pass

class Ticket(object):
    def __init__(self, http_status, uri, content_type, stream=None):
        self.http_status = http_status
        self.uri = str(uri)
        self.content_type = str(content_type)
        self.stream = stream
    def is_complete(self):
        return self.http_status in (200, 201)
    def get_uri(self):
        return self.uri
    def get_content_type(self):
        return self.content_type
    def get_stream(self):
        if self.stream:
            result = self.stream
            self.stream = None
            return result
        else:
            return self.fetch_stream()
    def fetch_stream(self):
        return fetch_stream(self.uri)


def parse_response(result):
    """
    Extract the visualization URI and the content-type from RAVE JSON.
    Parameters:
        result: httplib.Result
            Result of an HTTP request
    """
    data = result.read()
    obj = json.parse(data)
    # Note that we're not doing the Right Thing here. (RFC 2616 says
    # HTTP 201 Created status should tell you where the resource is
    # in the Location: header -- we're just parsing the ticket.)
    return Ticket(result.status, obj['uri'], obj['content-type'])


def parse_url(url):
    (scheme, network_loc, path, query, frag_id) = urlsplit(url)
    if scheme == 'http':
        default_port = 80
    elif scheme == 'https' and https_supported:
        default_port = 443
    else:
        raise RaveCommunicationError(500, 'Unsupported scheme in URL: %s'
                                     % scheme)
    if network_loc.find(':') == -1:
        host = network_loc
        port = default_port
    else:
        (host, port) = network_loc.split(':')
        port = int(port)
    return (scheme, host, port, path, query)

def compose_params(params):
    return urlencode(params)

def get_connection(scheme, host, port):
    if scheme == 'http':
        connection = HTTPConnection
    elif scheme == 'https' and https_supported:
        connection = HTTPSConnection
    else:
        raise RaveCommunicationError('Unsupported scheme: %s' % scheme)
    return connection(host, port)

def fetch_stream(stream_url, timeout=600, delay=10):
    (scheme, host, port, path, query) = parse_url(stream_url)
    request_url = path
    if query:
        request_url = request_url + '?' + query
    start_time = time.time()
    conn = None
    while True:
        # DO NOT CLOSE!
        conn = get_connection(scheme, host, port)
        conn.request('GET', request_url)
        res = conn.getresponse()
        if res.status == 200:
            return res
        elif res.status <> 404:
            raise RuntimeError("Error %d from server: %s" %
                               (res.status, res.reason))
        cur_time = time.time()
        if timeout and (cur_time - start_time) >= timeout:
            raise RaveTimeoutError()
        else:
            time.sleep(delay)

def _null_debugger(msg):
    pass

def fetch_ticket(service_url, query_name, params=None, wait=None,
                 debug=_null_debugger):
    """
    Get a ticket from raved.
    Return: (elapsed, ticket)
        elapsed: int
            number of seconds the raved query took
        ticket: Ticket
            Ticket object describing the desired resource
    """
    if params and not isinstance(params, str):
        params = compose_params(params)
    (scheme, host, port, path, query) = parse_url(service_url)
    if not path.endswith('/'):
        path = path + '/'
    request_url = path + query_name
    request_wait = 'forever'
    if params:
        request_url = request_url + "?" + params
    if wait:
        request_wait = str(wait)
    # Talk to raved
    conn = None
    try:
        try:
            res_start_time = time.time()
            conn = get_connection(scheme, host, port)
            debug("GET %s" % request_url)
            conn.request('GET', request_url,
                         headers={'X-Rave-Wait': request_wait})
            res = conn.getresponse()
            res_elapsed_time = (time.time() - res_start_time)
            
        except socket.error, e:
            raise RaveCommunicationError(500, 'Socket error connecting: %s'
                                         % e[1])
    finally:
        if conn: conn.close()
    debug("res.status == %d" % res.status)
    if res.status not in (200, 201, 202):
        debug("Raising exception")
        raise RaveCommunicationError(res.status,
                                     'HTTP Error from RAVE: %d %s' %
                                     (res.status, res.reason))
    # Parse the response into a ticket
    try:
        if res.status == 202:
            debug("Returning %.2f, None" % res_elapsed_time)
            return res_elapsed_time, None
        else:
            debug("Returning %.2f, ticket" % res_elapsed_time)
            return res_elapsed_time, parse_response(res)
    except Exception, e:
        raise RaveCommunicationError(500, 'Problem parsing response data: %s'
                                     % str(e))
                                     
