/*
 * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.tests.clientserver;

import org.eclipse.persistence.testing.framework.*;
import org.eclipse.persistence.sessions.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.sessions.DatabaseLogin;

@SuppressWarnings("deprecation")
public class ConcurrentTestWithReadLocks extends org.eclipse.persistence.testing.framework.AutoVerifyTestCase {
    public static int numThreads = 8;
    public boolean isCheckerThread;
    public static Server server;
    public static boolean execute = true;
    public static Thread[] threadList = new Thread[numThreads];
    public static long[] timeList = new long[numThreads];
    public static long runTime;
    public int index;

    public ConcurrentTestWithReadLocks(long runtime) {
        setDescription("Test Simulates a highly concurrent situation with ReadLocks");
        ConcurrentTestWithReadLocks.runTime = runtime;
    }

    protected ConcurrentTestWithReadLocks(boolean checkerThread, int index) {
        this.isCheckerThread = checkerThread;
        this.index = index;
    }

    @Override
    public void reset() {
        ConcurrentTestWithReadLocks.execute = false;
        for (int count = 0; count < numThreads; ++count) {
            try {
                ConcurrentTestWithReadLocks.threadList[count].join();
            } catch (InterruptedException ex) {
                throw new TestProblemException("Test thread was interrupted.  Test failed to run properly");
            }
        }
        ConcurrentTestWithReadLocks.server.logout();
    }

    @Override
    public void setup() {
        ConcurrentTestWithReadLocks.execute = true;
        try {
            getSession().getLog().write("WARNING, some tests may take 3 minutes or more");
            getSession().getLog().flush();
        } catch (java.io.IOException e) {
        }

        try {
            DatabaseLogin login = (DatabaseLogin)getSession().getLogin().clone();
            ConcurrentTestWithReadLocks.server = new Server(login, numThreads, numThreads + 2);
            ConcurrentTestWithReadLocks.server.serverSession.setSessionLog(getSession().getSessionLog());
            ConcurrentTestWithReadLocks.server.login();
            ConcurrentTestWithReadLocks.server.copyDescriptors(getSession());
        } catch (ValidationException ex) {
            this.verify();
        }

        for (int count = 0; count < numThreads; ++count) {
            ConcurrentTestWithReadLocks.threadList[count] = new Thread(new ConcurrentTestWithReadLocks(false, count).runnable());
            ConcurrentTestWithReadLocks.timeList[count] = System.currentTimeMillis();
        }
    }

    @Override
    public void test() {
        for (int count = 0; count < numThreads; ++count) {
            ConcurrentTestWithReadLocks.threadList[count].start();
        }

        Thread checker = new Thread(new ConcurrentTestWithReadLocks(true, -1).runnable());
        checker.start();
        try {
            checker.join();
        } catch (InterruptedException ex) {
            throw new TestProblemException("Test thread was interrupted.  Test failed to run properly");
        }
    }

    @Override
    public void verify() {
        if (!execute) {
            for (int count = 0; count < numThreads; ++count) {
                threadList[count].stop();
            }
            getSession().getIdentityMapAccessor().initializeAllIdentityMaps();
            throw new TestErrorException("This test caused a deadlock in TopLink.  see bug 3049635");
        }
    }

    public Runnable runnable() {
        return new Runnable() {
                // This section represents the executing threads
                // If the type is set to checker then this set the thread
                // to watch the other threads for deadlock.  If none occurs then
                // the test will time out.
                @Override
                public void run() {
                    if (org.eclipse.persistence.testing.tests.clientserver.ConcurrentTestWithReadLocks.this.isCheckerThread) {
                        watchOtherThreads();
                    } else {
                        executeUntilStopped();
                    }
                }
            };
    }

    public void watchOtherThreads() {
        long startTime = System.currentTimeMillis();
        while (((System.currentTimeMillis() - startTime) < (runTime + 30000)) && ConcurrentTestWithReadLocks.execute) {
            for (int localIdex = 0; localIdex < numThreads; ++localIdex) {
                if ((System.currentTimeMillis() - ConcurrentTestWithReadLocks.timeList[localIdex]) > 30000) {
                    execute = false;
                    break;
                }
            }
            try {
                Thread.sleep(30000);
            } catch (InterruptedException ex) {
                throw new TestProblemException("Test thread was interrupted.  Test failed to run properly");
            }
        }
    }

    public void executeUntilStopped() {
        Session session = ConcurrentTestWithReadLocks.server.serverSession.acquireClientSession();
        DeadLockEmployee employee = (DeadLockEmployee)session.readObject(DeadLockEmployee.class);

        //    session.release();
        while (ConcurrentTestWithReadLocks.execute) {
            //     session = this.server.serverSession.acquireClientSession();
            ConcurrentTestWithReadLocks.timeList[this.index] = System.currentTimeMillis();

            /* Aquire Unit Of Work */
            UnitOfWork uow = session.acquireUnitOfWork();

            /* Register */
            DeadLockEmployee workingEmp = (DeadLockEmployee)uow.registerObject(employee);

            /* make an Refresh */
            uow.refreshObject(workingEmp);
            uow.commit();
        }
    }
}
