cdef extern from "Python.h":
   object PyString_FromStringAndSize(char *s, int len)

cdef extern from "netinet/in.h":
   ctypedef struct in_addr:
      char* s_addr                      # don't actually use it
   ctypedef struct sockaddr_in:
      in_addr sin_addr
   char *inet_ntoa(in_addr)

cdef extern from "sys/time.h":
   cdef struct timeval:
      int tv_sec                        # or use long? mac uses int32_t
      int tv_usec

# why can't this just use <> too?
cdef extern from "zephyr/zephyr.h":
  ctypedef int Code_t
  Code_t ZInitialize()
  ctypedef enum ZNotice_Kind_t:
    UNSAFE
    UNACKED
    ACKED
    HMACK
    HMCTL
    SERVACK
    SERVNAK
    CLIENTACK
    STAT
  ctypedef struct ZUnique_Id_t:
      in_addr zuid_addr
      timeval	tv
  ctypedef struct ZNotice_t:
    ZNotice_Kind_t z_kind
    unsigned short	z_port
    timeval z_time
    ZUnique_Id_t	z_uid
    #define z_sender_addr	z_uid.zuid_addr
    char		*z_class
    char		*z_class_inst
    char		*z_opcode
    char		*z_sender
    char		*z_recipient
    char		*z_default_format
    char		*z_message
    int  		z_message_len
  ctypedef Code_t (*Z_AuthProc) (ZNotice_t*, char *, int, int *)
  Code_t ZMakeAuthentication(ZNotice_t*, char *,int, int*)
  Code_t ZSendNotice(ZNotice_t *znt, Z_AuthProc)
  Code_t ZReceiveNotice(ZNotice_t *, sockaddr_in *)
  ctypedef struct ZSubscription_t:
    char	*zsub_recipient
    char	*zsub_class
    char	*zsub_classinst
  Code_t ZSubscribeTo(ZSubscription_t *sublist, int nitems, unsigned int port)
  Code_t ZSubscribeToSansDefaults(ZSubscription_t *sublist, int nitems, unsigned int port)
  Code_t ZUnsubscribeTo(ZSubscription_t *sublist, int nitems, unsigned int port)
  Code_t ZCancelSubscriptions(unsigned int port)
  char* error_message(int)
  ctypedef struct ZAsyncLocateData_t:
    char		*user
    # ZUnique_Id_t	uid
    char		*version
  Code_t ZRequestLocations(char *, ZAsyncLocateData_t *, ZNotice_Kind_t, Z_AuthProc)
  Code_t ZParseLocations(ZNotice_t *notice, ZAsyncLocateData_t *zald, int *nlocs, char **user)
  ctypedef struct ZLocations_t:
     char	*host
     char	*time
     char	*tty
  Code_t ZGetLocations(ZLocations_t *location, int *numlocs)
  #define ZERR_NOMORELOCS (-772103667L)
  ctypedef enum dummy_zerr_t:
     ZERR_NOLOCATIONS
     ZERR_NOMORELOCS
  # support for a receiving client
  Code_t ZOpenPort(unsigned short *port)
  char *ZGetSender()
  int ZPending()
  #define ZGetFD()	__Zephyr_fd
  int __Zephyr_fd
  #define ZGetRealm()	__Zephyr_realm
  char __Zephyr_realm[]

class ComErrException(Exception):
  pass

cdef comret(Code_t ret):
   if ret != 0:
      raise ComErrException(error_message(ret))

def zinit(force=[]):
   cdef Code_t ret
   if len(force):
      return
   force.append(None)
   ret = ZInitialize()
   print "init done"
   comret(ret)

def openport():
   cdef Code_t ret
   cdef unsigned short port
   port = 0
   ret = ZOpenPort(&port)
   comret(ret)
   return port

def zgetfd():
   return __Zephyr_fd

def zgetrealm():
   return __Zephyr_realm

cdef class CNotice:
  cdef ZNotice_t znt
  def __init__(self, zclass, zinst, opcode, sender, recip, deffmt):
     zinit()
     self.znt.z_class = zclass
     print "zclass set"
     self.znt.z_class_inst = zinst
     self.znt.z_opcode = opcode
     self.znt.z_sender = sender
     self.znt.z_recipient = recip
     self.znt.z_default_format = deffmt
     self.znt.z_kind = ACKED
     self.znt.z_port = 0
  def send(self, sig, message, auth = None):
     msgbuf = sig + chr(0) + message
     cdef Code_t ret
     cdef Z_AuthProc zauth
     self.znt.z_message = msgbuf
     self.znt.z_message_len = len(msgbuf)
     zauth = NULL
     if auth:
        zauth = ZMakeAuthentication
     ret = ZSendNotice(&self.znt, zauth)
     self.znt.z_message = NULL
     comret(ret)
  def zreceive(self):
     cdef Code_t ret
     ret = ZReceiveNotice(&self.znt, NULL)
     comret(ret)

     message = ""
     mlen = self.znt.z_message_len
     if mlen > 0 and self.znt.z_message[mlen-1] == 0:
        mlen = mlen - 1
     if mlen > 0:
        message = PyString_FromStringAndSize(self.znt.z_message, mlen)

     timet = self.znt.z_time.tv_sec + self.znt.z_time.tv_usec / 1e6
     addr = inet_ntoa(self.znt.z_uid.zuid_addr)

     return self.znt.z_class, self.znt.z_class_inst, self.znt.z_opcode, self.znt.z_sender, self.znt.z_recipient, self.znt.z_default_format, message, timet, addr

class Notice(CNotice):
  def __init__(self, zclass, zinst, opcode, sender, recip, deffmt):
     self.zclass = zclass
     self.zinst = zinst
     self.opcode = opcode
     self.sender = sender
     self.recip = recip
     self.deffmt = deffmt
     CNotice.__init__(self, self.zclass, self.zinst, self.opcode, self.sender, self.recip, self.deffmt)
  def pending(self):
     return ZPending()
  def receive(self):
     self.zclass, self.zinst, self.opcode, self.sender, self.recip, self.deffmt, self.message, self.time, self.sender_addr = self.zreceive()

cdef class CSubscription:
  cdef ZSubscription_t zsub
  def __init__(self, zrecip, zclass, zinst):
     self.zsub.zsub_recipient = zrecip
     self.zsub.zsub_class = zclass
     self.zsub.zsub_classinst = zinst
  # consider using property/__get__/__set__ here?
  def sub(self, port):
     cdef Code_t ret
     zinit()
     ret = ZSubscribeToSansDefaults(&self.zsub, 1, port)
     if ret != 0:
        raise ComErrException(error_message(ret))
  def unsub(self, port):
     cdef Code_t ret
     zinit()
     ret = ZUnsubscribeTo(&self.zsub, 1, port)
     if ret != 0:
        raise ComErrException(error_message(ret))
  def cancel(self, port):
     cdef Code_t ret
     zinit()
     ret = ZCancelSubscriptions(port)
     if ret != 0:
        raise ComErrException(error_message(ret))

cdef class CAsyncLocation:
   cdef ZAsyncLocateData_t ald
   def request(self, user):
      cdef Code_t ret
      ret = ZRequestLocations(user, &self.ald, UNSAFE, ZMakeAuthentication)
      comret(ret)

cdef class LocNotice(CNotice):
   def parseloc(self):
      cdef Code_t ret
      cdef int numlocs
      cdef char *whichuser
      ret = ZParseLocations(&self.znt, NULL, &numlocs, &whichuser)
      comret(ret)
      # turns out we don't care how many, we can just go until ZGetLocations gives ZERR_NOMORELOCS...
      return whichuser

cdef class CLocation:
   cdef ZLocations_t loc
   def getloc(self):
      cdef Code_t ret
      cdef int one
      one = 1
      ret = ZGetLocations(&self.loc, &one)
      if ret == 0:
         return (self.loc.host, self.loc.time, self.loc.tty)
      if ret == ZERR_NOMORELOCS:
         return None
      if ret == ZERR_NOLOCATIONS:
         return None
      comret(ret)

def sender():
   return ZGetSender()