/*
 * Copyright 2013 Oracle and/or its affiliates.
 * All rights reserved.  You may not modify, use,
 * reproduce, or distribute this software except in
 * compliance with  the terms of the License at:
 * http://developers.sun.com/license/berkeley_license.html
 */


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dukestutoring.ejb;

import dukestutoring.entity.*;
import dukestutoring.util.CalendarUtil;
import dukestutoring.util.StatusType;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Root;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;


/**
 *
 * @author ian
 */
@Stateless
@Named
@Path("/status")
public class RequestBean {
    private static final Logger logger = Logger.getLogger(
                "dukestutoring.ejb.RequestBean");
    @Resource
    SessionContext ctx;
    private CriteriaBuilder cb;
    @PersistenceContext
    private EntityManager em;
    private TutoringSession todaysSession;

    @PostConstruct
    private void init() {
        cb = em.getCriteriaBuilder();
    }

    public String checkIn(Student student) {
        String result;

        if (student == null) {
            logger.warning("Student is null!");
            result = "failedToCheckIn";
        }

        // get today's session from the convenience method
        TutoringSession tutoringSession = this.getTodaysSession();

        List<Student> students = tutoringSession.getStudents();

        if (!students.contains(student)) {
            logger.info("Adding student to session.");
            tutoringSession.getStudents()
                           .add(student);
            logger.info("Adding today's tutoring session to student.");

            List<TutoringSession> sessions = student.getSessions();

            if (sessions.isEmpty() || (sessions == null)) {
                logger.info("Student's sessions list is empty.");
                sessions = new ArrayList<TutoringSession>();
            }

            sessions.add(tutoringSession);
        }

        // set the status
        logger.log(
                Level.INFO,
                "Setting {0}''s status to IN",
                student.getFirstName());
        student.setStatus(StatusType.IN);
        // create a new status entry
        logger.info("Creating a new status entry");

        StatusEntry entry = new StatusEntry(
                    StatusType.IN,
                    student,
                    tutoringSession);
        // add the status entry to the tutoring session
        logger.info("Adding status entry to tutoring session");
        tutoringSession.getStatusEntries()
                       .add(entry);
        // persist the status entry
        logger.info("Persisting status entry");
        em.persist(entry);
        // modify the student
        logger.log(
                Level.INFO,
                "Merging status change to {0}",
                student.getFirstName());
        em.merge(student);
        // modify the tutoring session
        logger.info("Merging the status entry to tutoring session");
        em.merge(tutoringSession);

        result = "checkinSucceeded";

        return result;
    }

    public String checkOut(Student student) {
        String result;

        // get today's session from the convenience method
        TutoringSession tutoringSession = this.getTodaysSession();
        logger.log(
                Level.INFO,
                "Setting {0}''s status to OUT",
                student.getFirstName());
        student.setStatus(StatusType.OUT);
        logger.log(
                Level.INFO,
                "Student {0} is {1}",
                new Object[] { student.getFirstName(), student.getStatus() });

        StatusEntry entry = new StatusEntry(
                    StatusType.OUT,
                    student,
                    tutoringSession);
        // add the status entry to the tutoring session
        tutoringSession.getStatusEntries()
                       .add(entry);
        // persist the status entry
        logger.info("Persisting status entry");
        em.persist(entry);
        // modify the student
        logger.log(
                Level.INFO,
                "Merging status change to {0}",
                student.getFirstName());
        em.merge(student);
        // modify the tutoring session
        logger.info("Merging the status entry to tutoring session");
        em.merge(tutoringSession);

        result = "checkoutSucceeded";

        return result;
    }

    public void checkOutAllStudents() {
        List<Student> students = this.getCheckedInStudents();
        Iterator<Student> i = students.iterator();

        while (i.hasNext()) {
            this.checkOut(i.next());
        }
    }

    public String atPark(Student student) {
        String result;

        // get today's session from the convenience method
        TutoringSession tutoringSession = this.getTodaysSession();
        logger.log(
                Level.INFO,
                "Setting {0}''s status to PLAYGROUND",
                student.getFirstName());
        student.setStatus(StatusType.PARK);

        StatusEntry entry = new StatusEntry(
                    StatusType.PARK,
                    student,
                    tutoringSession);
        // add the status entry to the tutoring session
        tutoringSession.getStatusEntries()
                       .add(entry);
        // persist the status entry
        logger.info("Persisting status entry");
        em.persist(entry);
        // modify the student
        logger.log(
                Level.INFO,
                "Merging status change to {0}",
                student.getFirstName());
        em.merge(student);
        // modify the tutoring session
        logger.info("Merging the status entry to tutoring session");
        em.merge(tutoringSession);

        result = "atParkSucceeded";

        return result;
    }

    public String backFromPark(Student student) {
        String result;

        // get today's session from the convenience method
        TutoringSession tutoringSession = this.getTodaysSession();
        student.setStatus(StatusType.IN);

        StatusEntry entry = new StatusEntry(
                    StatusType.IN,
                    student,
                    tutoringSession);
        // add the status entry to the tutoring session
        tutoringSession.getStatusEntries()
                       .add(entry);
        // persist the status entry
        logger.info("Persisting status entry");
        em.persist(entry);
        // modify the student
        logger.info("Merging status change to student");
        em.merge(student);
        // modify the tutoring session
        logger.info("Merging the status entry to tutoring session");
        em.merge(tutoringSession);

        result = "backFromParkSucceeded";

        return result;
    }

    public List<Student> getAllStudents() {
        CriteriaQuery<Student> cq = em.getCriteriaBuilder()
                                      .createQuery(Student.class);
        Root<Student> student = cq.from(Student.class);
        cq.select(student);
        cq.where(cb.isTrue(student.get(Student_.active)));
        cq.distinct(true);

        TypedQuery<Student> q = em.createQuery(cq);

        return q.getResultList();
    }

    public List<Student> getStudentsAtPark() {
        return this.getStudentsByStatus(StatusType.PARK);
    }

    public List<Student> getCheckedInStudents() {
        return this.getStudentsByStatus(StatusType.IN);
    }

    public List<Student> getCheckedOutStudents() {
        return this.getStudentsByStatus(StatusType.OUT);
    }

    public Student getStudentByName(
        String lastName,
        String firstName) {
        CriteriaQuery<Student> cq = em.getCriteriaBuilder()
                                      .createQuery(Student.class);
        Root<Student> student = cq.from(Student.class);
        cq.select(student);
        cq.where(
                cb.equal(
                    student.get(Student_.lastName),
                    lastName));
        cq.where(
                cb.equal(
                    student.get(Student_.firstName),
                    firstName));
        cq.distinct(true);

        TypedQuery<Student> q = em.createQuery(cq);

        return q.getSingleResult();
    }

    public List<TutoringSession> getAllTutoringSessions() {
        CriteriaQuery<TutoringSession> cq = cb.createQuery(
                    TutoringSession.class);
        Root<TutoringSession> session = cq.from(TutoringSession.class);
        cq.select(session);
        cq.distinct(true);

        TypedQuery<TutoringSession> q = em.createQuery(cq);
        List<TutoringSession> sessions = q.getResultList();
        logger.log(
                Level.INFO,
                "Total number of sessions: {0}",
                sessions.size());

        return sessions;
    }

    public void createTutoringSession() {
        try {
            todaysSession = new TutoringSession();

            // Create a formatter to pretty print the date.
            SimpleDateFormat formatter = new SimpleDateFormat("E, MMM d, yyyy");
            logger.log(
                    Level.INFO,
                    "Creating new tutoring session for {0}.",
                    formatter.format(todaysSession.getSessionDate().getTime()));
            // Store it in the database
            em.persist(todaysSession);
        } catch (Exception e) {
            logger.warning("Couldn't create a new session!");
        }
    }

    // convenience method for getting the current session
    public TutoringSession getTodaysSession() {
        // get today's date and trim the time information
        Calendar today = Calendar.getInstance();
        CalendarUtil.stripTime(today);

        // find today's session
        SimpleDateFormat formatter = new SimpleDateFormat("E, MMM d, yyyy");
        logger.log(
                Level.INFO,
                "Finding tutoring session for {0}",
                formatter.format(today.getTime()));

        CriteriaQuery<TutoringSession> cq = cb.createQuery(
                    TutoringSession.class);
        Root<TutoringSession> tutoringSession = cq.from(TutoringSession.class);

        cq.select(tutoringSession);
        cq.where(
                cb.equal(
                        tutoringSession.get(TutoringSession_.sessionDate),
                        today));
        cq.distinct(true);

        TypedQuery<TutoringSession> q = em.createQuery(cq);
        TutoringSession session = null;

        try {
            session = q.getSingleResult();
            logger.info("Found session for today.");
        } catch (NoResultException e) {
            logger.info("Today's session not found. Creating a new session.");
            session = new TutoringSession();
            em.persist(session);
        }

        return session;
    }

    public List<StatusEntry> getStatusEntriesByDate(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        CriteriaQuery<StatusEntry> cq = cb.createQuery(StatusEntry.class);
        Root<StatusEntry> statusEntry = cq.from(StatusEntry.class);

        cq.select(statusEntry);
        cq.where(
                cb.equal(
                    statusEntry.get(StatusEntry_.statusDate),
                    cal));
        cq.distinct(true);

        TypedQuery<StatusEntry> q = em.createQuery(cq);

        return q.getResultList();
    }

    public List<StatusEntry> getCurrentStatusEntries() {
        CriteriaQuery<StatusEntry> cq = cb.createQuery(StatusEntry.class);
        Root<StatusEntry> statusEntry = cq.from(StatusEntry.class);

        cq.select(statusEntry);
        cq.where(
                cb.equal(
                        statusEntry.get(StatusEntry_.tutoringSession),
                        this.getTodaysSession()));
        cq.distinct(true);

        TypedQuery<StatusEntry> q = em.createQuery(cq);

        return q.getResultList();
    }

    @GET
    @Path("/guardian/{guardianEmail}")
    @Produces({
        "application/xml",
        "application/json"
    })
    @RolesAllowed({
        "Guardian",
        "Administrator"
    })
    public Guardian getGuardianByEmail(@PathParam("guardianEmail")
    String email) {
        logger.log(
                Level.INFO,
                "Principal is: {0}",
                ctx.getCallerPrincipal().getName());

        CriteriaQuery<Guardian> cq = cb.createQuery(Guardian.class);
        Root<Guardian> guardian = cq.from(Guardian.class);

        cq.select(guardian);
        cq.where(
                cb.equal(
                    guardian.get(Guardian_.email),
                    email));
        cq.distinct(true);

        TypedQuery<Guardian> q = em.createQuery(cq);

        return q.getSingleResult();
    }

    @GET
    @Path("email/{guardianEmail}")
    @Produces({
        "application/xml",
        "application/json"
    })
    @RolesAllowed({
        "Guardian",
        "Administrator"
    })
    public List<Student> getStatusByGuardianEmail(
        @PathParam("guardianEmail")
    String email) {
        logger.log(
                Level.INFO,
                "Principal is: {0}",
                ctx.getCallerPrincipal().getName());

        CriteriaQuery<Student> cq = cb.createQuery(Student.class);
        Root<Student> student = cq.from(Student.class);
        Join<Student, Guardian> guardian = student.join(Student_.guardians);

        cq.select(student);
        cq.where(
                cb.equal(
                    guardian.get(Guardian_.email),
                    email));
        cq.distinct(true);

        TypedQuery<Student> q = em.createQuery(cq);
        List<Student> results = q.getResultList();
        logger.log(
                Level.INFO,
                "Guardian {0}has {1} students.",
                new Object[] { email, results.size() });

        return results;
    }

    @GET
    @Path("id/{guardianId}")
    @Produces({
        "application/xml",
        "application/json"
    })
    public List<Student> getStatusByGuardianId(@PathParam("guardianId")
    Long id) {
        logger.log(
                Level.INFO,
                "Principal is: {0}",
                ctx.getCallerPrincipal().getName());

        CriteriaQuery<Student> cq = cb.createQuery(Student.class);
        Root<Student> student = cq.from(Student.class);
        Join<Student, Guardian> guardian = student.join(Student_.guardians);

        cq.select(student);
        cq.where(
                cb.equal(
                    guardian.get(Guardian_.id),
                    id));
        cq.distinct(true);

        TypedQuery<Student> q = em.createQuery(cq);

        return q.getResultList();
    }

    // convenience method for getting students by status
    private List<Student> getStudentsByStatus(StatusType statusType) {
        CriteriaQuery<Student> cq = cb.createQuery(Student.class);
        Root<Student> student = cq.from(Student.class);
        cq.select(student);
        cq.where(
                cb.equal(
                    student.get(Student_.status),
                    statusType));
        cq.distinct(true);

        TypedQuery<Student> q = em.createQuery(cq);

        return q.getResultList();
    }
}
