/*
 * main.cxx
 *
 * Main source for for OpenAM
 *
 * A H.323 answering machine application.
 *
 * Copyright (c) 1993-2001 Equivalence Pty. Ltd.
 *
 * 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 are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): ______________________________________.
 *
 * $Log: main.cxx,v $
 * Revision 1.93  2005/02/03 01:21:42  csoutheren
 * Added called party to call log and command parameters
 *
 * Revision 1.92  2004/05/26 04:01:57  csoutheren
 * Changed to work completely with new plugin codecs
 *
 * Revision 1.91  2004/05/10 13:07:19  rjongbloed
 * Changed G.726 and MS-ADPCM to plug in codecs.
 *
 * Revision 1.90  2004/05/04 12:21:16  rjongbloed
 * Converted LPC-10 codec to plug in.
 *
 * Revision 1.89  2004/05/03 13:25:46  rjongbloed
 * Converted everything to be codec plug in freindly
 * Removed GSM and G.729 as now plug ins are "the way"!
 *
 * Revision 1.88  2004/04/06 11:27:49  rjongbloed
 * Changes to support native C++ Run Time Type Information
 * Changes for codec plug ins
 *
 * Revision 1.87  2004/01/31 07:38:21  rjongbloed
 * Changed HAS_IXJ usage to if defined not if non-zero
 *
 * Revision 1.86  2004/01/02 02:52:10  csoutheren
 * Thanks to Michal Zygmuntowicz for these changes
 *   Added support for iLBC codec
 *   Added ability to specify a gatekeeper password,
 *   Fixed small bug with Speex capabilities being accidentally included.
 *
 * Revision 1.85  2003/12/02 09:46:59  csoutheren
 * Added --loop option thanks to Jan Willamowius
 *
 * Revision 1.84  2002/11/13 10:23:12  rogerh
 * Enable Speex support by default.
 *
 * Revision 1.83  2002/11/10 08:12:42  robertj
 * Moved constants for "well known" ports to better place (OPAL change).
 *
 * Revision 1.82  2002/08/21 08:48:09  rogerh
 * Fix some problems caused by the user specif OGM changes
 *
 * Revision 1.81  2002/08/21 06:55:09  rogerh
 * Support all 5 Speex bitrates.
 *
 * Revision 1.80  2002/08/16 02:04:30  craigs
 * Ensure compilation without Speex installed
 *
 * Revision 1.79  2002/08/15 18:52:49  rogerh
 * Add support for one of the Speex codecs
 *
 * Revision 1.78  2002/08/05 10:06:06  robertj
 * Changed to use the version of G.7231. file capability/codec as used in
 *   opalvxml module. Prevents duplicate symbol link errors.
 *
 * Revision 1.77  2002/06/28 00:30:41  robertj
 * Fixed Clone() for G.723 file capability, must clone contents!
 *
 * Revision 1.76  2002/05/08 03:15:35  robertj
 * Removed unecessary initialisation of frameBuffer PBYTEArray
 *
 * Revision 1.75  2002/04/01 14:45:43  craigs
 * Fixed flag to end call after OGM
 *
 * Revision 1.74  2002/04/01 13:16:09  craigs
 * Added ability to kill calls at end of playing OGM
 * Added ability to play different OGMs depending on target DN
 *
 * Revision 1.73  2002/02/21 07:21:52  rogerh
 * Change the outgoing audio's FrameDelay() code to use the PAdaptiveDelay
 * class which should give clearer audio for the OGMs.
 *
 * Revision 1.72  2002/02/04 13:23:24  rogerh
 * Change the silence frame back to a 4 byte SID. It caused Quicnet cards
 * to play a loud noise instead of silence.
 *
 * Revision 1.71  2002/01/31 16:12:26  rogerh
 * Clear the end of the frame buffer if we cannot read a full frame.
 *
 * Revision 1.70  2002/01/26 09:40:03  rogerh
 * Make OpenAM send out G.723.1 OGM audio files correctly.
 *
 * Revision 1.69  2002/01/25 11:50:10  rogerh
 * dd --port option to play and record commands so you can use a
 * telephone on the POTS port or use speakers and microphone.
 *
 * Revision 1.68  2002/01/25 08:58:46  rogerh
 * Send out silence using a standard 24 byte frame instead of a 4 byte SID.
 * This is a workaround to support an end point which does not understand SID.
 *
 * Revision 1.67  2002/01/22 04:15:04  craigs
 * Updated for move of PWavFile to PTCLib
 * Replaced AudioDelay with PAdaptiveDelay
 *
 * Revision 1.66  2002/01/11 16:07:15  rogerh
 * Make PCMExt non static as it is used externally in cmds.cxx
 *
 * Revision 1.65  2001/11/18 23:07:36  craigs
 * Fixed problem whereby recording could start when queue of OGMs was not empty
 * Thanks to Frank Derks
 *
 * Revision 1.64  2001/10/24 10:45:33  rogerh
 * replace 4 byte SID frames with 24 byte frames of silence. Windows Media
 * Player's codec cannot handle SID frames.
 *
 * Revision 1.63  2001/10/24 10:28:59  rogerh
 * Make -m option find g7231 wav files.
 * Fix bug where record file format was set by the outgoing codec type.
 * (fixes bug when NetMeeting decides to receive G.723.1 and send MS-GSM)
 *
 * Revision 1.62  2001/10/24 09:01:07  rogerh
 * Fix a mistake in the 24 byte frames used for silence.
 *
 * Revision 1.61  2001/10/16 12:28:43  rogerh
 * Add support for saving g.723.1 audio data into a g.723.1 WAV File
 *
 * Revision 1.60  2001/10/15 14:55:03  rogerh
 * Add error message
 *
 * Revision 1.59  2001/10/15 14:20:10  rogerh
 * Add support for reading G.723.1 WAV files for the G.723.1 codec.
 * Recorded messages are still saved out in raw .g723 files.
 *
 * Revision 1.58  2001/10/15 07:09:38  rogerh
 * New sox homepage
 *
 * Revision 1.57  2001/10/14 08:22:54  rogerh
 * Record the silence in G.723.1 connections to the record file.
 * This also gives a proper fix for the 100% CPU utilisation bug.
 *
 * Revision 1.56  2001/10/13 07:13:18  rogerh
 * Add a 5ms sleep if the length of the G.723.1 audio data is zero.
 * This hack stops the 100% CPU usage seen when recording G.723.1 files.
 * Submitted by Maurizio Beni <m.beni@deimositalia.com>
 *
 * Revision 1.55  2001/10/04 23:55:00  craigs
 * Added deletion of file in PCM_RecordFile destructor thanks to Patrick Koorevaar
 *
 * Revision 1.54  2001/10/02 11:02:31  rogerh
 * Use the audio delay class when writing G723.1 audio files.
 * Add a quick hack.Set the frameBuffer to 1024 so OpenAM tworks on Unix boxes
 *
 * Revision 1.53  2001/09/29 07:11:48  rogerh
 * Delete ogmChanel in destructor - Patrick Koorevaar <pkoorevaar@hotmail.com>
 * Only call conn.ClearCall() once after we pass the record time limit.
 * Reported by Patrick.
 *
 * Revision 1.52  2001/09/28 00:14:30  robertj
 * Changed BYTE* to PBYTEArray so get automatic memory management.
 * Fixed redundant entries in argument parsing, thanks Patrick Koorevaar
 *
 * Revision 1.51  2001/09/24 22:39:42  craigs
 * Added commands to play and record data files, esp G.723.1
 *
 * Revision 1.50  2001/08/24 14:04:29  rogerh
 * Delete the listener if StartListener() fails.
 *
 * Revision 1.49  2001/08/13 00:01:15  robertj
 * Fixed #ifdef for memory check code to use PMEMORY_CHECK and not _DEBUG
 *
 * Revision 1.48  2001/07/23 09:17:36  rogerh
 * Add the LPC10 codec, a low quality and low bitrate codec.
 *
 * Revision 1.47  2001/07/23 04:01:10  rogerh
 * remove debugging info
 *
 * Revision 1.46  2001/07/20 03:36:58  robertj
 * Minor cosmetic changes to new PWAVFile class.
 *
 * Revision 1.45  2001/07/19 10:05:27  rogerh
 * PWAVFile is now part of the standard PWLib.
 *
 * Revision 1.44  2001/07/17 14:33:01  rogerh
 * Support writing of .wav audio files from PCM codecs (eg G711 and GSM).
 * .wav files are written out by default now. If you still want to record
 * to raw audio files with a .sw extension, use the --recordraw option.
 *
 * Revision 1.43  2001/07/17 12:02:37  rogerh
 * Change title, OpenAm -> OpenAM
 *
 * Revision 1.42  2001/07/14 07:44:29  rogerh
 * Add .wav file support to the OGM.
 * The -m option now looks for .wav and then .sw file extensions.
 * .wav file must be 16 bit mono at 8000 Hz
 *
 * Revision 1.41  2001/07/11 15:47:55  rogerh
 * Add G711 A-Law codec, reported by Niels Svennekjær <linux@post.tele.dk>
 *
 * Revision 1.40  2001/07/01 07:38:57  rogerh
 * Add Microsoft GSM codec. Also allocate memory for frameBuffer dynamically
 * as different codecs can have different frame sizes.
 *
 * Revision 1.39  2001/06/29 11:13:15  rogerh
 * Add AudioDelay class which removes the jitter in recorded files.
 *
 * Revision 1.38  2001/06/29 09:01:17  rogerh
 * Put back a line accidentally deleted in the last commit
 *
 * Revision 1.37  2001/06/29 06:34:57  rogerh
 * Add mutex locks in PCM_Recordfile. This solves the race condition where
 * Close() was called while Write() was still running.
 *
 * Revision 1.36  2001/04/27 07:08:46  robertj
 * Fixed 100% Cip problem, thanks APinaev@microtest.ru
 *
 * Revision 1.35  2001/03/20 23:42:55  robertj
 * Used the new PTrace::Initialise function for starting trace code.
 *
 * Revision 1.34  2001/01/25 07:27:14  robertj
 * Major changes to add more flexible OpalMediaFormat class to normalise
 *   all information about media types, especially codecs.
 *
 * Revision 1.33  2000/10/20 23:11:29  robertj
 * Fixed incorrect parameter parsing string that stopped -l from working, thanks Bruno BOSQUED
 *
 * Revision 1.32  2000/10/19 06:55:41  robertj
 * Fixed compiler crash by rearranging loop.
 *
 * Revision 1.31  2000/08/29 23:11:41  robertj
 * Fixed MSVC warnings.
 *
 * Revision 1.30  2000/08/29 12:32:07  craigs
 * Fixed problems with recording messages
 *
 * Revision 1.29  2000/08/28 16:42:59  craigs
 * Finally fixed problems with G.723.1. All codecs now working
 *
 * Revision 1.28  2000/08/28 09:13:54  robertj
 * Fixed MSVC compiler warnings.
 *
 * Revision 1.27  2000/08/28 07:49:26  craigs
 * New code to maybe get G.723.1 replaying working
 *
 * Revision 1.26  2000/08/28 00:38:37  craigs
 * Added support for setting listening port number
 *
 * Revision 1.25  2000/08/27 23:42:24  craigs
 * Fixed problem with playback of messages
 * Fixed problem with recording messages
 *
 * Revision 1.24  2000/06/20 02:38:27  robertj
 * Changed H323TransportAddress to default to IP.
 *
 * Revision 1.23  2000/06/17 09:14:52  robertj
 * Added setting of closed flag when closing OGM.
 *
 * Revision 1.22  2000/05/25 13:25:47  robertj
 * Fixed incorrect "save" parameter specification.
 *
 * Revision 1.21  2000/05/25 12:06:17  robertj
 * Added PConfigArgs class so can save program arguments to config files.
 *
 * Revision 1.20  2000/05/11 11:47:11  robertj
 * Fixed alpha linux GNU compiler problems.
 *
 * Revision 1.19  2000/05/10 05:14:25  robertj
 * Changed capabilities so has a function to get name of codec, instead of relying on PrintOn.
 *
 * Revision 1.18  2000/05/09 11:22:15  craigs
 * Fixed problems caused by new jitter buffer code
 * and fixed OGM problems
 *
 * Revision 1.17  2000/05/09 02:41:32  craigs
 * Added extra debugging, and fixed problems with OGM in non-IVR mode
 *
 * Revision 1.16  2000/04/26 03:18:38  craigs
 * Fixed problem when GSM specified as preferred codec
 *
 * Revision 1.15  2000/04/25 23:34:22  craigs
 * Added lots of new code, including outgoing and incoming
 * multiplexors, and the start of an IVR system
 *
 * Revision 1.14  2000/01/13 04:03:45  robertj
 * Added video transmission
 *
 * Revision 1.13  2000/01/07 08:28:09  robertj
 * Additions and changes to line interface device base class.
 *
 * Revision 1.12  1999/12/10 01:44:46  craigs
 * Added ability to set interface
 *
 * Revision 1.11  1999/12/01 04:38:25  robertj
 * Added gatekeeper support to OpenAM
 *
 * Revision 1.10  1999/11/11 00:27:49  robertj
 * Changed OnAnswerCall() call back function  to allow for asyncronous response.
 *
 * Revision 1.9  1999/11/06 13:27:48  craigs
 * Added extra output and changed for new library changes
 *
 * Revision 1.8  1999/10/29 10:57:04  robertj
 * Added answering machine project.
 *
 * Revision 1.7  1999/10/24 12:50:37  craigs
 * Fixed G723.1 capability, and added ability for discrete OGMs
 *
 * Revision 1.6  1999/10/24 08:24:56  craigs
 * Added GSM capability back in
 *
 * Revision 1.5  1999/10/24 08:19:58  craigs
 * Fixed problem that caused crash when unknown codecs used
 *
 * Revision 1.4  1999/10/24 03:29:07  craigs
 * Fixed problem with -h parsing
 *
 * Revision 1.3  1999/10/24 03:08:49  craigs
 * Fixed problem with recording zero length messages, and added autodelete of files
 *
 * Revision 1.2  1999/10/22 09:56:24  craigs
 * Fixed various compile warnings
 *
 * Revision 1.1  1999/10/11 00:15:18  craigs
 * Initial version
 *
 */

#include <ptlib.h>
#include <ptlib/pipechan.h>

#include "version.h"

#include "opalvxml.h"
#include "main.h"

PCREATE_PROCESS(OpenAm);

#define new PNEW

#define	DEFAULT_MSG_LIMIT	30
#define DEFAULT_CALL_LOG	"call_log.txt"

#define	G7231_SAMPLES_PER_BLOCK	240

#define CHECK_PCM   1
#define CHECK_G7231 2

#define	MENU_PREFIX		"UserMenu-"

static PMutex logMutex;
static PTextFile logFile;
static PFilePath logFilename = DEFAULT_CALL_LOG;

PString G7231Ext	= ".g723";
PString WAVExt          = ".wav";
PString PCMExt          = ".sw";

static void LogMessage(const PString & str)
{
  PTime now;
  PString msg = now.AsString("hh:mm:ss 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();
}

static void LogCall(const PFilePath & fn,
                    const PString & from,
                    const PString & user,
                    unsigned len,
                    const PString & codec,
                    const PString & product,
                    const PString & to)
{
  PString addr = from;
  LogMessage(addr & "\"" + user + "\"" & PString(PString::Unsigned, len) & codec & "\"" + product + "\"" & "\"" + fn + "\"" & "\"" + to + "\"");
}


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

OpenAm::OpenAm()
  : PProcess("OpenH323 Project", "OpenAM",
             MAJOR_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER)
{
}


OpenAm::~OpenAm()
{
}


void OpenAm::Main()
{
  cout << GetName()
       << " Version " << GetVersion(TRUE)
       << " by " << GetManufacturer()
       << " on " << GetOSClass() << ' ' << GetOSName()
       << " (" << GetOSVersion() << '-' << GetOSHardware() << ")\n\n";

  PConfigArgs args(GetArguments());

  args.Parse(
             "D-disable:"
             "d-directory:"
             "g-gatekeeper:"         "n-no-gatekeeper."
             "-g711-ulaw."           "-no-g711-ulaw."
             "-g711-alaw."           "-no-g711-alaw."
             "-g711message:"         "-no-g711message."
             "-g7231."               "-no-g7231."
             "-g7231message:"        "-no-g7231message."
      "-ilbc."                "-no-ilbc."
      "-ilbcmessage:"         "-no-ilbcmessage."
             "-gsm."                 "-no-gsm."
             "-gsmmessage:"          "-no-gsmmessage."
             "h-help."
             "H-hangup."             "-no-hangup."
             "i-interface:"          "-no-interface."
             "k-kill."               "-no-kill."
             "l-limit:"              "-no-limit."
             "-listenport:"          "-no-listenport."
             "-lpc10message:"        "-no-lpc10message."
             "-speexmessage:"        "-no-speexmessage."
             "m-message:"            "-no-message."
             "-no-recordg7231."
             "-loop."
#if PTRACING
             "o-output:"
#endif
             "P-prefer:"
             "-pcm."                 "-no-pcm."
             "-pcmmessage:"          "-no-pcmmessage."
             "-port:"
             "q-quicknet:"           "-no-quicknet:"
             "r-run:"                "-no-run."
	     "-recordraw."
             "-require-gatekeeper."  "-no-require-gatekeeper."
             "-save."
#if PMEMORY_CHECK
             "-setallocationbreakpoint:"
#endif
#if PTRACING
             "t-trace."
#endif
	     "u-username:"           "-no-username."
      "p-password:"
          , FALSE);

#if PMEMORY_CHECK
  if (args.HasOption("setallocationbreakpoint"))
    PMemoryHeap::SetAllocationBreakpoint(args.GetOptionString("setallocationbreakpoint").AsInteger());
#endif

#if PTRACING
  PTrace::Initialise(args.GetOptionCount('t'),
                     args.HasOption('o') ? (const char *)args.GetOptionString('o') : NULL);
#endif

  if (args.HasOption('h')) {
    cout << "Usage : " << GetName() << " [options]\n"
            "Options:\n"
            "  -d --directory dir  : Put recorded mesages into dir\n"
            "  -l --limit secs     : Limit recorded messages to secs duration (default " << DEFAULT_MSG_LIMIT << ")\n"
            "  -m --pcmmessage fn  : Set outgoing message for PCM derived codecs (G.711/GSM) to fn\n"
            "  --g7231message fn   : Set outgoing message for G723.1 codec to fn\n"
            "  --g711message fn    : Set outgoing message for G711 codec to fn\n"
            "  --gsmmessage fn     : Set outgoing message for GSM codec to fn\n"
            "  --lpc10message fn   : Set outgoing message for LPC10 codec to fn\n"
            "  --speexmessage fn   : Set outgoing message for Speex codec to fn\n"
     "  --ilbcmessage fn    : Set outgoing message for iLBC codec to fn\n"
            "  --loop              : loop message, no recording\n"

            "  --recordraw         : Record PCM audo in raw files (.sw) instead of .wav\n"
            "  -r --run cmd        : Run this command after each recorded message\n"
            "  -k --kill           : Kill recorded files after user command\n"
            "  -H --hangup         : hangup after playing message\n"
            "  -u --username str   : Set the local endpoint name to str\n"
     "  -p --password str   : Set the gatekeeper password to str\n"
            "  -i --interface ip   : Bind to a specific interface\n"
            "  --listenport port   : Listen on a specific port\n"
            "  -g --gatekeeper host: Specify gatekeeper host.\n"
            "  -n --no-gatekeeper  : Disable gatekeeper discovery.\n"
            "  --require-gatekeeper: Exit if gatekeeper discovery fails.\n"
            "  -D --disable codec  : Disable the specified codec (may be used multiple times)\n"
            "  -P --prefer codec   : Prefer the specified codec (may be used multiple times)\n"
#if PTRACING
            "  -t --trace          : Enable trace, use multiple times for more detail\n"
            "  -o --output         : File for trace output, default is stderr\n"
#endif
            "     --save           : Save arguments in configuration file\n"
            "  -h --help           : Display this help message\n";
    return;
  }

  args.Save("save");

#ifdef HAS_IXJ
  if (args.GetCount() > 0) {
    if (args[0] *= "record") 
      RecordFile(args);
    else if (args[0] *= "play") 
      PlayFile(args);
    else
      cerr << "unknown command \"" << args[0] << "\"" << endl;
    return;
  }
#endif

  unsigned callLimit = DEFAULT_MSG_LIMIT;
  if (args.HasOption('l')) {
    callLimit = args.GetOptionString('l').AsInteger();
    if (callLimit > 3600) {
      cout << "warning: maximum call length " << callLimit << " is out of range. Using " << DEFAULT_MSG_LIMIT << " instead\n";
      callLimit = DEFAULT_MSG_LIMIT;
    } else if (callLimit == 0)
      cout << "warning: recorded message call limit disabled\n";
  }
  cout << "Recorded messages limited to " << callLimit << " seconds\n";

  PString runCmd;
  if (args.HasOption('r')) {
    runCmd = args.GetOptionString('r');
    cout << "Executing \"" << runCmd << "\" after each message" << endl;
  }

  PDirectory dir;
  if (args.HasOption('d'))
    dir = args.GetOptionString('d');

  int flags = 0;

  if (args.HasOption("no-recordg7231")) {
    cout << "Supressing recording of G723.1 messages" << endl;
    flags |= MyH323EndPoint::NoRecordG7231;
  }
  if (args.HasOption('k')) {
    cout << "Deleting recorded files after processing" << endl;
    if (runCmd.IsEmpty()) 
      cout << "WARNING: recorded files will be deleted even though no run command is present" << endl;
    flags |= MyH323EndPoint::DeleteAfterRecord;
  }

  if (args.HasOption('H'))
    flags |= MyH323EndPoint::HangupAfterPlay;
  
  MyH323EndPoint endpoint(callLimit, runCmd, dir, flags);

  PString userName = "OpenH323 Answering Machine v" + GetVersion();
  if (args.HasOption('u'))
    userName = args.GetOptionString('u');
  endpoint.SetLocalUserName(userName);

  if (args.HasOption('p')) {
    const PString password = args.GetOptionString('p');
    endpoint.SetGatekeeperPassword(password);
  }
  
  if (!endpoint.Initialise(args))
    return;

  // start the H.323 listener
  H323ListenerTCP * listener;
  PIPSocket::Address interfaceAddress(INADDR_ANY);
  WORD listenPort = H323EndPoint::DefaultTcpPort;

  if (args.HasOption("listenport"))
    listenPort = (WORD)args.GetOptionString("listenport").AsInteger();

  if (args.HasOption('i'))
    interfaceAddress = PIPSocket::Address(args.GetOptionString('i'));

  listener  = new H323ListenerTCP(endpoint, interfaceAddress, listenPort);

  if (!endpoint.StartListener(listener)) {
    cout <<  "Could not open H.323 listener port on "
         << listener->GetListenerPort() << endl;
    delete listener;
    return;
  }

  if (args.HasOption('g')) {
    PString gkName = args.GetOptionString('g');
    if (endpoint.SetGatekeeper(gkName, new H323TransportUDP(endpoint)))
      cout << "Gatekeeper set: " << *endpoint.GetGatekeeper() << endl;
    else {
      cout << "Error registering with gatekeeper at \"" << gkName << '"' << endl;
      return;
    }
  }
  else if (!args.HasOption('n')) {
    cout << "Searching for gatekeeper..." << flush;
    if (endpoint.DiscoverGatekeeper(new H323TransportUDP(endpoint)))
      cout << "\nGatekeeper found: " << *endpoint.GetGatekeeper() << endl;
    else {
      cout << "\nNo gatekeeper found." << endl;
      if (args.HasOption("require-gatekeeper"))
        return;
    }
  }

  cout << "Waiting for incoming calls for \"" << endpoint.GetLocalUserName() << '"' << endl;

  for (;;) 
    PThread::Current()->Sleep(5000);
}


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


MyH323EndPoint::MyH323EndPoint(unsigned _callLimit,
                               const PString & _runCmd,
                               const PDirectory & _dir,
                               int   _flags)
  : callLimit(_callLimit), runCmd(_runCmd), dir(_dir), flags(_flags)
{
}

BOOL MyH323EndPoint::OnIncomingCall(H323Connection & _conn,
                                    const H323SignalPDU & setupPDU,
                                    H323SignalPDU &)
{
  MyH323Connection & conn = (MyH323Connection &)_conn;

  // see if incoming call is to a getway address
  PString number;
  if (setupPDU.GetDestinationE164(number)) 
    conn.SetE164Number(number);

  return TRUE;
}

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

BOOL MyH323EndPoint::Initialise(PConfigArgs & args)
{
  // format for record files, raw or wav
  if (args.HasOption("recordraw")) 
    SetRecordWav(FALSE);
  else
    SetRecordWav(TRUE);

  // get G723.1 OGM
  if (args.HasOption("g7231message"))
    g7231Ogm = args.GetOptionString("g7231message");
  else if (args.HasOption('m'))  {
    if (PFile::Exists(args.GetOptionString('m') + "_g7231" + WAVExt)) {
      g7231Ogm = args.GetOptionString('m') + "_g7231" + WAVExt;
    }
    else if (PFile::Exists(args.GetOptionString('m') + PCMExt)) {
      g7231Ogm = args.GetOptionString('m') + G7231Ext;
    }
  }

  if (!g7231Ogm.IsEmpty()) {
    // check if the file exists. (do not check if filename contains %s)
    if ((g7231Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(g7231Ogm)) {
      cout << "warning: cannot open G723.1 OGM file \"" << g7231Ogm << "\"" << endl;
      g7231Ogm = "";
    } 
  }

  if (g7231Ogm.IsEmpty()) 
    cout << "No G.723.1 outgoing message set\n";
  else {
    cout << "Using \"" << g7231Ogm << "\" as G.723.1 outgoing message\n";
  }


  // Get the OGM message for the 'PCM' codecs
  // Check if the file specified exists. If it does, use it.
  // If it does not exist, try with .wav and .sw extensions.
  if (args.HasOption("pcmmessage")) {
    pcmOgm = args.GetOptionString("pcmmessage");
  }
  else if (args.HasOption('m')) {
      if (PFile::Exists(args.GetOptionString('m'))) {
        pcmOgm = args.GetOptionString('m');
      }
      else if (PFile::Exists(args.GetOptionString('m') + WAVExt)) {
        pcmOgm = args.GetOptionString('m') + WAVExt;
      }
      else if (PFile::Exists(args.GetOptionString('m') + PCMExt)) {
        pcmOgm = args.GetOptionString('m') + PCMExt;
      }
  }

  if (args.HasOption("loop"))
    SetLoopMessage(TRUE);
  else
	SetLoopMessage(FALSE);


  // By default, use the pcmOgm for all the PCM codecs, but allow the user
  // to override them.
  gsmOgm   = pcmOgm;
  g711Ogm  = pcmOgm;
  lpc10Ogm = pcmOgm;
  speexOgm = pcmOgm;
  ilbcOgm  = pcmOgm;

  // We can set the filename for specific codecs.
  if (args.HasOption("gsmmessage")) 
    gsmOgm = args.GetOptionString("gsmmessage");

  if (args.HasOption("g711message")) 
    g711Ogm = args.GetOptionString("g711message");

  if (args.HasOption("lpc10message")) 
    lpc10Ogm = args.GetOptionString("lpc10message");

  if (args.HasOption("speexmessage")) 
    speexOgm = args.GetOptionString("speexmessage");

  if (args.HasOption("ilbcmessage")) 
    ilbcOgm = args.GetOptionString("ilbcmessage");
    
  // Check GSM OGM message
  if (!gsmOgm.IsEmpty()) {
    if ((gsmOgm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(gsmOgm)) {
      cout << "warning: cannot open GSM OGM file \"" << gsmOgm << "\"" << endl;
      gsmOgm = "";
    }
  } 
  if (gsmOgm.IsEmpty()) 
    cout << "No GSM outgoing message set\n";
  else {
    cout << "Using \"" << gsmOgm << "\" as GSM outgoing message\n";
  }

  // Check G.711 OGM message 
  if (!g711Ogm.IsEmpty()) {
    if ((g711Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(g711Ogm)) {
      cout << "warning: cannot open G711 OGM file \"" << g711Ogm << "\"" << endl;
      g711Ogm = "";
    }
  } 
  if (g711Ogm.IsEmpty()) 
    cout << "No G711 outgoing message set\n";
  else {
    cout << "Using \"" << g711Ogm << "\" as G.711 outgoing message\n";
  }

  // Check LPC10 OGM message 
  if (!lpc10Ogm.IsEmpty()) {
    if ((lpc10Ogm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(lpc10Ogm)) {
      cout << "warning: cannot open LPC10 OGM file \"" << lpc10Ogm << "\"" << endl;
      lpc10Ogm = "";
    }
  } 
  if (lpc10Ogm.IsEmpty()) 
    cout << "No LPC10 outgoing message set\n";
  else {
    cout << "Using \"" << lpc10Ogm << "\" as LPC10 outgoing message\n";
  }

  // Check Speex OGM message 
  if (!speexOgm.IsEmpty()) {
    // check if the file exists. (do not check if filename contains %s)
    if ((speexOgm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(speexOgm)) {
      cout << "warning: cannot open Speex OGM file \"" << speexOgm << "\"" << endl;
      speexOgm = "";
    }
  } 
  if (speexOgm.IsEmpty()) 
    cout << "No Speex outgoing message set\n";
  else {
    cout << "Using \"" << speexOgm << "\" as Speex outgoing message\n";
  }


  // Check iLBC OGM message 
  if (!ilbcOgm.IsEmpty()) {
    // check if the file exists. (do not check if filename contains %s)
    if ((ilbcOgm.Find("%s") == P_MAX_INDEX) && !PFile::Exists(ilbcOgm)) {
      cout << "warning: cannot open iLBC OGM file \"" << ilbcOgm << "\"" << endl;
      ilbcOgm = "";
    }
  } 
  if (ilbcOgm.IsEmpty()) 
    cout << "No iLBC outgoing message set\n";
  else {
    cout << "Using \"" << ilbcOgm << "\" as iLBC outgoing message\n";
  }
  if (g7231Ogm.IsEmpty() && gsmOgm.IsEmpty() && g711Ogm.IsEmpty()
                         && lpc10Ogm.IsEmpty() 
			 && speexOgm.IsEmpty()
			 ) {
    cerr << "Must specify at least one outgoing message" << endl;
    return FALSE;
  }

  AddAllCapabilities(0, 0, "*");

  PString removeString;
  if (gsmOgm.IsEmpty())
    removeString = removeString & OpalGSM0610 & "MS-GSM";

  if (speexOgm.IsEmpty())
    removeString = removeString & "Speex";
 
  if (g711Ogm.IsEmpty())
    removeString = removeString & "711";

  if (lpc10Ogm.IsEmpty())
    removeString = removeString & "LPC-10";

  if (ilbcOgm.IsEmpty())
    removeString = removeString & "iLBC";

  if (!g7231Ogm.IsEmpty())
    SetCapability(0, 0, new G7231_File_Capability);

  capabilities.Remove(args.GetOptionString('D').Lines());
  if (!removeString.IsEmpty())
    capabilities.Remove(removeString.Tokenise(' '));
  capabilities.Reorder(args.GetOptionString('P').Lines());

  cout << "Codecs (in preference order):\n" << setprecision(2) << capabilities << endl;

  return TRUE;
}

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

PCM_RecordFile::PCM_RecordFile(MyH323Connection & _conn, const PFilePath & _fn, unsigned _callLimit)
  : conn(_conn), fn(_fn), callLimit(_callLimit)
{
  recordStarted = FALSE;
  timeLimitExceeded = FALSE;
  closed        = FALSE;
  dataWritten   = FALSE;

  // If the file name ends in .wav then open the output as a WAV file.
  // Otherwise open it as a raw file.
  if ((_fn.Right(4)).ToLower() == ".wav")
    fileclass = new PWAVFile(_fn, PFile::WriteOnly,
			     PFile::ModeDefault,PWAVFile::PCM_WavFile);
  else
    fileclass = new PFile(_fn, PFile::WriteOnly);
}

void PCM_RecordFile::StartRecording()
{
  PWaitAndSignal mutex(pcmrecordMutex);

  if (recordStarted)
    return;

  PTRACE(1, "Starting recording to " << fn);

  PTime now;
  recordStarted = TRUE;
  finishTime = now + (callLimit * 1000);
}

BOOL PCM_RecordFile::Close()
{
  PWaitAndSignal mutex(pcmrecordMutex);

  closed = TRUE;
  return fileclass->Close();
}

BOOL PCM_RecordFile::Write(const void * buf, PINDEX len)
{
  // Wait for the mutex, and Signal it at the end of this function
  PWaitAndSignal mutex(pcmrecordMutex);

  // If the record file has been closed, or if the time limit has
  // been exceeded, then return immediatly.
  if (closed || timeLimitExceeded)
    return FALSE;

  if (!recordStarted) {
    DelayFrame(len);
    return TRUE;
  }

  PTime now;
  if ((callLimit != 0) && (now >= finishTime)) {
    PTRACE(1, "Terminating call due to timeout");
    conn.ClearCall();
    timeLimitExceeded = TRUE;
    return TRUE;
  }

  DelayFrame(len);

  dataWritten = TRUE;

  return WriteFrame(buf, len);
}

BOOL PCM_RecordFile::WriteFrame(const void * buf, PINDEX len)
{
  //cerr << "Writing PCM " << len << endl;
  return  fileclass->Write(buf, len);
}

void PCM_RecordFile::DelayFrame(PINDEX len)
{
  delay.Delay(len/16);
}

PCM_RecordFile::~PCM_RecordFile()
{
  PWaitAndSignal mutex(pcmrecordMutex);

  if (!dataWritten) {
    PTRACE(1, "Deleting " << fn << " as no data recorded");
    fileclass->Remove(fn);
  }

  delete fileclass;
}

///////////////////////////////////////////////////////////////
// Override some of the PCM_RecordFile functions to write
// G723.1 data instead of PCM data.

G7231_RecordFile::G7231_RecordFile(MyH323Connection & _conn, const PFilePath & _fn, unsigned _callLimit)
  : PCM_RecordFile(_conn, _fn, _callLimit)
{
  // If the record file is a .wav file, we need to close the file
  // that PCM_RecordFile will have opened, and reopen it as a G.723.1 Wav file.
  if ((_fn.Right(4)).ToLower() == ".wav") {
    fileclass->Remove(_fn);
    delete fileclass;
    fileclass = new PWAVFile(_fn, PFile::WriteOnly,
			     PFile::ModeDefault,PWAVFile::G7231_WavFile);
  }
}

BOOL G7231_RecordFile::WriteFrame(const void * buf, PINDEX /*len*/)
{
  int frameLen = G7231_File_Codec::GetFrameLen(*(BYTE *)buf);
//  cerr << "Writing G7231 " << frameLen << endl;
  return fileclass->Write(buf, frameLen);
}

void G7231_RecordFile::DelayFrame(PINDEX /*len*/)
{
  // Ignore the len parameter as that is the compressed size.
  // We must delay by the actual sample time.
  delay.Delay((G7231_SAMPLES_PER_BLOCK*2)/16);
}

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

static BOOL MatchString(const PString & str1, const PString str2)
{
  if (str1.GetLength() != str2.GetLength())
    return FALSE;

  PINDEX len = str1.GetLength();

  PINDEX i;
  for (i = 0; i < len; i++) 
    if ((str1[i] != '?') && (str2[i] != '?') && (str1[i] != str2[i]))
      return FALSE;

  return TRUE;
}

static PINDEX FindMatch(const PStringList & list, const PString & key)
{
  PINDEX maxKeyLen = 0;
  PINDEX i;

  PINDEX keyLen = key.GetLength();
  PINDEX listLen = list.GetSize();

  for (i = 0; i < listLen; i++)
    maxKeyLen = PMAX(maxKeyLen, list[i].GetLength());

  if (keyLen == 0 || maxKeyLen == 0)
    return P_MAX_INDEX;

  if (keyLen > maxKeyLen)
    return P_MAX_INDEX;

  PINDEX len = 1;
  while (len <= keyLen) {
    PString subStr = key.Left(len);

    PINDEX matches = 0;
    PINDEX lastMatch = P_MAX_INDEX;
    PINDEX i;

    // look for a match to the substring
    for (i = 0; i < list.GetSize(); i++) {
      if ((list[i].GetLength() >= keyLen) && MatchString(list[i].Left(len), subStr)) {
        matches++;
        lastMatch = i;
      }
    }

    // if we got ONE match, we have a winner
    if (matches == 1)
      return lastMatch+1;

    // if we have no matches, then there is no point continuing
    if (matches == 0)
      return P_MAX_INDEX;

    // if we have more than one match, try the next char
    len++;
  }

  // too many matches
  return 0;
}


MyH323Connection::MyH323Connection(MyH323EndPoint & _ep, unsigned callReference)
  : H323Connection(_ep, callReference), ep(_ep)
{
  basename = psprintf("%04i%02i%02i_%02i%02i%02i", callStartTime.GetYear(), callStartTime.GetMonth(),  callStartTime.GetDay(),
                                                   callStartTime.GetHour(), callStartTime.GetMinute(), callStartTime.GetSecond());
  recordFile = NULL;
  ogmChannel = NULL;

  receiveCodecName = transmitCodecName = "none";

  cout << "Opening connection" << endl;

  currentMenu = 0;
  digits = "";

  PConfig config;
  PStringList sections = config.GetSections();
  PINDEX i;
  for (i = 0; i < sections.GetSize(); i++) {
    if (sections[i].Find(MENU_PREFIX) == 0) 
      menuNames.AppendString(sections[i]);
  }

}

BOOL MyH323Connection::OnReceivedSignalSetup(const H323SignalPDU & setupPDU)
{
  if (!H323Connection::OnReceivedSignalSetup(setupPDU))
    return FALSE;

  // extract the called number
  const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body;
  if (setup.HasOptionalField(H225_Setup_UUIE::e_destinationAddress) && setup.m_destinationAddress.GetSize() > 0) {
    calledParty = H323GetAliasAddressString(setup.m_destinationAddress[0]);
  }

  return TRUE;
}

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

  PTime now;
  PTimeInterval interval = now - recordStartTime;
  PString addr = GetControlChannel().GetRemoteAddress();

  PString codecStr = receiveCodecName + "/" + transmitCodecName;
  unsigned duration = (unsigned)((interval.GetMilliSeconds()+999)/1000);

  LogCall(recordFn, addr, GetRemotePartyName(), duration, codecStr, product, calledParty);

  if ((recordFile!= NULL) && (recordFile->WasRecordStarted()) && !ep.GetRunCmd().IsEmpty()) {
    PString cmdStr = ep.GetRunCmd() &
                     recordFn &
                     "'" + addr + "'" &
                     "\"" + GetRemotePartyName() + "\"" &
                     PString(PString::Unsigned, duration) &
                     "\"" + codecStr + "\"" &
                     "\"" + product + "\"" &
                     "\"" + calledParty + "\"";
    PTRACE(1, "Executing : " << cmdStr);
    system((const char *)cmdStr);
  } else {
    PTRACE(1, "No action to perform at end of record");
  }

  if (ogmChannel != NULL)
    delete ogmChannel;

  if (recordFile != NULL)
    delete recordFile;

  if (ep.GetDeleteAfterRecord()) {
    PTRACE(1, "Removing " << recordFn << " as requested by option");
    PFile::Remove(recordFn);
  }
}

H323Connection::AnswerCallResponse
     MyH323Connection::OnAnswerCall(const PString & caller,
                                    const H323SignalPDU & setupPDU,
                                    H323SignalPDU & /*connectPDU*/)
{
  product = "Unknown";

  const H225_Setup_UUIE & setup = setupPDU.m_h323_uu_pdu.m_h323_message_body;
  const H225_EndpointType & epInfo = setup.m_sourceInfo;

  if (epInfo.HasOptionalField(H225_EndpointType::e_vendor)) {
    const H225_VendorIdentifier & vendorInfo = epInfo.m_vendor;
    if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_productId))
      product = vendorInfo.m_productId.AsString();
    if (vendorInfo.HasOptionalField(H225_VendorIdentifier::e_versionId))
      product = product + "/" + vendorInfo.m_versionId.AsString();
  }
  
  cout << "Accepting call from " << caller << " using " << product << endl;

  return AnswerCallNow;
}

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

  PString ogm;
  BOOL isPCM = FALSE;

  if (codecName == OPAL_G7231_6k3) {
    ogm   = ep.GetG7231OGM();
    isPCM = FALSE;
  } else {
    static OpalMediaFormat::List list = H323PluginCodecManager::GetMediaFormats();
    if (list.GetValuesIndex(codecName) == P_MAX_INDEX) {
      cerr << "Unknown codec \"" << codecName << endl;
      return FALSE;
    }
    isPCM = TRUE;
    if (codecName == OPAL_G711_ULAW_64K || codecName == OPAL_G711_ALAW_64K)
      ogm = ep.GetG711OGM();
    else if (codecName == OPAL_GSM0610)
      ogm = ep.GetGSMOGM();
    else if (codecName.Find("iLBC") != P_MAX_INDEX)
      ogm = ep.GetiLBCOGM();
    else if (codecName.Find("Speex") != P_MAX_INDEX)
      ogm = ep.GetSPEEXOGM();
    else
      ogm = ep.GetG711OGM();
  }

  PWaitAndSignal mutex(connMutex);

  if ((recordFile == NULL) && (isEncoding == FALSE)) {
    if (isPCM) {
      if (ep.GetRecordWav() == TRUE)
	      recordFn = ep.GetDirectory() + (basename + ".wav");
      else
	      recordFn = ep.GetDirectory() + (basename + ".sw");
      recordFile = new PCM_RecordFile  (*this, recordFn, ep.GetCallLimit());
    } else {
      if (ep.GetRecordWav() == TRUE)
	      recordFn = ep.GetDirectory() + (basename + ".wav");
      else
	      recordFn = ep.GetDirectory() + (basename + ".g723");
      recordFile = new G7231_RecordFile(*this, recordFn, ep.GetCallLimit());
    }
  }

  if ((ogmChannel == NULL) && (isEncoding == TRUE)) {
    if (isPCM)
      ogmChannel = new PCM_OGMChannel(*this);
    else
      ogmChannel = new G7231_OGMChannel(*this);
  }

  if (isEncoding) {

    if (ep.GetHangupAfterPlay())
      ogmChannel->SetPlayOnce();

	if (ep.GetLoopMessage())
	  ogmChannel->SetLoopMessage();

    if (ogm.Find("%s"))
      ogm.Replace("%s", e164Number);

    transmitCodecName = codecName;
    if (!StartMenu(0)) {
      if (!PFile::Exists(ogm)) {
        cerr << "error: cannot find OGM \"" << ogm << "\"" << endl;
        return FALSE;
      }
      else
        ogmChannel->QueueFile(ogm);
      if (!ep.GetNoRecordG7231())
        ogmChannel->SetRecordTrigger();
    }
    codec.AttachChannel(ogmChannel, FALSE);
  } else {
    receiveCodecName = codecName;
    codec.AttachChannel(recordFile, FALSE);
  }

  return TRUE;
}

BOOL MyH323Connection::OnStartLogicalChannel(H323Channel & channel)
{
  if (!H323Connection::OnStartLogicalChannel(channel))
    return FALSE;

  cout << "Started logical channel: ";

  switch (channel.GetDirection()) {
    case H323Channel::IsTransmitter :
      cout << "sending ";
      break;

    case H323Channel::IsReceiver :
      cout << "receiving ";
      break;

    default :
      break;
  }

  cout << channel.GetCapability() << endl;

  return TRUE;
}

void MyH323Connection::StartRecording()
{
  recordFile->StartRecording();
}

void MyH323Connection::OnUserInputString(const PString & value)
{
  PINDEX i;
  for (i = 0; i < value.GetLength(); i++) {
    OnUserInputChar(value[i]);
  }
}

BOOL MyH323Connection::StartMenu(int menuNumber)
{
  digits = "";
  currentMenu = menuNumber;
  PString menuName = psprintf("%s%i", MENU_PREFIX, menuNumber);
  if (menuNames.GetStringsIndex(menuName) == P_MAX_INDEX) 
    return FALSE;

  PTRACE(1, "Starting menu " << menuNumber);

  PConfig menu(menuName);
  PString startCmd = menu.GetString("start");

  if (!startCmd.IsEmpty())
    ProcessMenuCmd(startCmd);

  return TRUE;
}

BOOL MyH323Connection::ProcessMenuCmd(const PString & cmdStr)
{
  PTRACE(1, "Processing menu cmd " << cmdStr);
  PStringArray tokens = cmdStr.Tokenise(" ", FALSE);
  int len = tokens.GetSize();
  if (len == 0)
    return TRUE;

  PString cmd = tokens[0];

  if ((len >= 2) && (cmd *= "play")) {
    ogmChannel->QueueFile(tokens[1]);
    if (len > 2) {
      cmd = "menu";
      tokens[1] = tokens[2];
    }
  }

  if ((len >= 2) && (cmd *= "menu")) {
    int newMenu = tokens[1].AsInteger();
    if (newMenu != currentMenu)
      StartMenu(newMenu);
  }

  else if (cmd *= "hangup")
    ogmChannel->SetHangupTrigger();

  else if (cmd *= "record")
    ogmChannel->SetRecordTrigger();

  return TRUE;
}

void MyH323Connection::OnUserInputChar(char ch)
{
  if (ch == '#') 
    digits += '$';
  else 
    digits += ch;

  PTRACE(1, "Processing digit string " << digits);

  ogmChannel->FlushQueue();

  PString menuName = psprintf("%s%i", MENU_PREFIX, currentMenu);
  if (menuNames.GetStringsIndex(menuName) == P_MAX_INDEX) {
    PTRACE(1, "Cannot find menu " << menuName);
    StartMenu(0);
    return;
  }

  PConfig menu(menuName);
  PStringList keys = menu.GetKeys();
  PINDEX keyMatch = FindMatch(keys, digits);

  // if key is still ambiguous, then keep collecting
  if (keyMatch == 0)
    return;

  PString cmd;
  if (keyMatch != P_MAX_INDEX) {
    PString key = keys[keyMatch-1];
    PTRACE(1, "Executing cmd for key " << key);
    cmd = menu.GetString(key);
  } else {
    PTRACE(1, "Cannot match cmd " << digits << " in menu " << menuName);
    cmd = menu.GetString("error", "menu 0");
  } 

  if (!cmd.IsEmpty()) {
    ProcessMenuCmd(cmd);
    digits = "";
  }
}

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

BOOL CheckWAVFileValid(PWAVFile *chan, int type) {
  // Check the wave file header
  if (!chan->IsValid()) {
    PTRACE(1, chan->GetName() << " wav file header invalid");
    return FALSE;
  }

  // Check the wave file format
  if ( (type == CHECK_PCM) && (chan->GetFormat() != 0x01) ){
    PTRACE(1, chan->GetName() << " is not a PCM format wav file");
    PTRACE(1, "It is format " << chan->GetFormat() );
    return FALSE;
  }

  if ( (type == CHECK_G7231) && 
       ((chan->GetFormat() != 0x42) && (chan->GetFormat() != 0x111)) ){
    PTRACE(1, chan->GetName() << " is not a G.723.1 format wav file");
    PTRACE(1, "It is format " << chan->GetFormat() );
    return FALSE;
  }

  // Check the sample rate for PCM wave files
  if ( (type == CHECK_PCM) &&
       ( (chan->GetSampleRate() != 8000)
       ||(chan->GetChannels() != 1)
       ||(chan->GetSampleSize() != 16) )
     ) {
    PTRACE(1, chan->GetName() << " is not a 16 Bit, Mono, 8000 Hz (8Khz) PCM wav file");
    PTRACE(1, "It is " << chan->GetSampleSize() << " bits, "
                       << (chan->GetChannels()==1 ? "mono " : "stereo ")
                       << chan->GetSampleRate() << " Hz");
    return FALSE;
  }

  return TRUE;
}

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

PCM_OGMChannel::PCM_OGMChannel(MyH323Connection & _conn)
  : conn(_conn)
{
  silentCount = 20;         // wait 20 frames before playing the OGM
  recordTrigger = FALSE;
  hangupTrigger = FALSE;
  closed        = FALSE;
  playOnce      = FALSE;
  loopMessage   = FALSE;

  frameLen = frameOffs = 0;
}


void PCM_OGMChannel::PlayFile(PFile * chan)
{ 
  PWaitAndSignal mutex(chanMutex);
//  if (IsOpen())
//    Close();

  if (!chan->Open(PFile::ReadOnly)) {
    PTRACE(1, "Cannot open file \"" << chan->GetName() << "\"");
    return;
  }

  PTRACE(1, "Playing file \"" << chan->GetName() << "\"");
  totalData = 0;
  SetReadChannel(chan, TRUE);
}


BOOL PCM_OGMChannel::IsWAVFileValid(PWAVFile *chan) {
  // Check that this is a PCM wave file
  return CheckWAVFileValid(chan, CHECK_PCM);
}


BOOL PCM_OGMChannel::Read(void * buffer, PINDEX amount)
{
  PWaitAndSignal mutex(chanMutex);

  // if the channel is closed, then return error
  if (closed)
    return FALSE;

  // Create the frame buffer using the amount of bytes the codec wants to
  // read. Different codecs use different read sizes.
  frameBuffer.SetMinSize(1024);//amount);

  // assume we are returning silence
  BOOL doSilence = TRUE;
  BOOL frameBoundary = FALSE;

  // if still outputting a frame from last time, then keep doing it
  if (frameOffs < frameLen) {
    frameBoundary = AdjustFrame(buffer, amount);
    doSilence = FALSE;

  } else {

    // if we are returning silence frames, then 
    if (silentCount > 0) 
      silentCount--;

    // if a channel is already open, don't do silence
    else if (GetBaseReadChannel() != NULL)
      doSilence = FALSE;

    // If not in silence and no existing channel, open a new file.
    else {
      PString * str = playQueue.Dequeue();
      if (str != NULL) {

        // check the file extension and open a .wav or a raw (.sw or .g723) file

        if (((*str).Right(4)).ToLower() == ".wav") {
          PWAVFile *chan;
          chan = new PWAVFile(*str, PFile::ReadOnly);
          if (!chan->IsOpen()) {
            PTRACE(1, "Cannot open file \"" << chan->GetName() << "\"");
            delete chan;
          } else {
	          if (!IsWAVFileValid(chan) ){
              PTRACE(1, chan->GetName() << " is not a valid wav file");
              delete chan;
              cerr << "wave file is invalid" << endl;
            } else {
              PTRACE(1, "Playing file \"" << chan->GetName() << "\"");
              totalData = 0;
              SetReadChannel(chan, TRUE);
              doSilence = FALSE;
            }

			if (loopMessage) {
              PTRACE(1, "Looping file \"" << *str << "\"");
              playQueue.Enqueue(new PString(*str));
			}
	  }

        } else { // raw file (eg .sw)
          PFile *chan;
          chan = new PFile(*str);
          if (!chan->Open(PFile::ReadOnly)) {
            PTRACE(1, "Cannot open file \"" << chan->GetName() << "\"");
            delete chan;
          } else {
            PTRACE(1, "Playing file \"" << chan->GetName() << "\"");
            totalData = 0;
            SetReadChannel(chan, TRUE);
            doSilence = FALSE;
          }
        }
        delete str;
      }
    }
  

    // if not doing silence, try and read from the file
    if (!doSilence) {
  
      if (ReadFrame(amount)) {
        frameBoundary = AdjustFrame(buffer, amount);
        totalData += amount;
  
      } else {

        PTRACE(1, "Finished playing " << totalData << " bytes");
        //closed = TRUE;
  
        PIndirectChannel::Close();
        silentCount = 5;   // always do 5 frames of silence after every file
  
        // hangup if required
        if (hangupTrigger || playOnce) 
          conn.ClearCall();
  
        // trigger record if required
        else if (recordTrigger) {
          if ((playQueue.GetSize() == 0) && (GetBaseReadChannel() == NULL))
            conn.StartRecording();
	      }
   
        // no silence
        doSilence = TRUE;
      }
    }
  }
  
  // start silence frame if required
  if (doSilence) {
    CreateSilenceFrame(amount);
    frameBoundary = AdjustFrame(buffer, amount);
  }

  // delay to synchronise to frame boundary
  if (frameBoundary)
    Synchronise(amount);

  return TRUE;
}

BOOL PCM_OGMChannel::Close()
{
  PWaitAndSignal mutex(chanMutex);
  closed = TRUE;
  PIndirectChannel::Close();
  return TRUE;
}

void PCM_OGMChannel::SetRecordTrigger()
{
  PWaitAndSignal mutex(chanMutex);
  recordTrigger = TRUE;
  if ((playQueue.GetSize() == 0) && (GetBaseReadChannel() == NULL))
    conn.StartRecording();
}

void PCM_OGMChannel::SetHangupTrigger()
{
  PWaitAndSignal mutex(chanMutex);
  hangupTrigger = TRUE;
  if (GetBaseReadChannel() == NULL)
    conn.ClearCall();
}

void PCM_OGMChannel::QueueFile(const PString & fn)
{
  PWaitAndSignal mutex(chanMutex);
  PTRACE(1, "Enqueueing file " << fn << " for playing");
  playQueue.Enqueue(new PString(fn));
}

void PCM_OGMChannel::FlushQueue()
{
  PWaitAndSignal mutex(chanMutex);

  if (GetBaseReadChannel() != NULL) {
    PIndirectChannel::Close();
    if (hangupTrigger) 
      conn.ClearCall();
    else if (recordTrigger) 
      conn.StartRecording();
  }

  PString * str;
  while ((str = playQueue.Dequeue()) != NULL)
    delete str;
}

BOOL PCM_OGMChannel::AdjustFrame(void * buffer, PINDEX amount)
{
  if ((frameOffs + amount) > frameLen) {
    cerr << "Reading past end of frame:offs=" << frameOffs << ",amt=" << amount << ",len=" << frameLen << endl;
    return TRUE;
  }
  //PAssert((frameOffs + amount) <= frameLen, "Reading past end of frame");

  memcpy(buffer, frameBuffer.GetPointer()+frameOffs, amount);
  frameOffs += amount;

  lastReadCount = amount;

  return frameOffs == frameLen;
}

void PCM_OGMChannel::Synchronise(PINDEX amount)
{
  ogm_delay.Delay(amount / 16);
}

BOOL PCM_OGMChannel::ReadFrame(PINDEX amount)
{
  frameOffs = 0;
  frameLen  = amount;

  BOOL result = PIndirectChannel::Read(frameBuffer.GetPointer(), frameLen);

  // if we did not read a full frame of audio, fill the end of the
  // frame with zeros.
  PINDEX count = GetLastReadCount();
  if (count < frameLen)
    memset(frameBuffer.GetPointer()+count, 0, frameLen-count);

  return result;
}

void PCM_OGMChannel::CreateSilenceFrame(PINDEX amount)
{
  frameOffs = 0;
  frameLen  = amount;
  memset(frameBuffer.GetPointer(), 0, frameLen);
}

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

G7231_OGMChannel::G7231_OGMChannel(MyH323Connection & conn)
  : PCM_OGMChannel(conn)
{
}

void G7231_OGMChannel::Synchronise(PINDEX /*amount*/)
{
  ogm_delay.Delay(30);
}

BOOL G7231_OGMChannel::ReadFrame(PINDEX /*amount*/)
{
  if (!PIndirectChannel::Read(frameBuffer.GetPointer(), 1))
    return FALSE;

  frameOffs = 0;
  frameLen = G7231_File_Codec::GetFrameLen(frameBuffer[0]);

  return PIndirectChannel::Read(frameBuffer.GetPointer()+1, frameLen-1);
}

void G7231_OGMChannel::CreateSilenceFrame(PINDEX /*amount*/)
{
  frameOffs = 0;
  frameLen  = 4;

  frameBuffer[0] = 2;
  memset(frameBuffer.GetPointer()+1, 0, 3);
}

BOOL G7231_OGMChannel::IsWAVFileValid(PWAVFile *chan) {
  // Check that this is a G.723.1 wave file
  return CheckWAVFileValid(chan, CHECK_G7231);
}

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


syntax highlighted by Code2HTML, v. 0.9.1