import os
from os.path import join
import shutil
import subprocess
from cStringIO import StringIO
import socket
from urllib import quote, urlencode

import httplib2
from zope.interface import implements
from lxml import etree

from sparrow.error import ConnectionError, DatabaseError, QueryError
from sparrow.interfaces import IConnector, IDatabase
from sparrow.utils import parse_sparql_result


class SesameConnector(object):
    implements(IConnector)
        
    def __init__(self, dburi):
        self._host, rest = dburi[7:].split(':', 1)
        self._port, self._name = rest.split('/', 1)
        self._url = 'http://%s:%s/openrdf-sesame' % (self._host, self._port)
        http = httplib2.Http()
        try:
            resp, content = http.request(
                '%s/repositories' % self._url,
                "GET",
                headers={'Accept': 'application/sparql-results+xml'})
        except socket.error:
            raise ConnectionError(
                'Can not connect to repository, is it running?')
        
        if resp['status'] != '200':
            raise ConnectionError(
                'Can not connect to server: %s' % resp['status'])
            
        for repo in parse_sparql_result(content):
            if repo['id']['value'] == self._name:
                break
        else:
            raise ConnectionError(
                'Server has no repository: %s' % self._name)
        
    def connect(self):
        return SesameDatabase(self._url, self._name)
    
    def disconnect(self, db):
        del db._http

class SesameDatabase(object):
    implements(IDatabase)
    
    def __init__(self, sesame_url, db_name):
        self._url = sesame_url
        self._name = db_name
        self._http = httplib2.Http()
        
    def formats(self):
        return ['ntriples', 'rdfxml', 'turtle', 'n3']
    
    def contexts(self):
        resp, content = self._http.request(
            '%s/repositories/%s/contexts' % (self._url, self._name),
            "GET",
            headers={'Accept': 'application/sparql-results+xml'})

        return [c['contextID']['value'].split(':', 1)[1] for c in
                parse_sparql_result(content)]

    def _get_mimetype(self, format):
        return {'ntriples': 'text/plain',
                'rdfxml': 'application/rdf+xml',
                'turtle': 'application/x-turtle',
                'n3': 'text/rdf+n3',
                'trix': 'application/trix',
                'trig': 'applcation/x-trig'}[format]

    def _get_context(self, context):
        return '<context:%s>' % context
        
    def register_prefix(self, prefix, namespace):        
        clength = str(len(namespace))
        resp, content = self._http.request(
            '%s/repositories/%s/namespaces/%s' % (
                self._url, self._name, prefix),
            'PUT',
            body=namespace,
            headers = {"Content-length": clength})
        if resp['status'] != '204':
            raise DatabaseError(content)

    def add(self, file, format, base_uri, context):
        data = file.read()
        clength = str(len(data))
        ctype = self._get_mimetype(format)
        params = urlencode({'context': self._get_context(context),
                            'baseURI': '<%s>' % base_uri})
        resp, content = self._http.request(
            '%s/repositories/%s/statements?%s' % (
                self._url, self._name, params),
            'POST',
            body=data,
            headers = {"Content-type": ctype,
                       "Content-length": clength})
        
        if resp['status'] != '204':
            raise DatabaseError(content)       
        
    def serialize(self, format, context):
        context = quote(self._get_context(context))
        ctype = self._get_mimetype(format)
        
        resp, content = self._http.request(
            '%s/repositories/%s/statements?context=%s' % (
                self._url, self._name, context),
            'GET',
            headers = {"Accept": ctype})
        if resp['status'] != '200':
            raise DatabaseError(content)

        return StringIO(content)
        
    def remove(self, file, format, base_uri, context):
        data = file.read()
        clength = str(len(data))
        ctype = self._get_mimetype(format)
        params = urlencode({'context': self._get_context(context),
                            'baseURI': '<%s>' % base_uri})
        resp, content = self._http.request(
            '%s/repositories/%s/statements?%s' % (
                self._url, self._name, params),
            'DELETE',
            body=data,
            headers = {"Content-type": ctype,
                       "Content-length": clength})
        if resp['status'] != '204':
            raise DatabaseError(content)
    
    def clear(self, context):
        context = quote(self._get_context(context))
        resp, content = self._http.request(
            '%s/repositories/%s/statements?context=%s' % (
                self._url, self._name, context),
            'DELETE')
        if resp['status'] != '204':
            raise DatabaseError(content)
        
    def count(self, context=None):
        if context is None:
            context = ''
        else:
            context = '?context='+quote(self._get_context(context))
        resp, content = self._http.request(
            '%s/repositories/%s/size%s' % (
                self._url, self._name, context),
            'GET')
        if resp['status'] != '200':
            raise DatabaseError(content)
        return int(content)
    
    def select(self, sparql):
        params = urlencode({'query': sparql,
                            'queryLn': 'SPARQL',
                            'infer':'false'})
        
        resp, content = self._http.request(
            '%s/repositories/%s?%s' % (
            self._url, self._name, params),
            'GET',
            headers={'Accept': 'application/sparql-results+xml'})
        
        if resp['status'] != '200':
            raise QueryError(content)

        return parse_sparql_result(content)
    
    def ask(self, sparql):
        params = urlencode({'query': sparql,
                            'queryLn': 'SPARQL',
                            'infer':'false'})
        
        resp, content = self._http.request(
            '%s/repositories/%s?%s' % (
            self._url, self._name, params),
            'GET',
            headers={'Accept': 'text/boolean'})

        if resp['status'] != '200':
            raise QueryError(content)

        if content == 'true':
            return True
        elif content == 'false':
            return False
        else:
            raise QueryError('ASK query got unexpected result: %s' % content)
    
    def construct(self, sparql, format):
        ctype = self._get_mimetype(format)        
        params = urlencode({'query': sparql,
                            'queryLn': 'SPARQL',
                            'infer':'false'})
        
        resp, content = self._http.request(
            '%s/repositories/%s?%s' % (
            self._url, self._name, params),
            'GET',
            headers={'Accept': ctype})
        
        if resp['status'] != '200':
            raise QueryError(content)

        return StringIO(content)

def start_server(host, port, uri, id, title, path):
    tomcat_dir = join(path ,'parts', 'tomcat-install')

    # check if port and host are configured in tomcats server.xml
    server_conf = join(tomcat_dir, 'conf', 'server.xml')
    doc = etree.parse(server_conf)
    connector_el = doc.xpath('//Service[@name="Catalina"]/Connector')[0]
    if int(connector_el.attrib['port']) != port:
        connector_el.attrib['port'] = str(port)
        doc.write(server_conf)
    engine_el = doc.xpath(
        '//Service[@name="Catalina"]/Engine[@name="Catalina"]')[0]
    if engine_el.attrib['defaultHost'] != host:
        engine_el.attrib['defaultHost'] = host

        host_el = engine_el.xpath('Host')[0]
        if host_el.attrib['name'] != host:
            host_el.attrib['name'] = host
        doc.write(server_conf)
    
    # copy the war files to tomcat
    sesame_dir = join(path , 'parts', 'sesame-install')
    if not os.path.isfile(join(tomcat_dir, 'webapps', 'openrdf-sesame.war')):
        shutil.copyfile(join(sesame_dir, 'war', 'openrdf-sesame.war'),
                        join(tomcat_dir, 'webapps', 'openrdf-sesame.war'))
    if not os.path.isfile(join(tomcat_dir, 'webapps', 'openrdf-workbench.war')):
        shutil.copyfile(join(sesame_dir, 'war', 'openrdf-workbench.war'),
                        join(tomcat_dir, 'webapps', 'openrdf-workbench.war'))
    
    
    catalina = join(tomcat_dir ,'bin', 'catalina.sh')
    
    os.system('%s run' % catalina)


SESAME_TEST_TEMPLATE = """
drop test.
yes
create memory.
test
Test Repository
false
0
"""

SESAME_MEMORY_TEMPLATE = """
create memory.
%(id)s
%(title)s
false
0
"""

SESAME_NATIVE_TEMPLATE = """
create native.
%(id)s
%(title)s
spoc, posc, opsc
"""

SESAME_MYSQL_TEMPLATE = """
create mysql.
%(id)s
%(title)s
com.mysql.jdbc.Driver
%(host)s
%(port)s
%(db)s
0
%(user)s
%(pwd)s
256
"""

def configure_server(host, port, uri, id, title, path):

    variables = {'id': id, 'title':title}
    
    if uri == 'memory':
        template = SESAME_MEMORY_TEMPLATE % variables
    elif uri == 'native':
        template = SESAME_NATIVE_TEMPLATE % variables
    elif uri.startswith('mysql://'):
        uri = uri[8:]
        userpart, dbpart = uri.split('@')
        user, pwd = userpart.split(':')
        host, db = dbpart.split('/', 1)
        if ':' in host:
            host, port = host.split(':', 1)
        else:
            port = '3306'
        variables['user'] = user
        variables['pwd'] = pwd
        variables['db'] = db
        variables['host'] = host
        variables['port'] = port
        template = SESAME_MYSQL_TEMPLATE % variables
    else:
        raise ValueError('Unknown Sesame backend URI: %s' % uri)
        
    conf_script = join(path, 'parts', 'sesame-install', 'bin', 'console.sh')
    proc = subprocess.Popen(conf_script, shell=True, stdin=subprocess.PIPE)
    # connect to sesame
    proc.communicate("connect http://%s:%s/openrdf-sesame." % (host, port)+
                     SESAME_TEST_TEMPLATE +
                     template)
