import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;
import java.util.function.Function;

/**
 * A singleton implementation that encapsulates the JPA logic.
 * You can configure the database connection in the
 * {@link /src/main/resources/META-INF/persistence.xml} file.
 */
public class JpaService
{
    private static JpaService instance;
    private EntityManagerFactory entityManagerFactory;

    private JpaService()
    {
        entityManagerFactory = Persistence.createEntityManagerFactory("jpa-hibernate-notenmanager");
    }

    public static synchronized JpaService getInstance()
    {
        return instance == null ? instance = new JpaService() : instance;
    }

    public void closeResource()
    {
        if(entityManagerFactory != null)
        {
            entityManagerFactory.close();
        }
    }

    public EntityManagerFactory getEntityManagerFactory()
    {
        return entityManagerFactory;
    }

    /**
     * Runs the specified function inside a transaction boundary. The function has
     * access to a ready to use {@link EntityManager} and can return any type of
     * value ({@code T}).
     *
     * @param function The function to run.
     * @param <T>      The function's return type.
     * @return the value returned by the specified function.
     */
    public <T> T runInTransaction(Function <EntityManager, T> function)
    {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        EntityTransaction entityTransaction = entityManager.getTransaction();
        boolean isPersisted = false;

        entityTransaction.begin();

        try
        {
            T returnValue = function.apply(entityManager);
            isPersisted = true;

            return returnValue;
        }
        finally
        {
            if(isPersisted)
            {
                entityTransaction.commit();
            }
            else
            {
                entityTransaction.rollback();
            }
        }
    }
}