#include "resip/dum/DialogEventStateManager.hxx"
#include "rutil/Random.hxx"
#include "rutil/Logger.hxx"

namespace resip
{

#define RESIPROCATE_SUBSYSTEM Subsystem::DUM

DialogEventStateManager::DialogEventStateManager()
   : mDialogEventHandler(nullptr)
{
}

// we've received an INVITE
void
DialogEventStateManager::onTryingUas(Dialog& dialog, const SipMessage& invite)
{
   DialogEventInfo* eventInfo = new DialogEventInfo();
   eventInfo->mDialogEventId = Random::getVersion4UuidUrn(); // !jjg! is this right?
   eventInfo->mDialogId = dialog.getId();
   eventInfo->mDirection = DialogEventInfo::Recipient;
   eventInfo->mCreationTimeSeconds = Timer::getTimeSecs();
   eventInfo->mInviteSession = InviteSessionHandle::NotValid();
   eventInfo->mRemoteOfferAnswer = (invite.getContents() != nullptr ? std::unique_ptr<Contents>(invite.getContents()->clone()) : nullptr);
   eventInfo->mLocalIdentity = dialog.getLocalNameAddr();
   eventInfo->mLocalTarget = dialog.getLocalContact().uri();  // !slg! TODO - fix me - the Dialog stored local contact has an empty hostname so that the stack will fill it in
   eventInfo->mRemoteIdentity = dialog.getRemoteNameAddr();
   eventInfo->mRemoteTarget = std::unique_ptr<Uri>(new Uri(dialog.getRemoteTarget().uri()));
   eventInfo->mRouteSet = dialog.getRouteSet();
   eventInfo->mState = DialogEventInfo::Trying;

   if (invite.exists(h_Replaces) &&
         invite.header(h_Replaces).isWellFormed())
   {
      Data replacesToTag = invite.header(h_Replaces).exists(p_toTag) ? invite.header(h_Replaces).param(p_toTag) : Data::Empty;
      Data replacesFromTag = invite.header(h_Replaces).exists(p_fromTag) ? invite.header(h_Replaces).param(p_fromTag) : Data::Empty;

      eventInfo->mReplacesId = std::unique_ptr<DialogId>(new DialogId(invite.header(h_Replaces).value(), 
         replacesToTag,
         replacesFromTag));

      std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.find(*(eventInfo->mReplacesId));
      if (it != mDialogIdToEventInfo.end())
      {
         // If the call to be replaced is early and it is a recipient, then it cannot get replaced (according to RFC3891)
         // so we don't want to set the flag in this case.  Using inverted logic statement.
         if (it->second->getState() != DialogEventInfo::Early || it->second->getDirection() != DialogEventInfo::Recipient)
         {
            it->second->mReplaced = true;
         }
      }
   }
   if (invite.exists(h_ReferredBy) && 
         invite.header(h_ReferredBy).isWellFormed())
   {
      eventInfo->mReferredBy = std::unique_ptr<NameAddr>(new NameAddr(invite.header(h_ReferredBy)));
   }

   mDialogIdToEventInfo[dialog.getId()] = eventInfo;

   TryingDialogEvent evt(*eventInfo, invite);
   mDialogEventHandler->onTrying(evt);
}

// we've sent an INVITE
void
DialogEventStateManager::onTryingUac(DialogSet& dialogSet, const SipMessage& invite)
{
   DialogId fakeId(dialogSet.getId(), Data::Empty);
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.find(fakeId);

   DialogEventInfo* eventInfo = 0;

   if (it != mDialogIdToEventInfo.end())
   {
      // .jjg. we will get in here if our INVITE gets challenged; just swallow the onTrying event in this case
      eventInfo = it->second;
      if (eventInfo->mState == DialogEventInfo::Trying)
      {
         return;
      }
   }
   else
   {
      eventInfo = new DialogEventInfo();
   }

   eventInfo->mDialogEventId = Random::getVersion4UuidUrn();
   eventInfo->mDialogId = DialogId(dialogSet.getId(), Data::Empty);
   eventInfo->mDirection = DialogEventInfo::Initiator;
   eventInfo->mCreationTimeSeconds = Timer::getTimeSecs();
   eventInfo->mInviteSession = InviteSessionHandle::NotValid();
   eventInfo->mLocalIdentity = invite.header(h_From);
   // ?bwc? Has something already checked for well-formedness here? 
   // Maybe DialogSet? We need to be absolutely certain that this exists and is
   // well-formed. Assert for now.
   resip_assert(!invite.empty(h_Contacts));
   resip_assert(invite.header(h_Contacts).front().isWellFormed());
   eventInfo->mLocalTarget = invite.header(h_Contacts).front().uri();
   eventInfo->mRemoteIdentity = invite.header(h_To);
   eventInfo->mLocalOfferAnswer = (invite.getContents() != nullptr ? std::unique_ptr<Contents>(invite.getContents()->clone()) : nullptr);
   eventInfo->mState = DialogEventInfo::Trying;

   if (invite.exists(h_ReferredBy) &&
         invite.header(h_ReferredBy).isWellFormed())
   {
      eventInfo->mReferredBy = std::unique_ptr<NameAddr>(new NameAddr(invite.header(h_ReferredBy)));
   }

   mDialogIdToEventInfo[eventInfo->mDialogId] = eventInfo;

   TryingDialogEvent evt(*eventInfo, invite);
   mDialogEventHandler->onTrying(evt);
}

// we've received a 1xx response without a remote tag
void
DialogEventStateManager::onProceedingUac(const DialogSet& dialogSet, const SipMessage& response)
{
   DialogId fakeId(dialogSet.getId(), Data::Empty);
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.lower_bound(fakeId);
   if (it != mDialogIdToEventInfo.end() &&
      it->first.getDialogSetId() == dialogSet.getId())
   {
      if (it->first.getRemoteTag().empty())
      {
         // happy day case; no forks yet; e.g INVITE/1xx (no tag)/1xx (no tag)
         DialogEventInfo* eventInfo = it->second;
         eventInfo->mState = DialogEventInfo::Proceeding;
         if (!response.empty(h_Contacts))
         {
            // ?bwc? Has something already checked for well-formedness here? 
            // Maybe DialogSet? Assert for now.
            resip_assert(response.header(h_Contacts).front().isWellFormed());
            eventInfo->mRemoteTarget = std::unique_ptr<Uri>(new Uri(response.header(h_Contacts).front().uri()));
         }
         ProceedingDialogEvent evt(*eventInfo);
         mDialogEventHandler->onProceeding(evt);
      }
      else
      {
         // forking; e.g. INVITE/180 (tag #1)/180 (no tag)

         // .jjg. The remote sender of the 180 (no tag) should either 'put up or shut up' as Byron put it
         // so we'll just ignore this...
      }
   }
}

// UAC: we've received a 1xx response WITH a remote tag
// UAS: we've sent a 1xx response WITH a local tag
void
DialogEventStateManager::onEarly(const Dialog& dialog, InviteSessionHandle is)
{
   DialogEventInfo* eventInfo = findOrCreateDialogInfo(dialog);

   if (eventInfo)
   {
      eventInfo->mState = DialogEventInfo::Early;
      eventInfo->mRouteSet = dialog.getRouteSet();
      eventInfo->mInviteSession = is;

      // local or remote target might change due to an UPDATE or re-INVITE
      eventInfo->mLocalTarget = dialog.getLocalContact().uri();   // !slg! TODO - fix me - the Dialog stored local contact has an empty hostname so that the stack will fill it in
      eventInfo->mRemoteTarget = std::unique_ptr<Uri>(new Uri(dialog.getRemoteTarget().uri()));

      EarlyDialogEvent evt(*eventInfo);
      mDialogEventHandler->onEarly(evt);
   }
}

void
DialogEventStateManager::onConfirmed(const Dialog& dialog, InviteSessionHandle is)
{
   DialogEventInfo* eventInfo = findOrCreateDialogInfo(dialog);

   if (eventInfo)
   {
      eventInfo->mInviteSession = is;
      eventInfo->mRouteSet = dialog.getRouteSet(); // won't change due to re-INVITEs, but is
                                                   // needed for the Trying --> Confirmed transition
      eventInfo->mState = DialogEventInfo::Confirmed;

      // local or remote target might change due to an UPDATE or re-INVITE
      eventInfo->mLocalTarget = dialog.getLocalContact().uri();   // !slg! TODO - fix me - the Dialog stored local contact has an empty hostname so that the stack will fill it in
      eventInfo->mRemoteTarget = std::unique_ptr<Uri>(new Uri(dialog.getRemoteTarget().uri()));

      // Set the latest sdp
      eventInfo->mLocalOfferAnswer = (is->hasLocalOfferAnswer() ? std::unique_ptr<Contents>(is->getLocalOfferAnswer().clone()) : nullptr);
      eventInfo->mRemoteOfferAnswer = (is->hasRemoteOfferAnswer() ? std::unique_ptr<Contents>(is->getRemoteOfferAnswer().clone()) : nullptr);

      // for the dialog that got the 200 OK
      std::shared_ptr<ConfirmedDialogEvent> confirmedEvt(new ConfirmedDialogEvent(*eventInfo));
      
      //mDialogEventHandler->onConfirmed(confirmedEvt);
      MultipleEventDialogEvent::EventVector events;

      // kill off any other dialogs in this dialog set, since certain proxy/registrars (like SER and sipX)
      // won't bother giving us updates on their status anyways!
      const DialogSetId& dialogSetId = dialog.getId().getDialogSetId();
      DialogId fakeId(dialogSetId, Data::Empty);
      std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.lower_bound(fakeId);
      while (it != mDialogIdToEventInfo.end() && 
            it->first.getDialogSetId() == dialogSetId)
      {
         DialogEventInfo::State dialogState = it->second->getState();
         if (dialogState == DialogEventInfo::Proceeding || dialogState == DialogEventInfo::Early)
         {
            // .jjg. we're killing a *specific* dialog *after* the successful completion of the initial INVITE transaction;
            // so just eliminate this dialog, not the entire dialogset
            events.emplace_back(std::shared_ptr<TerminatedDialogEvent>(onDialogTerminatedImpl(it->second, InviteSessionHandler::RemoteCancel)));
            delete it->second;
            mDialogIdToEventInfo.erase(it++);
         }
         else
         {
            it++;
         }
      }

      if (!events.empty())
      {
         events.push_back(confirmedEvt);
         MultipleEventDialogEvent multipleEvt(events);
         mDialogEventHandler->onMultipleEvents(multipleEvt);
      }
      else
      {
         mDialogEventHandler->onConfirmed(*confirmedEvt);
      }
   }
}

void
DialogEventStateManager::onTerminated(const Dialog& dialog, const SipMessage& msg, InviteSessionHandler::TerminatedReason reason)
{
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.find(dialog.getId());
   if (it != mDialogIdToEventInfo.end())
   {
      DialogEventInfo::State dialogState = it->second->getState();
      if (dialogState == DialogEventInfo::Confirmed)
      {
         // .jjg. we're killing a *specific* dialog *after* the successful completion of the initial INVITE transaction;
         // so just elminate this dialog, not the entire dialogset
         std::unique_ptr<TerminatedDialogEvent> evt(onDialogTerminatedImpl(it->second, reason, getResponseCode(msg), getFrontContact(msg)));
         mDialogEventHandler->onTerminated(*evt);
         delete it->second;
         mDialogIdToEventInfo.erase(it++);
      }
      else
      {
         onDialogSetTerminatedImpl(dialog.getId().getDialogSetId(), msg, reason);
      }
   }
   else
   {
      onDialogSetTerminatedImpl(dialog.getId().getDialogSetId(), msg, reason);
   }
}

void
DialogEventStateManager::onTerminated(const DialogSet& dialogSet, const SipMessage& msg, InviteSessionHandler::TerminatedReason reason)
{
   onDialogSetTerminatedImpl(dialogSet.getId(), msg, reason);
}

void
DialogEventStateManager::onDialogSetTerminatedImpl(const DialogSetId& dialogSetId, const SipMessage& msg, InviteSessionHandler::TerminatedReason reason)
{
   DialogEventInfo* eventInfo = NULL;

   /**
    * cases:
    *    1) UAC: INVITE/180 (tag #1)/180 (tag #2)/486 (tag #2)
    *    2) UAS: INVITE/100/486 (tag #1)
    *    3) UAS: INVITE/100/180 (tag #1)/486 (tag #1)
    */

   //find dialogSet.  All non-confirmed dialogs are destroyed by this event.
   //Confirmed dialogs are only destroyed by an exact match.

   DialogId fakeId(dialogSetId, Data::Empty);
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.lower_bound(fakeId);

   while (it != mDialogIdToEventInfo.end() && 
          it->first.getDialogSetId() == dialogSetId)
   {
      eventInfo = it->second;
      std::unique_ptr<TerminatedDialogEvent> evt(onDialogTerminatedImpl(eventInfo, reason, getResponseCode(msg), getFrontContact(msg)));
      mDialogEventHandler->onTerminated(*evt);
      delete it->second;
      mDialogIdToEventInfo.erase(it++);
   }
}

TerminatedDialogEvent*
DialogEventStateManager::onDialogTerminatedImpl(DialogEventInfo* eventInfo, 
                                                InviteSessionHandler::TerminatedReason reason,
                                                int responseCode,
                                                Uri* remoteTarget)
{
   eventInfo->mState = DialogEventInfo::Terminated;

   // .jjg. when we get an INVITE w/Replaces, we mark the replaced dialog event info 
   // as 'replaced' (see onTryingUas);
   // when the replaced dialog is ended, it will be ended normally with a BYE or CANCEL, 
   // but since we've marked it as 'replaced' we can update the termination reason
   InviteSessionHandler::TerminatedReason actualReason = reason;

   if (eventInfo->mReplaced)
   {
      actualReason = InviteSessionHandler::Replaced;
   }

   if (remoteTarget)
   {
      eventInfo->mRemoteTarget = std::unique_ptr<Uri>(remoteTarget);
   }

   TerminatedDialogEvent* evt = new TerminatedDialogEvent(*eventInfo, actualReason, responseCode);
   return evt;
   //mDialogEventHandler->onTerminated(evt);
}

int 
DialogEventStateManager::getResponseCode(const SipMessage& msg)
{
   int respCode = 0;
   if (msg.isResponse())
   {
      respCode = msg.header(h_StatusLine).responseCode();
   }
   return respCode;
}

Uri* 
DialogEventStateManager::getFrontContact(const SipMessage& msg)
{
   Uri* pContact = NULL;
   if (msg.isResponse())
   {
      if (!msg.empty(h_Contacts))
      {
         // ?bwc? Has something already checked for well-formedness here? 
         // Maybe DialogSet? Assert for now.
         resip_assert(msg.header(h_Contacts).front().isWellFormed());
         pContact = new Uri(msg.header(h_Contacts).front().uri());
      }
   }
   return pContact;
}

DialogEventStateManager::DialogEventInfos 
DialogEventStateManager::getDialogEventInfo() const
{
   DialogEventStateManager::DialogEventInfos infos;
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::const_iterator it = mDialogIdToEventInfo.begin();
   for (; it != mDialogIdToEventInfo.end(); it++)
   {
      infos.push_back(*(it->second));
   }
   return infos;
}

DialogEventStateManager::DialogEventInfos 
DialogEventStateManager::getDialogEventInfo(const Uri& entityUri, bool bMatchRemoteIdentityOnly) const
{
   DialogEventStateManager::DialogEventInfos infos;
   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::const_iterator it = mDialogIdToEventInfo.begin();
   for (; it != mDialogIdToEventInfo.end(); it++)
   {
      if (!bMatchRemoteIdentityOnly)
      {
         // Return all calls to or from the requested entity
         if (it->second->getLocalIdentity().uri().getAOR(false) == entityUri.getAOR(false) ||
            it->second->getRemoteIdentity().uri().getAOR(false) == entityUri.getAOR(false))
         {
            infos.push_back(*(it->second));
         }
      }
      else
      {
         // Return only those dialogs whose remote identity matches the requested entity
         if (it->second->getRemoteIdentity().uri().getAOR(false) == entityUri.getAOR(false))
         {
            infos.push_back(*(it->second));
         }
      }
   }
   return infos;
}

DialogEventInfo* 
DialogEventStateManager::findOrCreateDialogInfo(const Dialog& dialog)
{
   DialogEventInfo* eventInfo = NULL;

   /**
    * cases:
    *    1) INVITE/180 (no tag)/183 (tag)
    *    2) INVITE/180 (tag)
    *    3) INVITE/180 (tag #1)/180 (tag #2)
    *
    */

   std::map<DialogId, DialogEventInfo*, DialogIdComparator>::iterator it = mDialogIdToEventInfo.find(dialog.getId());

   if (it != mDialogIdToEventInfo.end())
   {
      return it->second;
   }
   else
   {
      // either we have a dialog set id with an empty remote tag, or we have other dialog(s) with different
      // remote tag(s)
      DialogId fakeId(dialog.getId().getDialogSetId(), Data::Empty);
      it = mDialogIdToEventInfo.lower_bound(fakeId);

      if (it != mDialogIdToEventInfo.end() && 
            it->first.getDialogSetId() == dialog.getId().getDialogSetId())
      {
         if (it->first.getRemoteTag().empty())
         {
            // convert this bad boy into a full on Dialog
            eventInfo = it->second;
            mDialogIdToEventInfo.erase(it);
            eventInfo->mDialogId = dialog.getId();
         }
         else
         {
            // clone this fellow member dialog, initializing it with a new id and creation time 
            DialogEventInfo* newForkInfo = new DialogEventInfo(*(it->second));
            newForkInfo->mDialogEventId = Random::getVersion4UuidUrn();
               newForkInfo->mCreationTimeSeconds = Timer::getTimeSecs();
            newForkInfo->mDialogId = dialog.getId();
            newForkInfo->mRemoteIdentity = dialog.getRemoteNameAddr();
            newForkInfo->mRemoteTarget = std::unique_ptr<Uri>(new Uri(dialog.getRemoteTarget().uri()));
            newForkInfo->mRouteSet = dialog.getRouteSet();
            eventInfo = newForkInfo;
         }
      }
      else
      {
         // .jjg. this can happen if onTryingUax(..) wasn't called yet for this dialog (set) id
         DebugLog(<< "DialogSetId " << fakeId << " was not found! This indicates a bug; onTryingUax() should have been called first!");
         return 0;
      }
   }

   mDialogIdToEventInfo[dialog.getId()] = eventInfo;

   return eventInfo;
}

} // namespace resip

/* ====================================================================
* The Vovida Software License, Version 1.0 
* 
* Copyright (c) 2000 Vovida Networks, Inc.  All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
* 
* 3. The names "VOCAL", "Vovida Open Communication Application Library",
*    and "Vovida Open Communication Application Library (VOCAL)" must
*    not be used to endorse or promote products derived from this
*    software without prior written permission. For written
*    permission, please contact vocal@vovida.org.
*
* 4. Products derived from this software may not be called "VOCAL", nor
*    may "VOCAL" appear in their name, without prior written
*    permission of Vovida Networks, Inc.
* 
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT ARE DISCLAIMED.  IN NO EVENT SHALL VOVIDA
* NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
* IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
* 
* ====================================================================
* 
* This software consists of voluntary contributions made by Vovida
* Networks, Inc. and many individuals on behalf of Vovida Networks,
* Inc.  For more information on Vovida Networks, Inc., please see
* <http://www.vovida.org/>.
*
*/
