/*
 * 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.batchee.extras.jpa;

import org.apache.batchee.doc.api.Documentation;
import org.apache.batchee.extras.locator.BeanLocator;

import javax.batch.api.BatchProperty;
import javax.batch.api.chunk.ItemReader;
import javax.batch.operations.BatchRuntimeException;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

@Documentation("Read JPA entities from a query.")
public class JpaItemReader extends EntityManagerLocator implements ItemReader {
    @Inject
    @BatchProperty
    @Documentation("The parameter provider ref or qualified name")
    private String parameterProvider;

    @Inject
    @BatchProperty
    @Documentation("The named query to execute")
    private String namedQuery;

    @Inject
    @BatchProperty
    @Documentation("The query to execute if the named query is empty")
    private String query;

    @Inject
    @BatchProperty
    @Documentation("Pagination size")
    private String pageSize;

    @Inject
    @BatchProperty
    @Documentation("Should entities be detached (default false)")
    private String detachEntities;

    @Inject
    @BatchProperty
    @Documentation("Should JPA transaction be used (default false)")
    private String jpaTransaction;

    private int page = 10;
    private int firstResult = 0;
    private BeanLocator.LocatorInstance<EntityManagerProvider> emProvider;
    private BeanLocator.LocatorInstance<ParameterProvider> paramProvider = null;
    private LinkedList<Object> items = new LinkedList<Object>();
    private boolean detach;
    private boolean transaction;

    @Override
    public void open(final Serializable checkpoint) throws Exception {
        final BeanLocator beanLocator = BeanLocator.Finder.get(locator);

        emProvider = findEntityManager();
        if (parameterProvider != null) {
            paramProvider = beanLocator.newInstance(ParameterProvider.class, parameterProvider);
        }
        if (pageSize != null) {
            page = Integer.parseInt(pageSize, page);
        }
        if (namedQuery == null && query == null) {
            throw new BatchRuntimeException("a query should be provided");
        }
        detach = Boolean.parseBoolean(detachEntities);
        transaction = Boolean.parseBoolean(jpaTransaction);
    }

    @Override
    public void close() throws Exception {
        if (emProvider != null) {
            emProvider.release();
        }
        if (paramProvider != null) {
            paramProvider.release();
        }
    }

    @Override
    public Object readItem() throws Exception {
        if (items.isEmpty()) {
            final Collection<?> objects = nextPage();
            if (objects == null || objects.isEmpty()) {
                return null;
            }

            items.addAll(objects);
            firstResult += items.size();
        }
        return items.pop();
    }

    private Collection<?> nextPage() {
        final EntityManager em = emProvider.getValue().newEntityManager();
        if (transaction) {
            em.getTransaction().begin();
        }
        final Query jpaQuery;
        try {
            if (namedQuery != null) {
                 jpaQuery = em.createNamedQuery(namedQuery);
            } else {
                jpaQuery = em.createQuery(query);
            }
            jpaQuery.setFirstResult(firstResult).setMaxResults(page);
            if (paramProvider != null) {
                paramProvider.getValue().setParameters(jpaQuery);
            }
            final List<?> resultList = jpaQuery.getResultList();
            if (detach) {
                for (final Object o : resultList) {
                    em.detach(o);
                }
            }
            return resultList;
        } finally {
            if (transaction) {
                em.getTransaction().commit();
            }
            emProvider.getValue().release(em);
        }
    }

    @Override
    public Serializable checkpointInfo() throws Exception {
        return null;
    }
}