/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.openjpa.persistence.lockmgr;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.persistence.LockModeType;
import jakarta.persistence.PessimisticLockScope;

import org.apache.openjpa.persistence.OpenJPAEntityManagerSPI;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;

public class TestLocking extends SQLListenerTestCase {
    String _phone = "5075555555";

    @Override
    protected String getPersistenceUnitName() {
        return "locking-test";
    }

    @Override
    public void setUp() {
        super.setUp(CLEAR_TABLES, Person.class, PhoneNumber.class
        // ,"openjpa.Log", "SQL=trace"
            );
        populate();
    }

    public void testExtendedLockScope() throws Exception {
        Map<String, Object> props = new HashMap<>();
        props.put("jakarta.persistence.lock.scope", PessimisticLockScope.EXTENDED);

        OpenJPAEntityManagerSPI em1 = emf.createEntityManager();
        OpenJPAEntityManagerSPI em2 = emf.createEntityManager();
        CommitterThread committer = new CommitterThread(em2);

        em1.getTransaction().begin();
        Person e1 = em1.find(Person.class, 1);
        assertEquals(1, e1.getPhoneNumbers().size());

        // This SHOULD lock Employee with id=1 AND the join table.
        //
        // pg 86
        // Element collections and relationships owned by the entity that are contained in join tables will be
        // locked if the jakarta.persistence.lock.scope property is specified with a value of
        // PessimisticLockScope.EXTENDED. The state of entities referenced by such relationships will
        // not be locked (unless those entities are explicitly locked). This property may be passed as an argument
        // to the methods of the EntityManager, Query, and TypedQuery interfaces that allow lock modes
        // to be specified or used with the NamedQuery annotation.

        em1.refresh(e1, LockModeType.PESSIMISTIC_FORCE_INCREMENT, props);

        // Kick off the committer thread
        committer.start();

        // Make sure to sleep at least for 5 seconds AFTER the committer calls commit
        while (System.currentTimeMillis() - committer.sleepStartTime < 5000) {
            Thread.sleep(5000);
        }
        // The committer should still be waiting because the em1.refresh(...) call should have locked the join table and
        // the remove can't complete
        assertFalse(committer.commitComplete);
        em1.getTransaction().commit();
        em1.close();
        // wait for child thread to finish
        committer.join();
    }

    private class CommitterThread extends Thread {
        OpenJPAEntityManagerSPI _em2;
        boolean inCommit = false;
        boolean commitComplete = false;
        long sleepStartTime = Long.MAX_VALUE;

        public CommitterThread(OpenJPAEntityManagerSPI e) {
            _em2 = e;
        }

        @Override
        public void run() {
            _em2.getTransaction().begin();
            PhoneNumber phoneNumber = _em2.find(PhoneNumber.class, _phone);
            _em2.remove(phoneNumber);
            inCommit = true;
            sleepStartTime = System.currentTimeMillis();
            _em2.getTransaction().commit();
            commitComplete = true;
            _em2.close();
        }
    }

    private void populate() {
        OpenJPAEntityManagerSPI em = emf.createEntityManager();
        em.getTransaction().begin();

        PhoneNumber p = new PhoneNumber(_phone);
        List<PhoneNumber> numbers = Arrays.asList(new PhoneNumber[] { p });

        Person e1 = new Person();
        e1.setId(1);
        e1.setPhoneNumbers(numbers);
        Person e2 = new Person();
        e2.setId(2);
        e2.setPhoneNumbers(numbers);

        p.setOwners(Arrays.asList(new Person[] { e1, e2 }));
        em.persist(e1);
        em.persist(e2);
        em.persist(p);

        em.getTransaction().commit();
        em.close();
    }
}
