/*
 * main.cxx
 *
 * A simple H.323 MCU
 *
 * Copyright (C) 2000 Equivalence Pty. Ltd.
 * Copyright (C) 2004 Post Increment
 *
 * 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 Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Portions of this code were written by Post Increment (http://www.postincrement.com) 
 * with the assistance of funding from Citron Networks (http://www.citron.com.tw)
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): Derek J Smithies (derek@indranet.co.nz)
 *                 Craig Southeren (craig@postincrement.com)

 * $Log: main.cxx,v $
 * Revision 2.13  2005/12/21 00:27:08  csoutheren
 * Disable sending room member lists as this code is never implemented in clients
 *
 * Revision 2.12  2004/05/26 06:54:31  csoutheren
 * Changed to be a PHTTPServiceProcess
 * Added ability to play WAV files on member entry and exit
 * Added additional documentation on all classes
 * Preparation for re-introducing video
 *
 * Revision 2.11  2004/05/10 13:07:19  rjongbloed
 * Changed G.726 and MS-ADPCM to plug in codecs.
 *
 * Revision 2.10  2004/05/04 12:21:16  rjongbloed
 * Converted LPC-10 codec to plug in.
 *
 * Revision 2.9  2004/05/03 13:25:47  rjongbloed
 * Converted everything to be codec plug in freindly
 * Removed GSM and G.729 as now plug ins are "the way"!
 *
 * Revision 2.8  2004/05/02 01:35:01  csoutheren
 * Move --disable-menu to outside video #ifdef
 *
 * Revision 2.7  2004/04/06 11:27:50  rjongbloed
 * Changes to support native C++ Run Time Type Information
 * Changes for codec plug ins
 *
 * Revision 2.6  2004/04/02 01:10:52  csoutheren
 * Fixed unitialised variable
 *
 * Revision 2.5  2004/03/31 03:40:25  csoutheren
 * Added various codecs back in
 *
 * Revision 2.4  2004/03/31 03:36:38  csoutheren
 * Fixed problem with user indication messages
 * Fixed problems with room listener and unlisten
 *
 * Revision 2.3  2004/03/30 04:02:54  csoutheren
 * Fixed problem with not using incoming alias for default room name
 *
 * Revision 2.2  2004/03/23 11:40:06  csoutheren
 * Fixed problem where deleting map element in-place causes crash at end of call
 * Fixed problem where referencing map by iterator rather than ID
 * Fixed code formatting problems
 *
 * Revision 2.1  2004/03/11 20:49:44  csoutheren
 * Removed warnings
 *
 * Revision 2.0  2004/03/08 02:06:24  csoutheren
 * Totally rewritten to use new connection locking mecahnism
 * Added ability to monitor conferences
 * Added initial support for H.323 MCU messages
 * Thanks to Citron Networks for supporting this work
 */

#include <ptlib.h>

#include <ptlib/pipechan.h>

#include "version.h"
#include "main.h"

PCREATE_PROCESS(OpenMCU);

const WORD DefaultHTTPPort = 1420;

static const char LogLevelKey[]           = "Log Level";
static const char UserNameKey[]           = "Username";
static const char PasswordKey[]           = "Password";
static const char HttpPortKey[]           = "HTTP Port";
static const char LocalUserNameKey[]      = "Local User Name";
static const char GatekeeperPasswordKey[] = "Gatekeeper Password";
static const char GatekeeperModeKey[]     = "Gatekeeper Mode";
static const char GatekeeperKey[]         = "Gatekeeper";
static const char DefaultRoomKey[]        = "Default room";
static const char DisableCodecsKey[]      = "Disable codecs";

static const char CallLogFilenameKey[]    = "Call log filename";
static const char InterfaceKey[]          = "Interface";

static const char ConnectingWAVFileKey[]  = "Connecting WAV File";
static const char EnteringWAVFileKey[]    = "Entering WAV File";
static const char LeavingWAVFileKey[]     = "Leaving WAV File";

#if P_SSL
static const char HTTPCertificateFileKey[]  = "HTTP Certificate";
#endif

static const char DefaultCallLogFilename[] = "mcu_log.txt"; 
static const char DefaultRoom[]            = "room101";

static const char DefaultConnectingWAVFile[] = "connecting.wav";
static const char DefaultEnteringWAVFile[]   = "entering.wav";
static const char DefaultLeavingWAVFile[]    = "leaving.wav";

static const char * GKModeLabels[] = { 
   "No gatekeeper", 
   "Find gatekeeper", 
   "Use gatekeeper", 
};

enum {
  Gatekeeper_None,
  Gatekeeper_Find,
  Gatekeeper_Explicit
};

#define GKMODE_LABEL_COUNT   (sizeof(GKModeLabels)/sizeof(char *))

#define new PNEW


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

class MainStatusPage : public PServiceHTTPString
{
  PCLASSINFO(MainStatusPage, PServiceHTTPString);

  public:
    MainStatusPage(OpenMCU & app, PHTTPAuthority & auth);
    
    virtual BOOL Post(
      PHTTPRequest & request,
      const PStringToString &,
      PHTML & msg
    );
  
  private:
    OpenMCU & app;
};

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

OpenMCU::OpenMCU()
  : OpenMCUProcessAncestor(ProductInfo)
{
  endpoint = NULL;
}

void OpenMCU::Main()
{
  Suspend();
}

BOOL OpenMCU::OnStart()
{
  // change to the default directory to the one containing the executable
  PDirectory exeDir = GetFile().GetDirectory();

#if defined(_WIN32) && defined(_DEBUG)
  // Special check to aid in using DevStudio for debugging.
  if (exeDir.Find("\\Debug\\") != P_MAX_INDEX)
    exeDir = exeDir.GetParent();
#endif
  exeDir.Change();

  httpNameSpace.AddResource(new PHTTPDirectory("data", "data"));
  httpNameSpace.AddResource(new PServiceHTTPDirectory("html", "html"));

  endpoint = new MyH323EndPoint();

  return PHTTPServiceProcess::OnStart();
}

void OpenMCU::OnStop()
{
  delete endpoint;
  endpoint = NULL;

  PHTTPServiceProcess::OnStop();
}

void OpenMCU::OnControl()
{
  // This function get called when the Control menu item is selected in the
  // tray icon mode of the service.
  PStringStream url;
  url << "http://";

  PString host = PIPSocket::GetHostName();
  PIPSocket::Address addr;
  if (PIPSocket::GetHostAddress(host, addr))
    url << host;
  else
    url << "localhost";

  url << ':' << DefaultHTTPPort;

  PURL::OpenBrowser(url);
}

BOOL OpenMCU::Initialise(const char * initMsg)
{
  PConfig cfg("Parameters");

  // Set log level as early as possible
  SetLogLevel((PSystemLog::Level)cfg.GetInteger(LogLevelKey, GetLogLevel()));
#if PTRACING
  if (GetLogLevel() >= PSystemLog::Warning)
    PTrace::SetLevel(GetLogLevel()-PSystemLog::Warning);
  else
    PTrace::SetLevel(0);
  PTrace::ClearOptions(PTrace::Timestamp);
  PTrace::SetOptions(PTrace::DateAndTime);
#endif

  // Get the HTTP basic authentication info
  PString adminUserName = cfg.GetString(UserNameKey);
  PString adminPassword = PHTTPPasswordField::Decrypt(cfg.GetString(PasswordKey));

  PHTTPSimpleAuth authority(GetName(), adminUserName, adminPassword);

  // Create the parameters URL page, and start adding fields to it
  PConfigPage * rsrc = new PConfigPage(*this, "Parameters", "Parameters", authority);

  // HTTP authentication username/password
  rsrc->Add(new PHTTPStringField(UserNameKey, 25, adminUserName));
  rsrc->Add(new PHTTPPasswordField(PasswordKey, 25, adminPassword));

  // Log level for messages
  rsrc->Add(new PHTTPIntegerField(LogLevelKey,
                                  PSystemLog::Fatal, PSystemLog::NumLogLevels-1,
                                  GetLogLevel(),
                                  "1=Fatal only, 2=Errors, 3=Warnings, 4=Info, 5=Debug"));

#if P_SSL
  // SSL certificate file.
  PString certificateFile = cfg.GetString(HTTPCertificateFileKey, "server.pem");
  rsrc->Add(new PHTTPStringField(HTTPCertificateFileKey, 25, certificateFile));
  if (!SetServerCertificate(certificateFile, TRUE)) {
    PSYSTEMLOG(Fatal, "BMAC\tCould not load certificate \"" << certificateFile << '"');
    return FALSE;
  }
#endif

  // HTTP Port number to use.
  WORD httpPort = (WORD)cfg.GetInteger(HttpPortKey, DefaultHTTPPort);
  rsrc->Add(new PHTTPIntegerField(HttpPortKey, 1, 32767, httpPort));

  endpoint->Initialise(cfg, rsrc);

  // get default "room" (conference) name
  defaultRoomName = cfg.GetString(DefaultRoomKey, DefaultRoom);
  rsrc->Add(new PHTTPStringField(DefaultRoomKey, 25, defaultRoomName));

  // get WAV file played to a user when they enter a conference
  connectingWAVFile = cfg.GetString(ConnectingWAVFileKey, DefaultConnectingWAVFile);
  rsrc->Add(new PHTTPStringField(ConnectingWAVFileKey, 50, connectingWAVFile));

  // get WAV file played to a conference when a new user enters
  enteringWAVFile = cfg.GetString(EnteringWAVFileKey, DefaultEnteringWAVFile);
  rsrc->Add(new PHTTPStringField(EnteringWAVFileKey, 50, enteringWAVFile));

  // get WAV file played to a conference when a new user enters
  leavingWAVFile = cfg.GetString(LeavingWAVFileKey, DefaultLeavingWAVFile);
  rsrc->Add(new PHTTPStringField(LeavingWAVFileKey, 50, leavingWAVFile));

  // default log file name
  logFilename = cfg.GetString(CallLogFilenameKey, DefaultCallLogFilename);
  rsrc->Add(new PHTTPStringField(CallLogFilenameKey, 50, logFilename));

  // see if audio loopback is required
  //audioLoopbackRoom = args.GetOptionString("audio-loopback");

  // get singleStream flag
  //endpoint.singleStream = args.HasOption("single-stream");

#ifndef NO_MCU_VIDEO
  // If videoLarge was specified
  endpoint.videoLarge = args.HasOption("videolarge");

  if (args.HasOption('v')) {
    //Add capability to allow the reception of video.

    // this is the only way I know of at present to transmit
    // CIF size video - pez
    // Do not allow CIF video if size is medium
    if (endpoint.videoLarge)
      endpoint.SetCapability(0, 1, new H323_H261Capability(0, 4, FALSE, FALSE, 6217)); // CIF
      endpoint.SetCapability(0, 1, new H323_H261Capability(2, 0, FALSE, FALSE, 6217)); // QCIF
  } 
#endif

  // Finished the resource to add, generate HTML for it and add to name space
  PServiceHTML html("System Parameters");
  rsrc->BuildHTML(html);
  httpNameSpace.AddResource(rsrc, PHTTPSpace::Overwrite);

  // Create the status page
  httpNameSpace.AddResource(new MainStatusPage(*this, authority), PHTTPSpace::Overwrite);

  // Add log file links
  if (!systemLogFileName && systemLogFileName != "-") {
    httpNameSpace.AddResource(new PHTTPFile("logfile.txt", systemLogFileName, authority));
    httpNameSpace.AddResource(new PHTTPTailFile("tail_logfile", systemLogFileName, authority));
  }

  //  create the home page
  static const char welcomeHtml[] = "welcome.html";
  if (PFile::Exists(welcomeHtml))
    httpNameSpace.AddResource(new PServiceHTTPFile(welcomeHtml, TRUE), PHTTPSpace::Overwrite);
  else {
    PHTML html;
    html << PHTML::Title("Welcome to OpenMCU")
         << PHTML::Body()
         << GetPageGraphic()
         << PHTML::Paragraph() << "<center>"

         << PHTML::HotLink("Parameters") << "Parameters" << PHTML::HotLink()
         << PHTML::Paragraph()
         << PHTML::HotLink("Status") << "Status" << PHTML::HotLink()
         << PHTML::Paragraph();

    if (!systemLogFileName && systemLogFileName != "-")
      html << PHTML::HotLink("logfile.txt") << "Full Log File" << PHTML::HotLink()
           << PHTML::BreakLine()
           << PHTML::HotLink("tail_logfile") << "Tail Log File" << PHTML::HotLink()
           << PHTML::Paragraph();
 
    html << PHTML::HRule()
         << GetCopyrightText()
         << PHTML::Body();
    httpNameSpace.AddResource(new PServiceHTTPString("welcome.html", html), PHTTPSpace::Overwrite);
  }

  // create monitoring page
  PString monitorText = "<!--#equival monitorinfo-->"
                        "Current Proxies: <!--#equival ProxyCount-->\n";
  httpNameSpace.AddResource(new PServiceHTTPString("monitor.txt", monitorText, "text/plain", authority), PHTTPSpace::Overwrite);

  // set up the HTTP port for listening & start the first HTTP thread
  if (ListenForHTTP(httpPort))
    PSYSTEMLOG(Info, "Opened master socket for HTTP: " << httpListeningSocket->GetPort());
  else {
    PSYSTEMLOG(Fatal, "Cannot run without HTTP port: " << httpListeningSocket->GetErrorText());
    return FALSE;
  }

  PSYSTEMLOG(Info, "Service " << GetName() << ' ' << initMsg);
  return TRUE;
}

void OpenMCU::OnConfigChanged()
{
}



PString OpenMCU::GetNewRoomNumber()
{
  static PAtomicInteger number = 100;
  return PString(PString::Unsigned, ++number);
}

void OpenMCU::LogMessage(const PString & str)
{
  static PMutex logMutex;
  static PTextFile logFile;

  PTime now;
  PString msg = now.AsString("dd/MM/yyyy") & str;
  logMutex.Wait();

  if (!logFile.IsOpen()) {
    logFile.Open(logFilename, PFile::ReadWrite);
    logFile.SetPosition(0, PFile::End);
  }

  logFile.WriteLine(msg);
  logFile.Close();
  logMutex.Signal();
}

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

void MyConferenceManager::OnMemberJoining(Conference * conf, ConferenceMember * member)
{ 
  if (!PIsDescendant(member, ConferenceFileMember))
    conf->AddMember(new ConferenceFileMember(OpenMCU::Current().GetEnteringWAVFile(), PFile::ReadOnly)); 
}

void MyConferenceManager::OnMemberLeaving(Conference * conf, ConferenceMember * member)
{ 
  if (!PIsDescendant(member, ConferenceFileMember))
    conf->AddMember(new ConferenceFileMember(OpenMCU::Current().GetLeavingWAVFile(), PFile::ReadOnly)); 
}

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

void SpliceMacro(PString & text, const PString & token, const PString & value)
{
  PRegularExpression RegEx("<?!--#status[ \t\r\n]+" + token + "[ \t\r\n]*-->?",
                           PRegularExpression::Extended|PRegularExpression::IgnoreCase);
  PINDEX pos, len;
  while (text.FindRegEx(RegEx, pos, len))
    text.Splice(value, pos, len);
}

PCREATE_SERVICE_MACRO_BLOCK(RoomStatus,P_EMPTY,P_EMPTY,block)
{
  return OpenMCU::Current().GetEndpoint().GetRoomStatus(block);
}

MainStatusPage::MainStatusPage(OpenMCU & _app, PHTTPAuthority & auth)
  : PServiceHTTPString("Status", "", "text/html; charset=UTF-8", auth),
    app(_app)
{
  PHTML html;

  html << PHTML::Title("OpenH323 MCU Status")
       << "<meta http-equiv=\"Refresh\" content=\"30\">\n"
       << PHTML::Body()
       << app.GetPageGraphic()
       << PHTML::Paragraph() << "<center>"

       //<< PHTML::Form("POST")

       << PHTML::TableStart("border=1")
       << PHTML::TableRow()
       << PHTML::TableHeader()
       << "&nbsp;Room&nbsp;Name&nbsp;"
       << PHTML::TableHeader()
       << "&nbsp;Room&nbsp;Members&nbsp;"
       << PHTML::TableHeader()

       << "<!--#macrostart RoomStatus-->"
         << PHTML::TableRow()
         << PHTML::TableData()
         << "<!--#status RoomName-->"
         << PHTML::TableData()
         << "<!--#status RoomMembers-->"
       << "<!--#macroend RoomStatus-->"

       << PHTML::TableEnd()

       << PHTML::Paragraph()

       //<< PHTML::Form()

       << PHTML::HRule()

       << app.GetCopyrightText()
       << PHTML::Body();

  string = html;
}


BOOL MainStatusPage::Post(PHTTPRequest & request,
                          const PStringToString & data,
                          PHTML & msg)
{
  /*
  PTRACE(2, "VGGK\tClear call POST received " << data);

  msg << PHTML::Title() << "Accepted Control Command" << PHTML::Body()
      << PHTML::Heading(1) << "Accepted Control Command" << PHTML::Heading(1);

  PWaitAndSignal m(app.endpointMutex);

  std::vector<MyGatekeeperH323EndPoint *>::iterator r;
  for (r = app.endpointList.begin(); r != app.endpointList.end(); ++r) {
    if ((*r)->OnPostControl(data, msg))
      msg << PHTML::Heading(2) << "No calls or endpoints!" << PHTML::Heading(2);
    PServiceHTML::ProcessMacros(request, msg, "html/status.html",
                                PServiceHTML::LoadFromFile|PServiceHTML::NoSignatureForFile);
  }

  msg << PHTML::Paragraph()
      << PHTML::HotLink(request.url.AsString()) << "Reload page" << PHTML::HotLink()
      << "&nbsp;&nbsp;&nbsp;&nbsp;"
      << PHTML::HotLink("/") << "Home page" << PHTML::HotLink();

*/
  return TRUE;
}


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

MyH323EndPoint::MyH323EndPoint()
{
#if 0
	terminalType = e_MCUWithAudioMP;
#ifndef NO_MCU_VIDEO
	terminalType = e_MCUWithAVMP;
#endif
  conferenceListener = NULL;
#endif
}

void MyH323EndPoint::Initialise(PConfig & cfg, PConfigPage * rsrc)
{
  // Local alias name for H.323 endpoint
  SetLocalUserName(cfg.GetString(LocalUserNameKey, "OpenH323 MCU v" + OpenMCU::Current().GetVersion()));

  // Gatekeeper mode
  PStringArray labels(GKMODE_LABEL_COUNT, GKModeLabels); 
  PINDEX idx = labels.GetStringsIndex(cfg.GetString(GatekeeperModeKey, labels[0]));  
  PINDEX gkMode = (idx == P_MAX_INDEX) ? 0 : idx;
  rsrc->Add(new PHTTPRadioField(GatekeeperModeKey, labels, gkMode));

  // Gatekeeper password
  PString gkName = cfg.GetString(GatekeeperKey);

  // Gatekeeper password
  PString gkPassword = PHTTPPasswordField::Decrypt(cfg.GetString(GatekeeperPasswordKey));
  SetGatekeeperPassword(gkPassword);

  // start the H.323 listeners
  PString defaultInterface = "*:1720";
  H323TransportAddressArray interfaces;
  PINDEX arraySize = cfg.GetInteger(PString(InterfaceKey) + " Array Size");
  if (arraySize == 0)
    StartListener(defaultInterface);
  else {
    for (int i = 0; i < arraySize; i++)
      interfaces.Append(new H323TransportAddress(cfg.GetString(psprintf("%s %u", InterfaceKey, i+1), "")));
    StartListeners(interfaces);
  }
  rsrc->Add(new PHTTPFieldArray(new PHTTPStringField(cfg.GetDefaultSection() + "\\" + InterfaceKey, InterfaceKey, 20, defaultInterface), FALSE));

  if (listeners.IsEmpty()) {
    PSYSTEMLOG(Fatal, "Main\tCould not open H.323 Listener");
  }

  /*
  if (args.GetOptionString('q').IsEmpty()) {
    endpoint.behind_masq = FALSE;
  } else {
    endpoint.masqAddressPtr = new PIPSocket::Address(args.GetOptionString('q'));
    endpoint.behind_masq = TRUE;
    cout << "Masquerading as address " << *(endpoint.masqAddressPtr) << endl;
  }
  */
    
  AddAllCapabilities(0, 0, "*");
    
  // disable codecs as required
  PString disableCodecs = cfg.GetString(DisableCodecsKey);
  rsrc->Add(new PHTTPStringField(DisableCodecsKey, 50, disableCodecs));
  if (!disableCodecs.IsEmpty()) {
    PStringArray toRemove = disableCodecs.Tokenise(' ', FALSE);
    capabilities.Remove(toRemove);
  }

#ifndef NO_MCU_VIDEO
  EnableVideoReception(args.HasOption('v'));

  int videoTxQual = 10;
  if (args.HasOption("videotxquality")) 
      videoTxQual = args.GetOptionString("videotxquality").AsInteger();
  endpoint.videoTxQuality = PMAX(1, PMIN(31, videoTxQual));

  int videoF = 2;
  if (args.HasOption("videofill")) 
    videoF = args.GetOptionString("videofill").AsInteger();
  endpoint.videoFill = PMAX(1, PMIN(99, videoF));

  int videoFPS = 10;
  if (args.HasOption("videotxfps")) 
    videoFPS = args.GetOptionString("videotxfps").AsInteger();
  endpoint.videoFramesPS = PMAX(1,PMIN(30,videoFPS));

  int videoBitRate = 0; //disable setting videoBitRate.
  if (args.HasOption("videobitrate")) {
    videoBitRate = args.GetOptionString("videobitrate").AsInteger();
    videoBitRate = 1024 * PMAX(16, PMIN(2048, videoBitRate));
  }
  endpoint.videoBitRate = videoBitRate;

  if (args.HasOption('v')) {
#if H323_AVCODEC
    PTRACE(3, "Video bitrate for ffmpeg h263 is " << videoBitRate);
    if (endpoint.videoLarge) 
      endpoint.SetCapability(0, 1, new H323_FFH263Capability(0, 0, 1, 0, 0, videoBitRate, videoFPS));
    endpoint.SetCapability(0, 1, new H323_FFH263Capability(0, 1, 0, 0, 0, videoBitRate, videoFPS));
#endif
#if H323_VICH263
    PTRACE(3, "Video bitrate for vic h263 is " << videoBitRate);
    if (endpoint.videoLarge) 
      endpoint.SetCapability(0, 1, new H323_H263Capability(0, 0, 1, 0, 0, videoBitRate, videoFPS));
    endpoint.SetCapability(0, 1, new H323_H263Capability(0, 1, 0, 0, 0, videoBitRate, videoFPS));
#endif
  }
#endif   /*End of test on NO_MCU_VIDEO */


  AddAllUserInputCapabilities(0, 2);

  switch (gkMode) {
    default:
    case Gatekeeper_None:
      break;

    case Gatekeeper_Find:
      if (!DiscoverGatekeeper(new H323TransportUDP(*this)))
        PSYSTEMLOG(Error, "No gatekeeper found");
      break;

    case Gatekeeper_Explicit:
      if (!SetGatekeeper(gkName, new H323TransportUDP(*this)))
        PSYSTEMLOG(Error, "Error registering with gatekeeper at \"" << gkName << '"');
  }

  PTRACE(2, "MCU\tCodecs (in preference order):\n" << setprecision(2) << GetCapabilities());;
}

H323Connection * MyH323EndPoint::CreateConnection(unsigned callReference)
{
  return new OpenMCUH323Connection(*this, callReference);
}


void MyH323EndPoint::TranslateTCPAddress(PIPSocket::Address &localAddr, const PIPSocket::Address &remoteAddr)
{

//  if (this->behind_masq && 
//      (remoteAddr.Byte1() != 192)) {
//    localAddr = *(this->masqAddressPtr);
//  }
  return;
}

PString MyH323EndPoint::GetRoomStatus(const PString & block)
{
  PString substitution;
  PWaitAndSignal m(conferenceManager.GetConferenceListMutex());
  ConferenceList & conferenceList = conferenceManager.GetConferenceList();

  ConferenceList::iterator r;
  for (r = conferenceList.begin(); r != conferenceList.end(); ++r) {

    // make a copy of the repeating html chunk
    PString insert = block;
    PStringStream members;
    members << "<table border=1>"
               "<tr>"
                 "<th>"
                 "&nbsp;Name&nbsp;"
                 "</th><th>"
                 "&nbsp;Duration&nbsp;"
                 "</th><th>"
                 "&nbsp;Codec&nbsp;"
                 "</th><th>"
                 "&nbsp;Packets/Bytes tx&nbsp;"
                 "</th><th>"
                 "&nbsp;Packets/Bytes rx&nbsp;"
                 "</th></tr>";

    Conference & conference = *(r->second);
    size_t memberListSize = 0;
    {
      PWaitAndSignal m(conference.GetMutex());
      Conference::MemberList & memberList = conference.GetMemberList();
      memberListSize = memberList.size();
      Conference::MemberList::const_iterator s;
      for (s = memberList.begin(); s != memberList.end(); ++s) {
        if (PIsDescendant(s->second, H323Connection_ConferenceMember)) {
          OpenMCUH323Connection & conn = *((H323Connection_ConferenceMember *)(s->second))->GetConnection();
          PTime now;
          PTime callStart = conn.GetConnectionStartTime();
          RTP_Session * session = conn.GetSession(RTP_Session::DefaultAudioSessionID);
          members << "<tr>"
                     "<td>"
                  << conn.GetRemotePartyName()
                  << "</td><td>"   
                  << (now -callStart) << " mins"
                  << "</td><td>"   
                  << conn.GetAudioTransmitCodecName() << '/' << conn.GetAudioReceiveCodecName()
                  << "</td><td>"   
                  << session->GetPacketsSent() << '/' << session->GetOctetsSent() 
                  << "</td><td>"   
                  << session->GetPacketsReceived() << '/' << session->GetOctetsReceived()
                  << "</td></tr>";
        }
      }
    }
    members << "</table>";
    SpliceMacro(insert, "RoomName",        conference.GetNumber());
    SpliceMacro(insert, "RoomMemberCount", PString(PString::Unsigned, (long)memberListSize));
    SpliceMacro(insert, "RoomMembers",     members);
    substitution += insert;
  }

  return substitution;
}


Conference * MyH323EndPoint::ConferenceRequest(H323Connection & connection, 
                                          const H323SignalPDU & setupPDU,
                           H323Connection::AnswerCallResponse & response)
{
  const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body;

  // get the conference ID from the incoming call
  OpalGloballyUniqueID conferenceID = setup.m_conferenceID;

  // we accept creates and joins. Invites come later :)
  if (setup.m_conferenceGoal.GetTag() != H225_Setup_UUIE_conferenceGoal::e_create &&
      setup.m_conferenceGoal.GetTag() != H225_Setup_UUIE_conferenceGoal::e_join) {
    PTRACE(3, "MCU\tUnknown conference goal " << setup.m_conferenceGoal.GetTag());
    response = H323Connection::AnswerCallDenied;
    return NULL;
  }

  // see if the destination address or called party matches the name of a room
  PString userSpecifiedRoom;
  BOOL userSpecifiedRoomFound = FALSE;
  if (setup.m_destinationAddress.GetSize() > 0) {
    PINDEX i;
    for (i = 0; !userSpecifiedRoomFound && (i < setup.m_destinationAddress.GetSize()); i++) {
      PString roomNumber = H323GetAliasAddressString(setup.m_destinationAddress[i++]);
      if (userSpecifiedRoom.IsEmpty())
        userSpecifiedRoom = roomNumber;
      userSpecifiedRoomFound = conferenceManager.HasConference(roomNumber);
    }
    PString calledPartyNumber;
    if (!userSpecifiedRoomFound && 
        setupPDU.GetQ931().GetCalledPartyNumber(calledPartyNumber) && 
        !calledPartyNumber.IsEmpty()) {
      if (userSpecifiedRoom.IsEmpty())
        userSpecifiedRoom = calledPartyNumber;
      userSpecifiedRoomFound = conferenceManager.HasConference(calledPartyNumber);
    }
  }

  // join an existing conference if one matches the ID
  if (conferenceManager.HasConference(conferenceID)) {

    // there is a small chance that the conference has disappeared since the HasConference above
    // so always use a new valid room number when creating the conference
    Conference * conference = conferenceManager.MakeConference(conferenceID, OpenMCU::Current().GetNewRoomNumber(), "");
    PTRACE(3, "MCU\tJoining conference by ID " << conference->GetID());
    response = H323Connection::AnswerCallNow;
    return conference;
  }

  // if we are doing a join, then we joining by ID failed
  if (setup.m_conferenceGoal.GetTag() == H225_Setup_UUIE_conferenceGoal::e_join) {
    PTRACE(3, "MCU\tJoin request for conference with unknown ID " << conferenceID);
    response = H323Connection::AnswerCallDeniedByInvalidCID;
    return NULL;
  }

  // if a room exists with the called party name, use that room even though the conference IDs don't match
  if (userSpecifiedRoomFound) {
    Conference * conference = conferenceManager.MakeConference(userSpecifiedRoom, "");
    PTRACE(3, "MCU\tJoining conference " << conference->GetID() << " specified by number " << userSpecifiedRoom);
    response = H323Connection::AnswerCallNow;
    return conference;
  }
    
  // if no room exists with the called party name, then create a new room by that name and with the specified conference ID
  if (!userSpecifiedRoom.IsEmpty()) {
    Conference * conference = conferenceManager.MakeConference(conferenceID, userSpecifiedRoom, "");
    PTRACE(3, "MCU\tCreating new conference " << conference->GetID() << " with number " << userSpecifiedRoom);
    response = H323Connection::AnswerCallNow;
    return conference;
  }

  // determine if the endpoint supports conference lists
  // ver 2 clients are supposed to handle conference lists, but we know NM does not
  // and we know OpenH323 apps prior to 1.14 cannot handle lists either
  BOOL canDoConferenceList = connection.GetSignallingVersion() >= 2;
  PString appInfo = connection.GetRemoteApplication();
  if (appInfo.Find("NetMeeting") != P_MAX_INDEX)
    canDoConferenceList = FALSE;
  else if (appInfo.Find("OpenH323") != P_MAX_INDEX) {
    PINDEX pos = appInfo.Find("OpenH323 ");
    if (pos != P_MAX_INDEX) {
      PStringArray version = appInfo.Mid(pos+9).Tokenise('.');
      if (!(version.GetSize() == 3 &&
            version[0].AsUnsigned() >= 1 &&
            version[1].AsUnsigned() >= 14 &&
            version[2].AsUnsigned() >= 0))
        canDoConferenceList = FALSE;
    }
  }

#if 0
  // if no conference, and no default room, then send the terminal a conference list if it can understand it
  if (canDoConferenceList) {

    H323SignalPDU facilityPDU;
    H225_Facility_UUIE & pdu = *facilityPDU.BuildFacility(connection, FALSE);

    pdu.RemoveOptionalField(H225_Facility_UUIE::e_callIdentifier);
    pdu.RemoveOptionalField(H225_Facility_UUIE::e_multipleCalls);
    pdu.RemoveOptionalField(H225_Facility_UUIE::e_maintainConnection);

    pdu.m_reason = H225_FacilityReason::e_conferenceListChoice;
    pdu.IncludeOptionalField(H225_Facility_UUIE::e_conferences);

    H225_ArrayOf_ConferenceList & conferenceList = pdu.m_conferences;
    conferenceList.SetSize(0);

    {
      PWaitAndSignal m(conferenceManager.GetConferenceListMutex());
      ConferenceList & roomList = conferenceManager.GetConferenceList();
      ConferenceList::const_iterator r;
      PINDEX i = 0;
      for (r = roomList.begin(); r != roomList.end(); ++r) {
        Conference & conf = *(r->second);
        if (conf.IsVisible()) {
          conferenceList.SetSize(conferenceList.GetSize()+1);
          H225_ConferenceList & conference = conferenceList[i++];

          conference.IncludeOptionalField(H225_ConferenceList::e_conferenceID);
          conference.m_conferenceID = conf.GetID();

          conference.IncludeOptionalField(H225_ConferenceList::e_conferenceAlias);
          H323SetAliasAddress(conf.GetName(), conference.m_conferenceAlias, -1);
        }
      }
    }

    PTRACE(3, "MCU\tSending conference list");

    // if the PDU write fails, then close the call
    if (!connection.WriteSignalPDU(facilityPDU))
      response = H323Connection::AnswerCallDenied;
    else 
      response = H323Connection::AnswerCallDeferred;

    return NULL;
  }
#endif

  // if there is a room to create, then join this call to that conference
  PString roomToCreate = OpenMCU::Current().GetDefaultRoomName();
  if (!roomToCreate.IsEmpty()) {
    Conference * conference = conferenceManager.MakeConference(roomToCreate, "");
    PTRACE(3, "MCU\tCreating/joining call to default room " << roomToCreate);
    response = H323Connection::AnswerCallNow;
    return conference;
  }

  PTRACE(3, "MCU\tRefusing call because no room specified, and no default room");
  response = H323Connection::AnswerCallDenied;
  return NULL;
}

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

OpenMCUH323Connection::OpenMCUH323Connection(MyH323EndPoint & _ep, unsigned callReference)
  : H323Connection(_ep, callReference), ep(_ep), connected(FALSE)
{
  incomingAudio    = NULL;
  outgoingAudio    = NULL;
  conference       = NULL;
  conferenceMember = NULL;
  sendingAllowed   = TRUE;

  audioReceiveCodecName = audioTransmitCodecName = "none";

#ifndef NO_MCU_VIDEO
  incomingVideo = NULL;
  outgoingVideo = NULL;
  videoReceiveCodecName = videoTransmitCodecName = "none";
#endif

  connected = FALSE;

  cout << "Opening connection" << endl;
}

OpenMCUH323Connection::~OpenMCUH323Connection()
{
  cout << "Closing connection" << endl;
}

void OpenMCUH323Connection::CleanUpOnCallEnd()
{
  if (conference != NULL && conferenceMember != NULL) {
    LogCall();
    conferenceMember->WaitForClose();
    ConferenceMember * cmToDelete = conferenceMember;
    conferenceMember = NULL;
    if (conference->RemoveMember(cmToDelete))
      ep.GetConferenceManager().RemoveConference(conference->GetID());
    delete cmToDelete;
  }

  H323Connection::CleanUpOnCallEnd();
}

H323Connection::AnswerCallResponse OpenMCUH323Connection::OnAnswerCall(const PString & /*caller*/,
                                                                  const H323SignalPDU & setupPDU,
                                                                  H323SignalPDU & /*connectPDU*/)
{
  H323Connection::AnswerCallResponse response;
  conference = ep.ConferenceRequest(*this, setupPDU, response);

  if (conference == NULL)
    return response;

  connected = TRUE;
  conferenceMember = new H323Connection_ConferenceMember(this);
  conference->AddMember(conferenceMember);

  return AnswerCallNow;
}


BOOL OpenMCUH323Connection::OnSendSignalSetup( H323SignalPDU & callProceedingPDU)
{
  // We are making a connection to a remote EP so add this connection to
  // the list of rooms and members.
  // We will add them to the default room as we have no method of
  // specifying which room our connection should join.

  connected = TRUE;
  conference = NULL;
  PTRACE(3, "Conference\tOutgoing connection");

  return H323Connection::OnSendSignalSetup( callProceedingPDU );
}


BOOL OpenMCUH323Connection::OpenAudioChannel(BOOL isEncoding, unsigned /* bufferSize */, H323AudioCodec & codec)
{
  PString codecName= codec.GetMediaFormat();

  codec.SetSilenceDetectionMode( H323AudioCodec::NoSilenceDetection );

//  if (!codec.IsDescendant(H323_GSM0610Codec::Class()) &&
//      - need to add MS-GSM here along with any other codecs
//      !codec.IsDescendant(H323_muLawCodec::Class())) {
//    cerr << "Unknown codec \"" << codecName << endl;
//    return FALSE;
//  }

  if ((incomingAudio == NULL) && sendingAllowed) {
    incomingAudio = new IncomingAudio(ep, *this);
  }

  if (outgoingAudio == NULL) {
    outgoingAudio = new OutgoingAudio(ep, *this);
    PFilePath fn = OpenMCU::Current().GetConnectingWAVFile();
    PTRACE(4, "MCU\tOpening outgoing audio with file " << fn);
    playFile.Open(fn, PFile::ReadOnly);
  }

  if (isEncoding) {
    audioTransmitCodecName = codecName;
    codec.AttachChannel(outgoingAudio, TRUE);
  } else if (sendingAllowed) {
    audioReceiveCodecName = codecName;
    codec.AttachChannel(incomingAudio, TRUE);
  }

  return TRUE;
}

#ifndef NO_MCU_VIDEO
BOOL OpenMCUH323Connection::OpenVideoChannel(BOOL isEncoding,
                                        H323VideoCodec & codec)
{
    PStringStream codecName;
    codecName << codec;

    PWaitAndSignal mutex(videoMutex);

    if (isEncoding) {
      if (outgoingVideo == NULL) {
        outgoingVideo = new OutgoingVideo(ep, *this, ep.videoFramesPS, ep.videoLarge);
        codec.AttachChannel(outgoingVideo,TRUE);
//       outgoingVideo->SetFrameSize(352>>1,288>>1);
      }

      /*At last. Modularity. The video codec is told the parameters of video compresion/decompression.
        The only thing the video codec knows about the ouside world is how to acquire/render data, which
	      is via the video channel, provided by the OutgoingVideo class.
	      
	      The codec does provide a second interface, through which ethernet packets enter (or leave) */
      videoTransmitCodecName = codecName;

      codec.SetTxQualityLevel(ep.videoTxQuality);
      codec.SetBackgroundFill(ep.videoFill);
      if (ep.videoBitRate != 0)
        codec.SetMaxBitRate(ep.videoBitRate);

    } else {
      if ((incomingVideo == NULL) && sendingAllowed)
        incomingVideo = new IncomingVideo(ep, *this);
        codec.AttachChannel(incomingVideo,TRUE);
        videoReceiveCodecName = codecName;
    }
  
    return TRUE;
}
#endif

void OpenMCUH323Connection::OnUserInputString(const PString & str)
{
  if (conferenceMember != NULL)
    conferenceMember->SendUserInputIndication(str);
}


BOOL OpenMCUH323Connection::OnIncomingAudio(const void * buffer, PINDEX amount)
{
  if (conferenceMember != NULL)
    conferenceMember->WriteAudio(buffer, amount);

  return TRUE;
}

BOOL OpenMCUH323Connection::OnOutgoingAudio(void * buffer, PINDEX amount)
{
  if (playFile.IsOpen()) {
    if (!playFile.Read(buffer, amount)) {
      PTRACE(4, "MCU\tFinished playing file");
      playFile.Close();
    } else {
      int len = playFile.GetLastReadCount();
      if (len < amount) {
        memset(((BYTE *)buffer)+len, 0, amount-len);
      }
      return TRUE;
    }
  }

  if (conferenceMember != NULL)
    conferenceMember->ReadAudio(buffer, amount);

  return TRUE;
}

#ifndef NO_MCU_VIDEO

BOOL OpenMCUH323Connection::OnOutgoingVideo(void * buffer, PINDEX & amount)
{
  peerList.OnOutgoingVideo(buffer, amount);
} 

BOOL OpenMCUH323Connection::OnIncomingVideo(const void * buffer, PINDEX amount)
{
  return conference->OnIncomingVideo(buffer, amount);
}

#endif

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

void OpenMCUH323Connection::LogCall(const BOOL accepted)
{
  H323TransportAddress address = GetControlChannel().GetRemoteAddress();
  PIPSocket::Address ip;
  WORD port;
  PStringStream stringStream, timeStream;
  address.GetIpAndPort(ip, port);
  timeStream << GetConnectionStartTime().AsString("hh:mm:ss");
  stringStream << ' ' << "caller-ip:" << ip << ':' << port << ' '
	             << GetRemotePartyName() 
               << " room:" << ((conference != NULL) ? conference->GetNumber() : PString());

  if (accepted) {
    PStringStream connectionDuration;
    connectionDuration << setprecision(0) << setw(5) << (PTime() - GetConnectionStartTime());
    OpenMCU::Current().LogMessage(timeStream + stringStream	+ " connection duration:" + connectionDuration);
  }
  else 
    OpenMCU::Current().LogMessage(timeStream + " Call denied:" + stringStream);		
}

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

OutgoingAudio::OutgoingAudio(H323EndPoint & _ep, OpenMCUH323Connection & _conn)
  : ep(_ep), conn(_conn)
{
  os_handle = 0;
}

void OutgoingAudio::CreateSilence(void * buffer, PINDEX amount)
{
  memset(buffer, 0, amount);
  lastReadCount = amount;
}

BOOL OutgoingAudio::Read(void * buffer, PINDEX amount)
{
  PWaitAndSignal mutexR(audioChanMutex);
  
  if (!IsOpen())
    return FALSE;

  if (!delay.Delay(amount / 16)) {

    // do the read call here, by calling conn.OnOutgoingAudio():
    BOOL doSilence = !conn.OnOutgoingAudio(buffer, amount);

    if (doSilence)
      CreateSilence(buffer, amount);
  }

  lastReadCount = amount;

  return TRUE;
}

BOOL OutgoingAudio::Close()
{
  if (!IsOpen()) 
    return FALSE;

  PWaitAndSignal mutexC(audioChanMutex);
  os_handle = -1;

  return TRUE;
}

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

IncomingAudio::IncomingAudio(H323EndPoint & _ep, OpenMCUH323Connection & _conn)
  : ep(_ep), conn(_conn)
{
  os_handle = 0;
}

BOOL IncomingAudio::Write(const void * buffer, PINDEX amount)
{
  PWaitAndSignal mutexW(audioChanMutex);
  
  if (!IsOpen())
    return FALSE;

  if (!delay.Delay(amount / 16))
    conn.OnIncomingAudio(buffer, amount);

  return TRUE;
}

BOOL IncomingAudio::Close()
{
  if (!IsOpen())
    return FALSE;

  PWaitAndSignal mutexA(audioChanMutex);
  os_handle = -1;

  return TRUE;
}

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


// End of File ///////////////////////////////////////////////////////////////


syntax highlighted by Code2HTML, v. 0.9.1