/****************************************************************************
 * Copyright (C) 2012-2015 ecsec GmbH.
 * All rights reserved.
 * Contact: ecsec GmbH (info@ecsec.de)
 *
 * This file is part of the Open eCard App.
 *
 * GNU General Public License Usage
 * This file may be used under the terms of the GNU General Public
 * License version 3.0 as published by the Free Software Foundation
 * and appearing in the file LICENSE.GPL included in the packaging of
 * this file. Please review the following information to ensure the
 * GNU General Public License version 3.0 requirements will be met:
 * http://www.gnu.org/copyleft/gpl.html.
 *
 * Other Usage
 * Alternatively, this file may be used in accordance with the terms
 * and conditions contained in a signed written agreement between
 * you and ecsec GmbH.
 *
 ***************************************************************************/

package org.openecard.scio;

import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.NoSuchAlgorithmException;
import javax.smartcardio.CardTerminals;
import javax.smartcardio.TerminalFactory;
import org.openecard.common.ifd.scio.SCIOTerminals;
import org.openecard.common.util.LinuxLibraryFinder;
import org.openecard.scio.osx.SunOSXPCSC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Proxy and abstracted Factory for SCIO PC/SC driver.
 *
 * @author Tobias Wich
 * @author Benedikt Biallowons
 */
public class PCSCFactory implements org.openecard.common.ifd.scio.TerminalFactory {

    private static final Logger logger = LoggerFactory.getLogger(PCSCFactory.class);
    private static final String ALGORITHM = "PC/SC";

    private final String osName;
    private TerminalFactory terminalFactory;

    /**
     * Default constructor with fixes for the faulty SmartcardIO library.
     *
     * @throws FileNotFoundException if pcsclite for Linux can't be found.
     * @throws NoSuchAlgorithmException if no PC/SC provider can be found.
     */
    public PCSCFactory() throws FileNotFoundException, NoSuchAlgorithmException {
	osName = System.getProperty("os.name");
	if (osName.startsWith("Linux")) {
	    File libFile = LinuxLibraryFinder.getLibraryPath("pcsclite", "1");
	    System.setProperty("sun.security.smartcardio.library", libFile.getAbsolutePath());
	}
	loadPCSC();
    }

    @Override
    public String getType() {
	return terminalFactory.getType();
    }

    @Override
    public SCIOTerminals terminals() {
	return new PCSCTerminals(this);
    }

    TerminalFactory getRawFactory() {
	return terminalFactory;
    }

    final void loadPCSC() throws NoSuchAlgorithmException {
	if (osName.contains("OS X")) {
	    // see https://developer.apple.com/library/mac/technotes/tn2002/tn2110.html#FINDINGMAC
	    terminalFactory = TerminalFactory.getInstance(ALGORITHM, null, new SunOSXPCSC());
	} else {
	    terminalFactory = TerminalFactory.getInstance(ALGORITHM, null);
	}
    }

    void reloadPCSC() {
	try {
	    // code taken from http://stackoverflow.com/questions/16921785/
	    Class pcscterminal = Class.forName("sun.security.smartcardio.PCSCTerminals");
	    Field contextId = pcscterminal.getDeclaredField("contextId");
	    contextId.setAccessible(true);

	    if (contextId.getLong(pcscterminal) != 0L) {
		// First get a new context value
		Class pcsc = Class.forName("sun.security.smartcardio.PCSC");
		Method SCardEstablishContext = pcsc.getDeclaredMethod("SCardEstablishContext", Integer.TYPE);
		SCardEstablishContext.setAccessible(true);

		Field SCARD_SCOPE_USER = pcsc.getDeclaredField("SCARD_SCOPE_USER");
		SCARD_SCOPE_USER.setAccessible(true);

		long newId = ((Long) SCardEstablishContext.invoke(pcsc, SCARD_SCOPE_USER.getInt(pcsc)));
		contextId.setLong(pcscterminal, newId);

		// Then clear the terminals in cache
		loadPCSC();
		CardTerminals terminals = terminalFactory.terminals();
		Field fieldTerminals = pcscterminal.getDeclaredField("terminals");
		fieldTerminals.setAccessible(true);
		Class classMap = Class.forName("java.util.Map");
		Method clearMap = classMap.getDeclaredMethod("clear");

		clearMap.invoke(fieldTerminals.get(terminals));
	    }
	} catch (NoSuchAlgorithmException ex) {
	    // if it worked once it will work again
	    String msg = "PCSC changed it's algorithm. There is something really wrong.";
	    logger.error(msg, ex);
	    throw new RuntimeException("PCSC changed it's algorithm. There is something really wrong.");
	} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchFieldException
		| NoSuchMethodException | SecurityException ex) {
	    logger.error("Failed to perform reflection magic to reload TerminalFactory.", ex);
	}
    }

}

@@ -1816,16 +1816,52 @@
 osName;%0A
+    private final String osVersion;%0A
     priv
@@ -2268,16 +2268,63 @@
 name%22);%0A
+%09osVersion = System.getProperty(%22os.version%22);%0A
 %09if (osN
@@ -2778,24 +2778,528 @@
 ory;%0A    %7D%0A%0A
+    private static Integer versionCompare(String str1, String str2) %7B%0A%09// code taken from http://stackoverflow.com/a/6702029%0A%09String%5B%5D vals1 = str1.split(%22%5C%5C.%22);%0A%09String%5B%5D vals2 = str2.split(%22%5C%5C.%22);%0A%09int i = 0;%0A%0A%09while (i %3C vals1.length && i %3C vals2.length && vals1%5Bi%5D.equals(vals2%5Bi%5D)) %7B%0A%09    i++;%0A%09%7D%0A%0A%09if (i %3C vals1.length && i %3C vals2.length) %7B%0A%09    return Integer.signum(Integer.valueOf(vals1%5Bi%5D).compareTo(Integer.valueOf(vals2%5Bi%5D)));%0A%09%7D%0A%0A%09return Integer.signum(vals1.length - vals2.length);%0A    %7D%0A%0A
     final vo
@@ -3374,16 +3374,58 @@
 (%22OS X%22)
+ && versionCompare(osVersion, %2210.10%22) %3C 0
 ) %7B%0A%09   

Use default PC/SC JNI library on OS X 10.10 and above