//
// Copyright (C) 2006 SIPfoundry Inc.
// License by SIPfoundry under the LGPL license.
// 
// Copyright (C) 2006 Pingtel Corp.
// Licensed to SIPfoundry under a Contributor Agreement.
//
//////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
#include <assert.h>
#include "os/OsSysLog.h"
#include "os/OsDateTime.h"

// APPLICATION INCLUDES
#include "CallStateEventBuilder_DB.h"

// DEFINES
// MACROS
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS

static const char CallRequestType = 'R';
static const char CallSetupType = 'S';
static const char CallEndType = 'E';
static const char CallFailureType = 'F';
static const char CallTransferType = 'T';

static const char* ModuleName = 
  "CallStateEventBuilder_DB";

static const char* ObserverEventTable = 
  "observer_state_events";
  
static const char* CallEventTable =
  "call_state_events";

static const char* CallEvent_Start =
  "INSERT INTO %s VALUES (DEFAULT,\'%s\',%d,"
  "timestamp \'";

static const char* CallEvent_NoFailure =
  "0,\'\',";
  
static const char* CallEvent_DefaultElement =
  "\'\',";
  
static const char* CallEvent_DefaultEndElement =
  "\'\'";
  
static const char* CallEvent_DefaultReferElement =
  "\'\',\'\',";  

// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

/* //////////////////////////// PUBLIC //////////////////////////////////// */

/* ============================ CREATORS ================================== */

/// Instantiate an event builder and set the observer name for its events
CallStateEventBuilder_DB::CallStateEventBuilder_DB(const char* observerDnsName ///< the DNS name to be recorded in all events
                                                     ) :
   CallStateEventBuilder(observerDnsName)
{
}


/// Destructor
CallStateEventBuilder_DB::~CallStateEventBuilder_DB()
{
}


/**
 * Generate a metadata event.
 * This method generates a complete event - it does not require that the callEventComplete method be called.
 */
void CallStateEventBuilder_DB::observerEvent(int sequenceNumber, ///< for ObserverReset, this should be zero
                                             const OsTime& timestamp,      ///< obtained using getCurTime(OsTime)
                                             ObserverEvent eventCode,
                                             const char* eventMsg ///< for human consumption
                                             )
{
   BuilderMethod eventMethod;
 
   switch (eventCode)
   {
   case ObserverReset:
      reset(); // because this event is ok any time, clear out any partial event.
      eventMethod = BuilderReset;
      break;
   default:
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, "observerEvent: invalid eventCode %d", eventCode);
      eventMethod = InvalidEvent;
      break;
   }

   if (builderStateIsOk(eventMethod))
   {
      newEvent(sequenceNumber, timestamp, ObserverEventTable);
   
      char buffer[256];
      snprintf(buffer, 256, "%d,\'%s\'", 
               eventCode, eventMsg);
      mCurrentEvent.append(buffer);

      mCallInfo.remove(0);
      mReferElement.remove(0);      
      mContactElement.remove(0);
      mReferElement.remove(0);
      mFailureElement.remove(0);
      mRequestUri.remove(0);

      mEventComplete = true;
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, "observerEvent: %d not allowed.", eventCode);
   }
}

/// Begin a Call Request Event - an INVITE without a to tag has been observed
/**
 * Requires:
 *   - callRequestEvent
 *   - addCallData (the toTag in the addCallRequest will be a null string)
 *   - addEventVia (at least for via index zero)
 *   - completeCallEvent
 */
void CallStateEventBuilder_DB::callRequestEvent(int sequenceNumber,
                                                 const OsTime& timestamp,      ///< obtain using getCurTime(OsTime)
                                                 const UtlString& contact
                                                 )
{
   if (builderStateIsOk(CallRequestEvent))
   {
      newEvent(sequenceNumber, timestamp, CallEventTable, CallRequestType);

      // Translate singe quotes
      UtlString ncontact;
      replaceSingleQuotes(contact, ncontact);
      mContactElement = "\'" + ncontact + "\',";         
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callRequestEvent not allowed.",
                    ModuleName);
   }
}


/// Begin a Call Setup Event - a 2xx response to an INVITE has been observed
/**
 * Requires:
 *   - callSetupEvent
 *   - addCallData
 *   - addEventVia (at least for via index zero)
 *   - completeCallEvent
 */
void CallStateEventBuilder_DB::callSetupEvent(int sequenceNumber,
                                               const OsTime& timestamp,      ///< obtain using getCurTime(OsTime)
                                               const UtlString& contact
                                               )
{
   if (builderStateIsOk(CallSetupEvent))
   {
      newEvent(sequenceNumber, timestamp, CallEventTable, CallSetupType);

      UtlString ncontact;
      replaceSingleQuotes(contact, ncontact); 
      mContactElement = "\'" + contact + "\',";
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callSetupEvent not allowed.", ModuleName);
   }
}


/// Begin a Call Failure Event - an error response to an INVITE has been observed
/**
 * Requires:
 *   - callFailureEvent
 *   - addCallData
 *   - addEventVia (at least for via index zero)
 *   - completeCallEvent
 */
void CallStateEventBuilder_DB::callFailureEvent(int sequenceNumber,
                                                 const OsTime& timestamp,      ///< obtain using getCurTime(OsTime)
                                                 int statusCode,
                                                 const UtlString& statusMsg
                                                 )
{
   if (builderStateIsOk(CallFailureEvent))
   {
      newEvent(sequenceNumber, timestamp, CallEventTable, CallFailureType);

      char buffer[256];
      snprintf(buffer, 256, "%d,\'%s\',", statusCode, statusMsg.data());
      mFailureElement = buffer;
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callFailureEvent not allowed.", ModuleName);
   }
}


/// Begin a Call End Event - a BYE request has been observed
/**
 * Requires:
 *   - callEndEvent
 *   - addCallData
 *   - addEventVia (at least for via index zero)
 *   - completeCallEvent
 */
void CallStateEventBuilder_DB::callEndEvent(const int sequenceNumber,
                                             const OsTime& timestamp      ///< obtain using getCurTime(OsTime)
                                             )
{
   if (builderStateIsOk(CallEndEvent))
   {
      newEvent(sequenceNumber, timestamp, CallEventTable, CallEndType);
      mFailureElement = CallEvent_NoFailure;
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callEndEvent not allowed.", ModuleName);
   }
}


/// Begin a Call Transfer Event - a REFER request has been observed
/**
 * Requires:
 *   - callTransferEvent
 *   - addCallData
 *   - completeCallEvent
 */
void CallStateEventBuilder_DB::callTransferEvent(int sequenceNumber, 
                                                  const OsTime& timeStamp, 
                                                  const UtlString& contact,
                                                  const UtlString& refer_to,
                                                  const UtlString& referred_by,
                                                  const UtlString& request_uri) 
{
   if (builderStateIsOk(CallTransferEvent))
   {
      newEvent(sequenceNumber, timeStamp, CallEventTable, CallTransferType);
      
      UtlString nvalue;
      replaceSingleQuotes(contact, nvalue);
      mContactElement = "\'" + nvalue + "\',";         

      replaceSingleQuotes(refer_to, nvalue);
      mReferElement = "\'" + nvalue + "\',";         

      replaceSingleQuotes(referred_by, nvalue);
      mReferElement += "\'" + nvalue + "\',";    

      replaceSingleQuotes(request_uri, nvalue);
      mRequestUri = "\'" + nvalue + "\'";         
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callEndEvent not allowed.", ModuleName);
   }   
}


/// Add the dialog and call information for the event being built.
void CallStateEventBuilder_DB::addCallData(const int cseqNumber,
                                           const UtlString& callId,
                                           const UtlString& fromTag,  /// may be a null string
                                           const UtlString& toTag,    /// may be a null string
                                           const UtlString& fromField,
                                           const UtlString& toField
                                           )
{
   if (builderStateIsOk(AddCallData))
   {
      // Allow for cseq field
      char buffer[32];
      snprintf(buffer, 31, "%d,", cseqNumber);
      mCallInfo = buffer;
      
      UtlString nvalue;
      replaceSingleQuotes(callId, nvalue);
      mCallInfo += "\'" + nvalue + "\',";        

      replaceSingleQuotes(fromTag, nvalue); 
      mCallInfo += "\'" + nvalue + "\',";         

      replaceSingleQuotes(toTag, nvalue);
      mCallInfo += "\'" + nvalue + "\',";         

      replaceSingleQuotes(fromField, nvalue);
      mCallInfo += "\'" + nvalue + "\',";       

      replaceSingleQuotes(toField, nvalue);
      mCallInfo += "\'" + nvalue + "\',";        
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callEndEvent not allowed.", ModuleName);
   }
}

   
/// Add a via element for the event
/**
 * Record a Via from the message for this event 
 * Calls to this routine are in reverse cronological order - the last
 * call for an event should be the via added by the message originator
 */
void CallStateEventBuilder_DB::addEventVia(const UtlString& via
                                            )
{
   if (!builderStateIsOk(AddVia))
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::callEndEvent not allowed.", ModuleName);
   }
}


/// Indicates that all information for the current call event has been added.
void CallStateEventBuilder_DB::completeCallEvent()
{
   if (builderStateIsOk(CompleteCallEvent))
   {
      mEventComplete = true;
   }
   else
   {
      assert(false);
      OsSysLog::add(FAC_SIP, PRI_ERR, 
                    "%s::completeCallEvent not allowed.", ModuleName);
   }
}

/// Clears all the object state
void CallStateEventBuilder_DB::reset()
{
   mCurrentEvent.remove(0);
   mCallInfo = CallEvent_DefaultElement;
   mViaHeader = CallEvent_DefaultElement;
   mLaterElement = CallEvent_DefaultElement;
   mContactElement = CallEvent_DefaultElement;
   mReferElement = CallEvent_DefaultReferElement;
   mFailureElement = CallEvent_NoFailure;
   mRequestUri = CallEvent_DefaultEndElement;
   mEndElement.remove(0);
   mEventComplete = false;
}

void CallStateEventBuilder_DB::newEvent(int sequenceNumber,
                                        const OsTime& timestamp,      ///< obtain using getCurTime(OsTime)
                                        const char* eventTable,
                                        const char eventType
                                        )
{
   char buffer[256];  // size as const int
   
   snprintf(buffer, 256, CallEvent_Start, eventTable, 
            observerName, sequenceNumber);

   mCurrentEvent = buffer;
   
   OsDateTime timeValue(timestamp);
   UtlString timeString;
   timeValue.getSqlTimeStringZ(timeString);
   mCurrentEvent.append(timeString.data()); 
   mCurrentEvent.append("\',");
   if (eventType != '-')
   {   
      mCurrentEvent.append("\'");
      mCurrentEvent.append(eventType);
      mCurrentEvent.append("\',");
   }
}

/// Copies the element into the provided UtlString
bool  CallStateEventBuilder_DB::finishElement(UtlString& event)
/**<
 * @returns
 * - true if the returned element is validly constructed
 * - false if not (a caller error)
 */
{
   bool isComplete = mEventComplete;
   event.remove(0);

   if (isComplete)
   {
      event.append(mCurrentEvent);
      event.append(mCallInfo);
      event.append(mContactElement);
      event.append(mReferElement);
      event.append(mFailureElement);
      event.append(mRequestUri);
      event.append(");");

      reset();
   }

   return isComplete;
}

void CallStateEventBuilder_DB::replaceSingleQuotes(const UtlString& value, UtlString& newValue)
{
   int startIndex = 0;
   int newIndex = 0;
   
   newValue = value;
   
   newIndex = newValue.index('\'', startIndex);
   while ((newIndex = newValue.index('\'', startIndex)) != UTL_NOT_FOUND)
   {
      startIndex = newIndex + 2;
      newValue = newValue.replace(newIndex, 1, "\\'");
   }
}



syntax highlighted by Code2HTML, v. 0.9.1