/*
 * Copyright (c) 2002-2015 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This product is licensed to you under the Apache License, Version 2.0 (the "License").
 * You may not use this product except in compliance with the License.
 *
 * This product may include a number of subcomponents with
 * separate copyright notices and license terms. Your use of the source
 * code for these subcomponents is subject to the terms and
 * conditions of the subcomponent's license, as noted in the LICENSE file.
 *
 */

package org.neo4j.ogm.integration;

import org.apache.http.client.HttpResponseException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.ClassRule;
import org.junit.Test;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.ogm.session.Utils;
import org.neo4j.ogm.session.result.ResultProcessingException;
import org.neo4j.ogm.session.transaction.LongTransaction;
import org.neo4j.ogm.session.transaction.Transaction;
import org.neo4j.ogm.session.transaction.TransactionManager;
import org.neo4j.ogm.testutil.Neo4jIntegrationTestRule;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
 * @author Michal Bachman
 */
public class TransactionRequestHandlerTest
{

    @ClassRule
    public static Neo4jIntegrationTestRule neo4jRule = new Neo4jIntegrationTestRule("2"); // idle tx killed after 2 secs

    private Session session;

    private static final CloseableHttpClient httpClient = HttpClients.createDefault();

    @Test
    public void testCreateLongTransaction() {

        TransactionManager txRequestHandler = new TransactionManager(httpClient, neo4jRule.url());
        try (Transaction tx = txRequestHandler.openTransaction(null)) {
            assertEquals(Transaction.Status.OPEN, tx.status());
        }
    }

    @Test
    public void testCreateConcurrentTransactions() {

        TransactionManager txRequestHandler = new TransactionManager(httpClient, neo4jRule.url());

        // note that the try-with-resources implies these transactions are nested, but they are in fact independent
        try (Transaction tx1 = txRequestHandler.openTransaction(null)) {
            try (Transaction tx2 = txRequestHandler.openTransaction(null)) {
                assertEquals(Transaction.Status.OPEN, tx1.status());
                assertEquals(Transaction.Status.OPEN, tx2.status());
            }
        }
    }


    @Test(expected = ResultProcessingException.class)
    public void shouldDetectErrorsOnCommitOfLongRunningTransaction() {
        SessionFactory sessionFactory = new SessionFactory("");
        session = sessionFactory.openSession(neo4jRule.url());
        Transaction tx = session.beginTransaction();
        session.query("GARBAGE", Utils.map());
        tx.commit();
    }

    @Test(expected = ResultProcessingException.class)
    public void shouldDetectErrorsOnCommitOfNonExistentTransaction() {
        TransactionManager txRequestHandler = new TransactionManager(httpClient, neo4jRule.url());
        Transaction tx = new LongTransaction(null, neo4jRule.url(), txRequestHandler);
        tx.commit();
    }

    @Test
    public void shouldBeAbleToStartMultipleConcurrentLongRunningTransactions() throws InterruptedException {

        SessionFactory sessionFactory = new SessionFactory();
        session = sessionFactory.openSession(neo4jRule.url());

        int numThreads = 100;

        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        CountDownLatch latch = new CountDownLatch( numThreads );

        for (int i = 0; i < numThreads; i++) {
            executor.submit(new TransactionStarter(latch));
        }
        latch.await(); // pause until the count reaches 0
        System.out.println("all threads running");
        executor.shutdown();
    }

    @Test
    public void shouldRollbackExplicitTransactionWhenServerTransactionTimeout() throws InterruptedException {

        SessionFactory sessionFactory = new SessionFactory();
        session = sessionFactory.openSession(neo4jRule.url());

        try (Transaction tx = session.beginTransaction()) {
            // Wait for transaction to timeout on server
            Thread.sleep(3000);
            // Try to purge database using timed-out transaction
            session.purgeDatabase();
            fail("Should have caught exception");
        } catch (ResultProcessingException rpe) {
            HttpResponseException cause = (HttpResponseException) rpe.getCause();
            assertEquals("Not Found", cause.getMessage());
            assertEquals(404, cause.getStatusCode());
        }
        // should pass, because previous transaction will be closed by try block
        session.purgeDatabase();
    }

    class TransactionStarter implements Runnable {

        private final CountDownLatch latch;

        public TransactionStarter(CountDownLatch latch) {
            this.latch = latch;
        }

        @Override
        public void run() {

            final Transaction tx = session.beginTransaction();
            System.out.println("opened a transaction: " + tx);
            latch.countDown();

            // now loop so this thread simulates a long running transaction
            for (;;);
        }
    }


}

@@ -4052,16 +4052,19 @@
 shutdown
+Now
 ();%0A    
@@ -5412,90 +5412,483 @@
  // 
-now loop so this thread simulates a long running transaction%0A            for (;;);
+run forever%0A            // but let the executor interrupt us to shut us down%0A            while(!Thread.currentThread().isInterrupted())%7B%0A                //do stuff%0A                try%7B%0A                    Thread.sleep(100);%0A                %7Dcatch(InterruptedException e)%7B%0A                    System.out.println(%22Stopping thread%22);%0A                    tx.rollback();%0A                    Thread.currentThread().interrupt(); //propagate interrupt%0A                %7D%0A            %7D
 %0A   

clean up worker threads to release CPU resources ASAP