#
# ===================================================================
#   Licensed to the Apache Software Foundation (ASF) under one
#   or more contributor license agreements.  See the NOTICE file
#   distributed with this work for additional information
#   regarding copyright ownership.  The ASF licenses this file
#   to you under the Apache License, Version 2.0 (the
#   "License"); you may not use this file except in compliance
#   with the License.  You may obtain a copy of the License at
# 
#     http://www.apache.org/licenses/LICENSE-2.0
# 
#   Unless required by applicable law or agreed to in writing,
#   software distributed under the License is distributed on an
#   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#   KIND, either express or implied.  See the License for the
#   specific language governing permissions and limitations
#   under the License.
# ===================================================================
#

import types
import ctypes


### make this work on all platforms
def _loadlib(which):
  return ctypes.cdll.LoadLibrary(which)

_serf = _loadlib('../../libserf-2.dylib')
_apr = _loadlib('libapr-1.dylib')
#_apu = _loadlib('libaprutil-1.dylib')


def _make_incomplete_ptr(name):
  "Create an incomplete pointer type, referencing the given name."
  cls = types.ClassType(name, (ctypes.Structure,), { })
  return ctypes.POINTER(cls)

POOL_P = _make_incomplete_ptr('apr_pool_t')
CONTEXT_P = _make_incomplete_ptr('serf_context_t')
CONN_P = _make_incomplete_ptr('serf_connection_t')
REQUEST_P = _make_incomplete_ptr('serf_request_t')
BKTALLOC_P = _make_incomplete_ptr('serf_bucket_allocator_t')

STATUS = ctypes.c_int

BATON = ctypes.py_object  # standard baton type: pass any Python object

class BUCKET(ctypes.Structure):
  pass
BUCKET_P = ctypes.POINTER(BUCKET)

class BUCKET_TYPE(ctypes.Structure):
  pass
BUCKET_TYPE_P = ctypes.POINTER(BUCKET_TYPE)
               
BUCKET._fields_ = [
  ('type', BUCKET_TYPE_P),
  ('data', BATON),
  ('allocator', BKTALLOC_P),
  ]
BUCKET_TYPE._fields_ = [
  ('name', ctypes.c_char_p),
  ('read', ctypes.CFUNCTYPE(STATUS, BUCKET_P, ctypes.c_int,
                            ctypes.POINTER(ctypes.c_char_p),
                            ctypes.POINTER(ctypes.c_int))),
  ('readline', ctypes.CFUNCTYPE(STATUS, BUCKET_P, ctypes.c_int,
                                ctypes.POINTER(ctypes.c_int),
                                ctypes.POINTER(ctypes.c_char_p),
                                ctypes.POINTER(ctypes.c_int))),
  ### needs to be fixed for Python to call/implement
  ('read_iovec', ctypes.CFUNCTYPE(STATUS, BUCKET_P, ctypes.c_int, ctypes.c_int,
                                  ctypes.c_void_p,  ### wrong. VECS
                                  ctypes.POINTER(ctypes.c_int))),
  ### needs to be fixed for Python to call/implement
  ('read_for_sendfile', ctypes.CFUNCTYPE(STATUS, BUCKET_P, ctypes.c_int,
                                         ctypes.c_void_p,  ### wrong. HDTR
                                         ctypes.c_void_p,  ### wrong. FILE
                                         ctypes.POINTER(ctypes.c_int),
                                         ctypes.POINTER(ctypes.c_int))),
  ('read_bucket', ctypes.CFUNCTYPE(BUCKET_P, BUCKET_P, BUCKET_TYPE_P)),
  ('peek', ctypes.CFUNCTYPE(STATUS, BUCKET_P,
                            ctypes.POINTER(ctypes.c_char_p),
                            ctypes.POINTER(ctypes.c_int))),
  ('destroy', ctypes.CFUNCTYPE(None, BUCKET_P)),
  ]

class SOCKADDR(ctypes.Structure):
  pass
SOCKADDR_P = ctypes.POINTER(SOCKADDR)
SOCKADDR._fields_ = [
  ('pool', POOL_P),
  ('hostname', ctypes.c_char_p),
  ('servname', ctypes.c_char_p),
  ('port', ctypes.c_int),
  ('family', ctypes.c_int),
  ('salen', ctypes.c_int),
  ('ipaddr_len', ctypes.c_int),
  ('addr_str_len', ctypes.c_int),
  ('ipaddr_ptr', ctypes.c_void_p),  ### wrong.
  ('next', ctypes.POINTER(SOCKADDR)),
  ('sa', ctypes.c_byte * 128),
  ]


def _define(name, restype, *argtypes):
  if name.startswith('serf_'):
    f = getattr(_serf, name)
  else:
    f = getattr(_apr, name)
  f.restype = restype
  f.argtypes = argtypes
  globals()[name] = f
  return f

_define('apr_initialize', STATUS)
_define('apr_terminate', None)
_define('apr_pool_destroy', None, POOL_P)


ABORTFUNC_F = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
_ALLOCATOR_P = _make_incomplete_ptr('apr_allocator_t')
_NULL_ABORTFUNC = ABORTFUNC_F()
_NULL_ALLOCATOR = _ALLOCATOR_P()
apr_pool_create_ex = _define('apr_pool_create_ex', STATUS,
                             ctypes.POINTER(POOL_P), POOL_P,
                             ABORTFUNC_F, _ALLOCATOR_P)
def apr_pool_create(parent):
  pool = POOL_P()  # NULL pointer
  status = apr_pool_create_ex(ctypes.byref(pool), parent,
                              _NULL_ABORTFUNC, _NULL_ALLOCATOR)
  return status, pool

_apr_strerror = _define('apr_strerror', ctypes.c_char_p,
                        STATUS, ctypes.c_char_p, ctypes.c_int)
def apr_strerror(status):
  buf = ctypes.create_string_buffer(200)
  return _apr_strerror(status, buf, len(buf))

_apr_sockaddr_info_get = _define('apr_sockaddr_info_get', STATUS,
                                 ctypes.POINTER(SOCKADDR_P),
                                 ctypes.c_char_p,
                                 ctypes.c_int,
                                 ctypes.c_int,
                                 ctypes.c_int,
                                 POOL_P)
def apr_sockaddr_info_get(hostname, family, port, flags, pool):
  psa = SOCKADDR_P()  # NULL pointer
  status = _apr_sockaddr_info_get(ctypes.byref(psa),
                                  hostname, family, port, flags, pool)
  return status, psa


_define('serf_context_create', CONTEXT_P, POOL_P)
_define('serf_config_proxy', None, SOCKADDR_P)
_define('serf_config_authn_types', None, ctypes.c_int)
_define('serf_context_run', STATUS, CONTEXT_P, ctypes.c_int, POOL_P)
_define('serf_error_string', ctypes.c_char_p, STATUS)
_define('serf_debug__closed_conn', None, CONN_P)
_define('serf_connection_close', STATUS, CONN_P)
_define('serf_connection_set_max_outstanding_requests', None,
        CONN_P, ctypes.c_uint)
_define('serf_bucket_socket_create', BUCKET_P, ctypes.c_void_p, BKTALLOC_P)
_define('serf_request_bucket_request_create', BUCKET_P,
        REQUEST_P, ctypes.c_char_p, ctypes.c_char_p, BUCKET_P, BKTALLOC_P)
_define('serf_request_get_alloc', BKTALLOC_P, REQUEST_P)
_define('serf_bucket_barrier_create', BUCKET_P, BUCKET_P, BKTALLOC_P)
_define('serf_bucket_response_create', BUCKET_P, BUCKET_P, BKTALLOC_P)
_define('serf_bucket_socket_create', BUCKET_P, ctypes.c_void_p, BKTALLOC_P)

CREDENTIALS_F = ctypes.CFUNCTYPE(STATUS,
                                 ctypes.POINTER(ctypes.c_char_p),
                                 ctypes.POINTER(ctypes.c_char_p),
                                 REQUEST_P, BATON,
                                 ctypes.c_int, ctypes.c_char_p,
                                 ctypes.c_char_p,
                                 POOL_P)
_define('serf_config_credentials_callback', CONTEXT_P, CREDENTIALS_F)

UNFREED_FUNC_F = ctypes.CFUNCTYPE(None, BATON, ctypes.c_void_p)
_define('serf_bucket_allocator_create', BKTALLOC_P,
        POOL_P, UNFREED_FUNC_F, BATON)

### serf takes an apr_uri_t struct rather than the two fields it needs :-(
class URI(ctypes.Structure):
  _fields_ = [ ('scheme', ctypes.c_char_p),
               ('hostinfo', ctypes.c_char_p),
               ('user', ctypes.c_char_p),
               ('password', ctypes.c_char_p),
               ('hostname', ctypes.c_char_p),
               ('port_str', ctypes.c_char_p),
               ('path', ctypes.c_char_p),
               ('query', ctypes.c_char_p),
               ('fragment', ctypes.c_char_p),
               ('hostent', ctypes.c_void_p),  ### wrong. struct hostent *
               ('port', ctypes.c_int),
               ('bitfield', ctypes.c_int),  ### ctypes can't do bitfields.
               ]
CONN_SETUP_F = ctypes.CFUNCTYPE(STATUS,
                                ctypes.c_void_p,  ### wrong. SKT.
                                ctypes.POINTER(BUCKET_P),
                                ctypes.POINTER(BUCKET_P),
                                BATON,
                                POOL_P)
CONN_CLOSED_F = ctypes.CFUNCTYPE(None, CONN_P, BATON, STATUS, POOL_P)
_define('serf_connection_create2', STATUS,
        ctypes.POINTER(CONN_P),
        CONTEXT_P, URI,
        CONN_SETUP_F, BATON,
        CONN_CLOSED_F, BATON,
        POOL_P)

ACCEPTOR_F = ctypes.CFUNCTYPE(BUCKET_P, REQUEST_P, BUCKET_P, BATON, POOL_P)
HANDLER_F = ctypes.CFUNCTYPE(STATUS, REQUEST_P, BUCKET_P, BATON, POOL_P)
REQ_SETUP_F = ctypes.CFUNCTYPE(STATUS,
                               REQUEST_P, BATON,
                               ctypes.POINTER(BUCKET_P),
                               ctypes.POINTER(ACCEPTOR_F),
                               ctypes.POINTER(BATON),
                               ctypes.POINTER(HANDLER_F),
                               ctypes.POINTER(BATON),
                               POOL_P)
_define('serf_connection_request_create', REQUEST_P,
        CONN_P, REQ_SETUP_F, BATON)
