/**
 * Copyright (c) Istituto Nazionale di Fisica Nucleare, 2006-2014.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.italiangrid.voms.test.mt;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Assert;

import org.italiangrid.voms.VOMSAttribute;
import org.italiangrid.voms.VOMSValidators;
import org.italiangrid.voms.ac.VOMSACValidator;
import org.italiangrid.voms.store.UpdatingVOMSTrustStore;
import org.italiangrid.voms.store.impl.DefaultUpdatingVOMSTrustStore;
import org.italiangrid.voms.test.utils.VOMSAA;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import eu.emi.security.authn.x509.NamespaceCheckingMode;
import eu.emi.security.authn.x509.X509CertChainValidatorExt;
import eu.emi.security.authn.x509.impl.OpensslCertChainValidator;
import eu.emi.security.authn.x509.impl.PEMCredential;
import eu.emi.security.authn.x509.proxy.ProxyCertificate;

public class TestConcurrentValidation {

  static X509CertChainValidatorExt sharedCertificateValidator;
  static UpdatingVOMSTrustStore sharedVOMSTrustStore;

  static final String trustAnchorsDir = "src/test/resources/trust-anchors";
  static final long trustAnchorsRefreshInterval = TimeUnit.SECONDS.toMillis(15);

  static final String vomsTrustStoreDir = "src/test/resources/vomsdir";
  static final long trustStoreRefreshInterval = TimeUnit.SECONDS.toMillis(5);

  static final int numHolderCredentials = 5;

  static PEMCredential[] holderCerts;

  static VOMSAA testVO_1, testVO_2;

  static final String aaCert = "src/test/resources/certs/test_host_cnaf_infn_it.cert.pem";
  static final String aaKey = "src/test/resources/certs/test_host_cnaf_infn_it.key.pem";

  static final String aaCert2 = "src/test/resources/certs/wilco_cnaf_infn_it.cert.pem";
  static final String aaKey2 = "src/test/resources/certs/wilco_cnaf_infn_it.key.pem";

  static final long NUM_ITERATIONS = 10;
  static final int NUM_WORKERS = 10;

  static final CyclicBarrier barrier = new CyclicBarrier(NUM_WORKERS + 1);

  static final ExecutorService pool = Executors.newCachedThreadPool();

  static final String[][] fqans = { { "/test.vo" }, { "/test.vo.2" } };

  static VOMSACValidator sharedValidator;

  static List<ProxyCertificate> testProxies;

  static final Random r = new Random();

  static void loadHolderCredentials() throws KeyStoreException,
    CertificateException, FileNotFoundException, IOException {

    holderCerts = new PEMCredential[numHolderCredentials];

    for (int i = 0; i < numHolderCredentials; i++) {
      String baseFileName = String.format("src/test/resources/certs/test%d", i);

      holderCerts[i] = new PEMCredential(new FileInputStream(baseFileName
        + ".key.pem"), new FileInputStream(baseFileName + ".cert.pem"),
        "pass".toCharArray());
    }
  }

  static void initVOs() throws KeyStoreException, CertificateException,
    FileNotFoundException, IOException {

    PEMCredential aaCred1 = new PEMCredential(new FileInputStream(aaKey),
      new FileInputStream(aaCert), (char[]) null);

    PEMCredential aaCred2 = new PEMCredential(new FileInputStream(aaKey2),
      new FileInputStream(aaCert2), (char[]) null);

    testVO_1 = new VOMSAA(aaCred1, "test.vo", "test-host.cnaf.infn.it", 15000);
    testVO_2 = new VOMSAA(aaCred2, "test.vo.2", "wilco.cnaf.infn.it", 15001);

  }

  static void initVOMSProxies() throws InvalidKeyException,
    CertificateParsingException, SignatureException, NoSuchAlgorithmException,
    IOException {

    testProxies = new ArrayList<ProxyCertificate>();

    for (int i = 0; i < numHolderCredentials; i++)
      for (int j = 0; j < 2; j++) {
        VOMSAA vo = (j == 0 ? testVO_1 : testVO_2);
        PEMCredential cert = holderCerts[i];

        ProxyCertificate proxy = vo.createVOMSProxy(cert,
          Arrays.asList(fqans[j]));
        testProxies.add(proxy);

      }
  }

  static X509Certificate[] getRandomProxy() {

    int randomIndex = r.nextInt(testProxies.size());
    return testProxies.get(randomIndex).getCertificateChain();
  }

  @BeforeClass
  public static void setup() throws KeyStoreException, CertificateException,
    FileNotFoundException, IOException, InvalidKeyException,
    SignatureException, NoSuchAlgorithmException {

    sharedVOMSTrustStore = new DefaultUpdatingVOMSTrustStore(
      Arrays.asList(vomsTrustStoreDir), trustStoreRefreshInterval);

    sharedCertificateValidator = new OpensslCertChainValidator(trustAnchorsDir,
      NamespaceCheckingMode.EUGRIDPMA_AND_GLOBUS, trustAnchorsRefreshInterval);

    loadHolderCredentials();
    initVOs();
    initVOMSProxies();
    sharedValidator = VOMSValidators.newValidator(sharedVOMSTrustStore,
      sharedCertificateValidator);
    System.out.println("Setup done.");
  }

  @AfterClass
  public static void tearDown() {

  }

  @Test
  public void test() throws InterruptedException, BrokenBarrierException {

    long start = System.currentTimeMillis();

    System.out.format("Workers: %d. Iterations: %d\n", NUM_WORKERS,
      NUM_ITERATIONS);

    for (int i = 0; i < NUM_WORKERS; i++)
      pool.execute(new ValidatorWorker());

    barrier.await();
    barrier.await();

    pool.shutdown();
    sharedVOMSTrustStore.cancel();
    sharedCertificateValidator.dispose();

    long duration = System.currentTimeMillis() - start;

    System.out
      .format(
        "Done. Test duration: %d milliseconds. Avg validation duration: %d milliseconds.\n",
        duration, duration / (NUM_WORKERS * NUM_ITERATIONS));
  }

  class ValidatorWorker implements Runnable {

    private volatile boolean shutdownRequested = false;

    private long iterations = 0;

    public void run() {

      try {

        barrier.await();

      } catch (InterruptedException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      } catch (BrokenBarrierException e1) {
        // TODO Auto-generated catch block
        e1.printStackTrace();
      }

      while (true) {

        if (iterations++ > NUM_ITERATIONS)
          break;

        if (shutdownRequested)
          return;

        VOMSACValidator validator = getValidator();

        try {

          X509Certificate[] chain = getRandomProxy();
          List<VOMSAttribute> attrs = validator.validate(chain);
          Assert.assertEquals(1, attrs.size());

        } catch (Exception e) {
          System.err.println(e.getMessage());

        }
      }

      try {
        barrier.await();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      } catch (BrokenBarrierException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

    public synchronized void shutdown() {

      shutdownRequested = true;
    }
  }

  static X509Certificate[] buildProxy(int credentialIndex, int voIndex)
    throws InvalidKeyException, CertificateParsingException,
    SignatureException, NoSuchAlgorithmException, IOException {

    VOMSAA vo = (voIndex == 0 ? testVO_1 : testVO_2);
    PEMCredential cert = holderCerts[credentialIndex];

    ProxyCertificate proxy = vo.createVOMSProxy(cert,
      Arrays.asList(fqans[voIndex]));
    return proxy.getCertificateChain();
  }

  static VOMSACValidator getValidator() {

    return sharedValidator;
  }
}
