/*
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This file is part of MjSip (http://www.mjsip.org)
 * 
 * MjSip is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * MjSip is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MjSip; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Author(s):
 * Luca Veltri (luca.veltri@unipr.it)
 */

/* Modified by:
 * Daina Interrante (daina.interrante@studenti.unipr.it)
 */

package org.zoolu.sip.dialog;


import org.zoolu.sip.address.*;
import org.zoolu.sip.transaction.*;
import org.zoolu.sip.dialog.*;
import org.zoolu.sip.message.*;
import org.zoolu.sip.header.*;
import org.zoolu.sip.header.*;
import org.zoolu.sip.provider.*;
import org.zoolu.tools.LogLevel;

import java.util.Date;


/** NotifierDialog.
  */
public class NotifierDialog extends Dialog implements TransactionClientListener/*, TransactionServerListener*/
{  
   /** String "active" */
   protected static final String ACTIVE="active";
   /** String "pending" */
   protected static final String PENDING="pending";
   /** String "terminated" */
   protected static final String TERMINATED="terminated";

   /** The SubscriberDialog listener */
   NotifierDialogListener listener;

   /** The current subscribe method */
   Message subscribe_req;

   /** The current subscribe transaction */
   TransactionServer subscribe_transaction;

   /** The current notify transaction */
   TransactionClient notify_transaction;

   /** The event name */
   String event;         

   /** The subscription id */
   String id;

   /** Internal state D_INIT (the starting point) */
   protected static final int D_INIT=0;   
   /** Internal state D_WAITING (listening for the first subscription request) */
   protected static final int D_WAITING=1;   
   /** Internal state D_SUBSCRIBED (first subscription request arrived) */
   protected static final int D_SUBSCRIBED=2;   
   /** Internal state D_PENDING (first subscription request has been accepted) */
   protected static final int D_PENDING=3;
   /** Internal state D_ACTIVE (subscription has been activated) */
   protected static final int D_ACTIVE=4;   
   /** Internal state D_TERMINATED (first subscription request has been refused or subscription has been terminated) */
   protected static final int D_TERMINATED=9;

      
   // ************************* Protected methods ************************

   /** Gets the dialog state */
   protected String getStatus()
   {  switch (status)
      {  case D_INIT       : return "D_INIT";
         case D_WAITING    : return "D_WAITING";   
         case D_SUBSCRIBED : return "D_SUBSCRIBED";
         case D_PENDING    : return "D_PENDING";
         case D_ACTIVE     : return "D_ACTIVE";
         case D_TERMINATED : return "D_TERMINATED";   
         default : return null;
      }  
   }

   // ************************** Public methods **************************

   /** Whether the dialog is in "early" state. */
   public boolean isEarly()
   {  return (status<D_PENDING);
   }

   /** Whether the dialog is in "confirmed" state. */
   public boolean isConfirmed()
   {  return (status>=D_PENDING && status<D_TERMINATED);
   }

   /** Whether the dialog is in "active" state. */
   public boolean isTerminated()
   {  return (status==D_TERMINATED);
   }

   /** Whether the subscription is "pending". */
   public boolean isSubscriptionPending()
   {  return (status>=D_SUBSCRIBED && status<D_ACTIVE);
   }

   /** Whether the subscription is "active". */
   public boolean isSubscriptionActive()
   {  return (status==D_ACTIVE);
   }

   /** Whether the subscription is "terminated". */
   public boolean isSubscriptionTerminated()
   {  return (status==D_TERMINATED);
   }

   /** Gets event type. */   
   public String getEvent()
   {  return event;
   }

   /** Gets the event "id" parameter. */   
   public String getId()
   {  return id;
   }


   // **************************** Costructors ****************************

   /** Creates a new NotifierDialog. */
   public NotifierDialog(SipProvider sip_provider, NotifierDialogListener listener)
   {  super(sip_provider);
      init(listener);
   }

   /** Creates a new NotifierDialog for the already received SUBSCRIBE request <i>subscribe</i>. */
   public NotifierDialog(SipProvider sip_provider, Message subscribe, NotifierDialogListener listener)
   {  super(sip_provider);
      init(listener);
      
      changeStatus(D_SUBSCRIBED);
      subscribe_req=subscribe;
      subscribe_transaction=new TransactionServer(sip_provider,subscribe,null);
      update(Dialog.UAS,subscribe);
      EventHeader eh=subscribe.getEventHeader();
      if (eh!=null)
      {  event=eh.getEvent();
         id=eh.getId();
      }
   }
   
   /** Inits the NotifierDialog. */
   private void init(NotifierDialogListener listener)
   {  this.listener=listener;
      this.subscribe_transaction=null;
      this.notify_transaction=null;
      this.subscribe_req=null;
      this.event=null;
      this.id=null;
      changeStatus(D_INIT);
   }


   // *************************** Public methods **************************

   /** Listen for the first subscription request. */
   public void listen()
   {  printLog("inside method listen()",LogLevel.MEDIUM);
      if (!statusIs(D_INIT))
      {  printLog("first subscription already received",LogLevel.MEDIUM);
         return;
      }
      // else
      changeStatus(D_WAITING);
      // listen for the first SUBSCRIBE request
      sip_provider.addSipProviderListener(new MethodIdentifier(SipMethods.SUBSCRIBE),this);
   }  

   /** Accepts the subscription request (sends a "202 Accepted" response). */
   public void accept(int expires, String contact)
   {  printLog("inside accept()",LogLevel.MEDIUM);
      respond(202,SipResponses.reasonOf(202),expires,contact,null,null);      
   }

   /** Refuses the subscription request. */
   public void refuse()
   {  printLog("inside refuse()",LogLevel.MEDIUM);
      respond(403,SipResponses.reasonOf(403),-1,null,null,null);      
   }  

   /** Responds with <i>code</i> and <i>reason</i>.
     * This method can be called when the InviteDialog is in D_INVITED, D_ReINVITED states */
   public void respond(int code, String reason, int expires, String contact, String content_type, String body)
   {  printLog("inside respond("+code+","+reason+")",LogLevel.MEDIUM);
      NameAddress contact_url=null;
      if (contact!=null) contact_url=new NameAddress(contact);
      Message resp=MessageFactory.createResponse(subscribe_req,code,SipResponses.reasonOf(code),contact_url);
      if (expires>=0) resp.setExpiresHeader(new ExpiresHeader(expires));
      if (body!=null) resp.setBody(content_type,body); 
      respond(resp);      
   }

   /** Responds with <i>resp</i>. */
   public void respond(Message resp)
   {  printLog("inside respond(resp)",LogLevel.MEDIUM);      
      if (resp.getStatusLine().getCode()>=200) update(UAS,resp);
      subscribe_transaction.respondWith(resp);   
   } 
  
   /** Activates the subscription (subscription goes into 'active' state). */
   public void activate()
   {  activate(SipStack.default_expires);
   }

   /** Activates the subscription (subscription goes into 'active' state). */
   public void activate(int expires)
   {  notify(ACTIVE,expires,null,null);
   }

   /** Makes the subscription pending (subscription goes into 'pending' state). */
   public void pending()
   {  pending(SipStack.default_expires);
   }

   /** Makes the subscription pending (subscription goes into 'pending' state). */
   public void pending(int expires)
   {  notify(PENDING,expires,null,null);
   }

   /** Terminates the subscription (subscription goes into 'terminated' state). */
   public void terminate()
   {  terminate(null);
   }

   /** Terminates the subscription (subscription goes into 'terminated' state). */
   public void terminate(String reason)
   {  Message req=MessageFactory.createNotifyRequest(this,event,id,null,null);
      SubscriptionStateHeader sh=new SubscriptionStateHeader(TERMINATED);
      if (reason!=null) sh.setReason(reason);
      //sh.setExpires(0);
      req.setSubscriptionStateHeader(sh);
      notify(req);
   }

   /** Sends a NOTIFY. */
   public void notify(String state, int expires, String content_type, String body)
   {  Message req=MessageFactory.createNotifyRequest(this,event,id,content_type,body);
      if (state!=null)
      {  SubscriptionStateHeader sh=new SubscriptionStateHeader(state);
         if (expires>=0) sh.setExpires(expires);
         req.setSubscriptionStateHeader(sh);
      }
      notify(req);
   }  

   /** Sends a NOTIFY. */
   public void notify(Message req)
   {  String subscription_state=req.getSubscriptionStateHeader().getState();
      if (subscription_state.equalsIgnoreCase(ACTIVE) && (statusIs(D_SUBSCRIBED) || statusIs(D_PENDING))) changeStatus(D_ACTIVE);
      else
      if (subscription_state.equalsIgnoreCase(PENDING) && statusIs(D_SUBSCRIBED)) changeStatus(D_PENDING);
      else
      if (subscription_state.equalsIgnoreCase(TERMINATED) && !statusIs(D_TERMINATED)) changeStatus(D_TERMINATED);

      TransactionClient notify_transaction=new TransactionClient(sip_provider,req,this);
      notify_transaction.request();
   }


   // ************** Inherited from TransactionClientListener **************
  
   /** When the TransactionClient is (or goes) in "Proceeding" state and receives a new 1xx provisional response */
   public void onTransProvisionalResponse(TransactionClient tc, Message resp)
   {  printLog("onTransProvisionalResponse()",LogLevel.MEDIUM);
      // do nothing.
   }
   
   /** When the TransactionClient goes into the "Completed" state receiving a 2xx response */
   public void onTransSuccessResponse(TransactionClient tc, Message resp)
   {  printLog("onTransSuccessResponse()",LogLevel.MEDIUM);
         StatusLine status_line=resp.getStatusLine();
      if (listener!=null) listener.onDlgNotificationSuccess(this,status_line.getCode(),status_line.getReason(),resp);
   }

   /** When the TransactionClient goes into the "Completed" state receiving a 300-699 response */
   public void onTransFailureResponse(TransactionClient tc, Message resp)
   {  printLog("onTransFailureResponse()",LogLevel.MEDIUM);
      StatusLine status_line=resp.getStatusLine();
      if (listener!=null) listener.onDlgNotificationFailure(this,status_line.getCode(),status_line.getReason(),resp);
   }
   
   /** When the TransactionClient goes into the "Terminated" state, caused by transaction timeout */
   public void onTransTimeout(TransactionClient tc)
   {  printLog("onTransTimeout()",LogLevel.MEDIUM);
      if (!statusIs(D_TERMINATED))
      {  changeStatus(D_TERMINATED);
         if (listener!=null) listener.onDlgNotifyTimeout(this);
      }
   }


   // ************** Inherited from SipProviderListener **************

   /** When a new Message is received by the SipProvider. */
   public void onReceivedMessage(SipProvider provider, Message msg)
   {  printLog("onReceivedMessage()",LogLevel.MEDIUM);
      if (statusIs(D_TERMINATED))
      {  printLog("subscription already terminated: message discarded",LogLevel.MEDIUM);
         return;
      }
      // else
      if(msg.isRequest() && msg.isSubscribe())
      {  if (statusIs(this.D_WAITING))
         {  // the first SUBSCRIBE request
            changeStatus(D_SUBSCRIBED);
            sip_provider.removeSipProviderListener(new MethodIdentifier(SipMethods.SUBSCRIBE));
         }
         subscribe_req=msg;
         NameAddress target=msg.getToHeader().getNameAddress();
         NameAddress subscriber=msg.getFromHeader().getNameAddress();
         EventHeader eh=msg.getEventHeader();
         if (eh!=null)
         {  event=eh.getEvent();
            id=eh.getId();
         }
         update(UAS,msg);
         subscribe_transaction=new TransactionServer(sip_provider,msg,null);
         if (listener!=null) listener.onDlgSubscribe(this,target,subscriber,event,id,msg);
      }
      else
      {  printLog("message is not a SUBSCRIBE: message discarded",LogLevel.HIGH);
      }
   }


   //**************************** Logs ****************************/

   /** Adds a new string to the default Log */
   protected void printLog(String str, int level)
   {  if (log!=null) log.println("NotifierDialog#"+dialog_sqn+": "+str,level+SipStack.LOG_LEVEL_DIALOG);  
   }

}
