package projectEQ2440.camera;

import projectEQ2440.QRcode.Decoder;
import projectEQ2440.utils.Queue;
import projectEQ2440.utils.ThreadManager;

/**
 * <b>public class PictureManager extends ThreadManager</b><br/>
 * Class to manage the queue of pictures and the calling to the decoder
 */
public class PictureManager extends ThreadManager {
	
	// Limits of number of pictures
	public final static int NUMBER_PICTURES_GOOD = 5;
	public final static int NUMBER_PICTURES_BAD = 17;
	public final static int NUMBER_PICTURES_IN_MEMORY_MAX = 18;
	
	// Minimum and maximum time between two pictures in millisec
	public final static int TIME_MIN_BETWEEN_PICTURES = 250;
	public final static int TIME_MAX_BETWEEN_PICTURES = 1450;
	private final static float TIME_RATIO = ((float) (TIME_MAX_BETWEEN_PICTURES-TIME_MIN_BETWEEN_PICTURES))/((float) (NUMBER_PICTURES_BAD-NUMBER_PICTURES_GOOD));
	
	// Time between images
	private int timeBetweenPictures;
	
	// Time of pictures taken
	public final static long DELAY_PICTURES_MAX = 1000; 
	private long delayPictures;
	
	// the list of saved picture
	private Queue<PreviewPicture> pictures;
	
	// The preview
	private Preview preview;
	
	// The decoder
	private Decoder decoder;
	
	// Last time for loop
	private long timeLoop;
	
	// Flag if it is set as an RGB manager
	private boolean flagRGB;
	
	// Number of pictures in memory
	private int nbrInMemory;
	
	/**
	 * <b>public PictureManager(Preview preview, Decoder decoder)</b><br/>
	 * Constructor initializing the preview and the decoder for the algorithm
	 * 
	 * @param preview : The Preview
	 * @param decoder : The Decoder
	 */
	public PictureManager(Preview preview, Decoder decoder) {
		super("PictureManager");
		
		this.preview = preview;
		this.decoder = decoder;
		
		timeBetweenPictures = TIME_MIN_BETWEEN_PICTURES;
		delayPictures = 0;
		nbrInMemory = 0;
		
		pictures = new Queue<PreviewPicture>();
		
		// Set the callback when the picture is ready
		preview.setMemorizeCallback(new Preview.MemorizeCallback() {
			@Override
			public void pictureReady(PreviewPicture picture) {
				
				// add the picture to the queue
				regulation(picture);
				
				// If the manager is started, we notice it for a new picture
				if (isStart()) wakeManager(); 
				// else we just decode the picture saved
				else decodePicture();
			}
		});
		
		// Set the callback when the decoding end
		this.decoder.setDecodeCallback(new Decoder.DecodeCallback() {
			@Override
			public void decodeEnd() {
				// We check if there is another picture to decode
				if (pictures.length() != 0) decodePicture();
			}
		});
		
		// Set the callback for the preview
		setPictureBW();
		
	}
	
	/**
	 * <b>public boolean setPictureBW()</b><br/>
	 * Set the picture manager to work with black and white pictured and return true.<br/>
	 * If the manager is running, do nothing and return false.
	 * 
	 * @return if the setting has been done.
	 */
	public boolean setPictureBW() {
		if (isStart()) return false;
		
		preview.setInfoBW();
		flagRGB = false;
		
		return true;
	}

	/**
	 * <b>public boolean setPictureRGB()</b><br/>
	 * Set the picture manager to work with colored pictured and return true.<br/>
	 * If the manager is running, do nothing and return false.
	 * 
	 * @return if the setting has been done.
	 */
	public boolean setPictureRGB() {
		if (isStart()) return false;
		
		preview.setInfoRGB();
		flagRGB = true;
		
		return true;
	}
	
	/**
	 * <b>public boolean isPictureRGB()</b><br/>
	 * Tell if the manager works with colored pictures
	 * 
	 * @return if it works with colored pictures
	 */
	public boolean isPictureRGB() {
		return flagRGB;
	}
	
	/**
	 * <b>public synchronized void cleanQueue()</b><br/>
	 * clean the queue of pictures
	 */
	public synchronized void cleanQueue() {
		nbrLoop = 0;
		pictures.clean();
		nbrInMemory = 0;
	}
	
	/**
	 * <b>public void takePicture(boolean autofocus)</b><br/>
	 * Call a new picture to the preview. If the manager is started, do nothing
	 * 
	 * @param autofocus : With or without Autofocus
	 */
	public void takePicture(boolean autofocus) {
		if (!isStart()) preview.memorize(true);
	}

	// TODO comments
	private void regulation(PreviewPicture picture) {
		// Time between two pictures
		if (pictures.length() <= NUMBER_PICTURES_GOOD) 
			timeBetweenPictures = TIME_MIN_BETWEEN_PICTURES;
		
		else if (pictures.length() < NUMBER_PICTURES_BAD) 
			timeBetweenPictures = ((int) ((pictures.length()-NUMBER_PICTURES_GOOD)*TIME_RATIO))+TIME_MIN_BETWEEN_PICTURES;
		
		else timeBetweenPictures = TIME_MAX_BETWEEN_PICTURES;
		
		// Save on SD card if too many pictures
		if (nbrInMemory >= NUMBER_PICTURES_IN_MEMORY_MAX) picture.saveOnSD();
		else nbrInMemory++;
		
		pictures.add(picture);
	}
	
	/**
	 * <b>private void decodePicture(PreviewPicture picture)</b><br/>
	 * Manage the decoding of the picture
	 * 
	 * @param picture : The picture to decode
	 */
	// TODO comments to change
	private synchronized void decodePicture() {
		// pop the next picture
		PreviewPicture picture = pictures.pop();
		if (picture == null) return;
		
		if (!picture.onSD()) nbrInMemory--;
		
		long test = picture.getInfo().time-delayPictures;
		
		// Regulation of time between images
		if (picture.getInfo().color == PreviewPicture.Info.RGB)
			if (test < DELAY_PICTURES_MAX)
				decoder.decodeRGBNextPicture(picture);
			else
				decoder.decodeRGBPicture(picture);
		else 
			if (test < DELAY_PICTURES_MAX)
				decoder.decodeNextPicture(picture);
			else
				decoder.decodePicture(picture);
		
		delayPictures = picture.getInfo().time;
	}
	
	/**
	 * <b>protected void initAction() throws InterruptedException</b><br/>
	 * Begin with calling a picture with Autofocus
	 */
	@Override
	protected void initAction() throws InterruptedException {
		pictures.clean();
		nbrInMemory = 0;
		preview.memorize(true);
	}

	/**
	 * <b>protected void loopAction() throws InterruptedException</b><br/>
	 * For each loop, the Thread wakes up the Decoder if it is waiting, waits the minimum time between 2 pictures and call a new picture without Autofocus. 
	 */
	@Override
	protected void loopAction() throws InterruptedException {
		if (decoder.waiting()) decodePicture();

		long time = System.currentTimeMillis() - timeLoop;
		if (time < timeBetweenPictures) Thread.sleep(timeBetweenPictures-time);
		timeLoop = System.currentTimeMillis();
		
		preview.memorize(false);
	}
	
	/**
	 * <b>protected void endAction() throws InterruptedException</b><br/>
	 * Do nothing
	 */
	@Override
	protected void endAction() throws InterruptedException {
		pictures.clean();
		nbrInMemory = 0;
	}
	
}
