package com.hammurapi.jcapture;

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.imageio.ImageIO;
import javax.swing.ProgressMonitor;

import com.flagstone.transform.Background;
import com.flagstone.transform.DefineTag;
import com.flagstone.transform.DoAction;
import com.flagstone.transform.Movie;
import com.flagstone.transform.MovieHeader;
import com.flagstone.transform.MovieTag;
import com.flagstone.transform.Place2;
import com.flagstone.transform.Remove;
import com.flagstone.transform.Remove2;
import com.flagstone.transform.ShowFrame;
import com.flagstone.transform.action.Action;
import com.flagstone.transform.action.BasicAction;
import com.flagstone.transform.coder.Coder;
import com.flagstone.transform.datatype.Bounds;
import com.flagstone.transform.datatype.CoordTransform;
import com.flagstone.transform.datatype.WebPalette;
import com.flagstone.transform.image.ImageTag;
import com.flagstone.transform.util.image.ImageDecoder;
import com.flagstone.transform.util.image.ImageRegistry;
import com.flagstone.transform.util.image.ImageShape;
import com.flagstone.transform.util.shape.Canvas;
import com.flagstone.transform.util.sound.SoundFactory;
import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame;
import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape;
import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.Image;
import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ImageReference;
import com.hammurapi.jcapture.VideoEncoder.Fragment.Frame.Shape.ShapeContent;

public class SwfEncoder implements VideoEncoder {

	@Override
	public String getFileExtension() {
		return "swf";
	}

	@Override
	public String getMimeType() {
		return "application/x-shockwave-flash";
	}
	
	@Override
	public String toString() {
		return "SWF";
	}

	@Override
	public Dimension encode(Config config, 
		com.hammurapi.jcapture.Movie source,
		OutputStream out)
		throws Exception {

        AtomicInteger uid = new AtomicInteger();
        
        /**
         * For reusing shape id's.
         */
        int maxId = Coder.USHORT_MAX;
        		        
        ButtonManager manager = null;
        if (config.isToolBar()) {
	    	manager = new ButtonManager();
	    	manager.loadLibrary(getClass().getResource("toolbar_buttons.swf"));				    			        
        	uid.set(manager.maxIdentifier()+1);
        }
        
        Canvas path = new Canvas();
        path.setPixels(false);
        
        int minImgLayer = 10;
        int imgLayer = minImgLayer;
        int maxImgLayer = maxId - 1000;
        
        int mouseLayer = maxImgLayer+1;
        int mouseUid = -1;
    	Place2 mousePlace = null;		       
    	ImageTag mouseImage = null;
    	
        int layer = maxImgLayer+2;        	
        
        int totalWork = 0; 
        for (Fragment frg: source.getFragments()) {
        	totalWork = frg.getFrames().size()+1;
        }
        
        ProgressMonitor progressMonitor = new ProgressMonitor(config.getParentComponent(), "Encoding to SWF", "Composing movie", 0, totalWork);
        int progressCounter = 0;
	        
        progressMonitor.setNote("Composing movie");
        boolean firstFrame = true;
        Dimension ret = null;
        Map<Image, ImageTag> imageCache = new IdentityHashMap<Image, ImageTag>();
        	        
        Movie movie = new Movie();
        
        Point prevMouseLocation = null;
        
        for (Fragment fragment: source.getFragments()) {
		      	        	
	        SoundFactory soundFactory = null;
	        boolean soundHeaderAdded = false;
	        File audio = fragment.getAudio();	        
	        if (audio!=null) {
		        progressMonitor.setNote("Loading sound");
		        soundFactory = new SoundFactory();
		        
		 		// MP3 conversion
		 		if (config.getMp3command()!=null && config.getMp3command().trim().length()>0) {
			 		audio = new File(audio.getAbsolutePath()+".mp3"); 
			 		Runtime runtime = Runtime.getRuntime();				 		 
				    Process proc = runtime.exec(MessageFormat.format(config.getMp3command(), new Object[] {fragment.getAudio().getAbsolutePath(), audio.getAbsolutePath()}));
				    proc.waitFor();
				    if (!fragment.getAudio().delete()) {
				    	fragment.getAudio().deleteOnExit();
					}
		 		}
		        
		        soundFactory.read(audio);
	        }
		        
		    progressMonitor.setProgress(++progressCounter);
		    
 			if (progressMonitor.isCanceled()) {
 				return null;
 			}
 			
		    for (Frame frame: fragment.getFrames()) {
	 			if (progressMonitor.isCanceled()) {
	 				return null;
	 			}
	 			
	 			boolean addStop = false;
		        		
		        if (firstFrame) {
		        	firstFrame = false;
		        	
		            MovieHeader header = new MovieHeader();
		            header.setCompressed(true);
		            header.setFrameRate(source.getFramesPerSecond());	            
		            
		        	int toolbarHeight = 29 * 20;
		        	int toolbarWidth = 495 * 20;

		        	int toolbarX = 0; // - image.getWidth()*20/2;
		        	int toolbarY = frame.getSize().height*20;		            
		            
					int movieWidth = frame.getSize().width*20;
					int movieHeight = frame.getSize().height*20;
					if (config.isToolBar()) {
						movieHeight+=toolbarHeight;
					}
					
					ret = new Dimension(movieWidth/20, movieHeight/20);
					
		        	float toolbarScaleX = (float) movieWidth / (float) toolbarWidth;
		        	float toolbarScaleY = 1.0f;
		        	
					Bounds movieBounds = new Bounds(0, 0, movieWidth, movieHeight);
					header.setFrameSize(movieBounds);
		            movie.add(header);
		            movie.add(new Background(WebPalette.WHITE.color()));
		            
		            if (config.isToolBar()) {	            
			        	// Add all the shapes etc used for buttons
			        	List<DefineTag> toolbarDefinitions = manager.getDefinitions();
						movie.getObjects().addAll(toolbarDefinitions);
			        			            
			        	Place2 placeBackground = manager.getButton("background", layer++, 0, 0);
			        	placeBackground.setTransform(new CoordTransform(toolbarScaleX, toolbarScaleY, 0, 0, toolbarX, toolbarY));
		
			        	// Get the button to use and give its position
			        	movie.add(placeBackground);
			        	movie.add(manager.getButton("play_button", layer++, toolbarX + 500, toolbarY + toolbarHeight / 2));
			        	movie.add(manager.getButton("progress_bar", layer++, toolbarX + 1000, toolbarY + toolbarHeight / 2));
			        	movie.add(manager.getButton("volume_control", layer++, toolbarX + 5600, toolbarY + toolbarHeight / 2));
			        	
			        	if (!config.isPlay()) {
			        		addStop = true;
			        	}
		            }
		        }
		        
		        if (!soundHeaderAdded && soundFactory!=null) {
		            movie.add(soundFactory.streamHeader(source.getFramesPerSecond()));
		            soundHeaderAdded = true;
		        }
			        
		        if (soundFactory!=null) {
			        MovieTag soundBlock = soundFactory.streamSound();
			        if (soundBlock != null) {
			            movie.add(soundBlock);
			        }
		        }
			        
		 		for (Shape shape: frame.getShapes()) {
		 			if (shape.getContent().coversEverything() || imgLayer==maxImgLayer) {
		 				for (int i=minImgLayer; i<=imgLayer; ++i) {
		 					movie.add(new Remove2(i));
		 				}
		 				imgLayer = minImgLayer;
		 			}
		 			
		 			ShapeContent shapeContent = shape.getContent();
		 			Image image;
		 			if (shapeContent instanceof Image) {
		 				image = (Image) shapeContent;
		 			} else if (shape.getContent() instanceof ImageReference) {
		 				image = ((ImageReference) shapeContent).getImage();
		 			} else {
		 				throw new IllegalArgumentException("Unexpected content type: "+shapeContent);
		 			}
		 			
		 			ImageTag imageTag = imageCache.get(image);
		 			if (imageTag==null) {
			            ImageDecoder decoder = ImageRegistry.getImageProvider("image/"+config.getImageFormat().toLowerCase());           
			            decoder.read(new ByteArrayInputStream(image.getImage().getImageBytes()));
			            imageTag = decoder.defineImage(uid.incrementAndGet());
			            imageCache.put(image, imageTag);
			            movie.add(imageTag);
		 			}
		 			
		 			int shapeId = uid.incrementAndGet();
			        DefineTag shapeTag = new ImageShape().defineShape(shapeId, imageTag);	
			        Place2 place = Place2.show(shapeTag.getIdentifier(), imgLayer++, shape.getLocation().x*20, shape.getLocation().y*20);
		            movie.add(shapeTag);
		            movie.add(place);			 			
		 		}
		    						        
		        Point mouseLocation = frame.getMousePointer();
		        if (mouseLocation!=null) {
	        		if (mouseImage==null) {
	        			BufferedImage mouseBi = ImageIO.read(getClass().getResource("mouse.png"));
	        	    	if (config.getScreenScale()<0.99 || config.getScreenScale() > 1.01) {
	        		    	BufferedImage scaled = new BufferedImage((int) (mouseBi.getWidth()*config.getScreenScale()), (int) (mouseBi.getHeight()*config.getScreenScale()), mouseBi.getType());
	        		    	Graphics2D g = scaled.createGraphics();
	        		    	g.setComposite(AlphaComposite.Src);
	        		    	g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
	        		    	g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
	        		    	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
	        		    	g.drawImage(mouseBi, 0, 0, scaled.getWidth(), scaled.getHeight(), null);
	        		    	g.dispose();
	        		    	mouseBi = scaled;
	        	    	}

	    				ByteArrayOutputStream baos = new ByteArrayOutputStream();        	
	    				ImageIO.write(mouseBi, "PNG", baos);
	    	        	baos.close();
	    	            ImageDecoder decoder = ImageRegistry.getImageProvider("image/png");           
	    	            decoder.read(new ByteArrayInputStream(baos.toByteArray()));
		            	mouseImage = decoder.defineImage(uid.incrementAndGet());
		            	movie.add(mouseImage);
	        		}
		                
	        		if (!mouseLocation.equals(prevMouseLocation)) {
	        			prevMouseLocation = mouseLocation;
			            mouseUid = uid.incrementAndGet();
				        DefineTag mShape = new ImageShape().defineShape(uid.incrementAndGet(), mouseImage); //createRect(mouseUid, 100, 100, WebPalette.RED.color());		
				        if (mousePlace==null) {
				        	mousePlace = Place2.show(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20);
				        } else {
				        	mousePlace = Place2.replace(mShape.getIdentifier(), mouseLayer, mouseLocation.x*20, mouseLocation.y*20);
				        }
			            movie.add(mShape);
			            movie.add(mousePlace);
	        		}
	        	} else if (mouseUid!=-1) {
	        		Remove remove = new Remove(mouseUid, mouseLayer);
	        		movie.add(remove);
	        	}
			     
		        if (addStop) {
		        	DoAction cmd = new DoAction(new ArrayList<Action>());
		        	cmd.add(BasicAction.STOP);
		        	movie.add(cmd);
		        }
	        	movie.add(ShowFrame.getInstance());
			        
	 			progressMonitor.setProgress(++progressCounter);			        	        
		    }

	 		progressMonitor.setProgress(++progressCounter);
	        if (soundFactory!=null) {
		 		progressMonitor.setNote("Recording trailing sound");
		        MovieTag block;
		        while ((block = soundFactory.streamSound()) != null) {
		            movie.add(block);
		            movie.add(ShowFrame.getInstance());
		        }
	        }
	        
	        if (audio!=null) {
	        	if (!audio.delete()) {
	        		audio.deleteOnExit();
	        	}
	        }
        }
					        
        if (!config.isLoop()) {
        	List<Action> actions = new ArrayList<Action>();
        	actions.add(BasicAction.STOP);
        	actions.add(BasicAction.END);
			DoAction doAction = new DoAction(actions);	        	
			movie.add(doAction);
            movie.add(ShowFrame.getInstance());
        }
        
        progressMonitor.setProgress(++progressCounter);
        progressMonitor.setNote("Encoding movie");
		movie.encodeToStream(out);
		source.close();
		return ret;
	}

}
