/**
 * @author      ETSI / STF481 / Yann Garcia
 * @version     $URL$
 *              $Id$
 */
package org.etsi.certificates.io;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.certificates.Helpers;
import org.etsi.common.ByteHelper;

public class CertificatesIO implements ICertificatesIO {
    
    /**
     * Regex pattern to extract signing private key
     */
    private static final String SIGNING_PRIVATE_KEY_PATTERN = "^SigningPrivate key\\s*=\\s*([A-Fa-f0-9]+)$";
    
    /**
     * Regex pattern to extract encrypt private key
     */
    private static final String ENCRYPT_PRIVATE_KEY_PATTERN = "^EncryptPrivate key\\s*=\\s*([A-Fa-f0-9]+)$";
    
    /**
     * Regex pattern to extract AA certificate
     */
    private static final String CERT_AA_PATTERN = "^AUTHORIZATION_AUTHORITY.dump\\s*=\\s*([A-Fa-f0-9]+)$";
    
    /**
     * Regex pattern to extract AT certificate
     */
    private static final String CERT_AT_PATTERN = "^AUTHORIZATION_TICKET.dump\\s*=\\s*([A-Fa-f0-9]+)$";
    
    /**
     * Regex compiler to extract signing private key
     */
    private Pattern signingPrivateKeyPattern = Pattern.compile(SIGNING_PRIVATE_KEY_PATTERN, Pattern.MULTILINE);
    
    /**
     * Regex compiler to extract encrypt private key
     */
    private Pattern encryptPrivateKeyPattern = Pattern.compile(ENCRYPT_PRIVATE_KEY_PATTERN, Pattern.MULTILINE);
    
    /**
     * Regex compiler to extract AA certificate
     */
    private Pattern certAaPattern = Pattern.compile(CERT_AA_PATTERN, Pattern.MULTILINE);
    
    /**
     * Regex compiler to extract AT certificate
     */
    private Pattern certAtPattern = Pattern.compile(CERT_AT_PATTERN, Pattern.MULTILINE);
    
    /**
     * Full path to access certificate files
     */
    private String _fullPath;
    
    /**
     * Memory cache for the certificates
     */
    private Map<String, byte[]> _cachedCertificates;
    
    /**
     * Memory cache for the signing private keys
     */
    private Map<String, byte[]> _cachedSigningPrivateKey;
    
    /**
     * Memory cache for the encrypt private keys
     */
    private Map<String, byte[]> _cachedEncryptPrivateKey;
    
    /**
     * Default constructor
     */
    public CertificatesIO() {
        _cachedCertificates = new ConcurrentHashMap<String, byte[]>();
        _cachedSigningPrivateKey = new ConcurrentHashMap<String, byte[]>();
        _cachedEncryptPrivateKey = new ConcurrentHashMap<String, byte[]>();
    } // End of Constructor
    
    /**
     * @desc    Load in memory cache the certificates available in the specified directory
     * @param   rootDirectory Root directory to access to the certificates identified by the certificate ID
     * @param   configId      A configuration identifier
     * @return  true on success, false otherwise
     */
    @Override
    public boolean loadCertificates(final String rootDirectory, final String configId) { // E.g. <rootDirectory path>, cfg01
        // Initialise the memory caches
        _cachedCertificates.clear();
        _cachedSigningPrivateKey.clear();
        _cachedEncryptPrivateKey.clear();
        
        // Build full path
        if ((rootDirectory == null) || (rootDirectory.length() == 0)) {
            _fullPath = System.getProperty("user.dir").replace("\\", "/");
        } else {
            _fullPath = rootDirectory.replace("\\", "/");
        }
        if (!_fullPath.endsWith("/")) {
            _fullPath += "/";
        }
        if ((configId != null) && (configId.length() != 0)) {
            _fullPath += configId.replace('.', '/'); 
        }
        _fullPath = _fullPath.toLowerCase();
        // Check the path
        File path = new File(_fullPath);
        if (!path.exists()) {
            System.err.println("CertificatesIO.readCertificate: path '" + _fullPath + "' does not found");
            return false;
        }
        
        return loadMemoryCache(path); // Load certificates and keys and return
    }
    
    /**
     * @desc    Unload from memory cache the certificates available
     * @return  true on success, false otherwise
     */
    @Override
    public boolean unloadCertificates() {
        _fullPath = null;
        _cachedSigningPrivateKey.clear();
        _cachedEncryptPrivateKey.clear();
        
        return true;
    }
    
    /**
     * @desc    Read the specified certificate
     * @param   certificateId the certificate identifier
     * @param   certificate   the expected certificate
     * @return  true on success, false otherwise
     */
    @Override
    public boolean readCertificate(final String key, final ByteArrayOutputStream certificate) {
        // Sanity check
        if (!_cachedCertificates.containsKey(key)) {
            System.err.println("CertificatesIO.readCertificate: key '" + key + "' not found");
            return false;
        }
        
        try {
            certificate.write(_cachedCertificates.get(key));
            
            return true;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return false;
    }
    
    /**
     * @desc    Read the private keys for the specified certificate
     * @param   keysId            the keys identifier
     * @param   signingPrivateKey the signing private key
     * @param   encryptPrivateKey the encrypt private key
     * @return  true on success, false otherwise
     */
    @Override
    public boolean readPrivateKeys(final String key, final ByteArrayOutputStream signingPrivateKey, final ByteArrayOutputStream encryptPrivateKey) {
        // Sanity check
        if (!_cachedSigningPrivateKey.containsKey(key) || !_cachedEncryptPrivateKey.containsKey(key)) {
            System.err.println("CertificatesIO.readPrivateKeys: key '" + key + "' not found");
            return false;
        }
        
        try {
            signingPrivateKey.write(_cachedSigningPrivateKey.get(key));
            encryptPrivateKey.write(_cachedEncryptPrivateKey.get(key));
            
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    
    private boolean loadMemoryCache(final File path) { // E.g. <path>/ta_config_a.txt
        // Retrieve the list of the files in the _fullpath
        try {
            List<File> files = Helpers.getInstance().getFileListing(path);
            // Create the memory cache
            for (File file : files) {
                addItem(file);
            } // End of 'for' statement
            
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return false;
    }

    private void addItem(final File file)  throws FileNotFoundException, IOException {
        String key = file.getName().substring(0, file.getName().indexOf('.')).toUpperCase();
        if (!_cachedCertificates.containsKey(key)) {
            FileInputStream fs = new FileInputStream(file.getCanonicalPath());
            byte bytes[] = new byte[(int) file.length()];
            fs.read(bytes);
            String content = new String(bytes);
            
            // Extract SigningPrivateKey. Key identifier is key.PRIVATE_KEYS
            // E.g. SigningPrivate key=0093D6EE437368BF5CEA48BDAF3A1654901997F1A869A6A794B4831D2B892E1815
            Matcher matcher = signingPrivateKeyPattern.matcher(content);
            if (matcher.find()) {
//                System.out.println("CertificatesIO.addItem: Add key '" + key + ".PRIVATE_KEYS" + "'");
                _cachedSigningPrivateKey.put(key + ".PRIVATE_KEYS", ByteHelper.hexStringToByteArray(matcher.group(1)));
            }
            // Extract EncryptPrivate. Key identifier is key.PRIVATE_KEYS
            // E.g. EncryptPrivate key=0756716E979FF003D7BA9A8DC7F50C57A7E84ABAF3CF0DD0956B5AF45ED438C8
            matcher = encryptPrivateKeyPattern.matcher(content);
            if (matcher.find()) {
//                System.out.println("CertificatesIO.addItem: Add key '" + key + ".PRIVATE_KEYS" + "'");
                _cachedEncryptPrivateKey.put(key + ".PRIVATE_KEYS", ByteHelper.hexStringToByteArray(matcher.group(1)));
            }
            
            // Extract AUTHORIZATION_AUTHORITY.dump. Key identifier is key.AA_CERT
            // E.g. AUTHORIZATION_AUTHORITY.dump=020901595119AEA8B454950210455453495F506C7567746573745F414180910000046DCBCF47429F6721647662B152952CC8858F79D70B61AFA0170823F56B2276D8B1E4B21D1D8C7C075A34449D0F567E8F2204C5CFBA7E849004811F6E7AA71CFD01010004EECBD2453864ED0A87BF163B1C608E52A2DB177ACCA04258E573745A5F177BE8EECBD2453864ED0A87BF163B1C608E52A2DB177ACCA04258E573745A5F177BE802202006C04080C04081240154241F6D5A391AED0303181DB9CF7C052616001DB9566E0526872A1D53F0D0052783500000E18A7752A83430DC15D517B17DABEC29916981CDB270732A8BD36A11E1163B6BFA55128E1B747304424D42BCBF000EC7D5B5433845FAE06CAD202CC706EC8FA3
            matcher = certAaPattern.matcher(content);
            if (matcher.find()) {
//                System.out.println("CertificatesIO.addItem: Add key '" + key + ".AA_CERT" + "'");
                _cachedCertificates.put(key + ".AA_CERT", ByteHelper.hexStringToByteArray(matcher.group(1)));
            }
            
            // Extract AUTHORIZATION_TICKET.dump. Key identifier is key.AT_CERT
            // E.g. AUTHORIZATION_TICKET.dump=020901A77169CDF5FCE1AE01008095000004C0E576B4056CF3922BE9092B667D8E1508A2E92FDFFD0F676407E552A5743FDB04C68CA774E11DAD5B26030F2C771FCB09AD3A961324E6417B9FAFCD402D78D801010004EE14051560092A42640250DC7E05E9898EF1092ED813A3921F09C1E803403A7ED7304E6CF82F000284DC3CA6644E8B6D33440762CA948E60454DEC52A156285C0220210AC040800100C040810100240154241F6D5A391AED0303181DB9CF7C052616001DB9566E0526872A1D53F0D00527835000008DCDEF69B73166204C9FCB503B4726C215DD92111A08D5A944F75F1DF5ABD6E3FBAA9189D9AA409B7F8A4D41DB693470371A44EACEED9D0B565B0D34ED9BBBDB
            matcher = certAtPattern.matcher(content);
            if (matcher.find()) {
//                System.out.println("CertificatesIO.addItem: Add key '" + key + ".AT_CERT" + "'");
                _cachedCertificates.put(key + ".AT_CERT", ByteHelper.hexStringToByteArray(matcher.group(1)));
            }
            
        } // else, ignore it
    }
    
} // End of class CertificatesIO
