/*
 * 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 controller.commands.CommandInvite;
import gov.nist.javax.sip.DialogTimeoutEvent;
import gov.nist.javax.sip.SipListenerExt;
import gui.Hauptfenster;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
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 {
    //Stringhilfen zum erzeugen von RandomString (Branch)

    private static final String TEST = "0123456789abcdefghijklmnopqrstuvwxyz";//für Random String
    private static final SecureRandom RANDOM = new SecureRandom(); //für Random String
    //Verbindungskonstanten
    private static final int MYSIPPORT = 5060;
    private static final int MYRTPPORT = 5004;
    private static final int MYRTCPPORT = 5005;
    private static final String PROTOCOLTYP = "UDP";

    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 AudioRinging audioRinging;

    //Hilfsbooleans
    private Boolean anrufer; //Hilfsvariable ob ich selbst anrufer bin
    private Boolean anrufAngenommen;
    private Boolean anrufCancel;
    private Boolean werdeAngerufen;

    //Variablen die im Laufe des Gespräches ermittelt werden
    //Eigene Daten
    private String myIPAddress;
    private String myName = "129";
    private String myTag;
    private Address contactAddress;
    private ContactHeader contactHeader;
    //ProxyServer
    public String proxyAddress;
    private int proxyRTPPort;
    private String proxyTag;
    //CallDaten
    private String branch;
    public CallIdHeader callID;
    private long cSequenz;
    private String RTPCodec;

    public SIPmodel(Hauptfenster view) //Konstruktor für eigene Daten     
    {
        this.view = view;
        anrufer = false;
        anrufAngenommen = false;
        anrufCancel = false;
        proxyAddress = "";
        proxyTag = "";
        cSequenz = 1;
        werdeAngerufen = false;

        audioRinging = new AudioRinging();
        myTag = createString(8); //erzeugt Random Tag mit Länge 8
        branch = createString(18);//erzeugt Random branch mit Länge 18
        try {
            //wird an sich erstmal nicht gebraucht aber später sinnvoll um eigene Daten zu initialisieren
            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.MYSIPPORT, this.PROTOCOLTYP);
            this.sipProvider = this.sipStack.createSipProvider(this.listeningPoint);
            this.sipProvider.addSipListener(this);
            this.contactAddress = this.addressFactory.createAddress("sip:" + this.myName + "@" + this.myIPAddress + ":" + this.MYSIPPORT);
            this.contactHeader = this.headerFactory.createContactHeader(contactAddress);
        } catch (Exception e) {
            System.out.println("Fehler bei Initialisierung eigener Addresse");
            System.exit(-1);
        }
    }

    private String createString(int anzahl) //Erzeugt Random String in bestimmter länge für branch und Tag
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < anzahl; i++) {
            sb.append(TEST.charAt(RANDOM.nextInt(TEST.length())));
        }
        return sb.toString();
    }

    public void sendRegister(String serverIP, int serverPort) throws ParseException, InvalidArgumentException, SipException {
        proxyAddress = serverIP + ":" + serverPort;
        SIPRegister register = new SIPRegister(proxyAddress, myTag, myIPAddress, MYSIPPORT, branch, addressFactory, headerFactory, messageFactory, contactHeader, sipProvider.getNewCallId());
        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?
        resetCallParameter();
        anrufer = true;
        callID = sipProvider.getNewCallId();
        SIPSessionDescription sdp = new SIPSessionDescription(myName, myIPAddress, getMYRTPPORT(), null, anrufer);
        SIPInvite sipinvite = new SIPInvite(sipaddresse, proxyaddresse, myIPAddress, MYSIPPORT, myTag, branch, callID, cSequenz, addressFactory, sdp.getSessionDescription(), headerFactory, messageFactory, contactHeader);
        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
        {
            resetCallParameter();
            anrufer = false;
            //Falls Anruf ankommt, wichtige Variablen Zwischenspeichern (Branch, Cseq, CallID, proxyTag)
            branch = ((ViaHeader) requestEvent.getRequest().getHeader("Via")).getBranch();
            cSequenz = ((CSeqHeader) requestEvent.getRequest().getHeader("Cseq")).getSeqNumber();
            proxyTag = ((FromHeader) requestEvent.getRequest().getHeader("From")).getTag();
            callID = ((CallIdHeader) requestEvent.getRequest().getHeader("CallID"));
            //Standartabfolge sobald man angerufen wird
            try {
                //Trying Response erzeugen und abschicken
                SIP100Trying tempTrying = new SIP100Trying(messageFactory, requestEvent, headerFactory);
                sipProvider.sendResponse(tempTrying.getTryResponse());
                view.getTxtArea().append("Trying geschickt\n");
                lg.info("Trying ist raus");

                //Ringing Response erzeugen und abschicken noch Schleife mit "Anruf annehmen"
                SIP180Ringing tempRinging = new SIP180Ringing(requestEvent, myTag, contactHeader, headerFactory, messageFactory);
                Timer timer = new Timer();

                timer.scheduleAtFixedRate(new TimerTask() {
                    int counter = 0;

                    @Override
                    public void run() {
                        try {
                            if (anrufAngenommen != true && counter % 4 == 0) //Falls man nicht abnimmt, klingeln + Ringing schicken
                            {
                                sipProvider.sendResponse(tempRinging.getResponseRing());
                                audioRinging.startRinging();
                            } else if (anrufAngenommen == true) //bei annehmen, klingeln aufhören, OK mit SDP schicken
                            {
                                audioRinging.endRinging();
                                audioRinging.endRinging();
                                SIP200Ok respcall = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
                                SIPSessionDescription sdp = new SIPSessionDescription(myName, myIPAddress, getMYRTPPORT(), requestEvent, anrufer);
                                respcall.addSDP(sdp.getSessionDescription(), myTag);
                                sipProvider.sendResponse(respcall.getResponse());
                                view.getTxtArea().append("Telefonat beginnt\n");
                                timer.cancel();
                            } else if (anrufCancel == true) {
                                timer.cancel();
                            }
                            counter++;
                        } catch (SipException ex) {

                        }
                    }
                }, 0, 1000);
            } catch (SipException ex) {
                lg.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else if ((Request.ACK).equals(requestEvent.getRequest().getMethod())) {

        } else if ((Request.CANCEL).equals(requestEvent.getRequest().getMethod())) {
            System.out.println("Anruf cancel");
            view.getTxtArea().append("Anruf wurde gecancelt");
            try {
                //Cancel Request -> Response 200 Ok + Response 487 Request Terminated
                //200 Ok
                anrufCancel = true;
                SIP200Ok tempOk = new SIP200Ok(messageFactory, requestEvent, contactHeader, headerFactory);
                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 (SipException ex) {
                Logger.getLogger(SIPmodel.class.getName()).log(Level.SEVERE, null, 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, MYSIPPORT, 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) {
            //Überprüfen ob Ok Header mit SDP ist
            if ((((ContentTypeHeader) responseEvent.getResponse().getHeader("Content-Type")).getContentSubType()).equals("sdp")) {
                lg.info("Erfolgreichen Response 200 OK mit SDP bekommen");
                getRTPParameters(responseEvent);
                //   SIPACK ack = new SIPACK();
                //  sipProvider.sendResponse(rspns);
                /*
                System.out.println("Starte RTP Session");
                //String[] s = null;
                SoundSenderDemo aDemo = null;
//        try {
//            aDemo = new SoundSenderDemo(true);
//        } catch (UnknownHostException | InterruptedException ex) {
//            Logger.getLogger(CommandInvite.class.getName()).log(Level.SEVERE, null, ex);
//        }
                try {
                    aDemo.SendDemo(null);
                } catch (UnknownHostException | InterruptedException ex) {
                    Logger.getLogger(CommandInvite.class.getName()).log(Level.SEVERE, null, ex);
                }
                System.out.println("Beende RTP Session");
                */
            } else {
                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 void byecall(String calluser) throws ParseException, InvalidArgumentException, SipException {
        cSequenz++;
        SIPBye tempBye = new SIPBye(calluser, proxyAddress, myName, myIPAddress, myTag, proxyTag, MYSIPPORT, branch, callID, cSequenz, messageFactory, contactHeader, addressFactory, headerFactory);
        sipProvider.sendRequest(tempBye.getByeRequest());
        lg.info("Erfolgreiches Senden des BYE Headers");
    }

    public void getRTPParameters(ResponseEvent responseEvent) {
        int identifier = 0;
        String[] reqbody = (responseEvent.getResponse()).toString().split("\\s|" + System.getProperty("line.seperator"));
        for (String req : reqbody) {
            if (identifier == 1) {
                proxyRTPPort = Integer.valueOf(req);
                identifier = 0;
            } else if (identifier == 2) {
                RTPCodec = req;
                identifier = 0;
            }
            if (req.startsWith("m=audio")) {
                identifier = 1;
            } else if (req.startsWith("a=rtpmap:")) {
                identifier = 2;
            }
        }
    }

    public void resetCallParameter() {
        anrufAngenommen = false;
        anrufer = false;
    }

    public void anrufAnnehmen() {
        anrufAngenommen = true;
    }

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

    public Integer getMyPort() {
        return MYSIPPORT;
    }

    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 MYSIPPORT to set
     */
    /**
     * @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 String getBranch() {
        return branch;
    }

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

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

    /**
     * @return the proxyRTPPort
     */
    public int getProxyRTPPort() {
        return proxyRTPPort;
    }

    /**
     * @return the RTPCodec
     */
    public String getRTPCodec() {
        return RTPCodec;
    }

    /**
     * @return the MYRTPPORT
     */
    public static int getMYRTPPORT() {
        return MYRTPPORT;
    }

    /**
     * @return the MYRTCPPORT
     */
    public static int getMYRTCPPORT() {
        return MYRTCPPORT;
    }
}
