/* 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */
/** 
	 * Zoom.java
	 * Autor: Alberto Población Navarro
	 * Añadido al proyecto en la version v0.1.8
	 * Añadido a:
	 		* Proyecto original: http://code.google.com/p/android-vnc-viewer
	 		* Release: 39
	 */
package android.androidVNC;

import java.io.IOException;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.Log;
import android.view.Display;
import android.widget.Toast;
import android.widget.ImageView.ScaleType;

public class Zoom {
	
	private static final String TAG = "ZOOM";
	
	private float zoom =1; 			//Escalado a realizar
	private final float variacion=0.1f;  	//Cantidad que se va a añadir al zoom en cada escalado
	private boolean first_time=true; 		//Flag para saber si es la primera ejecucion 
	private int ancho=0; 					//Ancho inicial de la imagen
 	private int alto=0; 					//Alto inicial de la imagen
	private int absoluteX=0;				// Posicion inicial en el eje X de la esquina superior izquierda 
	private int absoluteY=0;				// Posicion inicial en el eje Y de la esquina superior izquierda 
	private int scrollXini=0;				// Desplazamiento inicial sobre la imagen en el eje X
	private int scrollYini=0;				// Desplazamiento inicial sobre la imagen en el eje Y
	private static Matrix matriz; 			//Matriz utilizada en el escalado
	final float MIN_ZOOM=0.6f; 				//Zoom out maximo
	final float MAX_ZOOM=1.6f; 				//Zoom in máximo
	final int IN=0; 						//Tipo de zoom : zoom in
	final int OUT=1;						//Tipo de zoom : zoom out
	
	private VncCanvas vncCanvas;
	private VncCanvasActivity vncCanvasActivity;
	
	private static Zoom instancia = null;
	
	static synchronized Zoom Instancia(VncCanvasActivity vnccanvasactivity, VncCanvas vnccanvas){
		if (instancia == null)
			instancia = new Zoom(vnccanvas, vnccanvasactivity);
		return instancia;
	}
	
	private Zoom(VncCanvas vnccanvas, VncCanvasActivity vnccanvasactivity) {
		vncCanvas=vnccanvas;
		vncCanvasActivity=vnccanvasactivity;
		matriz = new Matrix();
	}
	/**
	 * Guarda la información del lienzo antes de realizarle un zoom
	 * @param vnccanvas Lienzo al cual se va a realizar un zoom
	 * @author Alberto Población Navarro
	 */
	private void guardar_datos_iniciales(){
		if(first_time){
			//Guardamos las dimensiones de la imagen
			ancho=vncCanvas.bitmapData.framebufferwidth;
			alto=vncCanvas.bitmapData.framebufferheight;
			//Guardamos el desplazamiento de la imagen en ScaleType.CENTER
			scrollXini=vncCanvas.getScrollX();
			scrollYini=vncCanvas.getScrollY();
			//Guardamos la posicion absoluta inicial de la esquina superior izquierda
			absoluteX=vncCanvasActivity.absoluteXPosition;
			absoluteY=vncCanvasActivity.absoluteYPosition;
			//Cantidad de zoom
			zoom=1;
			first_time=false;
		}
	}
	/**	
	 * Realiza un "zoom in" a la imagen centrada en el raton, llamando a la funcion zoom con el tipo IN
	 * @param vncCanvas Lienzo al cual se va a realizar un zoom
	 * @param vncCanvasActivity Actividad que se esta ejecutando y que muestra el lienzo
	 * @return True si se realiza correctamente, false en caso contrario
	 * @author Alberto Población Navarro
	 */
	public boolean zoom_in(){
		return zoom(vncCanvas, vncCanvasActivity, IN);	
	}
	/**
	 * Realiza un "zoom out" a la imagen centrada en el raton, lamando a la funcion zoom con el tipo IN
	 * @param vncCanvas Lienzo al cual se va a realizar un zoom
	 * @param vncCanvasActivity Actividad que se esta ejecutando y que muestra el lienzo
	 * @return True si se realiza correctamente, false en caso contrario
	 * @author Alberto Población Navarro
	 */
	public boolean zoom_out(){
		return zoom(vncCanvas, vncCanvasActivity, OUT);
		
	}
	/**
     * Redondea un valor double a un numero especifico de decimales
     * @param val el valor que se va a redondear.
     * @param places el numero de decimales a redondear.
     * @return val valor redeando a los decimales.
     */
	public static double round(double val, int places) {
		 long factor = (long)Math.pow(10,places);
		 // Cambia el decimal el numero de posiciones necesarias a la derecha
		 val = val * factor;
		 // Redondea al entero mas cercano
		 long tmp = Math.round(val);
		 // Cambia el decimal al numero de posiciones a la izquierda
		 return (double)tmp / factor;
	}
	/**
	 * Realiza el zoom a la imagen centrada en raton del tipo indicada en el parametro type
	 * @param vncCanvas Lienzo al cual se va a realizar un zoom
	 * @param vncCanvasActivity Actividad que se esta ejecutando y que muestra el lienzo
	 * @param type Tipo de zoom: zoom in o zoom out
	 * @return true si se realiza el zoom correctamente, false en caso contrario
	 * @author Alberto Población Navarro
	 */
	private boolean zoom(VncCanvas vncCanvas, VncCanvasActivity vncCanvasActivity, int type){
		Notificacion notificacion = Notificacion.Instancia(vncCanvasActivity);
		//Guardamos los valores iniciales de la imagen para poder restaurarla.
		guardar_datos_iniciales();
		//Añadimos zoom necesario segun el tipo
		if(type==IN) 
			zoom+=variacion;
		else //type == OUT 
			zoom-=variacion;
		zoom=(float)round((double)zoom,2);
				
		Log.d(TAG, "Zoom: "+zoom);
		if((zoom < MAX_ZOOM)&&(zoom > MIN_ZOOM)){
			notificacion.notificar("Zoom realizado", "Zoom establecido en "+zoom+"%");
			AndroidLog.Instancia().log(TAG, "Zoom establecido en "+zoom+"%");
			//Configuramos la escala que vamos a aplicar a la imagen
			matriz.setScale(zoom ,zoom);
			//Establecemos la matriz con la escala al lienzo
			vncCanvas.setImageMatrix(matriz);
			//Establecemos el tipo de escalado a matriz
			vncCanvas.setScaleType(ScaleType.MATRIX);
			//Dado que el tamaño de la imagen va a cambiar, cambiamos las variables que guardan dicho valor
			vncCanvas.bitmapData.framebufferheight= (int)(alto*zoom);
			vncCanvas.bitmapData.framebufferwidth= (int)(ancho*zoom);
			//En un principio se centra el zoom siempr en el raton
			//El comentario siguiente es una aproximación a centrar la vista al centro de la pantalla si no vemos el raton
			//Posiblemente solucionado con la solucion del rectangulo abajo
			/**
			//Si no vemos el raton cenntramos la vista en el centro de la parte del escritorio que estamos viendo y posicionamos el raton en esa posicion
			if(vncCanvas.mouseX > vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth()/zoom)
				vncCanvas.mouseX=(int)(vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth()/(2*zoom));
			else if(vncCanvas.mouseX < vncCanvasActivity.absoluteXPosition*zoom)
				vncCanvas.mouseX=(int)(vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth()/(2*zoom));
			else if(vncCanvas.mouseY > vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight()/zoom)
				vncCanvas.mouseY=(int)(vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight()/(2*zoom));
			else if(vncCanvas.mouseY < vncCanvasActivity.absoluteYPosition*zoom)
				vncCanvas.mouseY=(int)(vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight()/(2*zoom));
			else{*/
			//Solución para centrar la vista del zoom si no vemos el raton (fase de pruebas)
			Display display=vncCanvasActivity.getWindowManager().getDefaultDisplay();
			Rect pantalla = new Rect(vncCanvasActivity.absoluteXPosition,
										vncCanvasActivity.absoluteYPosition,
										vncCanvasActivity.absoluteXPosition+display.getWidth(),
										vncCanvasActivity.absoluteYPosition+display.getHeight());
			
			if(pantalla.contains(vncCanvas.mouseX, vncCanvas.mouseY)){
				//Si el raton esta en la pantalla, centramos la vista en el raton
				Log.d(TAG, "El puntero se muestra por pantalla");
				vncCanvasActivity.absoluteXPosition=(int)(vncCanvas.mouseX*zoom-vncCanvas.getWidth()/2);
				vncCanvasActivity.absoluteYPosition=(int)(vncCanvas.mouseY*zoom-vncCanvas.getHeight()/2);
				
			}else{
				//El raton no esta en la pantalla se centra la vista en el centro de nuestra pantalla actual
				Log.d(TAG, "El puntero NO se muestra por pantalla");
				vncCanvas.mouseX=(int)(vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth()/(2*zoom));
				vncCanvas.mouseY=(int)(vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight()/(2*zoom));
			}
			//Centramos el raton en el centro de la imagen	
			vncCanvas.warpMouse(vncCanvas.mouseX,vncCanvas.mouseY);
			//Desplazamiento que se va a realizar en la imagen
			int scrollX=vncCanvasActivity.absoluteXPosition;
			int scrollY=vncCanvasActivity.absoluteYPosition;
		
			//Si superamos los limites de la imagen el zoom se centra en la esquina sin salir de la imagen
			//Limites izquierdo y derecho
			if(scrollX<0){
				vncCanvasActivity.absoluteXPosition=scrollX=0;
				
			}else if ((vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth())>vncCanvas.bitmapData.framebufferwidth){
				vncCanvasActivity.absoluteXPosition= scrollX=vncCanvas.bitmapData.framebufferwidth-vncCanvas.getWidth();
			}
			//Limites superior e inferior
			if(scrollY<0){
				vncCanvasActivity.absoluteYPosition=scrollY=0;
			}
			else if ((vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight())>vncCanvas.bitmapData.framebufferheight){
				vncCanvasActivity.absoluteYPosition=scrollY=vncCanvas.bitmapData.framebufferheight-vncCanvas.getHeight()-vncCanvas.getTop();
			}

			//Desplazamos a imagen a la posicion indicada.			
			vncCanvas.scrollTo(scrollX,scrollY);
			//En caso de realizarse un Zoom Out se actualiza todo el bitmap, ya que se mostrará mas pantalla
			//remota de lo que vemos a tamaño real entraria en la pantalla del movil
			//if (type == OUT){
				try {
					//Actualizamos la imagen
					vncCanvas.bitmapData.rfb.writeFramebufferUpdateRequest(vncCanvasActivity.absoluteXPosition,
																			vncCanvasActivity.absoluteYPosition,
																			display.getWidth(),
																			display.getHeight(), true);
					Log.d(TAG, "WriteFrameBuffer");
				} catch (IOException e) {
					//En caso de no poder actualizar la imagen se muestra un aviso
					notificacion.notificar(vncCanvasActivity.getString(R.string.imposible_zoom), vncCanvasActivity.getString(R.string.cause)+": "+ e.toString());
					new AlertDialog.Builder(vncCanvasActivity)
			          .setTitle(vncCanvasActivity.getString(R.string.imposible_zoom))
			          .setMessage(vncCanvasActivity.getString(R.string.cause)+": "+ e.toString())
			          .setIcon(android.R.drawable.ic_dialog_alert)
			          .setPositiveButton(android.R.string.ok, null)
			          .show();
					return false;
				}
			//}
			return true;
			
		}else{
			//Si se ha alcanzado los limites del zoom, actualizamos la variable y lo avisamos
			if(type==IN) zoom -= variacion;
			else zoom += variacion;
			
			new AlertDialog.Builder(vncCanvasActivity)
	          .setTitle(vncCanvasActivity.getString(R.string.max_zoom))
	          .setMessage(vncCanvasActivity.getString(R.string.no_scale))
	          .setIcon(android.R.drawable.ic_dialog_info)
	          .setPositiveButton(android.R.string.ok, null)
	          .show();
			return false;
		}
	}
	/**
	 * Elimina cualquier zoom realizado a la imagen y la muestra en su escala original
	 * @param vncCanvas Lienzo al cual se ha realizado un zoom
	 * @param vncCanvasActivity Actividad que se esta ejecutando y que muestra el lienzo
	 * @author Alberto Población Navarro
	 */
	public void reset(){
		//Guardamos los valores iniciales de la imagen para poder restaurarla.
		guardar_datos_iniciales();
		if(first_time == false)
		{
			new AlertDialog.Builder(vncCanvasActivity)
	        .setTitle(vncCanvasActivity.getString(R.string.reset))
	        .setMessage(vncCanvasActivity.getString(R.string.initial_pos))
	        .setIcon(android.R.drawable.ic_dialog_info)
	        .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){
				@Override
				public void onClick(DialogInterface dialog, int which) {
						//Restauramos escalado, tamaño y desplazamiento de la imagen
						Log.d("ZOOM", "Entro en el reset");
						zoom=1;
						matriz.setScale(zoom, zoom);
						vncCanvas.setImageMatrix(matriz);
						vncCanvas.setScaleType(ScaleType.MATRIX);
						vncCanvas.scrollTo(scrollXini, scrollYini);
						
						vncCanvas.bitmapData.framebufferwidth=ancho;
						vncCanvas.bitmapData.framebufferheight=alto;
						
						vncCanvasActivity.absoluteXPosition=absoluteX;
						vncCanvasActivity.absoluteYPosition=absoluteY;
						vncCanvas.warpMouse(vncCanvasActivity.absoluteXPosition+vncCanvas.getWidth(), vncCanvasActivity.absoluteYPosition+vncCanvas.getHeight());
						
						first_time=true;
				}
	        })
	        .setNegativeButton(android.R.string.cancel, null)
	        .show();
		}else//Si no se ha realizado ningun zoom no se puede realizar un reset
		Toast.makeText(vncCanvasActivity, vncCanvasActivity.getString(R.string.no_zoom), Toast.LENGTH_SHORT).show();
		
	}
	/**
	 * Devuelve el valor que tenga actualmente el zoom para que solo pueda modificarse en Zoom.java
	 * @return valor de zoom
	 * @author Alberto Población Navarro
	 */
	public float getZoom(){
		return zoom;
	}
	/**
	 * Devuelve la matriz que se aplique al escalado para que solo pueda modificarse en Zoom.java
	 * @return Matriz
	 * @author Alberto Población Navarro
	 */
	static public Matrix getMatrix(){
		return matriz;
	}
	/**
	 * Liberamos la instancia para que se cree una nueva la proxima vez. 
	 */
	@Override
	protected void finalize(){
		instancia = null;
	}
}

