/**
 * @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) {
            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=68E45C8BBE0ABFA80188E62D082D7A1E5FD4D637D99A5826781280F3865B5819
            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=00B65233550BE5A9866CF871D7712691CC47532A08C620D20579FB02BEFA3D1C5F
            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=02015791FDC6CE82402F0210455453495F506C7567746573745F414180910000040E5F7F47D7A165E2DC403F1E8BAA14E6FF04ADE282CB22212056444CE0F193B01493338B6A8A5D36B2BE9CD3D1F5C708862BCD4539F9D99A0D9F7347066A92C001010004402A571AF5293651B3E2A931CEB1009805EB92E44288D08510E67BA629F14D61402A571AF5293651B3E2A931CEB1009805EB92E44288D08510E67BA629F14D6102202006C04080C040812401146757031A5617030303181DB9CF7C052616001DB9566E0526872A1D53F0D0052783500000A3EECD601A7D17D4C1EB4B8205EEFAE45E170872D49741941B5C9AEDEAA76E38938A67F98DCD46D390B9CF4C16AD324B9E6520565994790029A841E4C820BE4D
            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=0201CDF3567352CD8B4401008095000004DCBD7FBD344A653D42420350D3CB84E68C1E618ACC16FF4BA31D9D2913530A1ADED6F691D23476E779CCA54C63327F28E9E943929CF5C459DDA01E459279CAA0010100041D2EE0E362BF2798C5148BB198B2EB3718CB0B8714580A70A7161A5550D71F364FF1D5098A18E2B9CE142A17B5558A9E54438823D146CC24A372E7CE1761AF100220210AC040800100C0408101002401146757031A5617030303181DB9CF7C052616001DB9566E0526872A1D53F0D0052783500000AE35B36EA820B183AD0AD732656E2606A8C373C95DFA580FF4C049C66FEFD76F0B558C16932B4DC2BA0D52966AC3D56AC20E606DEDE63B625B4B80FDE1269335
            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
