package org.academiadecodigo.bootcamp.fxlogin.persistence.dao.jpa;

import org.academiadecodigo.bootcamp.fxlogin.model.AbstractModel;
import org.academiadecodigo.bootcamp.fxlogin.persistence.TransactionException;
import org.academiadecodigo.bootcamp.fxlogin.persistence.dao.Dao;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.List;

/**
 * MIT License
 * (c) 2017 Ricardo Constantino
 */

public class JpaDao<T extends AbstractModel> implements Dao<T> {

    private Class<T> objClass;

    @PersistenceContext
    private EntityManager em;

    JpaDao(Class<T> objClass) {
        this.objClass = objClass;
    }

    @Override
    public void saveOrUpdate(T entity) throws TransactionException {
        try {
            em.merge(entity);
        } catch (PersistenceException e) {
            throw new TransactionException(e);
        }
    }

    @Override
    public List<T> findAll() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(objClass);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(root);

        return em.createQuery(criteriaQuery).getResultList();
    }

    @Override
    public List<T> findAll(String prop, Object value) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(objClass);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(root);

        criteriaQuery.where(builder.equal(root.get(prop), value));

        return em.createQuery(criteriaQuery).getResultList();
    }

    @Override
    public T findById(Integer id) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(objClass);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(root);
        criteriaQuery.where(builder.equal(root.get("id"), id));

        T fetched;
        try {
            fetched = em.createQuery(criteriaQuery).getSingleResult();
        } catch (NoResultException e) {
            fetched = null;
        }
        return fetched;
    }

    @Override
    public T find(String prop, Object value) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(objClass);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(root);
        criteriaQuery.where(builder.equal(root.get(prop), value));

        T fetched;
        try {
            fetched = em.createQuery(criteriaQuery).getSingleResult();
        } catch (NoResultException e) {
            fetched = null;
        }
        return fetched;
    }

    @Override
    public Long count() {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(builder.count(root));

        Long count;
        try {
            count = em.createQuery(criteriaQuery).getSingleResult();
        } catch (NoResultException e) {
            count = 0L;
        }

        return count;
    }

    @Override
    public void deleteById(Integer id) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = builder.createQuery(objClass);
        Root<T> root = criteriaQuery.from(objClass);

        criteriaQuery.select(root);
        criteriaQuery.where(builder.equal(root.get("id"), id));

        try {
            T fetched = em.createQuery(criteriaQuery).getSingleResult();
            em.remove(fetched);
        } catch (NoResultException e) {
            // do nothing if nothing found
        }
    }

}
