import ij.plugin.*;
import ij.*;
import ij.io.*;
import ij.process.*;
import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.awt.event.*;
import ij.gui.*;
import ij.plugin.filter.PlugInFilter;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.imageio.*;
import javax.imageio.stream.*;
import com.xuggle.xuggler.demos.CaptureScreenToFile;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.ICodec;
import com.xuggle.mediatool.IMediaViewer;
import static com.xuggle.xuggler.Global.DEFAULT_TIME_UNIT;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.concurrent.TimeUnit;

public class Capture_Screen implements PlugIn, MouseListener, ActionListener {

	ImagePlus img;
	ImageCanvas canvas;
	int fps = 1, framerate, frameLose, n=0, maxFps = 1, quality;
	long bitrate;
	int X_pres = -1, Y_pres = -1, X_rel = -1, Y_rel = -1, height = 0, width = 0, x = 0, y = 0;
	boolean coord = false, stopCapture = false, saveImages = false, saveVideo = true;
	Font arial = new Font("Arial",Font.PLAIN,14);
	ScreenGrabber sg = new ScreenGrabber();
	Roi r;
	DirectoryChooser dir;
	String directory, typeVideo = "mp4", typeImages = "jpeg", log="";
	
	public void run(String arg) 
	{
		IJ.showMessage("Welcome","Welcome to Capture Screen Plugin.\nSelect output directory.");
		dir = new DirectoryChooser("Select save folder");
		if(dir.getDirectory() == null) return;
		directory = dir.getDirectory();
		
		//new TestThread().start();
		
		IJ.showMessage("First step","Now open video player you want to capture, place it on your desktop and press Ok.");
		IJ.wait(500);
		
		img = sg.captureScreen();
		
		ImageWindow iw = new ImageWindow(img);
		canvas = iw.getCanvas();
		canvas.addMouseListener(this);
		
		new WaitForUserDialog("Area selection","Using the Rectangular Selection Tool, select area to capture and press Ok").show();
		calcolaHeightWidth();
		
		while(height <= 0 || width <= 0) 
		{
			GenericDialog gd = new GenericDialog("Error");
			gd.addMessage("Invalid coordinates!\nPress Ok to reselect area to capture or Cancel to exit.", new Font("Arial",Font.PLAIN,14));
			gd.showDialog();
			if (gd.wasCanceled()) 
			{	iw.close();
				return;
			}
			new WaitForUserDialog("Error","Invalid coordinates!\nUsing the Rectangular Selection Tool, select area to capture and press Ok").show();
			calcolaHeightWidth();
		}
		
		r = new Roi(x, y, width, height);
		r.setFillColor(new Color(0,255,0));
		img.setRoi(r);
		
		GenericDialog gd = new GenericDialog("Confirm");
		gd.addMessage("Confirm the selected area?\nPress Ok to continue or Cancel to exit.", arial);
		gd.showDialog();
		if (gd.wasCanceled()) 
		{	
			iw.close();
			return;
		}
		iw.close();
		
		String[] typesImages = {"jpeg","png"};
		String[] typesVideo = {"mp4","flv","mov"};
		
		gd = new GenericDialog("Selection output mode");
		//gd.addSlider("Frames per second", 1, maxFps, 1);
		gd.addCheckbox("    Save as images", false);
		gd.addChoice("Images type*", typesImages, "jpeg");
		gd.addSlider("   JPEG quality", 1, 100, 100);
		gd.addCheckbox("    Save as video", true);
		gd.addChoice("Video type", typesVideo, "mp4");
		gd.addMessage("\n*PNG is lossless and this leads an higher\nexecution time.", new Font("Arial",Font.PLAIN,11));
		
		gd.showDialog();
		if (gd.wasCanceled()) return;
		//fps = (int)gd.getNextNumber();
		saveImages = gd.getNextBoolean();
		typeImages = gd.getNextChoice();
		if(!saveImages) typeImages = "jpeg";
		quality = (int)gd.getNextNumber();
		saveVideo = gd.getNextBoolean();
		typeVideo = gd.getNextChoice();
		
		if(!saveImages && !saveVideo)
		{
			IJ.showMessage("Error","You must select at least one output type.");
			return;
		}
		IJ.showMessage("Info","In according to options chosen, now your system's perfomance will be tested.\nPress Ok to continue.");
		fps = testFps();
		
		//new TestThread().start();
		
		gd = new GenericDialog("Selection Fps");
		gd.addSlider("Frames per second", 1, fps, (int)(fps/2));
		gd.showDialog();
		if(gd.wasCanceled()) return;
		
		fps = (int)gd.getNextNumber();
		
		framerate = 1000 / fps;
		
		IJ.wait(500);
		IJ.showProgress(0.0f);
		
		gd = new GenericDialog("Capture");
		gd.addMessage("1. Press Start to start capture screen.\n2. Press Stop to stop capture screen\n3. Press Ok to Save the capture.", arial);
		Button start;
		Button stop;
		Panel pannello;	
		pannello = new Panel();
		start = new Button("Start");
		start.addActionListener(this);
		pannello.add(start);
		stop = new Button("Stop");
		stop.addActionListener(this);
		pannello.add(stop);
		gd.addPanel(pannello);
			
		gd.showDialog();
		if(gd.wasCanceled()) return;
		if(n == 0)
		{
			IJ.showMessage("Error","0 frames captured.");
			return;
		}
		IJ.showProgress(0.0f);
		if(saveImages)
		{
			int k = 0;
			File source = new File(directory+"temp\\");
			File destination = new File(directory);
			if(typeImages.equals("jpeg") && quality < 100)
			{
				try
				{	
					float qual = (float)(quality/100.0f);
					for (int i = 0; i < n; i++) 
					{
						File imageFile = new File(directory + "temp\\" + "capture"+i+".jpeg");
						File compressedImageFile = new File(directory+"capture"+i+".jpeg");
						InputStream is = new FileInputStream(imageFile);
						OutputStream os = new FileOutputStream(compressedImageFile);
						BufferedImage image = ImageIO.read(is);
						Iterator<javax.imageio.ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpeg");
						javax.imageio.ImageWriter writer = (javax.imageio.ImageWriter) writers.next();
						ImageOutputStream ios = ImageIO.createImageOutputStream(os);
						writer.setOutput(ios);
						javax.imageio.ImageWriteParam param = writer.getDefaultWriteParam();
						param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
						param.setCompressionQuality(qual);
						writer.write(null, new IIOImage(image, null, null), param);
						is.close();
						os.close();
						ios.close();
						writer.dispose();
						imageFile.delete();
						IJ.showProgress((double)((1.0f/n)*(i+1)));
					}
					source.delete();
				}
				catch(Exception e) {}

			}
			else
			{
				for (File files : source.listFiles()) 
				{
					files.renameTo(new File(directory, files.getName()));
					files.delete();
					IJ.showProgress((double)((1.0f/n)*(++k)));
				}
				source.delete();
			}
		}
		
		if(saveVideo)
		{
			try
			{
				
				int videoStreamIndex = 0;
				int videoStreamId = 0;
				long frameRate = DEFAULT_TIME_UNIT.convert(framerate, MILLISECONDS);
				int count = 0;
				String outputFilename = directory + "capture." + typeVideo;
				IMediaWriter writer = ToolFactory.makeWriter(outputFilename);
				
				if(typeVideo.equals("mp4"))
					writer.addVideoStream(videoStreamIndex, videoStreamId, ICodec.ID.CODEC_ID_MPEG4, width, height);
				if(typeVideo.equals("flv"))
					writer.addVideoStream(videoStreamIndex, videoStreamId, ICodec.ID.CODEC_ID_FLV1, width, height);
				if(typeVideo.equals("mov"))
					writer.addVideoStream(videoStreamIndex, videoStreamId, width, height);	
				
				IJ.showProgress(0.0f);
				String dirImg = directory;
				if(!saveImages)	dirImg = directory + "temp\\";
				while (count < n)
				{
					BufferedImage src = ImageIO.read(new File(dirImg + "capture" + count + "." + typeImages));
					BufferedImage frame = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
					Graphics2D gr = (Graphics2D)frame.createGraphics();
					gr.drawImage(src, 0, 0, null);
					
					writer.encodeVideo(videoStreamIndex, frame, (count * frameRate),  DEFAULT_TIME_UNIT);
					IJ.showProgress((double)((1.0f/n)*(++count)));
				}
				writer.close();
				File file = new File(directory + "capture." + typeVideo);
				bitrate = ((file.length()/1024)/((n/fps)-1))*8;
			}
			catch(Exception e) {}
			
			IJ.showProgress(0.0f);
			if(!saveImages)
			{
				int i = 0;
				File source = new File(directory + "temp\\");
				for (File files : source.listFiles()) 
				{
					files.delete();
					IJ.showProgress((double)((1.0f/n)*(++i)));
				}
				source.delete();
			}
			
		}
		generaReport();
		IJ.showMessage("Info",n + " frame captured.\nCapture completed and report generated!");
		IJ.showProgress(1.0f);
	}
	
	public void actionPerformed(ActionEvent e) 
	{
        if(e.getActionCommand().equals("Start"))
		{	
			new CaptureThread().start();
		}
		if(e.getActionCommand().equals("Stop"))
        {  
			stopCapture = true;
		}
	}
	
	class CaptureThread extends Thread 
	{
		public CaptureThread() 
		{
			super();
		}
		public void run() 
		{
		try{
			int time;
			long start, end;
			String tempDir = directory + "temp\\";
			new File(tempDir).mkdirs();
			Rectangle screenRect = new Rectangle(x, y, width, height);
			
			while(!stopCapture)
			{
				IJ.showProgress(0.0f);
				for(int i = 0; i < fps; i++)
				{
					start = System.nanoTime();
					BufferedImage capture = new Robot().createScreenCapture(screenRect);
					ImageIO.write(capture, typeImages, new File(tempDir+"capture"+n+"." + typeImages));
					n++;
					IJ.showProgress((double)((1.0f/fps)*(i+1)));
					end = System.nanoTime();
					time = framerate - ((int)((end - start) / 1000000));
					if(time > 0) 
						IJ.wait(time);
					else
						frameLose++;
				}
				IJ.showProgress(1.0f);
			}
		}
		catch (Exception e) {}
		}
	}
	
	public void calcolaHeightWidth()
	{
		if(X_pres < X_rel && Y_pres < Y_rel)
		{
			width = X_rel - X_pres;
			height = Y_rel - Y_pres;
			x = X_pres;
			y = Y_pres;
		}
		if(X_pres > X_rel && Y_pres > Y_rel)
		{
			width = X_pres - X_rel;
			height = Y_pres - Y_rel;
			x = X_rel;
			y = Y_rel;
		}
		if(X_pres > X_rel && Y_pres < Y_rel)
		{
			width = X_pres - X_rel;
			height = Y_rel - Y_pres;
			x = X_rel;
			y = Y_pres;
		}
		if(X_pres < X_rel && Y_pres > Y_rel)
		{
			width = X_rel - X_pres;
			height = Y_pres - Y_rel;
			x = X_pres;
			y = Y_rel;
		}
	}
	
	public int testFps()
	{
		int time = 0, maxFps = 0, nTest = 25;
		long start, end;
		String tempDir = directory+"test\\";
		try{
			
			new File(tempDir).mkdirs();
			Rectangle screenRect = new Rectangle(x, y, width, height);
			IJ.showProgress(0.0f);
			for(int i = 0; i < nTest; i++)
			{
				start = System.nanoTime();
				BufferedImage capture = new Robot().createScreenCapture(screenRect);
				ImageIO.write(capture, typeImages, new File(tempDir+"tmp"+i+"."+ typeImages));
				end = System.nanoTime();
				time += (int)((end - start) / 1000000);
				IJ.showProgress((double)((1.0f/nTest)*(i+1)));
			}
			time = time/nTest;
			maxFps = (int)(1000 / time);
			maxFps = maxFps - 1;
			for(int i = 0; i < nTest; i++)
			{
				File source = new File(directory + "test\\");
				for (File files : source.listFiles()) 
				{
					files.delete();
				}
				source.delete();
			}
			
			
		}
		catch (Exception e) {}
		return maxFps;
	}
	
	public void generaReport()
	{
		DateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
	    Date date = new Date();
		String report = "<html><head><meta http-equiv=Content-Type content=text/html;charset=utf-8></head><body><h1>Capture Screen Plugin Report</h1>" +
						"User : " + System.getProperty("user.name") + "<br>" +
						"OS : ";
						if(IJ.isWindows()) report = report + "Windows" + "<br>";
						if(IJ.isLinux()) report = report + "Linux" + "<br>";
						if(IJ.isMacOSX()) report = report + "MaxOSX" + "<br>";
						if(IJ.isMacintosh()) report = report + "Macinhtosh" + "<br>";
		report = report + "ImageJ version: " + IJ.getVersion() + "<br>" +
						"Date generation report: " + dateFormat.format(date) + "<br>" +
						"<hr><h2>Steps Summary</h2><ol>" + 
						"<li><b>Selection of the output directory</b><br><br>" +
						"Has been selected " + directory + "</li><br>" +
						"<li><b>Selection area to be captured</b><br><br>" +
						"Has been selected an area of screen of " + width + " x " + height + " pixel.</li><br>" +
						"<li><b>Selection settings record and output</b><br><br>" + 
						"Has been selected a framerate of " + fps + " fps.<br>";
						if(saveImages) 
						{
							report = report + "Has been selected the option to save all the captured images.<br>";
							if(typeImages.equals("jpeg"))
								report = report + "Has been selected ." + typeImages + " such as images encoding, with a quality of " + quality +"%.<br>";
							else
								report = report + "Has been selected ." + typeImages + " such as images encoding.<br>";
						}
						if(saveVideo) 
						{
							report = report + "Has been selected the option to save the captured video.<br>";
							report = report + "Has been selected ." + typeVideo + " such as video encoding.<br>";
						}
		report = report + "</li><br><li><b>Capture and encoding</b><br><br>" + 
						"Has been captured a total of " + n + " frame.<br>";
						if(saveImages) report = report + "The " + n + " images captured has been saved in " + directory +"<br>";
						if(saveVideo) 
						{
							report = report + "The encode video capture." + typeVideo + " has been saved in " + directory +"<br>";
							report = report + "The bitrate of the video is ~ " + bitrate + " kbps.<br>";
						}
		report = report + "</ol></body></html>";
		try 
		{
			File file = new File(directory+"report.html");
			FileOutputStream out = new FileOutputStream(file);
			PrintStream print = new PrintStream(file);
			print.print(report);
			print.close();
			out.close();
			//file.close();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	
	public void mouseClicked(MouseEvent e) {}
	
	public void mousePressed(MouseEvent e) {
		int offscreenX = canvas.offScreenX(e.getX());
		int offscreenY = canvas.offScreenY(e.getY());
		X_pres = offscreenX;
		Y_pres = offscreenY;
	}
		
	public void mouseReleased(MouseEvent e) {
		int offscreenX = canvas.offScreenX(e.getX());
		int offscreenY = canvas.offScreenY(e.getY());
		X_rel = offscreenX;
		Y_rel = offscreenY;
	}
		
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}
	
}