
import java.applet.*;
import java.awt.Container;
import java.awt.FileDialog;
import java.awt.Frame;
import java.net.*;
import java.io.*;
import java.util.concurrent.SynchronousQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import netscape.javascript.*;

public class MediaExporter extends Applet {

   private static final long serialVersionUID = 349441547471105161L;
   private ExportThread exportThread;
   private JSObject jsObject = null;

   private static int JSCALL_NEWDATACHUNK = 0;
   private static int JSCALL_EXPORTERREADY  = 1;
   private String jsCallbacks[] = new String[2];
   
   private SynchronousQueue<String[]> exportQueue = null;
   private JavaScriptEventThread javaScriptEventThread = null;
   
   public boolean rtspSupported()
   {
      return true;
   }

   public void init()
   {
		 
   }
	 
   public void start()
   {
      jsObject = JSObject.getWindow(this);
      exportThread = null;
         
      // Get JavaScript callback names 
      jsCallbacks[JSCALL_NEWDATACHUNK]   = getParameter("onNewDataChunk");
      jsCallbacks[JSCALL_EXPORTERREADY]  = getParameter("onExporterReady");
      
      exportQueue = new SynchronousQueue<String[]>();
      
      javaScriptEventThread = new JavaScriptEventThread();
      javaScriptEventThread.start();
      
     
   }

   public void stop()
   {
      cancelExport(); 
      if (javaScriptEventThread != null) javaScriptEventThread.done();
   }
/*	
        public void destroy()
        {
		 
        }
*/ 

   public String selectOutputFile(String defaultFilename)
   {
      Frame parent = findParentFrame();
      FileDialog fd = new FileDialog(parent, "Export media clip", FileDialog.SAVE);
            
      fd.setFile(defaultFilename);
      fd.show(true);
         
      if (fd.getFile() == null) return null;
      
      defaultFilename = fd.getDirectory() + fd.getFile();
     // if(!defaultFilename.endsWith(".mov")) defaultFilename += ".mov";
   
      return defaultFilename;
   }
   
   public boolean startExport(String url,
                              String outFilename)
   {
      if ( outFilename == null || outFilename.length() == 0)
      {
         System.out.println("Output file name is not specified.");
         return false;
      }
      
      String params[] = {url, outFilename};
      
      try {
         exportQueue.put(params);
      }  
      catch (InterruptedException e) 
      {	
         e.printStackTrace();
      }
      
      return true;
        
   }
     
   public void cancelExport()
   {
      if (exportThread != null)
      {
         exportThread.cancel();
      }
   }
     
   private Frame findParentFrame()
   {
      Container c = this;
      while(c != null)
      {
         if (c instanceof Frame)
            return (Frame)c;
         c = c.getParent();
      }
      return (Frame)null;
   }
     
   private void callJSFunction(int name, String params)
   {      
	  
      String callbackName = jsCallbacks[name];
      if (callbackName != null)
      {
         try 
         {
            jsObject.eval(callbackName + "(" + params + ")");
         }
         catch (Exception ex) 
         {
            System.out.println("Caught " + ex.getClass().getName() + ", msg=" + ex.getMessage() + " in " + callbackName + " callback");
         }
      }
       
   }
     
   
   /** 
       To prevent Security exception from js code
       Taken from http://forum.java.sun.com/thread.jspa?forumID=63&threadID=524815
   */
   class JavaScriptEventThread extends Thread 
   {
      public void run() 
      {
         callJSFunction(JSCALL_EXPORTERREADY, "");
         while (true) 
         {
            try 
            {
               String[] params = new String[3];
               params = exportQueue.take();
               if (params[0].compareTo("done") == 0) 
               {
                  break;
               }
               else 
               {
                  try 
                  {
                     exportThread = new ExportThread(params[0], params[1]);
                  } 
                  catch (MalformedURLException ex) 
                  {
                     ex.printStackTrace();
						
                     callJSFunction(JSCALL_NEWDATACHUNK, "0,0,\"" + ex.toString() + "\"");
                  }         
                	     
                  exportThread.start();                  
               }
            } 
            catch (InterruptedException ex) {}
         }
      }
     
      public void done()
      {
         try 
         {
            String params[] = {"done"};
            exportQueue.put(params);
         } 
         catch (InterruptedException e) {}        	
      }
   }  
   
   class ExportThread extends Thread
   {
      private Boolean canceled   = false;
      private String outFileName = null;
      private String url = null;
    	 
      public ExportThread(String url, 
                          String filename) throws MalformedURLException
      {   		 
         super("ExportThread");
         setDaemon(true);
         
         this.url = url;
         this.outFileName = filename;    	

      }
    	  
      public void cancel()
      {
         canceled = true;
      }
    	 
      public void run()
      {
         RandomAccessFile of = null;
         DataInputStream is = null;
         OutputStream    os = null;
         Boolean success = false; 

         /*  if (!setOutFileName() || url == null) 
         {
            System.out.println("Export cancelled");
            callJSFunction(JSCALL_NEWDATACHUNK, "0,100,null");
            return;
            }*/
                           
         try
         {           	    
            String host  = getHostFromURL(url); 
            int    port  = getPortFromURL(url, 8554); 

            Socket socket = new Socket(host, port);

            is = new DataInputStream(socket.getInputStream());
            os = socket.getOutputStream();

            System.out.println("Connected to " + host + ":" + port);

            String req = "XEXPORT " + url + " RTSP/1.0\r\nCSeq: 1\r\n\r\n";
         
            System.out.println("Request:");
            System.out.println(req);

            os.write(req.getBytes());

            String resp  = is.readLine();
            boolean signed = false;

            while (true) 
            {       
               String line =  is.readLine();
               if (line == null || line.length() == 0)
                  break;

               if (line.contains("X-Signed: yes"))
                  signed = true;

//            resp += line + "\r\n";
            }

            System.out.println("Response:");
            System.out.println(resp);
          
            if (!resp.contains("RTSP/1.0 200"))
            {
               socket.close();
               throw new Exception(resp.replaceAll("RTSP/1.0 \\d+ ", ""));
            }

				
            byte data[] = new byte[65536*4];
            String header, contentType = null;
            int contentLength = -1; 
            int oldprogress = 0, progress = 0;
            long seek = 0;
            long clipSize = 0;

            Pattern contentTypeRegex       = Pattern.compile("^Content-Type: (.+)");
            Pattern contentLengthRegex     = Pattern.compile("^Content-Length: (.+)");
            Pattern xProgressRegex         = Pattern.compile("^X-Progress: (.+)%");
            Pattern xSeekRegex             = Pattern.compile("^X-Seek: (.+)");
            	  
            System.out.println("Downloading " + outFileName + " from " + url);

            of = new RandomAccessFile(outFileName, "rw");
            of.setLength(0);

            while ((header = is.readLine()) != null && !canceled)
            {
               Matcher m;
               if ((m=contentTypeRegex.matcher(header)).find())
               {
                  contentType = m.group(1);
               }
               else if ((m=contentLengthRegex.matcher(header)).find())
               {
                  contentLength = Integer.parseInt(m.group(1));
               }
               else if ((m=xProgressRegex.matcher(header)).find())
               {
                  progress = Integer.parseInt(m.group(1));
               }
               else if ((m=xSeekRegex.matcher(header)).find())
               {
                  seek = Long.parseLong(m.group(1));
               }

               if (header.length() == 0 && contentLength >= 0)
               { 
                  if (contentLength == 0 && progress == 100) // we are done..
                  {
                     success = true;
                     if (signed && !(data[4] == 'd' && data[5] == 's' && data[6] == 'i' && data[7] == 'g'))
                     {
                        // container is not signed.
                        callJSFunction(JSCALL_NEWDATACHUNK, "0,100,\"Digital signature is missing in the movie. Please contact your system administrator.\"");
                     }
                     else
                     {
                        callJSFunction(JSCALL_NEWDATACHUNK, "0,100,null");
                     }
                     break;
                  }

                  int bytesRead = 0;
                  while (bytesRead < contentLength)
                  {
                     int ret = is.read(data, bytesRead, contentLength - bytesRead);
                     if (ret <= 0)
                     {
                        System.out.println("ERROR ret <= 0");
                        callJSFunction(JSCALL_NEWDATACHUNK, "0,0,\"Read data from network failed\"");
                        break;
                	                
                     }
                     bytesRead += ret;
                  }

                	      
                  if (!contentType.equalsIgnoreCase("video/quicktime"))
                  {
                     System.out.println("ERROR: content type is " + contentType);
                     System.out.println(new String(data));
                     
                     callJSFunction(JSCALL_NEWDATACHUNK, "0,0,\"Export failed due server issues\"");
                     break;
                  }
                  
                  clipSize += contentLength;
                  
                  /*
                    System.out.println("read " + contentLength +" bytes");
                    System.out.println("progress: " + progress);
                    System.out.println("seek: " + seek);
                  */
                  if (oldprogress != progress)
                  {
                     callJSFunction(JSCALL_NEWDATACHUNK, clipSize + "," + progress + "," + "null");
                     oldprogress = progress;
                  }

                  if (seek != 0)
                  {
                     of.seek(seek);
                     seek = 0;
                  }
                	       
                  of.write(data, 0, contentLength);
                	        
                  contentLength = -1;
               }    
            }
            

            if (of != null) of.close();
            if (is != null) is.close();
            if (os != null) os.close();
          
         }
         catch (Exception ex) 
         {
            System.err.println("Error: " + ex.toString());
            ex.printStackTrace(System.err);
            
            callJSFunction(JSCALL_NEWDATACHUNK, "0,0,\"" + ex.getMessage() + "\"");
         }
         
         
         if (success)
         {
            System.out.println(outFileName + " downloaded successfully");
         }
         else  
         {	
            new File(outFileName).delete();
         		
            System.out.println(outFileName + " download failed");
         }
         
      }

      private int getPortFromURL(String url, int defaultPort) 
      {
         // Trim schema and path
         String hostStr = null;
         int sepIdx = url.indexOf("//");
         if (sepIdx < 0) return defaultPort;
         hostStr = url.substring(sepIdx + 2);
         sepIdx = hostStr.indexOf("/");
         if (sepIdx < 0) return defaultPort;
         hostStr = hostStr.substring(0, sepIdx);
    
         sepIdx = hostStr.indexOf(':');
         if (sepIdx < 0) 
         {
            return defaultPort;
         } 
         else 
         {
            String portStr = hostStr.substring(sepIdx + 1).trim();
            try 
            {
               return Integer.parseInt(portStr);
            } 
            catch (NumberFormatException e) 
            {
               return defaultPort;
            }
         }
      }

      private String getHostFromURL(String url) 
      {
         // Trim schema and path
         String hostStr = null;
         int sepIdx = url.indexOf("//");
         if (sepIdx < 0) return "";
         hostStr = url.substring(sepIdx + 2);
         sepIdx = hostStr.indexOf("/");
         if (sepIdx < 0) return "";
         hostStr = hostStr.substring(0, sepIdx);
    

         sepIdx =  hostStr.indexOf(':');
         if (sepIdx < 0) 
         {
            return hostStr;
         } 
         else 
         {
            return hostStr.substring(0, sepIdx);
         }
      }

   }

	 
}
