/*
 * opal_c.cxx
 *
 * "C" language interface for OPAL
 *
 * Open Phone Abstraction Library (OPAL)
 *
 * Copyright (c) 2008 Vox Lucida
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Open Phone Abstraction Library.
 *
 * The Initial Developer of the Original Code is Vox Lucida (Robert Jongbloed)
 *
 * This code was initially written with the assisance of funding from
 * Stonevoice. http://www.stonevoice.com.
 *
 * Contributor(s): ______________________________________.
 *
 * $Revision: 20808 $
 * $Author: csoutheren $
 * $Date: 2008-09-01 10:20:19 +0200 (lun 01 sep 2008) $
 */

#include <ptlib.h>
#include <opal.h>
#include <opal/manager.h>
#include <opal/pcss.h>
#include <opal/localep.h>
#include <h323/h323ep.h>
#include <sip/sipep.h>
#include <iax2/iax2ep.h>
#include <lids/lidep.h>
#include <opal/ivr.h>

#include <queue>


class OpalManager_C;


ostream & operator<<(ostream & strm, OpalRegistrationStates state)
{
  static const char * States[] = { "Successful", "Removed", "Failed", "Retrying", "Restored" };
  return strm << States[state];
}


inline bool IsNullString(const char * str)
{
  return str == NULL || *str == '\0';
}


class OpalMessageBuffer
{
  public:
    OpalMessageBuffer(OpalMessageType type);
    ~OpalMessageBuffer();

    OpalMessage * operator->() const { return  (OpalMessage *)m_data; }
    OpalMessage & operator *() const { return *(OpalMessage *)m_data; }
    operator OpalMessage *() const   { return  (OpalMessage *)m_data; }

    void SetString(const char * * variable, const char * value);
    void SetError(const char * errorText);

    OpalMessage * Detach();

  private:
    size_t m_size;
    char * m_data;
    std::vector<size_t> m_strPtrOffset;
};

#define SET_MESSAGE_STRING(msg, member, str) (msg).SetString(&(msg)->member, str)


#if P_AUDIO

class OpalPCSSEndPoint_C : public OpalPCSSEndPoint
{
  public:
    OpalPCSSEndPoint_C(OpalManager_C & manager);

    virtual PBoolean OnShowIncoming(const OpalPCSSConnection &);
    virtual PBoolean OnShowOutgoing(const OpalPCSSConnection &);

  private:
    OpalManager_C & m_manager;
};

#endif // P_AUDIO


class OpalLocalEndPoint_C : public OpalLocalEndPoint
{
  public:
    OpalLocalEndPoint_C(OpalManager_C & manager);

    virtual bool OnOutgoingCall(const OpalLocalConnection &);
    virtual bool OnIncomingCall(OpalLocalConnection &);
    virtual bool OnReadMediaFrame(const OpalLocalConnection &, const OpalMediaStream & mediaStream, RTP_DataFrame &);
    virtual bool OnWriteMediaFrame(const OpalLocalConnection &, const OpalMediaStream &, RTP_DataFrame & frame);
    virtual bool OnReadMediaData(const OpalLocalConnection &, const OpalMediaStream &, void *, PINDEX, PINDEX &);
    virtual bool OnWriteMediaData(const OpalLocalConnection &, const OpalMediaStream &, const void *, PINDEX, PINDEX &);

    OpalMediaDataFunction m_mediaReadData;
    OpalMediaDataFunction m_mediaWriteData;
    OpalMediaDataType     m_mediaDataHeader;

  private:
    OpalManager_C & m_manager;
};


#if OPAL_SIP
class SIPEndPoint_C : public SIPEndPoint
{
  public:
    SIPEndPoint_C(OpalManager_C & manager);

    virtual void OnRegistrationStatus(
      const PString & aor,
      PBoolean wasRegistering,
      PBoolean reRegistering,
      SIP_PDU::StatusCodes reason
    );

  private:
    OpalManager_C & m_manager;
};
#endif


class OpalManager_C : public OpalManager
{
  public:
    OpalManager_C(unsigned version)
      : localEP(NULL)
#if P_AUDIO
      , pcssEP(NULL)
#endif
      , m_apiVersion(version)
      , m_messagesAvailable(0, INT_MAX)
    {
    }

    ~OpalManager_C()
    {
      ShutDownEndpoints();
    }

    bool Initialise(const PCaselessString & options);

    void PostMessage(OpalMessageBuffer & message);
    OpalMessage * GetMessage(unsigned timeout);
    OpalMessage * SendMessage(const OpalMessage * message);

    virtual void OnEstablishedCall(OpalCall & call);
    virtual PBoolean OnOpenMediaStream(OpalConnection & connection, OpalMediaStream & stream);
    virtual void OnClosedMediaStream(const OpalMediaStream & stream);
    virtual void OnUserInputString(OpalConnection & connection, const PString & value);
    virtual void OnUserInputTone(OpalConnection & connection, char tone, int duration);
    virtual void OnMWIReceived(const PString & party, MessageWaitingType type, const PString & extraInfo);
    virtual void OnClearedCall(OpalCall & call);

  private:
    void HandleSetGeneral   (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleSetProtocol  (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleRegistration (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleSetUpCall    (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleAnswerCall   (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleUserInput    (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleClearCall    (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleHoldCall     (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleRetrieveCall (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleTransferCall (const OpalMessage & message, OpalMessageBuffer & response);
    void HandleMediaStream  (const OpalMessage & command, OpalMessageBuffer & response);
    void HandleSetUserData  (const OpalMessage & command, OpalMessageBuffer & response);

    void OnIndMediaStream(const OpalMediaStream & stream, OpalMediaStates state);

    OpalLocalEndPoint_C * localEP;
#if P_AUDIO
    OpalPCSSEndPoint_C  * pcssEP;
#endif

    unsigned                  m_apiVersion;
    std::queue<OpalMessage *> m_messageQueue;
    PMutex                    m_messageMutex;
    PSemaphore                m_messagesAvailable;
    OpalMessageAvailableFunction m_messageAvailableCallback;
};


class PProcess_C : public PProcess
{
public:
  PProcess_C(const PCaselessString & options)
  {
#if PTRACING
    unsigned level = 0;
    static char const TraceLevelKey[] = "TraceLevel=";
    PINDEX pos = options.Find(TraceLevelKey);
    if (pos != P_MAX_INDEX)
      level = options.Mid(pos+sizeof(TraceLevelKey)-1).AsUnsigned();

    PString filename = "DEBUGSTREAM";
    static char const TraceFileKey[] = "TraceFile=";
    pos = options.Find(TraceFileKey);
    if (pos != P_MAX_INDEX) {
      pos += sizeof(TraceFileKey) - 1;
      PINDEX end;
      if (options[pos] == '"')
        end = options.Find('"', ++pos);
      else
        end = options.Find(' ', pos);
      filename = options(pos, end-1);
    }

    unsigned traceOpts = PTrace::Timestamp|PTrace::Thread|PTrace::Blocks;
    if (options.Find("TraceAppend") != P_MAX_INDEX)
      traceOpts |= PTrace::AppendToFile;

    PTrace::Initialise(level, filename, traceOpts);
    PTRACE(3, "OpalC\tStart Up.");
#endif
  }

  ~PProcess_C()
  {
#if PTRACING
    PTRACE(3, "OpalC\tShut Down.");
    PTrace::SetStream(NULL);
#endif
  }

private:
  virtual void Main()
  {
  }
};

struct OpalHandleStruct
{
  OpalHandleStruct(unsigned version, const PCaselessString & options)
    : process(options)
    , manager(version)
  {
  }

  PProcess_C     process;
  OpalManager_C  manager;
};


///////////////////////////////////////////////////////////////////////////////

OpalMessageBuffer::OpalMessageBuffer(OpalMessageType type)
  : m_size(sizeof(OpalMessage))
  , m_data((char *)malloc(m_size))
{
  memset(m_data, 0, m_size);
  (*this)->m_type = type;
}


OpalMessageBuffer::~OpalMessageBuffer()
{
  if (m_data != NULL)
    free(m_data);
}


void OpalMessageBuffer::SetString(const char * * variable, const char * value)
{
  PAssert((char *)variable >= m_data && (char *)variable < m_data+m_size, PInvalidParameter);

  size_t length = strlen(value)+1;

  char * newData = (char *)realloc(m_data, m_size + length);
  if (PAssertNULL(newData) != m_data) {
    // Memory has moved, this invalidates pointer variables so recalculate them
    int delta = newData - m_data;
    char * endData = m_data + m_size;
    for (size_t i = 0; i < m_strPtrOffset.size(); ++i) {
      const char ** ptr = (const char **)(newData + m_strPtrOffset[i]);
      if (*ptr >= m_data && *ptr < endData)
        *ptr += delta;
    }
    variable += delta/sizeof(char *);
    m_data = newData;
  }

  char * stringData = m_data + m_size;
  memcpy(stringData, value, length);
  m_size += length;

  *variable = stringData;

  m_strPtrOffset.push_back((char *)variable - m_data);
}


void OpalMessageBuffer::SetError(const char * errorText)
{
  OpalMessage * message = (OpalMessage *)m_data;
  PTRACE(2, "OpalC API\tCommand " << message->m_type << " error: " << errorText);

  message->m_type = OpalIndCommandError;
  m_strPtrOffset.clear();
  SetString(&message->m_param.m_commandError, errorText);
}


OpalMessage * OpalMessageBuffer::Detach()
{
  OpalMessage * message = (OpalMessage *)m_data;
  m_data = NULL;
  return message;
}


///////////////////////////////////////

OpalLocalEndPoint_C::OpalLocalEndPoint_C(OpalManager_C & mgr)
  : OpalLocalEndPoint(mgr)
  , m_mediaReadData(NULL)
  , m_mediaWriteData(NULL)
  , m_mediaDataHeader(OpalMediaDataPayloadOnly)
  , m_manager(mgr)
{
}


bool OpalLocalEndPoint_C::OnOutgoingCall(const OpalLocalConnection & connection)
{
  PTRACE(4, "OpalC\tOnOutgoingCall " << connection);
  const OpalCall & call = connection.GetCall();
  OpalMessageBuffer message(OpalIndAlerting);
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyA, call.GetPartyA());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyB, call.GetPartyB());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_callToken, call.GetToken());
  PTRACE(4, "OpalC API\tOnOutgoingCall:"
            " token=\"" << message->m_param.m_callSetUp.m_callToken << "\""
            " A=\""     << message->m_param.m_callSetUp.m_partyA << "\""
            " B=\""     << message->m_param.m_callSetUp.m_partyB << '"');
  m_manager.PostMessage(message);
  return true;
}


static void SetIncomingCall(OpalMessageBuffer & message, const OpalConnection & connection)
{
  PSafePtr<OpalConnection> network = connection.GetOtherPartyConnection();
  PAssert(network != NULL, PLogicError); // Should not happen!

  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_callToken, connection.GetCall().GetToken());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_localAddress, network->GetLocalPartyURL());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_remoteAddress, network->GetRemotePartyURL());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_remotePartyNumber, network->GetRemotePartyNumber());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_remoteDisplayName, network->GetRemotePartyName());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_calledAddress, network->GetCalledPartyURL());
  SET_MESSAGE_STRING(message, m_param.m_incomingCall.m_calledPartyNumber, network->GetCalledPartyNumber());

  PTRACE(4, "OpalC API\tOpalIndIncomingCall: token=\""  << message->m_param.m_incomingCall.m_callToken << "\"\n"
            "  Local  - URL=\"" << message->m_param.m_incomingCall.m_localAddress << "\"\n"
            "  Remote - URL=\"" << message->m_param.m_incomingCall.m_remoteAddress << "\""
                    " E.164=\"" << message->m_param.m_incomingCall.m_remotePartyNumber << "\""
                  " Display=\"" << message->m_param.m_incomingCall.m_remoteDisplayName << "\"\n"
            "  Dest.  - URL=\"" << message->m_param.m_incomingCall.m_calledAddress << "\""
                    " E.164=\"" << message->m_param.m_incomingCall.m_calledPartyNumber << '"');
}


bool OpalLocalEndPoint_C::OnIncomingCall(OpalLocalConnection & connection)
{
  PTRACE(4, "OpalC\tOnIncomingCall " << connection);
  OpalMessageBuffer message(OpalIndIncomingCall);
  SetIncomingCall(message, connection);
  m_manager.PostMessage(message);
  return true;
}


bool OpalLocalEndPoint_C::OnReadMediaFrame(const OpalLocalConnection & connection,
                                           const OpalMediaStream & mediaStream,
                                           RTP_DataFrame & frame)
{
  if (m_mediaDataHeader != OpalMediaDataWithHeader)
    return false;

  if (m_mediaReadData == NULL)
    return false;

  int result = m_mediaReadData(connection.GetCall().GetToken(),
                               mediaStream.GetID(),
                               mediaStream.GetMediaFormat().GetName(),
                               connection.GetUserData(),
                               frame.GetPointer(),
                               frame.GetSize());
  if (result < 0)
    return false;

  frame.SetPayloadSize(result-frame.GetHeaderSize());
  return true;
}


bool OpalLocalEndPoint_C::OnWriteMediaFrame(const OpalLocalConnection & connection,
                                            const OpalMediaStream & mediaStream,
                                            RTP_DataFrame & frame)
{
  if (m_mediaDataHeader != OpalMediaDataWithHeader)
    return false;

  if (m_mediaWriteData == NULL)
    return false;

  int result = m_mediaWriteData(connection.GetCall().GetToken(),
                                mediaStream.GetID(),
                                mediaStream.GetMediaFormat().GetName(),
                                connection.GetUserData(),
                                frame.GetPointer(),
                                frame.GetHeaderSize()+frame.GetPayloadSize());
  return result >= 0;
}


bool OpalLocalEndPoint_C::OnReadMediaData(const OpalLocalConnection & connection,
                                          const OpalMediaStream & mediaStream,
                                          void * data,
                                          PINDEX size,
                                          PINDEX & length)
{
  if (m_mediaDataHeader != OpalMediaDataPayloadOnly)
    return false;

  if (m_mediaReadData == NULL)
    return false;

  int result = m_mediaReadData(connection.GetCall().GetToken(),
                               mediaStream.GetID(),
                               mediaStream.GetMediaFormat().GetName(),
                               connection.GetUserData(),
                               data,
                               size);
  if (result < 0)
    return false;

  length = result;
  return true;
}


bool OpalLocalEndPoint_C::OnWriteMediaData(const OpalLocalConnection & connection,
                                           const OpalMediaStream & mediaStream,
                                           const void * data,
                                           PINDEX length,
                                           PINDEX & written)
{
  if (m_mediaDataHeader != OpalMediaDataPayloadOnly)
    return false;

  if (m_mediaWriteData == NULL)
    return false;

  int result = m_mediaWriteData(connection.GetCall().GetToken(),
                                mediaStream.GetID(),
                                mediaStream.GetMediaFormat().GetName(),
                                connection.GetUserData(),
                                (void *)data,
                                length);
  if (result < 0)
    return false;

  written = result;
  return true;
}


///////////////////////////////////////

#if P_AUDIO

OpalPCSSEndPoint_C::OpalPCSSEndPoint_C(OpalManager_C & mgr)
  : OpalPCSSEndPoint(mgr)
  , m_manager(mgr)
{
}


PBoolean OpalPCSSEndPoint_C::OnShowIncoming(const OpalPCSSConnection & connection)
{
  PTRACE(4, "OpalC\tOnShowIncoming " << connection);
  OpalMessageBuffer message(OpalIndIncomingCall);
  SetIncomingCall(message, connection);
  m_manager.PostMessage(message);
  return true;
}


PBoolean OpalPCSSEndPoint_C::OnShowOutgoing(const OpalPCSSConnection & connection)
{
  PTRACE(4, "OpalC\tOnShowOutgoing " << connection);
  const OpalCall & call = connection.GetCall();
  OpalMessageBuffer message(OpalIndAlerting);
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyA, call.GetPartyA());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyB, call.GetPartyB());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_callToken, call.GetToken());
  PTRACE(4, "OpalC API\tOnShowOutgoing:"
            " token=\"" << message->m_param.m_callSetUp.m_callToken << "\""
            " A=\""     << message->m_param.m_callSetUp.m_partyA << "\""
            " B=\""     << message->m_param.m_callSetUp.m_partyB << '"');
  m_manager.PostMessage(message);
  return true;
}

#endif // P_AUDIO


///////////////////////////////////////

#if OPAL_SIP

SIPEndPoint_C::SIPEndPoint_C(OpalManager_C & mgr)
  : SIPEndPoint(mgr)
  , m_manager(mgr)
{
}


void SIPEndPoint_C::OnRegistrationStatus(const PString & aor,
                                         PBoolean wasRegistering,
                                         PBoolean reRegistering,
                                         SIP_PDU::StatusCodes reason)
{
  OpalMessageBuffer message(OpalIndRegistration);
  SET_MESSAGE_STRING(message, m_param.m_registrationStatus.m_protocol, OPAL_PREFIX_SIP);
  SET_MESSAGE_STRING(message, m_param.m_registrationStatus.m_serverName, aor);
  if (reason == SIP_PDU::Information_Trying)
    message->m_param.m_registrationStatus.m_status = OpalRegisterRetrying;
  else if (reason/100 == 2) {
    if (wasRegistering)
      message->m_param.m_registrationStatus.m_status = reRegistering ? OpalRegisterRestored : OpalRegisterSuccessful;
    else
      message->m_param.m_registrationStatus.m_status = OpalRegisterRemoved;
  }
  else {
    PStringStream strm;
    strm << "Error " << reason << " in SIP ";
    if (!wasRegistering)
      strm << "un";
    strm << "registration.";
    SET_MESSAGE_STRING(message, m_param.m_registrationStatus.m_error, strm);
    message->m_param.m_registrationStatus.m_status = wasRegistering ? OpalRegisterFailed : OpalRegisterRemoved;
  }
  PTRACE(4, "OpalC\tOnRegistrationStatus " << aor << ", status=" << message->m_param.m_registrationStatus.m_status);
  m_manager.PostMessage(message);
}

#endif

///////////////////////////////////////

bool OpalManager_C::Initialise(const PCaselessString & options)
{
  PString defProto, defUser;
  PINDEX  defProtoPos = P_MAX_INDEX, defUserPos = P_MAX_INDEX;

#if OPAL_H323
  PINDEX h323Pos = options.Find("h323");
  if (h323Pos < defProtoPos) {
    defProto = "h323";
    defProtoPos = h323Pos;
  }
#endif

#if OPAL_SIP
  PINDEX sipPos = options.Find("sip");
  if (sipPos < defProtoPos) {
    defProto = "sip";
    defProtoPos = sipPos;
  }
#endif

#if OPAL_IAX2
  PINDEX iaxPos = options.Find("iax2");
  if (iaxPos < defProtoPos) {
    defProto = "iax2:<da>";
    defProtoPos = iaxPos;
  }
#endif

#if OPAL_LID
  PINDEX potsPos = options.Find("pots");
  if (potsPos < defUserPos) {
    defUser = "pots:<dn>";
    defUserPos = potsPos;
  }

  PINDEX pstnPos = options.Find("pstn");
  if (pstnPos < defProtoPos) {
    defProto = "pstn:<dn>";
    defProtoPos = pstnPos;
  }
#endif

  PINDEX pcPos = options.Find("pc");
  if (pcPos < defUserPos) {
    defUser = "pc:<du>";
    defUserPos = pcPos;
  }

  PINDEX localPos = options.Find("local");
  if (localPos < defUserPos) {
    defUser = "local:<du>";
    defUserPos = localPos;
  }


#if OPAL_IVR
  if (options.Find("ivr") != P_MAX_INDEX) {
    new OpalIVREndPoint(*this);
    AddRouteEntry(".*:#=ivr:"); // A hash from anywhere goes to IVR
  }
#endif

#if OPAL_H323
  if (h323Pos != P_MAX_INDEX) {
    new H323EndPoint(*this);
    AddRouteEntry("h323:.*=" + defUser);
  }
#endif

#if OPAL_SIP
  if (sipPos != P_MAX_INDEX) {
    new SIPEndPoint_C(*this);
    AddRouteEntry("sip:.*=" + defUser);
  }
#endif

#if OPAL_IAX2
  if (options.Find("iax2") != P_MAX_INDEX) {
    new IAX2EndPoint(*this);
    AddRouteEntry("iax2:.*=" + defUser);
  }
#endif

#if OPAL_LID
  if (potsPos != P_MAX_INDEX || pstnPos != P_MAX_INDEX) {
    new OpalLineEndPoint(*this);

    if (potsPos != P_MAX_INDEX)
      AddRouteEntry("pots:.*=" + defProto + ":<da>");
    if (pstnPos != P_MAX_INDEX)
      AddRouteEntry("pstn:.*=" + defUser + ":<da>");
  }
#endif

#if P_AUDIO
  if (pcPos != P_MAX_INDEX) {
    pcssEP = new OpalPCSSEndPoint_C(*this);
    AddRouteEntry("pc:.*=" + defProto + ":<da>");
  }
#endif

  if (localPos != P_MAX_INDEX) {
    localEP = new OpalLocalEndPoint_C(*this);
    AddRouteEntry("local:.*=" + defProto + ":<da>");
  }

  return true;
}


void OpalManager_C::PostMessage(OpalMessageBuffer & message)
{
  m_messageMutex.Wait();
  if (m_messageAvailableCallback == NULL || m_messageAvailableCallback(message)) {
    m_messageQueue.push(message.Detach());
    m_messageMutex.Signal();
  }
  m_messagesAvailable.Signal();
}


OpalMessage * OpalManager_C::GetMessage(unsigned timeout)
{
  OpalMessage * msg = NULL;

  if (m_messagesAvailable.Wait(timeout)) {
    m_messageMutex.Wait();

    if (!m_messageQueue.empty()) {
      msg = m_messageQueue.front();
      m_messageQueue.pop();
    }

    m_messageMutex.Signal();
  }

  return msg;
}


OpalMessage * OpalManager_C::SendMessage(const OpalMessage * message)
{
  if (message == NULL)
    return NULL;

  OpalMessageBuffer response(message->m_type);

  switch (message->m_type) {
    case OpalCmdSetGeneralParameters :
      HandleSetGeneral(*message, response);
      break;
    case OpalCmdSetProtocolParameters :
      HandleSetProtocol(*message, response);
      break;
    case OpalCmdRegistration :
      HandleRegistration(*message, response);
      break;
    case OpalCmdSetUpCall :
      HandleSetUpCall(*message, response);
      break;
    case OpalCmdAnswerCall :
      HandleAnswerCall(*message, response);
      break;
    case OpalCmdUserInput :
      HandleUserInput(*message, response);
      break;
    case OpalCmdClearCall :
      HandleClearCall(*message, response);
      break;
    case OpalCmdHoldCall :
      HandleHoldCall(*message, response);
      break;
    case OpalCmdRetrieveCall :
      HandleRetrieveCall(*message, response);
      break;
    case OpalCmdTransferCall :
      HandleTransferCall(*message, response);
      break;
    case OpalCmdMediaStream :
      HandleMediaStream(*message, response);
      break;
    case OpalCmdSetUserData :
      HandleSetUserData(*message, response);
      break;
    default :
      return NULL;
  }

  return response.Detach();
}


void OpalManager_C::HandleSetGeneral(const OpalMessage & command, OpalMessageBuffer & response)
{
#if P_AUDIO
  if (pcssEP != NULL) {
    SET_MESSAGE_STRING(response, m_param.m_general.m_audioRecordDevice, pcssEP->GetSoundChannelRecordDevice());
    if (!IsNullString(command.m_param.m_general.m_audioRecordDevice))
      pcssEP->SetSoundChannelRecordDevice(command.m_param.m_general.m_audioRecordDevice);

    SET_MESSAGE_STRING(response, m_param.m_general.m_audioPlayerDevice, pcssEP->GetSoundChannelPlayDevice());
    if (!IsNullString(command.m_param.m_general.m_audioPlayerDevice))
      pcssEP->SetSoundChannelPlayDevice(command.m_param.m_general.m_audioPlayerDevice);
  }
#endif // P_AUDIO

#if OPAL_VIDEO
  PVideoDevice::OpenArgs video = GetVideoInputDevice();
  SET_MESSAGE_STRING(response, m_param.m_general.m_videoInputDevice, video.deviceName);
  if (!IsNullString(command.m_param.m_general.m_videoInputDevice)) {
    video.deviceName = command.m_param.m_general.m_videoInputDevice;
    SetVideoInputDevice(video);
  }

  video = GetVideoOutputDevice();
  SET_MESSAGE_STRING(response, m_param.m_general.m_videoOutputDevice, video.deviceName);
  if (!IsNullString(command.m_param.m_general.m_videoOutputDevice)) {
    video.deviceName = command.m_param.m_general.m_videoOutputDevice;
    SetVideoOutputDevice(video);
  }

  video = GetVideoPreviewDevice();
  SET_MESSAGE_STRING(response, m_param.m_general.m_videoPreviewDevice, video.deviceName);
  if (!IsNullString(command.m_param.m_general.m_videoPreviewDevice)) {
    video.deviceName = command.m_param.m_general.m_videoPreviewDevice;
    SetVideoPreviewDevice(video);
  }
#endif // OPAL_VIDEO

  PStringStream strm;
  strm << setfill('\n') << GetMediaFormatOrder();
  SET_MESSAGE_STRING(response, m_param.m_general.m_mediaOrder, strm);
  if (!IsNullString(command.m_param.m_general.m_mediaOrder))
    SetMediaFormatOrder(PString(command.m_param.m_general.m_mediaOrder).Lines());

  strm.flush();
  strm << setfill('\n') << GetMediaFormatMask();
  SET_MESSAGE_STRING(response, m_param.m_general.m_mediaMask, strm);
  if (!IsNullString(command.m_param.m_general.m_mediaMask))
    SetMediaFormatMask(PString(command.m_param.m_general.m_mediaMask).Lines());

  strm = "audio";
#if OPAL_VIDEO
  if (CanAutoStartReceiveVideo())
    strm << " video";
  if (!IsNullString(command.m_param.m_general.m_autoRxMedia))
    SetAutoStartReceiveVideo(strstr(command.m_param.m_general.m_autoRxMedia, "video") != NULL);
#endif
  SET_MESSAGE_STRING(response, m_param.m_general.m_autoRxMedia, strm);

  strm = "audio";
#if OPAL_VIDEO
  if (CanAutoStartTransmitVideo())
    strm << " video";
  if (!IsNullString(command.m_param.m_general.m_autoTxMedia))
    SetAutoStartTransmitVideo(strstr(command.m_param.m_general.m_autoTxMedia, "video") != NULL);
#endif
  SET_MESSAGE_STRING(response, m_param.m_general.m_autoTxMedia, strm);

  SET_MESSAGE_STRING(response, m_param.m_general.m_natRouter, GetTranslationHost());
  if (!IsNullString(command.m_param.m_general.m_natRouter)) {
    if (!SetTranslationHost(command.m_param.m_general.m_natRouter)) {
      response.SetError("Could not set NAT router address.");
      return;
    }
  }

  SET_MESSAGE_STRING(response, m_param.m_general.m_stunServer, GetSTUNServer());
  if (!IsNullString(command.m_param.m_general.m_stunServer)) {
    if (!SetSTUNServer(command.m_param.m_general.m_stunServer)) {
      response.SetError("Could not set STUN server address.");
      return;
    }
    if (GetSTUN()->GetNatType() == PSTUNClient::BlockedNat)
      response.SetError("STUN indicates Blocked NAT.");
  }

  response->m_param.m_general.m_tcpPortBase = GetTCPPortBase();
  response->m_param.m_general.m_tcpPortMax = GetTCPPortMax();
  if (command.m_param.m_general.m_tcpPortBase != 0)
    SetTCPPorts(command.m_param.m_general.m_tcpPortBase, command.m_param.m_general.m_tcpPortMax);

  response->m_param.m_general.m_udpPortBase = GetUDPPortBase();
  response->m_param.m_general.m_udpPortMax = GetUDPPortMax();
  if (command.m_param.m_general.m_udpPortBase != 0)
    SetUDPPorts(command.m_param.m_general.m_udpPortBase, command.m_param.m_general.m_udpPortMax);

  response->m_param.m_general.m_rtpPortBase = GetRtpIpPortBase();
  response->m_param.m_general.m_rtpPortMax = GetRtpIpPortMax();
  if (command.m_param.m_general.m_rtpPortBase != 0)
    SetRtpIpPorts(command.m_param.m_general.m_rtpPortBase, command.m_param.m_general.m_rtpPortMax);

  response->m_param.m_general.m_rtpTypeOfService = GetRtpIpTypeofService();
  if (command.m_param.m_general.m_rtpTypeOfService != 0)
    SetRtpIpTypeofService(command.m_param.m_general.m_rtpTypeOfService);

  response->m_param.m_general.m_rtpMaxPayloadSize = GetMaxRtpPayloadSize();
  if (command.m_param.m_general.m_rtpMaxPayloadSize != 0)
    SetMaxRtpPayloadSize(command.m_param.m_general.m_rtpMaxPayloadSize);

  response->m_param.m_general.m_minAudioJitter = GetMinAudioJitterDelay();
  response->m_param.m_general.m_maxAudioJitter = GetMaxAudioJitterDelay();
  if (command.m_param.m_general.m_minAudioJitter != 0 && command.m_param.m_general.m_maxAudioJitter != 0)
    SetAudioJitterDelay(command.m_param.m_general.m_minAudioJitter, command.m_param.m_general.m_maxAudioJitter);

  if (m_apiVersion < 2)
    return;

  OpalSilenceDetector::Params silenceDetectParams = GetSilenceDetectParams();
  response->m_param.m_general.m_silenceDetectMode = (OpalSilenceDetectModes)(silenceDetectParams.m_mode+1);
  if (command.m_param.m_general.m_silenceDetectMode != 0)
    silenceDetectParams.m_mode = (OpalSilenceDetector::Mode)(command.m_param.m_general.m_silenceDetectMode-1);
  response->m_param.m_general.m_silenceThreshold = silenceDetectParams.m_threshold;
  if (command.m_param.m_general.m_silenceThreshold != 0)
    silenceDetectParams.m_threshold = command.m_param.m_general.m_silenceThreshold;
  response->m_param.m_general.m_signalDeadband = silenceDetectParams.m_signalDeadband;
  if (command.m_param.m_general.m_signalDeadband != 0)
    silenceDetectParams.m_signalDeadband = command.m_param.m_general.m_signalDeadband;
  response->m_param.m_general.m_silenceDeadband = silenceDetectParams.m_silenceDeadband;
  if (command.m_param.m_general.m_silenceDeadband != 0)
    silenceDetectParams.m_silenceDeadband = command.m_param.m_general.m_silenceDeadband;
  response->m_param.m_general.m_silenceAdaptPeriod = silenceDetectParams.m_adaptivePeriod;
  if (command.m_param.m_general.m_silenceAdaptPeriod != 0)
    silenceDetectParams.m_adaptivePeriod = command.m_param.m_general.m_silenceAdaptPeriod;
  SetSilenceDetectParams(silenceDetectParams);

  OpalEchoCanceler::Params echoCancelParams = GetEchoCancelParams();
  response->m_param.m_general.m_echoCancellation = (OpalEchoCancelMode)(echoCancelParams.m_mode+1);
  if (command.m_param.m_general.m_echoCancellation != 0)
    echoCancelParams.m_mode = (OpalEchoCanceler::Mode)(command.m_param.m_general.m_echoCancellation-1);
  SetEchoCancelParams(echoCancelParams);

  if (m_apiVersion < 3)
    return;

#if P_AUDIO
  if (pcssEP != NULL) {
    response->m_param.m_general.m_audioBuffers = pcssEP->GetSoundChannelBufferDepth();
    if (command.m_param.m_general.m_audioBuffers != 0)
      pcssEP->SetSoundChannelBufferDepth(command.m_param.m_general.m_audioBuffers);
  }
#endif

  if (m_apiVersion < 5)
    return;

  if (localEP != NULL) {
    response->m_param.m_general.m_mediaReadData = localEP->m_mediaReadData;
    if (command.m_param.m_general.m_mediaReadData != NULL)
      localEP->m_mediaReadData = command.m_param.m_general.m_mediaReadData;

    response->m_param.m_general.m_mediaWriteData = localEP->m_mediaWriteData;
    if (command.m_param.m_general.m_mediaWriteData != NULL)
      localEP->m_mediaWriteData = command.m_param.m_general.m_mediaWriteData;

    response->m_param.m_general.m_mediaDataHeader = localEP->m_mediaDataHeader;
    if (command.m_param.m_general.m_mediaDataHeader != 0)
      localEP->m_mediaDataHeader = command.m_param.m_general.m_mediaDataHeader;
  }

  if (m_apiVersion < 8)
    return;

  m_messageMutex.Wait();
  response->m_param.m_general.m_messageAvailable = m_messageAvailableCallback;
  m_messageAvailableCallback = command.m_param.m_general.m_messageAvailable;
  m_messageMutex.Signal();
}


void FillOpalProductInfo(const OpalMessage & command, OpalMessageBuffer & response, OpalProductInfo & info)
{
  SET_MESSAGE_STRING(response, m_param.m_protocol.m_vendor,  info.vendor);
  SET_MESSAGE_STRING(response, m_param.m_protocol.m_name,    info.name);
  SET_MESSAGE_STRING(response, m_param.m_protocol.m_version, info.version);

  response->m_param.m_protocol.m_t35CountryCode   = info.t35CountryCode;
  response->m_param.m_protocol.m_t35Extension     = info.t35Extension;
  response->m_param.m_protocol.m_manufacturerCode = info.manufacturerCode;

  if (!IsNullString(command.m_param.m_protocol.m_vendor))
    info.vendor = command.m_param.m_protocol.m_vendor;

  if (!IsNullString(command.m_param.m_protocol.m_name))
    info.name = command.m_param.m_protocol.m_name;

  if (!IsNullString(command.m_param.m_protocol.m_version))
    info.version = command.m_param.m_protocol.m_version;

  if (command.m_param.m_protocol.m_t35CountryCode != 0 && command.m_param.m_protocol.m_manufacturerCode != 0) {
    info.t35CountryCode   = (BYTE)command.m_param.m_protocol.m_t35CountryCode;
    info.t35Extension     = (BYTE)command.m_param.m_protocol.m_t35Extension;
    info.manufacturerCode = (WORD)command.m_param.m_protocol.m_manufacturerCode;
  }
}


static void StartStopListeners(OpalEndPoint * ep, const PString & interfaces, OpalMessageBuffer & response)
{
  if (ep == NULL)
    return;

  ep->RemoveListener(NULL);
  if (interfaces.IsEmpty())
    return;

  PStringArray interfaceArray;
  if (interfaces != "*")
    interfaceArray = interfaces.Lines();
  if (!ep->StartListeners(interfaceArray))
    response.SetError("Could not start listener(s).");
}


void OpalManager_C::HandleSetProtocol(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_protocol.m_prefix)) {
    SET_MESSAGE_STRING(response, m_param.m_protocol.m_userName, GetDefaultUserName());
    if (!IsNullString(command.m_param.m_protocol.m_userName))
      SetDefaultUserName(command.m_param.m_protocol.m_userName);

    SET_MESSAGE_STRING(response, m_param.m_protocol.m_displayName, GetDefaultUserName());
    if (!IsNullString(command.m_param.m_protocol.m_displayName))
      SetDefaultDisplayName(command.m_param.m_protocol.m_displayName);

    OpalProductInfo product = GetProductInfo();
    FillOpalProductInfo(command, response, product);
    SetProductInfo(product);

    if (command.m_param.m_protocol.m_interfaceAddresses != NULL) {
#if OPAL_H323
      StartStopListeners(FindEndPoint(OPAL_PREFIX_H323), command.m_param.m_protocol.m_interfaceAddresses, response);
#endif
#if OPAL_SIP
      StartStopListeners(FindEndPoint(OPAL_PREFIX_SIP),  command.m_param.m_protocol.m_interfaceAddresses, response);
#endif
#if OPAL_IAX2
      StartStopListeners(FindEndPoint(OPAL_PREFIX_IAX2),  command.m_param.m_protocol.m_interfaceAddresses, response);
#endif
    }

    return;
  }

  OpalEndPoint * ep = FindEndPoint(command.m_param.m_protocol.m_prefix);
  if (ep == NULL) {
    response.SetError("No such protocol prefix");
    return;
  }

  SET_MESSAGE_STRING(response, m_param.m_protocol.m_userName, ep->GetDefaultLocalPartyName());
  if (!IsNullString(command.m_param.m_protocol.m_userName))
    ep->SetDefaultLocalPartyName(command.m_param.m_protocol.m_userName);

  SET_MESSAGE_STRING(response, m_param.m_protocol.m_displayName, ep->GetDefaultDisplayName());
  if (!IsNullString(command.m_param.m_protocol.m_displayName))
    ep->SetDefaultDisplayName(command.m_param.m_protocol.m_displayName);

  OpalProductInfo product = ep->GetProductInfo();
  FillOpalProductInfo(command, response, product);
  ep->SetProductInfo(product);

  if (command.m_param.m_protocol.m_interfaceAddresses != NULL)
    StartStopListeners(ep, command.m_param.m_protocol.m_interfaceAddresses, response);
}


void OpalManager_C::HandleRegistration(const OpalMessage & command, OpalMessageBuffer & response)
{
  OpalEndPoint * ep = FindEndPoint(command.m_param.m_registrationInfo.m_protocol);
  if (ep == NULL) {
    response.SetError("No such protocol prefix");
    return;
  }

#if OPAL_H323
  H323EndPoint * h323 = dynamic_cast<H323EndPoint *>(ep);
  if (h323 != NULL) {
    if (command.m_param.m_registrationInfo.m_timeToLive == 0) {
      if (!h323->RemoveGatekeeper())
        response.SetError("Failed to initiate H.323 gatekeeper unregistration.");
    }
    else {
      if (!IsNullString(command.m_param.m_registrationInfo.m_identifier))
        h323->AddAliasName(command.m_param.m_registrationInfo.m_identifier);
      h323->SetGatekeeperPassword(command.m_param.m_registrationInfo.m_password, command.m_param.m_registrationInfo.m_authUserName);
      if (!h323->UseGatekeeper(command.m_param.m_registrationInfo.m_hostName, command.m_param.m_registrationInfo.m_adminEntity))
        response.SetError("Failed to initiate H.323 gatekeeper registration.");
    }
    return;
  }
#endif

#if OPAL_SIP
  SIPEndPoint * sip = dynamic_cast<SIPEndPoint *>(ep);
  if (sip != NULL) {
    if (IsNullString(command.m_param.m_registrationInfo.m_hostName) &&
          (IsNullString(command.m_param.m_registrationInfo.m_identifier) ||
                 strchr(command.m_param.m_registrationInfo.m_identifier, '@') == NULL)) {
      response.SetError("No domain specified for SIP registration.");
      return;
    }

    PStringStream aor;
    PString host;
    if (IsNullString(command.m_param.m_registrationInfo.m_identifier))
      aor << GetDefaultUserName() << '@' << command.m_param.m_registrationInfo.m_hostName;
    else {
      aor << command.m_param.m_registrationInfo.m_identifier;
      if (strchr(command.m_param.m_registrationInfo.m_identifier, '@') != NULL)
        host = command.m_param.m_registrationInfo.m_hostName;
      else
        aor << '@' << command.m_param.m_registrationInfo.m_hostName;
    }

    if (command.m_param.m_registrationInfo.m_timeToLive == 0) {
      if (!sip->Unregister(aor))
        response.SetError("Failed to initiate SIP unregistration.");
    }
    else {
      SIPRegister::Params regParams;
      regParams.m_addressOfRecord = aor;
      regParams.m_contactAddress = host;
      regParams.m_authID = command.m_param.m_registrationInfo.m_authUserName;
      if (regParams.m_authID.IsEmpty())
        regParams.m_authID = regParams.m_addressOfRecord.Left(regParams.m_addressOfRecord.Find('@'));
      regParams.m_password = command.m_param.m_registrationInfo.m_password;
      regParams.m_realm = command.m_param.m_registrationInfo.m_adminEntity;
      regParams.m_expire = command.m_param.m_registrationInfo.m_timeToLive;
      if (m_apiVersion >= 7 && command.m_param.m_registrationInfo.m_restoreTime > 0)
        regParams.m_restoreTime = command.m_param.m_registrationInfo.m_restoreTime;

      if (!sip->Register(regParams))
        response.SetError("Failed to initiate SIP registration.");

      if (m_apiVersion >= 10) {
        SIPSubscribe::Params mwiParams(SIPSubscribe::MessageSummary);
        mwiParams.m_targetAddress = aor;
        regParams.m_contactAddress = host;
        mwiParams.m_authID = regParams.m_authID;
        mwiParams.m_password = regParams.m_password;
        mwiParams.m_realm = regParams.m_realm;
        mwiParams.m_expire = command.m_param.m_registrationInfo.m_messageWaiting;
        mwiParams.m_restoreTime = regParams.m_restoreTime;
        sip->Subscribe(mwiParams);
      }
    }
    return;
  }
#endif

  response.SetError("Protocol prefix does not support registration.");
}


void OpalManager_C::HandleSetUpCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_callSetUp.m_partyB)) {
    response.SetError("No destination address provided.");
    return;
  }

  PString partyA = command.m_param.m_callSetUp.m_partyA;
  if (partyA.IsEmpty()) {
#if P_AUDIO
    if (pcssEP != NULL)
      partyA = "pc:*";
    else
#endif
    if (localEP != NULL)
      partyA = "local:*";
    else
      partyA = "pots:*";
  }

  PString token;
  if (SetUpCall(partyA, command.m_param.m_callSetUp.m_partyB, token)) {
    SET_MESSAGE_STRING(response, m_param.m_callSetUp.m_partyA, partyA);
    SET_MESSAGE_STRING(response, m_param.m_callSetUp.m_partyB, command.m_param.m_callSetUp.m_partyB);
    SET_MESSAGE_STRING(response, m_param.m_callSetUp.m_callToken, token);
  }
  else
    response.SetError("Call set up failed.");
}


void OpalManager_C::HandleAnswerCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  if (
#if P_AUDIO
      pcssEP == NULL &&
#endif
      localEP == NULL) {
    response.SetError("Can only answer calls to PC.");
    return;
  }

#if P_AUDIO
  if (pcssEP != NULL && pcssEP->AcceptIncomingConnection(command.m_param.m_callToken))
    return;
#endif

  if (localEP != NULL && localEP->AcceptIncomingCall(command.m_param.m_callToken))
    return;

  response.SetError("No call found by the token provided.");
}


void OpalManager_C::HandleUserInput(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_userInput.m_userInput)) {
    response.SetError("No user input provided.");
    return;
  }

  if (IsNullString(command.m_param.m_userInput.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_userInput.m_callToken);
  if (call == NULL) {
    response.SetError("No call found by the token provided.");
    return;
  }

  PSafePtr<OpalConnection> connection = call->GetConnection(0, PSafeReadOnly);
  while (connection->IsNetworkConnection()) {
    ++connection;
  if (connection == NULL) {
    response.SetError("No suitable connection for user input.");
    return;
  }
  }

  if (command.m_param.m_userInput.m_duration == 0)
    connection->OnUserInputString(command.m_param.m_userInput.m_userInput);
  else
    connection->OnUserInputTone(command.m_param.m_userInput.m_userInput[0], command.m_param.m_userInput.m_duration);
}


void OpalManager_C::HandleClearCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  const char * callToken;
  OpalConnection::CallEndReason reason;

  if (m_apiVersion < 9) {
    callToken = command.m_param.m_callToken;
    reason = OpalConnection::EndedByLocalUser;
  }
  else {
    callToken = command.m_param.m_clearCall.m_callToken;
    reason = (OpalConnection::CallEndReason)command.m_param.m_clearCall.m_reason;
  }

  if (IsNullString(callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  if (!ClearCall(callToken, reason))
    response.SetError("No call found by the token provided.");
}


void OpalManager_C::HandleHoldCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_callToken);
  if (call == NULL) {
    response.SetError("No call found by the token provided.");
    return;
  }

  if (call->IsOnHold()) {
    response.SetError("Call is already on hold.");
    return;
  }

  call->Hold();
}


void OpalManager_C::HandleRetrieveCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_callToken);
  if (call == NULL) {
    response.SetError("No call found by the token provided.");
    return;
  }

  if (!call->IsOnHold()) {
    response.SetError("Call is not on hold.");
    return;
  }

  call->Retrieve();
}


void OpalManager_C::HandleTransferCall(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_callSetUp.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  if (IsNullString(command.m_param.m_callSetUp.m_partyB)) {
    response.SetError("No destination address provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_callSetUp.m_callToken);
  if (call == NULL) {
    response.SetError("No call available by the token provided.");
    return;
  }

  PSafePtr<OpalConnection> connection = call->GetConnection(0, PSafeReadOnly);
  if (IsNullString(command.m_param.m_callSetUp.m_partyA)) {
    if (!connection->IsNetworkConnection())
      ++connection;
  }
  else {
    do {
      if (connection->GetLocalPartyURL() == command.m_param.m_callSetUp.m_partyA)
        break;
      ++connection;
    } while (connection != NULL);
  }

  if (connection == NULL) {
    response.SetError("Call does not have suitable connection to transfer.");
    return;
  }

  connection->TransferConnection(command.m_param.m_callSetUp.m_partyB);
}


void OpalManager_C::HandleMediaStream(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_mediaStream.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_mediaStream.m_callToken);
  if (call == NULL) {
    response.SetError("No call found by the token provided.");
    return;
  }

  PSafePtr<OpalConnection> connection = call->GetConnection(0, PSafeReadOnly);
  while (connection->IsNetworkConnection()) {
    ++connection;
    if (connection == NULL) {
      response.SetError("No suitable connection for media stream control.");
      return;
    }
  }

  OpalMediaType mediaType;
  bool source = false;
  if (!IsNullString(command.m_param.m_mediaStream.m_type)) {
    PString typeStr = command.m_param.m_mediaStream.m_type;
    mediaType = typeStr.Left(typeStr.Find(' '));
    source = typeStr.Find("out") != P_MAX_INDEX;
  }

  OpalMediaStreamPtr stream;
  if (!IsNullString(command.m_param.m_mediaStream.m_identifier))
    stream = connection->GetMediaStream(command.m_param.m_mediaStream.m_identifier);
  else if (!IsNullString(command.m_param.m_mediaStream.m_type))
    stream = connection->GetMediaStream(mediaType, source);
  else {
    response.SetError("No identifer or type provided to locate media stream.");
    return;
  }

  if (stream == NULL && command.m_param.m_mediaStream.m_state != OpalMediaStateOpen) {
    response.SetError("Could not locate media stream.");
    return;
  }

  switch (command.m_param.m_mediaStream.m_state) {
    case OpalMediaStateNoChange :
      break;

    case OpalMediaStateOpen :
      if (mediaType.empty())
        response.SetError("Must provide type and direction to open media stream.");
      else {
        OpalMediaFormat mediaFormat(command.m_param.m_mediaStream.m_format);
        unsigned sessionID = 0;
        if (stream != NULL)
          sessionID = stream->GetSessionID();
        if (source)
          call->OpenSourceMediaStreams(*connection, mediaType, sessionID, mediaFormat);
        else
          call->OpenSourceMediaStreams(*call->GetOtherPartyConnection(*connection), mediaType, sessionID, mediaFormat);
      }
      break;

    case OpalMediaStateClose :
      connection->CloseMediaStream(*stream);
      break;

    case OpalMediaStatePause :
      stream->SetPaused(true);
      break;

    case OpalMediaStateResume :
      stream->SetPaused(false);
      break;
  }
}

void OpalManager_C::HandleSetUserData(const OpalMessage & command, OpalMessageBuffer & response)
{
  if (IsNullString(command.m_param.m_setUserData.m_callToken)) {
    response.SetError("No call token provided.");
    return;
  }

  PSafePtr<OpalCall> call = FindCallWithLock(command.m_param.m_setUserData.m_callToken);
  if (call == NULL) {
    response.SetError("No call found by the token provided.");
    return;
  }

  PSafePtr<OpalLocalConnection> connection = call->GetConnectionAs<OpalLocalConnection>();
  if (connection == NULL) {
    response.SetError("No suitable connection for media stream control.");
    return;
  }

  connection->SetUserData(command.m_param.m_setUserData.m_userData);
}

void OpalManager_C::OnEstablishedCall(OpalCall & call)
{
  OpalMessageBuffer message(OpalIndEstablished);
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyA, call.GetPartyA());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_partyB, call.GetPartyB());
  SET_MESSAGE_STRING(message, m_param.m_callSetUp.m_callToken, call.GetToken());
  PTRACE(4, "OpalC API\tOnEstablishedCall:"
            " token=\"" << message->m_param.m_callSetUp.m_callToken << "\""
            " A=\""     << message->m_param.m_callSetUp.m_partyA << "\""
            " B=\""     << message->m_param.m_callSetUp.m_partyB << '"');
  PostMessage(message);
}


void OpalManager_C::OnIndMediaStream(const OpalMediaStream & stream, OpalMediaStates state)
{
  const OpalConnection & connection = stream.GetConnection();
  if (!connection.IsNetworkConnection())
    return;

  OpalMessageBuffer message(OpalIndMediaStream);
  SET_MESSAGE_STRING(message, m_param.m_mediaStream.m_callToken, connection.GetCall().GetToken());
  SET_MESSAGE_STRING(message, m_param.m_mediaStream.m_identifier, stream.GetID());
  PString type;
  switch (stream.GetMediaFormat().GetDefaultSessionID()) {
    case OpalMediaFormat::DefaultAudioSessionID :
      type = "audio";
      break;
    case OpalMediaFormat::DefaultVideoSessionID :
      type = "video";
      break;
    case OpalMediaFormat::DefaultDataSessionID :
      type = "fax";
      break;
  }
  type += stream.IsSource() ? " in" : " out";
  SET_MESSAGE_STRING(message, m_param.m_mediaStream.m_type, type);
  SET_MESSAGE_STRING(message, m_param.m_mediaStream.m_format, stream.GetMediaFormat().GetName());
  message->m_param.m_mediaStream.m_state = state;
  PTRACE(4, "OpalC API\tOnIndMediaStream:"
            " token=\"" << message->m_param.m_userInput.m_callToken << "\""
            " id=\"" << message->m_param.m_mediaStream.m_identifier << '"');
  PostMessage(message);
}


PBoolean OpalManager_C::OnOpenMediaStream(OpalConnection & connection, OpalMediaStream & stream)
{
  if (!OpalManager::OnOpenMediaStream(connection, stream))
    return false;

  OnIndMediaStream(stream, OpalMediaStateOpen);
  return true;
}


void OpalManager_C::OnClosedMediaStream(const OpalMediaStream & stream)
{
  OnIndMediaStream(stream, OpalMediaStateClose);
  OpalManager::OnClosedMediaStream(stream);
}


void OpalManager_C::OnUserInputString(OpalConnection & connection, const PString & value)
{
  OpalMessageBuffer message(OpalIndUserInput);
  SET_MESSAGE_STRING(message, m_param.m_userInput.m_callToken, connection.GetCall().GetToken());
  SET_MESSAGE_STRING(message, m_param.m_userInput.m_userInput, value);
  message->m_param.m_userInput.m_duration = 0;
  PTRACE(4, "OpalC API\tOnUserInputString:"
            " token=\"" << message->m_param.m_userInput.m_callToken << "\""
            " input=\"" << message->m_param.m_userInput.m_userInput << '"');
  PostMessage(message);

  OpalManager::OnUserInputString(connection, value);
}


void OpalManager_C::OnUserInputTone(OpalConnection & connection, char tone, int duration)
{
  char input[2];
  input[0] = tone;
  input[1] = '\0';

  OpalMessageBuffer message(OpalIndUserInput);
  SET_MESSAGE_STRING(message, m_param.m_userInput.m_callToken, connection.GetCall().GetToken());
  SET_MESSAGE_STRING(message, m_param.m_userInput.m_userInput, input);
  message->m_param.m_userInput.m_duration = duration;
  PTRACE(4, "OpalC API\tOnUserInputTone:"
            " token=\"" << message->m_param.m_userInput.m_callToken << "\""
            " input=\"" << message->m_param.m_userInput.m_userInput << '"');
  PostMessage(message);

  OpalManager::OnUserInputTone(connection, tone, duration);
}


void OpalManager_C::OnMWIReceived(const PString & party, MessageWaitingType type, const PString & extraInfo)
{
  OpalMessageBuffer message(OpalIndMessageWaiting);
  SET_MESSAGE_STRING(message, m_param.m_messageWaiting.m_party, party);
  static const char * const TypeNames[] = { "Voice", "Fax", "Pager", "Multimedia", "Text", "None" };
  if ((size_t)type < sizeof(TypeNames)/sizeof(TypeNames[0]))
    SET_MESSAGE_STRING(message, m_param.m_messageWaiting.m_type, TypeNames[type]);
  SET_MESSAGE_STRING(message, m_param.m_messageWaiting.m_extraInfo, extraInfo);
  PTRACE(4, "OpalC API\tOnMWIReceived: party=\"" << message->m_param.m_messageWaiting.m_party << '"');
  PostMessage(message);

  OpalManager::OnMWIReceived(party, type, extraInfo);
}


void OpalManager_C::OnClearedCall(OpalCall & call)
{
  OpalMessageBuffer message(OpalIndCallCleared);
  SET_MESSAGE_STRING(message, m_param.m_callCleared.m_callToken, call.GetToken());

  static const char * const CallEndReasonStrings[] = {
    "Local party cleared call",
    "Local party did not accept call",
    "Local party declined to answer call",
    "Remote party cleared call",
    "Remote party refused call",
    "Remote party did not answer in required time",
    "Remote party stopped calling",
    "Call failed due to a transport error",
    "Connection to remote failed",
    "Gatekeeper has cleared call",
    "Call failed as could not find user",
    "Call failed due to insufficient bandwidth",
    "Call failed as could not find common media capabilities",
    "Call was forwarded",
    "Call failed security check",
    "Local party busy",
    "Local party congested",
    "Remote party busy",
    "Remote switch congested",
    "Remote party could but be reached",
    "Remote party application is not running",
    "Remote party host is off line",
    "Remote system failed temporarily",
    "Call cleared with unmapped Q.931 cause code",
    "Call cleared due to an enforced duration limit",
    "Call cleared due to invalid conference ID",
    "Call cleared due to missing dial tone",
    "Call cleared due to missing ringback tone",
    "Call cleared because the line is out of service",
    "Call cleared because another call is answered"
  };

  PStringStream str;
  OpalConnection::CallEndReason reason = call.GetCallEndReason();
  str << (unsigned)reason << ": ";

  if (reason < PARRAYSIZE(CallEndReasonStrings))
    str << CallEndReasonStrings[reason];
  else if ((reason&OpalConnection::EndedWithQ931Code) != 0)
    str << "Call cleared with Q.931 code " << (reason >> 8);
  else
    str << "Call cleared with unknown cause code.";

  SET_MESSAGE_STRING(message, m_param.m_callCleared.m_reason, str);
  PTRACE(4, "OpalC API\tOnClearedCall:"
            " token=\""  << message->m_param.m_callCleared.m_callToken << "\""
            " reason=\"" << message->m_param.m_callCleared.m_reason << '"');
  PostMessage(message);

  OpalManager::OnClearedCall(call);
}


///////////////////////////////////////////////////////////////////////////////

extern "C" {

  OpalHandle OPAL_EXPORT OpalInitialise(unsigned * version, const char * options)
  {
    PCaselessString optionsString = IsNullString(options) ? "pcss h323 sip iax2 pots pstn ivr" : options;

    unsigned callerVersion = 1;
    if (version != NULL) {
      callerVersion = *version;
      if (callerVersion > OPAL_C_API_VERSION)
        *version = OPAL_C_API_VERSION;
    }

    OpalHandle opal = new OpalHandleStruct(callerVersion, optionsString);
    if (opal->manager.Initialise(optionsString))
      return opal;

    delete opal;
    return NULL;
  }


  void OPAL_EXPORT OpalShutDown(OpalHandle handle)
  {
    delete handle;
  }


  OpalMessage * OPAL_EXPORT OpalGetMessage(OpalHandle handle, unsigned timeout)
  {
    return handle == NULL ? NULL : handle->manager.GetMessage(timeout);
  }


  OpalMessage * OPAL_EXPORT OpalSendMessage(OpalHandle handle, const OpalMessage * message)
  {
    return handle == NULL ? NULL : handle->manager.SendMessage(message);
  }


  void OPAL_EXPORT OpalFreeMessage(OpalMessage * message)
  {
    if (message != NULL)
      free(message);
  }

}; // extern "C"


///////////////////////////////////////////////////////////////////////////////
