// RtspServer.java
// $Id: RtspServer.java,v 1 1997/07/10 15:00 osofia Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.rtsp.server;

import java.io.*;
import java.util.*;
import java.net.*;
import java.awt.*;
import w3c.rtsp.common.*;

/** 
 * RtspServer is instancieted by rtspd (Rtsp daemon) when a client send request.
//there is one RtspServer by RtspClient.
//A server can also send request to the client.sendvids
//Communication object are in the w3.www.rtsp package.
 */

class RtspServer implements Runnable{
  protected TimerStop timer=null;
  protected Socket client;
  protected DataInputStream input;
  protected PrintWriter output;
  protected static int SERVER_NB=0;//Servers actives number.
  protected static int SESSION_NB=0;//Session  number.
  protected RtspState state=null;
  protected Process p=null;
  protected boolean pause=false;
  protected int clientport=-1;
  protected int session_nb=-1;
  protected String logfilename=null;
  protected FileWriter mywriter=null;
  protected String PATH=null;
  private static final String monthnames[] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    };

  //vcr is streams server called to satisfy the client request.
  //we can also used for audio stream a prog called speak freely.a use exemple is given
  //close to the vcr one.

  protected String audio_server_used=new String("sfmike");
  protected String SFMIKE_PATH=null;
  protected String server_used=new String("vcr");
  protected String VCR_PATH=null;
  protected File myfile=null,vcrfile=null;
  protected String ABSPATH=null;
  
  public RtspServer(Socket client,String path){
    this.client=client;
    System.out.println("\nABS PATH:"+path);
    this.ABSPATH=path;
    this.SFMIKE_PATH=new String(path.concat(File.separator+
					    "helpers"+
					    File.separator+
					    "sfmike"+
					    File.separator));
    this.VCR_PATH=new String(path.concat(File.separator+
					 "helpers"+
					 File.separator+
					 "VCR"+
					 File.separator));
    this.logfilename= new String(path.concat(File.separator+
					     "server"+
					     File.separator+
					     "logs"+
					     File.separator+
					     "rtspd.log"));
    SERVER_NB++;
    session_nb=SESSION_NB++;
    try{
      input = new DataInputStream(client.getInputStream());
      output= new PrintWriter(client.getOutputStream());
      state = new RtspState("S"); 
    }
    catch(IOException E){// if problem try to close the socket.
      try{
	client.close();
      }
      catch(IOException Ex){
      }
    }
    new Thread(this).start();//Tread is active.
  }

  public synchronized void run(){
    String message="SERVER MESSAGE";
    int  ch=-1,len=-1;
    char line[]=null;
    String length=null;
    boolean msg=true;
    boolean un=true;

    // here,we treat the client message.

    try{
      StatusLine sl=new StatusLine("RTSP/1.0",200,333,"OK");
      ResponseHeader rh=new ResponseHeader();
      EntityHeader eh=new EntityHeader();
    loop:
      while ( true ) {
	line=new char[1024];
	// Read next line of input: reading header.
	while (true) {
	  int lineSize=0;
	  // read the header and extract content-length.

	  while(msg==true)
	    switch(ch = input.read()) {
	    case -1:
	      break loop;
	    case '\r':
	      input.mark(5);
	      if((ch=input.read())=='\n')
	        if((ch=input.read())=='\r')
		  if((ch=input.read())=='\n'){
		    //System.out.print("\nHEADER END ... ");
		    msg=false;
		    break;
		  }
		  else{
		    line[lineSize++]='\r';
		    line[lineSize++]='\n';
		    line[lineSize++]='\r';
		    // input.reset();
		    break;
		  }
		else{
		  System.out.print("\r\n");
		  line[lineSize++]='\r';
		  line[lineSize++]='\n';
		}
	      else
		line[lineSize++]='\r';
	    default:
	      System.out.print(new Character((char) ch));
	      line[lineSize++]=(char)ch;
	      break;
	    }
	  String stline=new String(line,0,lineSize);
	  StringTokenizer l=new StringTokenizer(stline);
	  while(l.hasMoreTokens()){
	    if(l.nextToken().equals("Content-Length:"))
	      length=l.nextToken();
	  }
	  len=Integer.parseInt(length);

	// create the space to receive the body.
	//mess is the body message.
	
	byte buff[]=new byte[len];
	for (int j = 0 ; j < len ; ) {
	  j += input.read(buff, j, len-j);
	}
	String mess = new String(buff, 0, len);
	msg=true;
	
	//recreate and treat the client message's.

	//we have received a request.
	
	if(isWhat(stline)==2){

	  // SRq is the request sent by the client.
	  
	  RtspRequest CRq=new RtspRequest(stline,mess);
	  TreatRequest(CRq);
	}
	
	//we have received a reply.

	if((isWhat(stline)==1)&&(un==true)){

	  //SRp is the reply sent by the client.

	  RtspReply SRp=new RtspReply(stline,mess);
	  RtspRequest SRq=new RtspRequest(new RequestLine("TEST",
					  new RtspURL("rtsp://WWW45.inria.fr"),
					  "RTSP/1.0",444),
					  new GeneralHeader(),
					  new RequestHeader(),
					  new EntityHeader(),
					  new TransportHeader(),
					  "SERVER REQUEST");
	  SRq.emit(output);
	  un=false;
	}
	if(isWhat(stline)==-1)//unknown message.
	  {
	    RtspReply badMessage=new RtspReply(new StatusLine("RTSP/1.0",
					       400,
					       000,
					      "Bad Message"),
					       new GeneralHeader(new Date()),
					       new ResponseHeader(),
					       new EntityHeader(),
					       new TransportHeader(),
					       "");
	    badMessage.emit(output);
	  }
	System.out.println("\n-------------------------------------------------\n");
	}
      }
    }catch(IOException E){
      SERVER_NB--;
      this.stop();
      E.printStackTrace();
    }catch(NullPointerException E){
      E.printStackTrace();
      SERVER_NB--;     
      System.out.println("\nEND OF CATCHING NULL POINTER EXCEPTION : \n");
      this.stop();
    }catch(ArrayIndexOutOfBoundsException E){
      SERVER_NB--;
      this.stop();
      E.printStackTrace();
    }
    //end of the loop.
    SERVER_NB--;//the server tread is dead.

  }

  //return 1 if it's a reply, 2 if it's request and else -1.

  public int isWhat(String message){
    int result=-1;
    if(message.startsWith("RTSP"))
      result=1;
    else
      result=2;
    
    return result; 
  }

  //we have the request,and we can treat it.

  public void TreatRequest(RtspRequest request){

    String ex=request.requestline.getUrlRequest().getExtensionFile();
    System.out.println("\nEXT FILE :"+"<"+ex+">");

    //change the server state.
    int state_result = state.nextState(request.requestline.getMethod());

    //here,we have to make the reply.

    //bad request.

    if(state_result==-1){
      RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						   405,
						 request.requestline.getSeqNo(),
						   "Method Not Allowed"),
				    new GeneralHeader(new Date()),
				    new ResponseHeader(),
				    new EntityHeader(),
				    new TransportHeader(),
				    "STATE MESSAGE ERROR");
      ReccordLog(request,reply);
      reply.emit(output);
    } 
    else{

      //we have to treat the request : setup
      
      if(request.requestline.getMethod().equals("SETUP")){

	//we have to reply and catch to port where the client want to receive stream.
	//And test if the client has got a session_nb.

	if(request.requestheader.getSession()==-1){
	  
	  RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						       request.requestline.getSeqNo(),
						       "OK"),
					new GeneralHeader(new Date()),
					new ResponseHeader(session_nb),
					new EntityHeader(),
					new TransportHeader("udp/rtp",8010),
					"");
	  ReccordLog(request,reply);
	  reply.emit(output);
	  clientport=request.transportheader.getPort();
	}
	else{
	  ResponseHeader firstRh=new ResponseHeader();
	  this.session_nb=request.requestheader.getSession();
	  firstRh.setSession(session_nb);
	  RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						       200,
						       request.requestline.getSeqNo(),
						       "OK"),
					new GeneralHeader(new Date()),
					firstRh,
					new EntityHeader(),
					new TransportHeader(),
					"");
	  ReccordLog(request,reply);
	  reply.emit(output);
	  clientport=request.transportheader.getPort();
	}
      }

      //treat the request "play".

      if((request.requestline.getMethod().equals("PLAY"))
	 &&
	 (state_result==0)){

	//we have to reply first and send the sound later.

	ResponseHeader rh=new ResponseHeader(session_nb);

	RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						     request.requestline.getSeqNo(),
						     "OK"),
				      new GeneralHeader(new Date()),
				      rh,
				      new EntityHeader(),
				      new TransportHeader(),
				      "");

	//testing if the asked file exists,and launching the associated treatement.


	{ boolean not_found=false;
          {

	  String extfile=request.requestline.getUrlRequest().getExtensionFile();
	  System.out.println("\nFile extension :"+"<"+extfile+">");
          String filename=
             new String(request.requestline.getUrlRequest().getFile());
	  if(extfile.equalsIgnoreCase("au")) {
            File tmp=new File(SFMIKE_PATH+"audio/"+filename);
            if (tmp.exists())
	      sendSound(request.requestline.getUrlRequest(),clientport,request);
            else
              not_found=true;
          }
	  else if(extfile.equalsIgnoreCase("vcr")) {
            File tmp=new File(VCR_PATH+filename);
            if (tmp.exists())
	      sendVideo(request.requestline.getUrlRequest(),clientport,request);
            else
              not_found=true;
          } else {
	    System.out.println("\nUNKNOWN TYPE !!!!!");
	    reply=new RtspReply(new StatusLine("RTSP/1.0",
					       404,
					       request.requestline.getSeqNo(),
					       "Not Found"),
				new GeneralHeader(new Date()),
				new ResponseHeader(session_nb),
				new EntityHeader(),
				new TransportHeader(),
				"");
	  }
	}
	if (not_found) {
	  System.out.println("\nUNKNOWN FILE !!!!!");
	  reply=new RtspReply(new StatusLine("RTSP/1.0",
						       404,
						       request.requestline.getSeqNo(),
						       "Not Found"),
					new GeneralHeader(new Date()),
					new ResponseHeader(session_nb),
					new EntityHeader(),
					new TransportHeader(),
					"");
	}
       }


	ReccordLog(request,reply);
	reply.emit(output);
      }
      if((request.requestline.getMethod().equals("PLAY"))&&(pause==true)){
	RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						     request.requestline.getSeqNo(),
						     "OK"),
				      new GeneralHeader(new Date()),
				      new ResponseHeader(session_nb),
				      new EntityHeader(),
				      new TransportHeader(),
				      "");
	ReccordLog(request,reply);
       	reply.emit(output);
	PlayAfterPause(); 
      }

      //treat the request stop.
      
      if((request.requestline.getMethod().equals("TEARDOWN"))&&(state_result==0)){
	RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						     request.requestline.getSeqNo(),
						     "OK"),
				      new GeneralHeader(new Date()),
				      new ResponseHeader(session_nb),
				      new EntityHeader(),
				      new TransportHeader(),
				      "");
	ReccordLog(request,reply);
	reply.emit(output);
	StopSendSound();
      }

   //we have to treat the request : Option
      
      if(request.requestline.getMethod().equals("OPTIONS")){
	
	//we have to reply and catch to port where the client want to receive stream.
	ResponseHeader RepHeader = new ResponseHeader(session_nb);
	RepHeader.setPublic("DESCRIBE, TEARDOWN, PLAY");
	RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						     request.requestline.getSeqNo(),
						     "OK"),
				      new GeneralHeader(new Date()),
				      RepHeader,
				      new EntityHeader(),
				      new TransportHeader(),
				      "");
	ReccordLog(request,reply);
	reply.emit(output);
      }

      //treat the request Pause.

      if((request.requestline.getMethod().equals("DESCRIBE"))){
	String descriptionBody =new String();
	File tmpf=null;
	//we have to test if the sdp associated file exits.
	
	if((tmpf=new File(ABSPATH+"/Sdp/"+"sdp-"+request.requestline.getUrlRequest().getFile())).exists()){
	  try{
	    BufferedReader br = new BufferedReader(new FileReader(tmpf));
	    String tmp=new String();
	    while((tmp=br.readLine())!=null){
	      descriptionBody+=("\n\r"+tmp);
	    }
	  }catch(IOException E){
	    System.out.println("\nERREUR I/O :");
	    E.printStackTrace();
	  }
	  RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						       200,
						       request.requestline.getSeqNo(),
						       "OK"),
					new GeneralHeader(new Date()),
					new ResponseHeader(session_nb),
					new EntityHeader(),
					new TransportHeader(),
					descriptionBody);
	  ReccordLog(request,reply);
	  reply.emit(output);
	}
	else{
	  System.out.println("\nSdp associated file doesn't exist");
	  RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						       404,
						       request.requestline.getSeqNo(),
						       "Not Found"),
					new GeneralHeader(new Date()),
					new ResponseHeader(session_nb),
					new EntityHeader(),
					new TransportHeader(),
					"");
	  ReccordLog(request,reply);
	  reply.emit(output);
	}
      }
      else{
	RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
						     200,
						     request.requestline.getSeqNo(),
						     "OK"),
				      new GeneralHeader(new Date()),
				      new ResponseHeader(session_nb),
				      new EntityHeader(),
				      new TransportHeader(),
				      "");
      } 
    }    
  }

  public void stop(){
    try{
      client.close();
    }
    catch(IOException E){
      // System.out.println("\n IO ERROR :"+E.getMessage());
    }
  } 

  //we have to make setup to known where we have to play host/port.

  public void sendSound(RtspURL sample,int port,RtspRequest request){
   

      try{
      String who=client.getInetAddress().getHostAddress();
      Runtime run=Runtime.getRuntime();
      p=run.exec(SFMIKE_PATH+audio_server_used+" "
		 +who+":"+port
		 +" -RTP "+SFMIKE_PATH
		 +"audio/"+sample.getFile());
    }catch(IOException E){
      System.out.println("\nI/O ERROR SENDING SOUND:");
      E.printStackTrace();
      RtspReply reply=new RtspReply(new StatusLine("RTSP/1.0",
					 404,
					 request.requestline.getSeqNo(),
					 "Not Found"),
			  new GeneralHeader(new Date()),
			  new ResponseHeader(session_nb),
			  new EntityHeader(),
			  new TransportHeader(),
			  "");
      
      ReccordLog(request,reply);
    }
    try{
      Thread.sleep(2000);
    }catch(InterruptedException Ex){
      Ex.printStackTrace();
    }
  } 


  public void sendVideo(RtspURL sample,int port,RtspRequest req){

    //we have to construct the command file compelled to use
    //vcr.

    try{

      //building the vcr config file.
      
      vcrfile=new File(VCR_PATH+"/"+req.requestline.getUrlRequest().getFile());
      FileWriter vcrWriter=new FileWriter(vcrfile);
      vcrWriter.write("<MBONE_VCR 1.0>\n");
      vcrWriter.write("# This file was generated by rtspd to use MBone VCR v1.4a02.\n");
      vcrWriter.write("session_date=Wed May 14 15:16:12 MET DST 1997\n");
      vcrWriter.write("session_name=cannes\n");
      vcrWriter.write("session_desc=sdaf (osofia@www47.inria.fr)\n");
      vcrWriter.write("session_max_ttl=127\n");
      vcrWriter.write("1=media_port="+(clientport)+"\n");
      //changes here
      System.out.println("\nADRESS:"+client.getInetAddress().getHostAddress());
      vcrWriter.write("1=media_ip="+client.getInetAddress().getHostAddress()+"\n");
      vcrWriter.write("1=media_name=audio\n");
      vcrWriter.write("1=media_type=1\n");
      vcrWriter.write("1=media_mute=0\n");
      vcrWriter.write("1=media_desc=\n");
      vcrWriter.write("1=media_cmd=vat\n");
      vcrWriter.write("1=media_cid=31411\n");
      vcrWriter.write("1=media_ttl=127\n");
      vcrWriter.write("2=media_port="+(clientport-2)+"\n");
      vcrWriter.write("2=media_ip="+client.getInetAddress().getHostAddress()+"\n");
      vcrWriter.write("2=media_name=video\n");
      vcrWriter.write("2=media_type=1\n");
      vcrWriter.write("2=media_mute=0\n");
      vcrWriter.write("2=media_desc=\n");
      vcrWriter.write("2=media_cmd=\n");
      vcrWriter.write("2=media_cid=60887\n");
      vcrWriter.write("2=media_ttl=16\n");

      //we have to calculate the duree of the doccument if it was specified
      //by the range field.
      
      Smpte duree=null;
      String file = new String(sample.getFile());
      RtspURL actual_url=req.requestline.getUrlRequest();
      if((file.lastIndexOf(':'))>(file.lastIndexOf(".vcr"))){
         String range_string= new String(file.substring(file.lastIndexOf(".vcr")+1));
	 System.out.println("\nRANGE :"+range_string);
	Range myrange=new Range(range_string);
	duree=myrange.differ();
      }

     //building the command file.
      
      myfile=new File(VCR_PATH+"CMD"+session_nb+".cmd");
      FileWriter myWriter=new FileWriter(myfile);
      myWriter.write("load "+VCR_PATH+sample.getFile()+"\n");
      myWriter.write("play\n");
      if(duree!=null){
	myWriter.write("wait "+duree+"\n");
	myWriter.write("stop\n");
	myWriter.write("quit\n");
      }
      myWriter.close();
      vcrWriter.close();

      String who=client.getInetAddress().getHostAddress();
      Runtime run=Runtime.getRuntime(); 
      System.out.println("\nLAUNCHING  :"+server_used);
      System.out.println(VCR_PATH+server_used+"  -c "+VCR_PATH+"CMD"+session_nb+".cmd");
      p=run.exec(VCR_PATH+server_used+"  -c "+VCR_PATH+"CMD"+session_nb+".cmd");

      //launching the TimerStop.

      //  if(duree!=null){
      //  System.out.println("\nDUREE:"+duree.toSecond());
      // timer=new TimerStop(duree.toSecond(),p);
      // timer.start();
      // }
	
    }catch(IOException E){
      System.out.println("\nERREUR I/O :");
      E.printStackTrace();
    }
  }

  //stop to send audio stream.kill the process associated.
  
  public void StopSendSound(){
    try{
      this.p.destroy();
      p=null;
      if(myfile!=null)
	myfile.delete();
    } catch(NullPointerException E){
      //  System.out.println("\n IO ERROR :"+E.getMessage());
    }
  }

  //set the audio stream int a pause state.

  public synchronized void Pause(){
    try{
    this.p.wait();
    }catch(InterruptedException E){
    System.out.println("\nINTERRUPED EXCEPTION ERROR :");
    E.printStackTrace();
    }
  }

  public synchronized void PlayAfterPause(){
    this.p.notify();
  } 


  public synchronized void ReccordLog(RtspRequest request,RtspReply reply){
    Date now=new Date();
    String user=null;
    try{
    this.mywriter=new FileWriter(logfilename,true);
    this.mywriter.write(client.getInetAddress().getHostName()
			+ " " + "-"                            // user name
			+ " " + ((user == null ) ? "-" : user) // auth user name
			+ ((now.getDate() < 10) ? " [0" : " [")
			+ (now.getDate()                       // current date
			   + "/" + monthnames[now.getMonth()]
			   + "/" + (now.getYear() + 1900)
			   + ((now.getHours() < 10)
			      ? (":0" + now.getHours())
			      : (":" + now.getHours()))
			   + ((now.getMinutes() < 10)
			      ? (":0" + now.getMinutes())
			      : (":" + now.getMinutes()))
			   + ((now.getSeconds() < 10)
			      ? (":0" + now.getSeconds())
			      : (":" + now.getSeconds()))
			   + ((now.getTimezoneOffset() < 0)
			      ? " " + (now.getTimezoneOffset() / 60)
			      : " +" + (now.getTimezoneOffset() / 60))
			   + "]")
			+ " \"" + request.requestline.getMethod()      // request line
			+ " " + request.requestline.getUrlRequest()
			+ " " + request.requestline.getVersion()
			+ "\" " + reply.getStatusLine().getStatusCode()        // reply status
			+ " -"                      // # of emited bytes
			+ "\n" );
    this.mywriter.close();
    }catch(IOException E){
      E.printStackTrace();
    }
  }

  public boolean isFilePresent(String filename){
    File tmp=new File(SFMIKE_PATH+"audio/"+filename);
    //   System.out.println("\n"+PATH+"/common/audio/"+filename);
    return tmp.exists();
  }

  public String getBanner() {
    return "Rtsp[alpha]";
  }
}














