package com.dnieadmin;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.ReaderCallback;
import android.nfc.Tag;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.dnieadmin.gui.MyCaCertificate;
import com.dnieadmin.gui.MyPasswordDialog;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.Security;

import de.tsenger.androsmex.data.CANSpecDO;
import de.tsenger.androsmex.data.CANSpecDOStore;
import de.tsenger.androsmex.pace.PaceException;
import es.gob.jmulticard.card.CryptoCardException;
import es.gob.jmulticard.card.dnie.FakeX509Certificate;
import es.gob.jmulticard.jse.provider.DnieProvider;
import es.gob.jmulticard.ui.passwordcallback.CancelledOperationException;
import es.gob.jmulticard.ui.passwordcallback.DNIeDialogManager;
import es.inteco.labs.net.DNIeCaCertsManager;
import es.inteco.labs.net.DroidHttpClient;

@SuppressLint("NewApi")
public class NFCOperationsEncKitKat extends Activity implements ReaderCallback {
	static private NfcAdapter myNfcAdapter = null;
	private Tag tagFromIntent;

	// Gestión del CAN
	private CANSpecDO canDnie;
	private CANSpecDOStore cansDO;

    private Activity myActivity;
	private Context myContext;
	Typeface fontType;

	private String m_SSLresultado;
	private String m_SSLtargetURL;
    private boolean bConexionOK = false;

	private int currentapiVersion;
	private int iDisplaySize;

    private static final String AUTH_CERT_ALIAS = "CertAutenticacion"; //$NON-NLS-1$

    private static final int SIZE_10_INCHES = 10;
    private static final int SIZE_7_INCHES  = 7;
    private static final int SIZE_5_INCHES  = 5;
    private static final int SIZE_4_INCHES  = 4;

    private KeyStore m_ksUserDNIe=null;

    final Handler myHandler = new Handler();
	private ProgressDialog progressBar;
	private String textoProcessDlg;
	private String textoResultDlg;

	final Runnable updateStatus = new Runnable() {
		public void run()
		{
			progressBar.setMessage(textoProcessDlg);
			progressBar.show();
		}
	};

	final Runnable cleanResult = new Runnable()
	{
		public void run()
		{
			// Cambiamos el fondo de pantalla
	    	findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo_secundario);

			textoResultDlg ="";
			findViewById(R.id.result1).setVisibility(TextView.INVISIBLE);
			((ImageView)findViewById(R.id.resultimg)).setImageResource(R.drawable.checked_false);
			findViewById(R.id.resultimg).setVisibility(ImageView.INVISIBLE);
			findViewById(R.id.result2).setVisibility(TextView.INVISIBLE);
			findViewById(R.id.resultinfo).setVisibility(TextView.INVISIBLE);
	        findViewById(R.id.BtnNFC_BACK).setVisibility(Button.INVISIBLE);
		}
	};

	final Runnable askForRead = new Runnable()
	{
		public void run()
		{
			// Cambiamos el fondo de pantalla
			findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo_secundario);

			textoResultDlg ="";
			((TextView)findViewById(R.id.result1)).setText(R.string.op_dgtreinit);
			findViewById(R.id.result1).setVisibility(TextView.VISIBLE);
			((ImageView)findViewById(R.id.resultimg)).setImageResource(R.drawable.dni30_grey_peq);
			findViewById(R.id.resultimg).setVisibility(ImageView.VISIBLE);
		}
	};

	final Runnable newRead = new Runnable()
	{
		public void run()
		{
			// Cambiamos el fondo de pantalla
	    	findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo_secundario);

			textoResultDlg ="";
			((TextView)findViewById(R.id.result1)).setText(R.string.process_msg_lectura);
			findViewById(R.id.result1).setVisibility(TextView.VISIBLE);
			((ImageView)findViewById(R.id.resultimg)).setImageResource(R.drawable.dni30_peq);
			findViewById(R.id.resultimg).setVisibility(ImageView.VISIBLE);
			findViewById(R.id.result2).setVisibility(TextView.INVISIBLE);
			findViewById(R.id.resultinfo).setVisibility(TextView.INVISIBLE);
	        findViewById(R.id.BtnNFC_BACK).setVisibility(Button.INVISIBLE);
		}
	};

public class MyTaskDNIe extends AsyncTask<Void, Integer, Void>
	{
		boolean bForzamosReinicio = true;

	    @Override
	    protected void onPreExecute()
	    {
			bConexionOK = false;

			myHandler.post(newRead);

			// Lanzamos el diálogo con el progreso
 			progressBar.setIndeterminate(true);
 			progressBar.setCancelable(false);
 			progressBar.setTitle(R.string.process_title);
 			progressBar.setMessage(getApplicationContext().getString(R.string.process_msg_dni));
 			textoProcessDlg=getApplicationContext().getString(R.string.process_msg_dni);
	    }

	    @Override
		protected void onProgressUpdate(Integer... values) {
			super.onProgressUpdate(values);

			progressBar.setMessage(textoProcessDlg);
			progressBar.show();
		}

		@Override
	    protected Void doInBackground(Void... arg0)
		{
			//////////////////////////////////////////////////////////////////////////////////
			// PASO 1: Leemos el CDF para obtener los certificados almacenados en la tarjeta
			//
			try
			{
				// Lanzamos la operación de lectura del DNIe
				CargarCDF();

				// Si llegamos hasta aquí, hemos solicitado y presentado el PIN,
				// de manera que ya no reintentaremos la conexión
				bForzamosReinicio = false;
			} catch (CryptoCardException e){
				bForzamosReinicio = false;
				return null;
			} catch (CancelledOperationException e){
				bForzamosReinicio = false;
				textoResultDlg = "Operación cancelada por el usuario.";
				return null;
			}  catch (PaceException e){
				// Si el código CAN es incorrecto, mostramos el error y no permitimos continuar.
				bForzamosReinicio   = false;
				textoResultDlg    	= e.getMessage();
				return null;
			} catch (Exception e){
				textoResultDlg = "Ocurrió un error durante la lectura de ficheros.";
				if (e.getMessage()!=null)
	    		{
	    			if (e.getMessage().contains("lost"))
	    				textoResultDlg = "Error de comunicación. Se ha perdido la conexión con el DNIe.";
	    			else
	    				textoResultDlg = e.getMessage();
	    		}

				return null;
			}

			//////////////////////////////////////////////////////////////////////////////////
			// PASO 2: Lanzamos la conexión SSL
			//
			try
			{
				// Lanzamos la operación de conexión con el servidor SSL
				bConexionOK = ConectarSSL();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}

			return null;
        }

	    @Override
	    protected void onPostExecute(Void result)
	    {
			// Destruímos el cuadro de diálogo
			progressBar.dismiss();

			// Si no hemos presentado el PIN aún, permitimos que se continuen las lecturas.
			// Es posible que el DNIe se haya movido sin querer así que intentamos reconectar
			if(bForzamosReinicio) {
				// Repintamos la pantalla
				myHandler.post(askForRead);

				// Si la versión de Android lo soporta, ajustamos la comprobación de presencia NFC a 1 segundo
				if (currentapiVersion > Build.VERSION_CODES.KITKAT)
					EnableReaderMode(1000);

				return;
			}

			// Desactivamos el modo reader de NFC
			DisableReaderMode();

			// Limpiamos el keystore y descargamos el provider del DNIe
			m_ksUserDNIe=null;
			Security.removeProvider("DNIeJCAProvider");

			// Si la operación se detuvo inesperadamente, mostramos el error y salimos.
			if(!bConexionOK)
			{
				HandleError(textoResultDlg);
				return;
			}

			// La operación se completó correctamente, así que actualizamos los controles
			myHandler.post(cleanResult);

			try {
				// Adaptamos el contenido web a las style sheet locales
				m_SSLresultado = m_SSLresultado.replace("/Fw", "file:///android_asset/Fw");
				m_SSLresultado = m_SSLresultado.replace("/assets/", "file:///android_asset/assets/");
				m_SSLresultado = m_SSLresultado.replace("/FWProjects", "file:///android_asset/FWProjects");
				m_SSLresultado = m_SSLresultado.replace(".jpg", ".png");

				// Lanzamos el intent con el resultado
				Intent myWebViewIntent = new Intent(NFCOperationsEncKitKat.this, MyWebView.class);
				myWebViewIntent.putExtra("htmlString", m_SSLresultado);
				startActivity(myWebViewIntent);

			}catch (Exception e)
			{
				e.printStackTrace();
			}
        }
    }

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);

        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.setContentView(R.layout.nfcactkitkat);

        // Inicializamos controles
        tagFromIntent 	= null;
    	myContext 		= NFCOperationsEncKitKat.this;
    	myActivity 		= ((Activity) myContext);

    	// Obtenemos el adaptador NFC
        myNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        myNfcAdapter.setNdefPushMessage(null, this);
    	myNfcAdapter.setNdefPushMessageCallback(null, this);

		// Lanzamos el diálogo con el progreso
		progressBar = new ProgressDialog(myContext);

		// Limpiamos controles
		findViewById(R.id.result2).setVisibility(TextView.INVISIBLE);
		findViewById(R.id.resultinfo).setVisibility(TextView.INVISIBLE);
		findViewById(R.id.BtnNFC_BACK).setVisibility(Button.INVISIBLE);

		currentapiVersion = android.os.Build.VERSION.SDK_INT;

		// Conexión con el DNIe
 		cansDO 	= new CANSpecDOStore(this);
 		canDnie = ((MyAppDNIEADMIN)getApplicationContext()).getCAN();

 		// Recuperamos la URL del servicio al que queríamos conectar
        m_SSLtargetURL =((MyAppDNIEADMIN)getApplicationContext()).getUrl();

        // Calculamos el tamaño de la pantalla
        iDisplaySize = getDisplaySize();

 		// Ajustamos tipo de letra
        fontType = Typeface.createFromAsset(myContext.getAssets(),"fonts/NeutraText-LightAlt.otf");
    	TextView myText = (TextView)findViewById(R.id.result1);
		myText.setTypeface(fontType);

		myText = (TextView)findViewById(R.id.result2);
		myText.setVisibility(TextView.INVISIBLE);
		myText.setTypeface(fontType);

		myText = (TextView)findViewById(R.id.resultinfo);
		myText.setVisibility(TextView.INVISIBLE);
		myText.setTypeface(fontType);

		///////////////////////////////////////////////////////////////////////////////////
		// Botón de vuelta al Activity anterior
    	Button btnNFCBack = (Button)findViewById(R.id.BtnNFC_BACK);
    	btnNFCBack.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {

                // Desactivamos el modo reader de NFC, si fuera neceario
                DisableReaderMode();

				// Devolvemos el valor del módulo al Activity anterior
				onBackPressed();
			}
		});
    }

    public void HandleError(String strError)
    {
    	// Recolocamos los textos e imágenes
    	RelativeLayout layoutMain = (RelativeLayout)findViewById(R.id.mainRelativeLayout);
    	LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)layoutMain.getLayoutParams();
        switch(iDisplaySize)
        {
            case SIZE_10_INCHES:
            case SIZE_7_INCHES:
                params.setMargins(0, 400, 0, 0);
                findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo800_dni);
                break;
            case SIZE_5_INCHES:
                params.setMargins(0, 600, 0, 0);
                findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo1080_dni);
                break;
            default:
                params.setMargins(0, 400, 0, 0);
                findViewById(R.id.fondopantalla).setBackgroundResource(R.drawable.fondo1080_dni);
                break;
        }
    	layoutMain.setLayoutParams(params);

    	// Indicamos el error en la activity
		((TextView)findViewById(R.id.result1)).setText(R.string.result_nok_title);
		findViewById(R.id.result1).setVisibility(TextView.VISIBLE);
		((TextView)findViewById(R.id.result2)).setText(R.string.result_nok_description);
		findViewById(R.id.result2).setVisibility(TextView.VISIBLE);
		((ImageView)findViewById(R.id.resultimg)).setImageResource(R.drawable.checked_false);
		findViewById(R.id.resultimg).setVisibility(ImageView.VISIBLE);
		((TextView)findViewById(R.id.resultinfo)).setText(strError);
		findViewById(R.id.resultinfo).setVisibility(TextView.VISIBLE);
		findViewById(R.id.BtnNFC_BACK).setVisibility(Button.VISIBLE);

		// Activamos botón "VOLVER"
        Button btnVolver = (Button)findViewById(R.id.BtnNFC_BACK);
        btnVolver.setVisibility(Button.VISIBLE);
        btnVolver.setClickable(true);
	}

    public void CargarCDF() throws Exception {

		// Leemos los certificados
		try
		{
			// Actualizamos el estado del progress Dlg
			textoProcessDlg = "Accediendo al DNIe...";
			myHandler.post(updateStatus);

			// Activamos el modo rápido para agilizar la carga.
			System.setProperty("es.gob.jmulticard.fastmode", "true");

			final DnieProvider p = new DnieProvider();
			p.setProviderTag(tagFromIntent);
			p.setProviderCan(canDnie.getCanNumber());
			Security.insertProviderAt(p, 1);

			// Cargamos certificados y keyReferences
			m_ksUserDNIe = KeyStore.getInstance("MRTD");
			m_ksUserDNIe.load(null, null);

			// Actualizamos la BBDD de los CAN para añadir estos datos si no los tuviéramos
			if(canDnie.getUserNif().length()==0){
				String certSubject = ((FakeX509Certificate) m_ksUserDNIe.getCertificate(AUTH_CERT_ALIAS)).getSubjectDN().toString();
				CANSpecDO newCan = new CANSpecDO(canDnie.getCanNumber(),
						certSubject.substring((certSubject.indexOf("CN=") + 3)),
						certSubject.substring(certSubject.indexOf("NIF ") + 4));
				cansDO.delete(canDnie);
				cansDO.save(newCan);
			}

			// Construímos el diálogo que solicitará el PIN
			MyPasswordDialog myFragment = new MyPasswordDialog(NFCOperationsEncKitKat.this, true);
			DNIeDialogManager.setDialogUIHandler(myFragment);

			// Actualizamos el estado del progressDialog
			textoProcessDlg = "Cargando certificados...";
			myHandler.post(updateStatus);

			// Forzamos a cargar los certificados reales pidiendo la clave
			m_ksUserDNIe.getKey(AUTH_CERT_ALIAS, null);
			m_ksUserDNIe.getCertificate(AUTH_CERT_ALIAS).getEncoded();

		} catch (CryptoCardException e) {
			textoResultDlg = "Ocurrió un error durante la lectura del DNIe.\n";
			textoResultDlg += e.getMessage();
			e.printStackTrace();
			throw e;
		} catch (CancelledOperationException e) {
			textoResultDlg = "Ocurrió un error durante la lectura del DNIe.\n";
			textoResultDlg += "La operación de firma ha sido cancelada.";
			e.printStackTrace();
			throw new CancelledOperationException(textoResultDlg);
		} catch (Exception e) {
			textoResultDlg = "Ocurrió un error durante la lectura del DNIe.\n";
			e.printStackTrace();
			if (e.getMessage().contains("CAN incorrecto"))
				throw new PaceException("Error al montar canal PACE. CAN incorrecto.");
			else
				throw new Exception(e.getMessage());
		}
	}

    public boolean ConectarSSL() throws Exception
    {
		try
		{
			// Procedemos a la conexión con el servidor
			m_SSLresultado = "";
			textoProcessDlg="Lanzando conexion...";
			myHandler.post(updateStatus);

			// Creamos el objeto que gestionará los certificados adicionales
			MyCaCertificate myCert = new MyCaCertificate(NFCOperationsEncKitKat.this);
			DNIeCaCertsManager.setCaCertHandler(myCert);

			HttpEntity webContent = DroidHttpClient.executeRequest(m_SSLtargetURL, myContext, m_ksUserDNIe);

			String codificacion = EntityUtils.getContentCharSet(webContent);
  			codificacion = (codificacion==null) ? "utf-8" : codificacion;

			m_SSLresultado = new String(EntityUtils.toByteArray(webContent),Charset.forName(codificacion));
			DroidHttpClient.cleanCookies();
		}
		catch (ClientProtocolException e) {
			textoResultDlg = "No se ha podido establecer la conexión.";
			throw new ClientProtocolException(textoResultDlg);
		}
		catch (SocketTimeoutException e) {
			textoResultDlg = "No se ha podido realizar la conexión. Se ha sobrepasado el tiempo de espera...";
			throw new SocketTimeoutException(textoResultDlg);
		}
		catch (IOException e) {
            textoResultDlg = "No se ha podido realizar la conexión. Asegúrese de que está conectado a Internet..."
                    + ((e.getMessage() != null) ? ("\n" + e.getMessage()) : "");
			throw new IOException(textoResultDlg);
		}
		catch (Exception e) {
            e.printStackTrace();
            String alternateMessage = "No se ha podido establecer la conexión SSL.";
            textoResultDlg = "Error en la conexión con el servidor.\n" + (e.getMessage()!= null ? ": " + e.getMessage() : alternateMessage);
            throw new Exception(textoResultDlg);
		}

		return true;
    }

	@Override
    public void onResume() {
		super.onResume();

		// Activamos la lectura NFC tras unos segundos para dar tiempo a colocar la tarjeta
	    Handler handler = new Handler();
	    handler.postDelayed(new Runnable() {
			public void run() {
				if (!bConexionOK) {
					if (currentapiVersion <= Build.VERSION_CODES.KITKAT)
						EnableReaderMode(20000);
					else
						EnableReaderMode(1000);
				}
			}
		}, 1000);

    }

	@Override
	public void onTagDiscovered(Tag tag) {
		tagFromIntent = tag;

		// En versiones anteriores a KITKAT no se puede ajustar en tiempo real el timer de comprobación
		if (currentapiVersion > Build.VERSION_CODES.KITKAT){
			// Reconfiguramos el adaptador NFC para que el control de presencia de NFC no nos tire el canal
			EnableReaderMode(30000);
		}

		MyTaskDNIe newTask = new MyTaskDNIe();
		newTask.execute();
	}

	private boolean EnableReaderMode (int msDelay)
	{
		// Ponemos en msDelay milisegundos el tiempo de espera para comprobar presencia de lectores NFC
		Log.d("", "Enabling Reader Mode (" + msDelay + " ms)...");
		Bundle options = new Bundle();
		options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, msDelay);
        myNfcAdapter.enableReaderMode( 	myActivity,
                                        this,
                                        NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK|
                                        NfcAdapter.FLAG_READER_NFC_B,
                                        options);

		return true;
	}

	private boolean DisableReaderMode()
	{
		// Desactivamos el modo reader de NFC
		Log.d("", "Disabling Reader Mode...");
		myNfcAdapter.disableReaderMode(myActivity);
		return true;
	}

    public int getDisplaySize()
    {
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        int width=dm.widthPixels;
        int height=dm.heightPixels;
        int dens=dm.densityDpi;
        double wi=(double)width/(double)dens;
        double hi=(double)height/(double)dens;
        double x = Math.pow(wi,2);
        double y = Math.pow(hi,2);
        double screenInches = Math.sqrt(x+y);

        if(screenInches>=9)
            return SIZE_10_INCHES;
        if(screenInches>=6)
            return SIZE_7_INCHES;
        if(screenInches>=4.5)
            return SIZE_5_INCHES;
        else
            return SIZE_4_INCHES;
    }
}