/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package model;

import gov.nist.javax.sip.DialogTimeoutEvent;
import gov.nist.javax.sip.SipListenerExt;
import gui.Hauptfenster;
import java.net.InetAddress;
import java.text.ParseException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.address.*;
import javax.sip.header.*;
import javax.sip.message.*;
import logger.OhmLogger;

/**
 *
 * @author Jan komplettes Model für SIPmodel evtl. Vererbung und für jeden SIP
 * Response/Request eigene Klasse oder Interface da es langsam unübersichtlich
 * wird :( ACK schicken muss noch ergänzt werden BYE muss noch ergänzt werden.
 */
public class SIPmodel implements SipListenerExt
{
  Hauptfenster view; //später entfernen wegen Zugriff
  public static final Logger lg = OhmLogger.getLogger();
  public SipFactory sipFactory;          // Used to access the SIP API.
  public SipStack sipStack;              // The SIP stack.
  public SipProvider sipProvider;        // Used to send SIP messages.
  public MessageFactory messageFactory;  // Used to create SIP message factory.
  public HeaderFactory headerFactory;    // Used to create SIP headers.
  public AddressFactory addressFactory;  // Used to create SIP URIs.
  public ListeningPoint listeningPoint;  // SIP listening IP address/port.
  public Properties properties;          // Other properties.

  private int myPort = 5060;
  private String myName = "129";
  public String protocolTyp = "UDP";
  public String myIPAddress;
  public String proxyAddress;
  public CallIdHeader callID;

  Boolean invite; //Bool für SDP falls ich anrufe

  //Hilfsvariablen wenn ich angerufen werde. evtl. noch optimieren
  RequestEvent evtWerdeAngerufen;
  Boolean werdeAngerufen;
  private int countWerdeAngerufen;

  private long cSequenz = 1;
  private String proxyTag;
  private String myTag = String.valueOf(new Random().nextInt());
  private int branch = Integer.valueOf(hashCode());
  private Address contactAddress;
  private ContactHeader contactHeader;

  public SIPmodel(Hauptfenster view) //Konstruktor für eigene Daten     
  {
    this.view = view;
    invite = true;
    proxyAddress = "";

    countWerdeAngerufen = 0;
    evtWerdeAngerufen = null;
    werdeAngerufen = false;

    try
    {
      //wird an sich erstmal nicht gebraucht aber später sinnvoll um eigene Daten zu initialisieren
      String abc = InetAddress.getLocalHost().getHostAddress();
      this.myIPAddress = InetAddress.getLocalHost().getHostAddress();
      this.sipFactory = SipFactory.getInstance();
      this.sipFactory.setPathName("gov.nist");
      this.properties = new Properties();
      this.properties.setProperty("javax.sip.STACK_NAME", "stack");
      this.sipStack = this.sipFactory.createSipStack(this.properties);
      this.messageFactory = this.sipFactory.createMessageFactory();
      this.headerFactory = this.sipFactory.createHeaderFactory();
      this.addressFactory = this.sipFactory.createAddressFactory();
      this.listeningPoint = this.sipStack.createListeningPoint(this.myIPAddress, this.myPort, this.protocolTyp);
      this.sipProvider = this.sipStack.createSipProvider(this.listeningPoint);
      this.sipProvider.addSipListener(this);
      this.contactAddress = this.addressFactory.createAddress("sip:" + myName + "@" + this.myIPAddress + ":" + this.myPort);
      this.contactHeader = this.headerFactory.createContactHeader(contactAddress);
    }
    catch (Exception e)
    {
      System.out.println("Fehler bei Initialisierung eigener Addresse");
      System.exit(-1);
    }
  }
  public int hashCode()
  {
    int temp = Integer.valueOf(myTag)* (new Random(100)).nextInt();
    return temp;
  }
  public void sendRegister(String serverIP, int serverPort) throws ParseException, InvalidArgumentException, SipException
  {
    proxyAddress = serverIP + ":" + serverPort;
    SIPRegister register = new SIPRegister(proxyAddress, myTag, myIPAddress, myPort, branch, addressFactory, headerFactory, messageFactory, contactHeader, sipProvider.getNewCallId());
    this.sipProvider.sendRequest(register.getRequest());
    lg.info("Erfolgreiches Senden der Registrierung");
  }
  public void sendInvitation(String sipaddresse, String proxyaddresse,
                             int serverPort) throws SipException
  { //ServerPort wird nicht verwendet?
    invite = true;
    callID = this.sipProvider.getNewCallId();
    SIPSessionDescription sdp = new SIPSessionDescription(myName, myIPAddress, evtWerdeAngerufen, true);
    SIPInvite sipinvite = new SIPInvite(sipaddresse, proxyaddresse, myIPAddress, myPort, myTag, branch, callID, cSequenz, addressFactory, sdp.getSessionDescription(), headerFactory, messageFactory, contactHeader);
    this.sipProvider.sendRequest(sipinvite.getInviterequest());
    lg.info("Erfolgreiches Senden der Invitation");
  }

  @Override
  public void processRequest(RequestEvent requestEvent)
  {
    lg.info("Bekomme Anfrage");
    if ((Request.OPTIONS).equals(requestEvent.getRequest().getMethod())) //Options Anfrage von Server nach erfolgreicher Registrierung
    {
      lg.info("Option Request von Server, erstellen 200 OK zu Server");
      try
      {
        SIP200Ok tempOk = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
        this.sipProvider.sendResponse(tempOk.getResponse());
        view.getTxtArea().append("Server Option Request erfolgreich beantwortet\n");
        lg.info("Erfolgreiches senden des 200Ok-Headers");
      }
      catch (SipException ex)
      {
        lg.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    //Aktive Kommunikation
    else if ((Request.INVITE).equals(requestEvent.getRequest().getMethod())) // Invite von anderem Benutzer
    {
      try
      {
        this.sipProvider.sendResponse(create180Ringing(requestEvent));
        view.getTxtArea().append("Ringing geschickt\n");
      }
      catch (Exception ex)//Exceptions evtl. genauer definieren
      {
        lg.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
      }
      countWerdeAngerufen++; //bessere Lösung muss her!!, was schickt andere User oder Proxy bei nicht erreichen
      FromHeader abc = (FromHeader) requestEvent.getRequest().getHeader("From");

      evtWerdeAngerufen = requestEvent;
      System.out.println(countWerdeAngerufen);
      if (countWerdeAngerufen < 7)
      {
        werdeAngerufen = true;
        lg.info("Hab Invitation bekommen");
        view.getTxtArea().append("Werde von " + abc.getAddress() + " angerufen\n");
      }
      else
      {
        werdeAngerufen = false;
        lg.info("Leider waren Sie nicht erreichbar");
        view.getTxtArea().append("Sie waren nicht erreichbar\n");
        countWerdeAngerufen = 0;
      }

    }
    else if ((Request.ACK).equals(requestEvent.getRequest().getMethod()))
    {

    }
    else if ((Request.CANCEL).equals(requestEvent.getRequest().getMethod()))
    {
      view.getTxtArea().append("Anruf wurde gecancelt");
      try
      {
        //Cancel Request -> Response 200 Ok + Response 487 Request Terminated
        //200 Ok
        werdeAngerufen = false;
        evtWerdeAngerufen = null; // sicher ist sicher
        SIP200Ok tempOk = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
        this.sipProvider.sendResponse(tempOk.getResponse());
        view.getTxtArea().append("Anrufender canceld Anfrage\n");
        lg.info("Erfolgreiches senden des 200 OkHeaders auf CancelAnfrage");
        //487 Request Terminated
      }
      catch (SipException ex)
      {
        Logger.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    else if ((Request.BYE).equals(requestEvent.getRequest().getMethod()))
    {
      try
      {
        SIP200Ok tempOk = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
        this.sipProvider.sendResponse(tempOk.getResponse());
      }
      catch (Exception ex)
      {

      }
    }
  }

  @Override
  public void processResponse(ResponseEvent responseEvent)
  {
    if (responseEvent.getResponse().getStatusCode() == Response.UNAUTHORIZED)
    {
      try
      {
        cSequenz += 1L;
        branch += 10;
        SIPUnauthReq req2 = new SIPUnauthReq(myName, proxyAddress, myTag, myIPAddress, branch, cSequenz, myPort, contactHeader, addressFactory, headerFactory, responseEvent, messageFactory);
        sipProvider.sendRequest(req2.getUnauthReq());
        view.getTxtArea().append("Erfolgreich registriert\n");
      }
      catch (SipException ex)
      {
        lg.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
      }

    }
    else if (responseEvent.getResponse().getStatusCode() == Response.OK)
    {
      lg.info("Erfolgreichen Response (200 OK) bekommen");
    }
    else if (responseEvent.getResponse().getStatusCode() == Response.RINGING)
    {
      ToHeader temp = (ToHeader) responseEvent.getResponse().getHeader("To");
      proxyTag = temp.getTag();

    }

  }

 

  public Response create180Ringing(RequestEvent requestEvent) throws ParseException, SipException
  {
    Response responsering = this.messageFactory.createResponse(180, requestEvent.getRequest());
    responsering.addHeader((ViaHeader) requestEvent.getRequest().getHeader("Via"));
    responsering.addHeader((FromHeader) requestEvent.getRequest().getHeader("From"));
    AllowHeader allow = this.headerFactory.createAllowHeader("NOTIFY,INVITE,ACK,CANCEL,BYE,REFER,INFO,OPTIONS,MESSAGE");
    //Bei Ringing muss persönlicher Tag ergänzt werden
    ToHeader temptoring = (ToHeader) requestEvent.getRequest().getHeader("To");
    proxyTag = temptoring.getTag();
    temptoring.setTag(this.myTag);
    responsering.addHeader(temptoring);
    cSequenz = ((CSeqHeader) requestEvent.getRequest().getHeader("Cseq")).getSeqNumber();
    responsering.addHeader((CSeqHeader) requestEvent.getRequest().getHeader("Cseq"));
    responsering.addHeader(contactHeader);
    responsering.addHeader(allow);
    AllowEventsHeader allowevent = this.headerFactory.createAllowEventsHeader("talk,hold,refer,call-info");// für was gebraucht
    responsering.addHeader(allowevent);
    return responsering;
  }
  public void annehmen() //Fehlende Bedingung 
  {
    RequestEvent requestEvent;
    if ((requestEvent = evtWerdeAngerufen) != null && werdeAngerufen == true)
    {
      countWerdeAngerufen = 0;  
      try
      { 
        //Werte für weitere Kommunikation abspeichern
        callID = (CallIdHeader) requestEvent.getRequest().getHeader("Call-ID");
        CSeqHeader tempCs = (CSeqHeader) requestEvent.getRequest().getHeader("Cseq");
        cSequenz = tempCs.getSeqNumber();
        FromHeader tempFrom = (FromHeader) requestEvent.getRequest().getHeader("From");
        proxyTag = tempFrom.getTag();
        //Trying Header erzeugen und abschicken 
        SIP100Trying tempTrying = new SIP100Trying(messageFactory, requestEvent, headerFactory);
        this.sipProvider.sendResponse(tempTrying.getTryResponse());
        view.getTxtArea().append("Trying geschickt\n");
        lg.info("Trying ist raus");

        //Ringing Header erzeugen und abschicken From tag: proxyTag; To Tag: persönlicher Tag
        SIP180Ringing tempRinging = new SIP180Ringing(requestEvent, myTag, proxyTag, contactHeader, headerFactory);
        sipProvider.sendResponse(tempRinging.getResponseRing());
        view.getTxtArea().append("Ringing geschickt\n");

        //200OK Header mit Session Description erstellen
        invite = false;
        SIP200Ok respcall = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
        SIPSessionDescription sdp = new SIPSessionDescription(myName, myIPAddress, requestEvent, false);
        respcall.addSDP(sdp.getSessionDescription(), myTag);
        System.out.println(respcall.getResponse());
        this.sipProvider.sendResponse(respcall.getResponse());
        view.getTxtArea().append("Telefonat beginnt\n");
        evtWerdeAngerufen = null;
      }
      catch (SipException ex)
      {
        Logger.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    else
    {
      view.getTxtArea().append("Zur Zeit gibt es keinen Anruf\n");
    }

  }

  public void byecall(String calluser) throws ParseException, InvalidArgumentException, SipException
  {
    invite = false;
    Address addressLine = this.addressFactory.createAddress("sip:" + calluser + "@" + proxyAddress);
    URI requestURI = addressLine.getURI();

    Address addressfrom = this.addressFactory.createAddress("sip:" + myName + "@" + myIPAddress);
    FromHeader from = this.headerFactory.createFromHeader(addressfrom, String.valueOf(this.myTag));

    Address addressTo = this.addressFactory.createAddress("sip:" + myName + "@" + proxyAddress);
    ToHeader to = this.headerFactory.createToHeader(addressTo, proxyTag); // Ergänzung TAG!!

    ArrayList via = new ArrayList(); //Via needs a List as input
    ViaHeader viaheader = this.headerFactory.createViaHeader(this.myIPAddress, this.myPort, "UDP", String.valueOf(branch));
    via.add(viaheader);

    cSequenz++;
    CSeqHeader cSeq = this.headerFactory.createCSeqHeader(cSequenz, "BYE");
    MaxForwardsHeader maxForwards = this.headerFactory.createMaxForwardsHeader(70);
    AllowHeader allow = this.headerFactory.createAllowHeader("NOTIFY,INVITE,ACK,CANCEL,BYE,REFER,INFO,OPTIONS,MESSAGE");
    ExpiresHeader expire = this.headerFactory.createExpiresHeader(3600); //int value 
    Request request = this.messageFactory.createRequest(requestURI, "BYE", callID, cSeq, from, to, via, maxForwards);
    request.addHeader(contactHeader);
    request.addHeader(allow);
    request.addHeader(expire);
    this.sipProvider.sendRequest(request);
    invite = false;

    lg.info("Erfolgreiches Senden des BYE Headers");
  }

  //Get Methoden falls man mal die Parameter braucht
  public String getmyName()
  {
    return myName;
  }

  public Integer getMyPort()
  {
    return myPort;
  }

  public String getMyIPAdress()
  {
    return myIPAddress;
  }

  // Herausfinden bei welchen Events die unteren Funktionen aufgerufen werden -> evtl brauchbar
  @Override
  public void processTimeout(TimeoutEvent timeoutEvent)
  {
    //view.getTxtArea().append("processTimeout\n"); 
  }

  @Override
  public void processIOException(IOExceptionEvent exceptionEvent)
  {
    //view.getTxtArea().append("processIOException\n"); 
  }

  @Override
  public void processTransactionTerminated(
          TransactionTerminatedEvent transactionTerminatedEvent)
  {
    //view.getTxtArea().append("processTransactionTerminated\n"); 
  }

  @Override
  public void processDialogTerminated(
          DialogTerminatedEvent dialogTerminatedEvent)
  {
    //view.getTxtArea().append("processDialogTerminated\n"); 
  }

  @Override
  public void processDialogTimeout(DialogTimeoutEvent timeoutEvent)
  {
    //view.getTxtArea().append("processDialogTerminated\n"); 
  }

  /**
   * @param myPort the myPort to set
   */
  public void setMyPort(int myPort)
  {
    this.myPort = myPort;
  }

  /**
   * @return the myName
   */
  public String getMyName()
  {
    return myName;
  }

  /**
   * @return the proxyTag
   */
  public String getProxyTag()
  {
    return proxyTag;
  }

  /**
   * @param proxyTag the proxyTag to set
   */
  public void setProxyTag(String proxyTag)
  {
    this.proxyTag = proxyTag;
  }

  /**
   * @return the myTag
   */
  public String getMyTag()
  {
    return myTag;
  }

  /**
   * @param myTag the myTag to set
   */
  public void setMyTag(String myTag)
  {
    this.myTag = myTag;
  }

  /**
   * @return the branch
   */
  public int getBranch()
  {
    return branch;
  }
}
