package projectEQ2440.QRcode;

import android.content.Context;
import projectEQ2440.camera.PictureManager;
import projectEQ2440.camera.Preview;
import projectEQ2440.message.MessageManager;

/**
 * <b>public class QRDecode</b><br/>
 * class which manage all the decoding and help others classes to work together
 */
public class QRDecode {
	
	// The preview
	private Preview preview;
	
	// The Decoder
	private Decoder decoder;
	
	// The Picture Manager
	private PictureManager pictureManager;
	
	// The Message Manager
	private MessageManager messageManager;
	
	// Flag to check if the decoding is start
	private boolean started;
	
	// The callback for interaction
	private QRDecodeCallback callback;
	
	// with or without flash
	private boolean isFlashing;
	
	/**
	 * <b>public QRDecode(Context context)</b><br/>
	 * The constructor using a given <i>Context</i> for generation of a preview
	 * 
	 * @param context : <i>Context</i> of the Activity
	 */
	public QRDecode(Context context) {
		
		// Generate the preview
		preview = new Preview(context);
		
		// Launch the initialization
		initialization();
	}

	/**
	 * <b>public QRDecode(Preview preview)</b><br/>
	 * The constructor using a given preview
	 * 
	 * @param preview : an already implemented preview
	 */
	public QRDecode(Preview preview) {
		
		// Generate the preview
		this.preview = preview;
		
		// Launch the initialization
		initialization();
	}
	
	/**
	 * <b>private void initialization()</b><br/>
	 * Initialize the attributes of the class
	 */
	private void initialization() {
		
		// Generate the decoder
		decoder = new Decoder();
		
		// Generate the picture manager
		pictureManager = new PictureManager(preview, decoder);
		
		// Generate the message manager
		messageManager = new MessageManager(decoder);
		
		// Set the callback to null to begin
		callback = null;
		
		// Initialize the flag
		started = false;
		
		// Initialize the flash tag
		isFlashing = false;
		
		// Set what to do in result
		messageManager.setMessageManagerCallback(new MessageManager.MessageManagerCallback() {
			@Override
			public void wrongFrame(byte[] data, int version) {
				if (callback != null) callback.wrongFrame(data, version);
			}
			
			@Override
			public void messageOutOfLoop(byte[] data, int version) {
				if (callback != null) callback.messageOut(data, version);
			}
			
			@Override
			public void messageComplete(byte[] data) {
				pictureManager.stopManager();
				started = false;
				if (callback != null) callback.decodingFinished(data);
			}
			
			@Override
			public void goodFrame(byte[] data, int length, int totalFrame, int frameNumber, int version) {
				if (callback != null) callback.goodFrame(data, length, totalFrame, frameNumber, version);
			}
			
			@Override
			public void state(int totalFrame, int decodedFrame, int currentFrame) {
				if (callback != null) callback.state(totalFrame, decodedFrame, currentFrame);
			}
			
			@Override
			public void flash() {
				if (isFlashing) {
					pictureManager.cleanQueue();
					preview.flashing();
				}
			}
		});
	}
	
	/**
	 * <b>public Preview getPreview()</b><br>
	 * Gives the <i>Preview</i> used
	 * 
	 * @return the preview
	 */
	public Preview getPreview() {
		return preview;
	}
	
	/**
	 * <b>public boolean isRunning()</b><br/>
	 * tell if the decoding is running
	 * 
	 * @return if it's running
	 */
	public boolean isRunning() {
		return started;
	}
	
	/**
	 * <b>public boolean isPictureRGB()</b><br/>
	 * tell if the algorithm works with color or not
	 * 
	 * @return it it works with colored QR codes
	 */
	public boolean isPictureRGB() {
		return pictureManager.isPictureRGB();
	}

	/**
	 * <b>public boolean setPictureBW()</b><br/>
	 * select to work with black and white QR codes
	 * 
	 * @return if the changement is done
	 */
	public boolean setPictureBW() {
		if (started) return false;
		else return pictureManager.setPictureBW();
	}

	/**
	 * <b>public boolean setPictureRGB()</b><br/>
	 * select to work with colored QR codes
	 * 
	 * @return if the changement is done
	 */
	public boolean setPictureRGB() {
		if (started) return false;
		else return pictureManager.setPictureRGB();
	}
	
	/**
	 * <b>public boolean isThereFrameCheck()</b><br/>
	 * tell if the algorithm checks the frame number before decoding to win time
	 * 
	 * @return if it is checking the frame number
	 */
	public boolean isThereFrameCheck() {
		return messageManager.isThereFrameCheck();
	}
	
	/**
	 * <b>public boolean setWithFrameCheck()</b><br/>
	 * active the checking of frame number
	 * 
	 * @return if the changement is done
	 */
	public boolean setWithFrameCheck() {
		if (started) return false;
		messageManager.withFrameCheck();
		return true;
	}
	
	/**
	 * <b>public boolean setWithoutFrameCheck()</b><br/>
	 * desactive the checking of frame number
	 * 
	 * @return if the changement is done
	 */
	public boolean setWithoutFrameCheck() {
		if (started) return false;
		messageManager.withoutFrameCheck();
		return true;
	}

	/**
	 * <b>public boolean isFlashing()</b><br/>
	 * tell if the algorihm apply a flash between the frames to indicate the reading of the frame is finished
	 * 
	 * @return if it flashing between frames
	 */
	public boolean isFlashing() {
		return isFlashing;
	}
	
	/**
	 * <b>public boolean setFlashOn()</b><br/>
	 * set the flashing between frames on
	 * 
	 * @return if the changement is done
	 */
	public boolean setFlashOn() {
		if (started) return false;
		isFlashing = true;
		return true;
	}

	/**
	 * <b>public boolean setFlashOff()</b><br/>
	 * set the flashing between frames off
	 * 
	 * @return if the changement is done
	 */
	public boolean setFlashOff() {
		if (started) return false;
		isFlashing = false;
		return false;
	}
	
	/**
	 * <b>public void setQRDecodeCallback(QRDecodeCallback callback)</b><br/>
	 * Set the callback for outside interaction
	 * 
	 * @param callback : the <i>QRDecodeCallback</i>
	 */
	public void setQRDecodeCallback(QRDecodeCallback callback) {
		this.callback = callback;
	}
	
	/**
	 * <b>public void start()</b><br/>
	 * Start the decoding
	 */
	public void start() {
		if (started) return;
		if (callback == null) return;

		started = true;
		messageManager.startManager();
		pictureManager.startManager();
	}
	
	/**
	 * <b>public void stop()</b><br/>
	 * Stop the decoding
	 */
	public void stop() {
		if (!started) return;
		
		pictureManager.stopManager();
		messageManager.stopManager();
		started = false;
	}
	
	/**
	 * <b>public void kill()</b><br/>
	 * Kill every thread (except preview which wait the destruction of its surface).<br/>
	 * <b>BE CAREFUL</b> : the object become totally useless after
	 */
	public void kill() {
		decoder.kill();
		decoder = null;
		
		pictureManager.killManager();
		pictureManager = null;
		
		messageManager.killManager();
		messageManager = null;
		
		try {
			this.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * <b>public abstract class QRDecodeCallback</b><br/>
	 * The Callback abstract class to manage the results outside
	 */
	public static abstract class QRDecodeCallback {
		
		/**
		 * <b>public abstract void decodingFinished(byte[] finalMessage)</b><br/>
		 * Function to implement : treat the final result of the decoding<br/>
		 * <b>BE CAREFUL</b> : This function must be implemented else the decoding won't be launched
		 * 
		 * @param finalMessage : final data at the end of the decoding
		 */
		public abstract void decodingFinished(byte[] finalMessage);
		
		/**
		 * <b>public void wrongFrame(byte[] data, int version)</b><br/>
		 * Manage when a frame, which doesn't follow the standard, is received
		 * 
		 * @param data : the data read (can be null)
		 * @param version : the version detected (can be 0)
		 */
		public void wrongFrame(byte[] data, int version) {
			
		}
		
		/**
		 * <b>public void goodFrame(byte[] data, int length, int totalFrame, int frameNumber, int version)</b><br/>
		 * Manage when a frame, following the standard, is received 
		 * 
		 * @param data : the data read
		 * @param length : the length of information (QR standard)
		 * @param totalFrame : the total number of frame read
		 * @param frameNumber : the number of the actual Frame
		 * @param version : the version read
		 */
		public void goodFrame(byte[] data, int length, int totalFrame, int frameNumber, int version) {
			
		}
		
		/**
		 * <b>public void messageOut(byte[] data, int version)</b><br/>
		 * Manage when a frame is received in spite of the managers are not running (a simple decoding for exemple)
		 * 
		 * @param data : The data read (can be null)
		 * @param version : the version read (can be 0)
		 */
		public void messageOut(byte[] data, int version) {
			
		}
		
		public void state(int totalFrame, int decodedFrame, int currentFrame) {
			
		}
		
	}
	
}
