package com.fp.firma.common;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.fp.firma.dto.TfirmDatosFirmante;
import com.fp.firma.keystore.KeyStoreProvider;
import com.fp.firma.keystore.LinuxKeyStoreProvider;
import com.fp.firma.keystore.WindowsJDK6KeyStoreProvider;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.CertificateInfo.X500Name;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.OcspClient;
import com.itextpdf.text.pdf.security.OcspClientBouncyCastle;
import com.itextpdf.text.pdf.security.PrivateKeySignature;
import com.itextpdf.text.pdf.security.TSAClient;
//import javax.persistence.NoResultException;
//import javax.persistence.Query;

/**
 * Clase que contiene m&eacute;todos utilitarios para manejar certificados
 * 
 * @author dcruz
 * 
 */
public class CertificateUtils {

    /*
     * CONSTANTES DESIGNADAS SEGUN LA NUEVA ESTRUCTURA DEL BCE
     */
    // OIDs de Campos del Certificado:
    public static final String OID_CEDULA_PASAPORTE = FirmMessages.getString("oid.cedula_pasaporte");

    public static final String OID_NOMBRES = FirmMessages.getString("oid.nombres_persona");

    public static final String OID_APELLIDO_1 = FirmMessages.getString("oid.apellido_persona1");

    public static final String OID_APELLIDO_2 = FirmMessages.getString("oid.apellido_persona2");

    public static final String OID_CARGO = FirmMessages.getString("oid.cargo");

    public static final String OID_INSTITUCION = FirmMessages.getString("oid.institucion");

    public static final String OID_DIRECCION = FirmMessages.getString("oid.direccion");

    public static final String OID_TELEFONO = FirmMessages.getString("oid.telefono");

    public static final String OID_CIUDAD = FirmMessages.getString("oid.ciudad");

    public static final String OID_RAZON_SOCIAL = FirmMessages.getString("oid.razon_social");

    public static final String OID_RUC = FirmMessages.getString("oid.ruc");

    public static final String X509 = "X.509";

    static {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }

    /**
     * Retorna el certificado del usuario contenido en la firma
     * 
     * @param store el store en el que se va a buscar los certificados
     * @param aliases los alias que estan dentro del keystore
     * @return el {@link java.security.Certificate}
     * @throws KeyStoreException
     */
    public synchronized static Certificate obtainCertificateInAlias(KeyStore store, Enumeration<String> aliases) throws KeyStoreException {
        Certificate certs[] = null;
        synchronized (aliases) {
            int i = 0;
            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();
                System.out.println("Se imprime los alias: " + i + " " + alias);
                certs = store.getCertificateChain(alias);
                for (int j = 0; j < certs.length; j++) {
                    X509Certificate certificate = (X509Certificate) certs[j];
                    boolean[] usages = certificate.getKeyUsage();
                    System.out.println("Certificado en primera posicion usado: " + j + " " + usages[0]);
                    if (usages[0]) {
                        return certificate;
                    }
                }
                i++;
            }

        }
        return null;
    }

    /**
     * Se devuelve todos los datos de la firma del usuario en base al certificado enviado
     * 
     * @param certificate el certificado enviado
     * @return Los datos del certificado de dicho usuario
     */
    public static TfirmDatosFirmante obtainDataForCertificate(Certificate certificate) {
        X509Certificate signedCertificate = (X509Certificate) certificate;
        TfirmDatosFirmante datosFirmante = new TfirmDatosFirmante();
        if (signedCertificate.getExtensionValue(OID_CEDULA_PASAPORTE) != null) {
            datosFirmante.setIdentificacion(new String(signedCertificate.getExtensionValue(OID_CEDULA_PASAPORTE)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_NOMBRES) != null) {
            datosFirmante.setNombre(new String(signedCertificate.getExtensionValue(OID_NOMBRES)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_APELLIDO_1) != null) {
            datosFirmante.setApellido1(new String(signedCertificate.getExtensionValue(OID_APELLIDO_1)).trim());
            if (signedCertificate.getExtensionValue(OID_APELLIDO_2) != null) {
                datosFirmante.setApellido2(new String(signedCertificate.getExtensionValue(OID_APELLIDO_2)).trim());
            } else {
                datosFirmante.setApellido2("");
            }
        }
        if (signedCertificate.getExtensionValue(OID_TELEFONO) != null) {
            datosFirmante.setTelefono(new String(signedCertificate.getExtensionValue(OID_TELEFONO)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_CARGO) != null) {
            datosFirmante.setCargo(new String(signedCertificate.getExtensionValue(OID_CARGO)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_CIUDAD) != null) {
            datosFirmante.setCiudad(new String(signedCertificate.getExtensionValue(OID_CIUDAD)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_DIRECCION) != null) {
            datosFirmante.setDireccion(new String(signedCertificate.getExtensionValue(OID_DIRECCION)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_INSTITUCION) != null) {
            datosFirmante.setInstitucion(new String(signedCertificate.getExtensionValue(OID_INSTITUCION)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_RAZON_SOCIAL) != null) {
            datosFirmante.setInstitucion(new String(signedCertificate.getExtensionValue(OID_RAZON_SOCIAL)).trim());
        }
        if (signedCertificate.getExtensionValue(OID_RUC) != null) {
            datosFirmante.setRuc(new String(signedCertificate.getExtensionValue(OID_RUC)).trim());
        }
        datosFirmante.setFechaInicioVigencia(signedCertificate.getNotBefore());
        datosFirmante.setFechaVigencia(signedCertificate.getNotAfter());
        return datosFirmante;
    }

    /**
     * Valida que el certificado del usuario sea un certificado valido contra el servicio OCSP del proveedor
     * 
     * @param certificateUser
     * @throws FileNotFoundException
     * @throws CertificateException
     */
    public static void validateOcsCertificate(Certificate certificateUser) throws FileNotFoundException, CertificateException {
        System.out.println(((X509Certificate) certificateUser).getIssuerDN().getName());
        CertificateFactory cf = CertificateFactory.getInstance(X509);
        InputStream subordStream = new FileInputStream(FirmMessages.getString("dir.ruta.base.repositorio") + "/"
                + FirmMessages.getString("nombre.certificado.subordinado"));
        X509Certificate subordCertificate = (X509Certificate) cf.generateCertificate(subordStream);
        check((X509Certificate) certificateUser, subordCertificate);
    }

    /**
     * Valida que los certificados enviados sean validos por la entidad certificadora
     * 
     * @param issuerCert certificado del usuario
     * @param x509Cert certificado que firmo el certificado expedido
     */
    public static void check(X509Certificate issuerCert, X509Certificate x509Cert) {
//        try {
//
//            BigInteger serialNumber = issuerCert.getSerialNumber();
//            X509CertificateHolder holder;
//
//            try {
//                holder = new X509CertificateHolder(x509Cert.getEncoded());
//            } catch (IOException e) {
//                throw new RuntimeException(e);
//            }
//
//            CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()
//                    .get(CertificateID.HASH_SHA1), holder, serialNumber);
//
//            OCSPReqBuilder ocspGen = new OCSPReqBuilder();
//            ocspGen.addRequest(id);
//            OCSPReq ocspReq = ocspGen.build();
//
//            // Ir al OCSP
//            String ocspUrl = CertificateUtil.getOCSPURL(issuerCert);
//
//            if (ocspUrl == null) {
//                System.out.println("URL de OCSP is null");
//                return;
//            }
//
//            URL url;
//
//            try {
//                url = new URL(ocspUrl);
//            } catch (MalformedURLException e) {
//                throw new RuntimeException(e);
//            }
//
//            HttpURLConnection con;
//            OCSPResp ocspResponse;
//            DataOutputStream dataOut = null;
//            try {
//                con = (HttpURLConnection) url.openConnection();
//
//                con.setRequestProperty("Content-Type", "application/ocsp-request");
//                con.setRequestProperty("Accept", "application/ocsp-response");
//                con.setDoOutput(true);
//
//                OutputStream out = con.getOutputStream();
//                dataOut = new DataOutputStream(new BufferedOutputStream(out));
//                dataOut.write(ocspReq.getEncoded());
//
//                System.out.println("Estado de respuesta de la peticion=" + con.getResponseCode());
//
//                /*
//                 * Se parsea la respuesta y se obtiene el estado del certificado retornado por el OCSP
//                 */
//                InputStream in = (InputStream) con.getContent();
//                byte[] resp = read(in); // Read the reponse
//                ocspResponse = new OCSPResp(resp);
//            } catch (IOException e) {
//                throw new FirmasException("ERROR AL ESTABLECER CONEXI\u00d3N AL SERVICIO: "+url);
//            } finally{
//            	if(dataOut != null){
//	            	try {
//	            		dataOut.flush();
//	            		dataOut.close();
//					} catch (IOException e) {}
//            	}
//            }
//
//            int status = ocspResponse.getStatus();
//            System.out.println("status=" + status);
//
//            BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject();
//
//            if (basicResponse != null) {
//                SingleResp[] responses = basicResponse.getResponses();
//                SingleResp response = responses[0];
//                CertificateStatus certStatus = (CertificateStatus) response.getCertStatus();
//
//                if (certStatus instanceof RevokedStatus) {
//                    System.out.println("REVOKED");
//                    RevokedStatus revokedStatus = (RevokedStatus) certStatus;
//                    System.out.println("Reason: " + revokedStatus.getRevocationReason());
//                    System.out.println("Date: " + revokedStatus.getRevocationTime());
//
//                    throw new FirmasException("ERROR, CERTIFICADO REVOCADO POR "+revokedStatus.getRevocationReason()+" CON FECHA "+  revokedStatus.getRevocationTime());
//                }
//            }
//        } catch (OCSPException e) {
//            throw new RuntimeException(e);
//        } catch (CertificateEncodingException e) {
//            throw new RuntimeException(e);
//        } 
    }

    /**
     * Devuelve los nombres de firmas de un documento PDF
     * 
     * @param docStream el documento a verificar
     * @return un lista de nombres de firmas
     */
    public static List<String> obtainNameSigns(InputStream docStream) {
        try {
            PdfReader docPdf = new PdfReader(docStream);
            AcroFields af = docPdf.getAcroFields();
            return af.getSignatureNames();
        } catch (IOException e) {
            System.err.println("Error al obtener los nombres del documentoss");
            return null;
        } finally{
        	
        }
    }
    
/**
 * M&eacute;todo para firmar documentos
 * @param fileSrc
 * @param usuario
 * @param password
 * @param compania
 * @param reason
 * @param location
 * @param rectangle
 * @param numPage
 * @param nameSign
 * @return
 * @throws Exception
 */
    public static byte[] sign(InputStream fileSrc, String usuario, String password, Integer compania, String reason, String location,
            Rectangle rectangle, int numPage, String nameSign, String pathCertificate) throws Exception {
        String path = pathCertificate;
        FileInputStream ceritificateInputStream = new FileInputStream(path);
        return sign(fileSrc, ceritificateInputStream, password, reason, location, false, true, rectangle, numPage, nameSign, null);
    }
    
    /**
     * Metodo para firmar documentos
     * @param fileSrc
     * @param usuario
     * @param password
     * @param compania
     * @param reason
     * @param location
     * @param rectangle
     * @param numPage
     * @param nameSign
     * @return
     * @throws Exception
     */
        public static byte[] sign(InputStream fileSrc, String usuario, String password, Integer compania, String reason, String location,
                Rectangle rectangle, int numPage, String nameSign) throws Exception {
            String path = "";
            FileInputStream ceritificateInputStream = new FileInputStream(path);
            return sign(fileSrc, ceritificateInputStream, password, reason, location, false, true, rectangle, numPage, nameSign, null);
        }

    /**
     * M&eacute;todo que firma un documento en base los par&aacute;metros indicados
     * 
     * @param fileSrc archivo origen
     * @param fileCert archivo firma en formato digital si no es null el firmado es con archivo caso contrario es token
     * @param password contrasena
     * @param reason raz&oacute;n o motivo de la firma.
     * @param location localizaci&oacute;n coemtario adicional de la firma.
     * @param withTS tiene TS estamapado de tiempo.
     * @param withOCSP tiene OCSP verificacion de la firma por el la entidad certificadora BCE.
     * @param rectangle lugar localizaci&acute;n firma
     * @param numPage n&uacute;mero pagina
     * @param nameSign nombre firma
     * @param tipoFirma es el tipo de token que se usar&aacute;(Token que tipo?)
     * @return el documento firmado
     */
    public static byte[] sign(InputStream fileSrc, InputStream fileCert, String password, String reason, String location, boolean withTS,
            boolean withOCSP, Rectangle rectangle, int numPage, String nameSign, String tipoFirma) {
    	OutputStream outputStream = null;
    	System.out.println("Ingreso a firmar");
        try {
            PdfReader pdfReader = new PdfReader(fileSrc);
            outputStream = new ByteArrayOutputStream(512);
            PdfStamper stamper = PdfStamper.createSignature(pdfReader, outputStream, '\0');
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            KeyStore store;
            String alias;
//            PrivateKey privateKey = null;
            ExternalSignature signature = null;
            if(fileCert != null){
	            store = KeyStore.getInstance("PKCS12");
	            store.load(fileCert, password.toCharArray());
	            alias = store.aliases().nextElement();
	            signature = new PrivateKeySignature((PrivateKey) store.getKey(alias, password.toCharArray()), DigestAlgorithms.SHA256,
	                    "SunRsaSign");
            } else{//firma por token
            	KeyStoreProvider provider = getKeyStoreProvider(tipoFirma);
            	store = provider.getKeystore(password.toCharArray());
            	alias = store.aliases().nextElement();
//            	privateKey = (PrivateKey) store.getKey(alias, null);
                signature = new PrivateKeySignature((PrivateKey) store.getKey(alias, null), DigestAlgorithms.SHA1,
                        null);
            }
//            appearance.setCrypto(privateKey, store.getCertificateChain(alias), null, null);
            appearance.setLayer2Text(generateSignText(store.getCertificateChain(alias), reason, location));
            appearance.setLayer2Font(new Font(Font.FontFamily.UNDEFINED, 8F));
            appearance.setVisibleSignature(rectangle, numPage, nameSign);
            TSAClient tsc = null;
            if (withTS) {

            }
            OcspClient ocsp = null;
            if (withOCSP) {
            	ocsp = new OcspClientBouncyCastle();
            }
//            stamper.close();
            ExternalDigest externalDigest = new BouncyCastleDigest();
            MakeSignature.signDetached(appearance, externalDigest, signature, store.getCertificateChain(alias), null, ocsp, tsc, 15000,
                    CryptoStandard.CADES);
            return ((ByteArrayOutputStream) outputStream).toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (DocumentException e) {
        	throw new RuntimeException(e);
        } catch (KeyStoreException e) {
        	throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
        	throw new RuntimeException(e);
        } catch (CertificateException e) {
        	throw new RuntimeException(e);
        } catch (GeneralSecurityException e) {
        	throw new RuntimeException(e);
        } catch (FirmasException e) {
        	throw new RuntimeException(FirmMessages.getString("FIR-0006"));
		} catch (Throwable e) {
        	throw new RuntimeException(e);
		} finally{
        	try {   
                    if(fileCert != null){
				fileCert.close();
                    }
			} catch (IOException e) {}
            try {
				fileSrc.close();
			} catch (IOException e) {}
            try {
                if(outputStream != null){
                
                        outputStream.close();
                    }
			} catch (IOException e) {}
        }
    }
    
    /**
     * Arma documento el texto a mostrar de la plantilla
     * @param chains certificados
     * @param reason raz&oacute;n
     * @param location lugar
     * @return una layout de la firma
     */
    private static String generateSignText(Certificate[] chains, String reason, String location){
    	StringBuffer textLayout = new StringBuffer("Firmado digitalmente por: ");
    	String name = null;
    	X500Name x500name = CertificateInfo.getSubjectFields((X509Certificate)chains[0]);
        if (x500name != null) {
        	name = x500name.getField("CN");
        	if (name == null)
        		name = x500name.getField("E");
        }
        if(name == null){
        	name = "";
        }
        textLayout.append(name).append("\n");
        if(reason != null){
        	textLayout.append("Raz\u00f3n: ").append(reason).append("\n");
        }
        if(location != null){
        	textLayout.append("Lugar: ").append(location).append("\n");
        }
        SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
        textLayout.append("Fecha: ").append(format.format(new Date(System.currentTimeMillis())));
    	return textLayout.toString();
    }
    
    /**
     * Retorna el keystore del token especificado
     * @param tipoToken
     * @return
     * @throws KeyStoreException 
     */
    private static KeyStoreProvider getKeyStoreProvider(String tipoToken) {
    	String osName = System.getProperty("os.name");
        KeyStoreProvider provider = null;
    	if(tipoToken == null){
    		return provider;
    	}
        if (osName.toUpperCase().indexOf("WINDOWS") == 0) {
        	provider = new WindowsJDK6KeyStoreProvider();
            if (tipoToken.equals("2")) {
                provider = new WindowsJDK6KeyStoreProvider();  // trabaja con librerias sera para eToken
            }else if (tipoToken.equals("1")) {
            	provider = new WindowsJDK6KeyStoreProvider();  // trabaja con librerias sera para iKey
            }else if (tipoToken.equals("3")) {
            	provider = new WindowsJDK6KeyStoreProvider();  // trabaja con librerias sera para iKey
            }
        } else {
            provider = new LinuxKeyStoreProvider();
        }
        return provider;
    }

    private static byte[] read(InputStream in) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        int next = in.read();
        while (next > -1) {
            bos.write(next);
            next = in.read();
        }
        bos.flush();
        return bos.toByteArray();
    }

    /**
     * Entrega el path en el que se encuentra el certificado digital.
     * 
     * @param cusuario Codigo de usuario.
     * @param ccompania Codigo de compania.
     * @return String
     * @throws Exception
     */
//    private static String getPath(String cusuario, Integer ccompania) throws Exception {
//        TfirmCertificado tfirmcertificado = getTfirmCertificado(cusuario);
//        TgeneParametersKey tgeneParametersKey = new TgeneParametersKey("PATH_CERTIFICADO_BCE", ccompania);
//        TgeneParameters tgeneParameters = TgeneParameters.find(PersistenceHelper.getEntityManager(), tgeneParametersKey);
//        String path = tgeneParameters.getTextvalue() + "/" + tfirmcertificado.getPk().getIdcertificado();
//        return path;
//    }

    /**
     * Sentencia que entrega la defincion vigente de un certificado digital para un usuario.
     */
//    private static final String JPQL = "from TfirmCertificado where t.codigousuario = :cusuario";

    /**
     * Entrega defincion de certificados digitales vigente.
     * 
     * @param cusuario Codigo de usuario.
     * @return TfirmCertificado.
     * @throws Exception
     */
//    private static TfirmCertificado getTfirmCertificado(String cusuario) throws Exception {
//        TfirmCertificado tfirmCertificado = null;
//        Query qry = PersistenceHelper.getEntityManager().createQuery(JPQL);
//        qry.setParameter("cusuario", cusuario);
//        try {
//            tfirmCertificado = (TfirmCertificado) qry.getSingleResult();
//        } catch (NoResultException e) {
//            throw new FirmasException("FIR-0001", "PARAMETROS DEL CERTIFICADO DIGITAL NO DEFINIDO PARA EL USUARIO: {0}", cusuario);
//        }
//        return tfirmCertificado;
//    }
    
  
    /*
        public static boolean validatePassword(InputStream fileSrc, String usuario, String password, Integer compania, String reason, String location,
                Rectangle rectangle, int numPage, String nameSign) throws Exception {
            String path = getPath(usuario, compania);
            FileInputStream ceritificateInputStream = new FileInputStream(path);
            return pass(fileSrc, ceritificateInputStream, password, reason, location, false, true, rectangle, numPage, nameSign);
        }
        
         
        private static boolean pass(InputStream fileSrc, InputStream fileCert, String password, String reason, String location, boolean withTS,
                boolean withOCSP, Rectangle rectangle, int numPage, String nameSign) {
            try {
                PdfReader pdfReader = new PdfReader(fileSrc);
                OutputStream outputStream = new ByteArrayOutputStream(512);
                PdfStamper stamper = PdfStamper.createSignature(pdfReader, outputStream, '\0');
                PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
                
                KeyStore store = KeyStore.getInstance("PKCS12");
                store.load(fileCert, password.toCharArray());
                String alias = store.aliases().nextElement();
                ExternalSignature signature = new PrivateKeySignature((PrivateKey) store.getKey(alias, password.toCharArray()), DigestAlgorithms.SHA256,
                        "SunRsaSign");
                return true;
            } catch (Exception e) {
            	return false;
            }
        }
        */
    
    
}
