/*
 * 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.query;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import jakarta.persistence.NoResultException;

import org.apache.openjpa.persistence.Extent;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAQuery;
import org.apache.openjpa.persistence.jdbc.FetchMode;
import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
import org.apache.openjpa.persistence.query.common.apps.RuntimeTest1;
import org.apache.openjpa.persistence.query.common.apps.RuntimeTest2;
import org.apache.openjpa.persistence.query.common.apps.RuntimeTest3;

/**
 * Test that ResultList objects behaver correctly.
 *
 * @author Marc Prud'hommeaux
 */
public class TestQueryResults extends BaseQueryTest {

    public TestQueryResults(String test) {
        super(test);
    }

    @Override
    public void setUp() {
        deleteAll(RuntimeTest1.class);

        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);
        em.persist(new RuntimeTest1("TestQueryResults1", 10));
        em.persist(new RuntimeTest1("TestQueryResults3", 10));
        em.persist(new RuntimeTest1("TestQueryResults5", 10));
        em.persist(new RuntimeTest3("TestQueryResults2", 10));
        em.persist(new RuntimeTest3("TestQueryResults4", 10));
        em.persist(new RuntimeTest3("TestQueryResults6", 10));
        endTx(em);
        endEm(em);
    }

    public void testQueryIteratorsReturnFalseForClosedQuery() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery("SELECT o FROM RuntimeTest1 o");
        List c = q.getResultList();
        Iterator i = c.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        q.closeAll();
        endTx(em);
        endEm(em);

        if (i.hasNext())
            fail("Iterator obtained from Query should return false "
                + "for hasNext() after Query has been closed");
    }

    public void testQueryIteratorsThrowExceptionForClosedQuery() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery("SELECT o FROM RuntimeTest1 o");
        List c = q.getResultList();
        Iterator i = c.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        q.closeAll();
        endTx(em);
        endEm(em);

        try {
            i.next();
            fail("Iterator.next() should have thrown Exception "
                + "after query.closeAll() was called");
        }
        catch (Exception e) {
            //
        }
    }

    public void testLazyQueryIteratorsReturnFalseForClosedem() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        String query = "SELECT o FROM RuntimeTest1 o";
        OpenJPAQuery q = em.createQuery(query);
        q.getFetchPlan().setFetchBatchSize(5);
        List c = q.getResultList();

        Iterator i = c.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        endTx(em);
        endEm(em);

        if (i.hasNext())
            fail("Lazy result iterator obtained from Query should return "
                + "false for hasNext() after em has been closed");
    }

    public void testEagerQueryIteratorsWorkForClosedem() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        String query = "SELECT o FROM RuntimeTest1 o";
        OpenJPAQuery q = em.createQuery(query);
        q.getFetchPlan().setFetchBatchSize(-1);

        List c = q.getResultList();

        Iterator i = c.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        endTx(em);
        endEm(em);

        if (!i.hasNext())
            fail("Eager result iterator obtained from Query should return "
                + "true for hasNext() after em has been closed");
    }

    public void testQueryResultIsList() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        String query = "SELECT o FROM RuntimeTest1 o";

        Collection c = (Collection) em.createQuery(query).getResultList();
        if (!(c instanceof List))
            fail("Collection (" + c.getClass() + ") should have "
                + "been a List instance");

        endTx(em);
        endEm(em);
    }

    public void testQueryResultSizeIsCorrect() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        em.persist(new RuntimeTest2("TestQueryResults1", 10));
        endTx(em);
        endEm(em);

        em = (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        String query = "SELECT r FROM RuntimeTest2 r WHERE r.stringField = "
            + "\'TestQueryResults1\'";
        List c = em.createQuery(query).getResultList();

        assertEquals(1, c.size());
        endTx(em);
        endEm(em);
    }

    public void testExtentIteratorsReturnFalseForClosedExtent() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);
        Extent extent = em.createExtent(RuntimeTest1.class, true);

        Iterator i = extent.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        extent.closeAll();

        if (i.hasNext())
            fail("Iterator obtained from Extent should return false "
                + "for hasNext() after Extent has been closed");

        endTx(em);
        endEm(em);
    }

    public void testExtentIteratorsThrowExceptionForClosedExtent() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        Extent extent = em.createExtent(RuntimeTest1.class, true);

        Iterator i = extent.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        extent.closeAll();

        try {
            i.next();
            fail("Iterator.next() should have thrown Exception "
                + "after Extent.closeAll() was called");
        } catch (Exception e) {
            // this is a *good* thing.
        }

        endTx(em);
        endEm(em);
    }

    public void testExtentIteratorsReturnFalseForClosedem() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        Extent extent = em.createExtent(RuntimeTest1.class, true);
        Iterator i = extent.iterator();
        if (!(i.hasNext()))
            fail("Iterator should have had next()");
        endTx(em);
        endEm(em);

        if (i.hasNext())
            fail("Iterator obtained from Extent should return false "
                + "for hasNext() after em has been closed");
    }

    public void testUniqueReturnsSingleResult() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        String query = "SELECT DISTINCT r FROM RuntimeTest1 r WHERE "
            + "r.stringField = \'TestQueryResults1\'";
        Object obj = em.createQuery(query).getSingleResult();

        assertTrue(obj instanceof RuntimeTest1);

        query = "SELECT DISTINCT r FROM RuntimeTest1 r WHERE r.stringField = "
            + "\'xxxx\'";
        OpenJPAQuery q = em.createQuery(query);
        try {
            Object l = q.getSingleResult();
            fail("Expected NoResultException since there is no RuntimeTest1 "
                 + "instance with stringfield=xxxx");
        } catch (NoResultException e) {
            // good
        }

        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testUniqueThrowsExceptionIfMultipleResults() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);
        String query = "SELECT DISTINCT r FROM RuntimeTest1 r";
        OpenJPAQuery q = em.createQuery(query);

        try {
            Object l = q.getSingleResult();
            fail("Unique query matched multiple results.");
        }
        catch (Exception jue) {
        }
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testImpossibleRangeReturnsEmptyList() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);
        String query = "SELECT r FROM RuntimeTest1 r";
        OpenJPAQuery q = em.createQuery(query);
        q.setFirstResult(2);
        q.setMaxResults(0);

        List results = q.getResultList();

        assertEquals(0, results.size());
        assertFalse(results.iterator().hasNext());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testImpossibleUniqueRangeReturnsNull() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery("SELECT DISTINCT r FROM RuntimeTest1 r "
            + "WHERE r.stringField = \'TestQueryResults1\'");
        q.setFirstResult(2);
        q.setMaxResults(0);
        assertTrue(
            "resultlist is not null its size is: " + q.getResultList().size(),
            q.getResultList().isEmpty());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testSingleResultUniqueRange() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery("SELECT DISTINCT r FROM RuntimeTest1 r "
            + "WHERE r.stringField = \'TestQueryResults1\'");
        q.setFirstResult(1);
        q.setMaxResults(1000000);

        assertTrue("resultlist is not empty", (q.getResultList()).isEmpty());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testMultiResultUniqueRange() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT DISTINCT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setFirstResult(1);
        q.setMaxResults(2);

        assertEquals("TestQueryResults2",
            ((RuntimeTest1) q.getResultList().get(0)).getStringField());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    /* This test is being commented because it was supposed to be a converted
     * test complementing the original JDO test which uses the setUnique()
     * method available in JDO Query. OpenJPAQuery does not have such a method
     * and hence this test does not make sense.
      public void testUniqueThrowsExceptionIfNonUniqueRange()
     {
         OpenJPAEntityManager em =
             (OpenJPAEntityManager) currentEntityManager();
         startTx(em);

         OpenJPAQuery q = em.createQuery(
            "SELECT DISTINCT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
         q.setFirstResult(1);
         q.setMaxResults(3);

         try
         {
             q.getResultList();
             fail("Unique allowed non-unique range.");
         }
         catch (Exception jue)
         {
         }
         q.closeAll();
         endTx(em);
         endEm(em);
     }
     */
    public void testFullRange() {
        try {
            OpenJPAEntityManager em =
                (OpenJPAEntityManager) currentEntityManager();
            startTx(em);

            OpenJPAQuery q = em.createQuery(
                "SELECT r FROM RuntimeTest1 ORDER BY r.stringField ASC");
            q.setSubclasses(false);
            q.setFirstResult(0);
            Long l = Long.MAX_VALUE;
            q.setMaxResults(l.intValue());

            List res = (List) q.getResultList();
            assertEquals(3, res.size());
            for (int i = 0; i < res.size(); i++)
                assertEquals("TestQueryResults" + (i * 2 + 1),
                    ((RuntimeTest1) res.get(i)).getStringField());
            q.closeAll();
            endTx(em);
            endEm(em);
        }
        catch (Exception uoe) {
            //FIXME:AFAM -- Figure out JPA Equivalence of createExtent(class,
            //false) ie how to restrict the query result to the base entity and
            //not the subclasses
        }
    }

    public void testFullRangeSubs() {
        try {
            OpenJPAEntityManager em =
                (OpenJPAEntityManager) currentEntityManager();
            startTx(em);

            OpenJPAQuery q = em.createQuery(
                "SELECT r FROM RuntimeTest1 ORDER BY r.stringField ASC");
            q.setFirstResult(0);
            Long l = Long.MAX_VALUE;
            q.setMaxResults(l.intValue());

            List res = (List) q.getResultList();
            assertEquals(6, res.size());
            for (int i = 0; i < res.size(); i++)
                assertEquals("TestQueryResults" + (i + 1),
                    ((RuntimeTest1) res.get(i)).getStringField());
            q.closeAll();
            endTx(em);
            endEm(em);
        }
        catch (Exception uoe) {
        }
    }

    public void testBeginRange() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setSubclasses(false);
        for (int i = 0; i < 4; i++) {
            q.setFirstResult(i);
            q.setMaxResults(100000);

            List res = (List) q.getResultList();
            assertEquals("they are not equal", 3 - i, res.size());
            int idx = 0;

            // try both random acess and iteration
            for (int j = 0; j < res.size(); j++)
                assertEquals("TestQueryResults" + (j * 2 + 1 + i * 2),
                    (((RuntimeTest1) res.get(j)).getStringField()));
            for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
                assertEquals("TestQueryResults" + (idx * 2 + 1 + i * 2),
                    ((RuntimeTest1) itr.next()).getStringField());
        }
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testBeginRangeSubs() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");

        for (int i = 0; i < 7; i++) {
            q.setFirstResult(i);
            Long l = Long.MAX_VALUE;
            q.setMaxResults(100000);

            List res = (List) q.getResultList();
            assertEquals(6 - i, res.size());
            int idx = 0;

            // try both random acess and iteration
            for (int j = 0; j < res.size(); j++)
                assertEquals("TestQueryResults" + (j + 1 + i),
                    ((RuntimeTest1) res.get(j)).getStringField());
            for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
                assertEquals("TestQueryResults" + (idx + 1 + i),
                    ((RuntimeTest1) itr.next()).getStringField());
        }
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testEndRange() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setSubclasses(false);

        for (int i = 0; i < 4; i++) {
            q.setFirstResult(0);
            q.setMaxResults(i);

            List res = (List) q.getResultList();
            assertEquals(i, res.size());
            int idx = 0;

            // try both random acess and iteration
            for (int j = 0; j < res.size(); j++)
                assertEquals("TestQueryResults" + (j * 2 + 1),
                    ((RuntimeTest1) res.get(j)).getStringField());
            for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
                assertEquals("TestQueryResults" + (idx * 2 + 1),
                    ((RuntimeTest1) itr.next()).getStringField());
        }
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testEndRangeSubs() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");

        for (int i = 0; i < 7; i++) {
            q.setFirstResult(0);
            q.setMaxResults(i);
            List res = (List) q.getResultList();
            assertEquals(i, res.size());
            int idx = 0;

            // try both random acess and iteration
            for (int j = 0; j < res.size(); j++)
                assertEquals("TestQueryResults" + (j + 1),
                    ((RuntimeTest1) res.get(j)).getStringField());
            for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
                assertEquals("TestQueryResults" + (idx + 1),
                    ((RuntimeTest1) itr.next()).getStringField());
        }
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testMidRange() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setSubclasses(false);

        q.setFirstResult(1);
        q.setMaxResults(3);
        List res = (List) q.getResultList();
        assertEquals(2, res.size());
        for (int i = 0; i < res.size(); i++)
            assertEquals("TestQueryResults" + (i * 2 + 1 + 2),
                ((RuntimeTest1) res.get(i)).getStringField());
        int idx = 0;
        for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
            assertEquals("TestQueryResults" + (idx * 2 + 1 + 2),
                ((RuntimeTest1) itr.next()).getStringField());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testMidRangeSubs() {
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setFirstResult(1);
        q.setMaxResults(3);

        List res = (List) q.getResultList();
        assertEquals(3, res.size());
        for (int i = 0; i < res.size(); i++)
            assertEquals("TestQueryResults" + (i + 1 + 1),
                ((RuntimeTest1) res.get(i)).getStringField());
        int idx = 0;
        for (Iterator itr = res.iterator(); itr.hasNext(); idx++)
            assertEquals("TestQueryResults" + (idx + 1 + 1),
                ((RuntimeTest1) itr.next()).getStringField());
        q.closeAll();
        endTx(em);
        endEm(em);
    }

    public void testPessimisticOrderedRange() {
        // test to make sure whatever machinations we do to get a range doesn't
        // interfere with FOR UPDATE
        OpenJPAEntityManager em =
            (OpenJPAEntityManager) currentEntityManager();
        startTx(em);

        OpenJPAQuery q = em.createQuery(
            "SELECT r FROM RuntimeTest1 r ORDER BY r.stringField ASC");
        q.setSubclasses(false);
        q.setFirstResult(0);
        q.setMaxResults(2);

        ((JDBCFetchPlan) q.getFetchPlan()).setEagerFetchMode(FetchMode.NONE);

        List res = (List) q.getResultList();
        assertEquals(2, res.size());
        assertEquals("TestQueryResults1",
            ((RuntimeTest1) res.get(0)).getStringField());
        assertEquals("TestQueryResults3",
            ((RuntimeTest1) res.get(1)).getStringField());
        q.closeAll();
        endTx(em);
        endEm(em);
    }
}

