package gov.va.vinci.dart.biz;

import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import gov.va.vinci.dart.common.ValidationHelper;
import gov.va.vinci.dart.common.exception.ValidationException;
import gov.va.vinci.dart.service.DartObjectFactory;
import gov.va.vinci.dart.usr.UserPreferences;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "event", schema = "hib")
public class Event extends BusinessObject {

    @JoinColumn(name = "requestid")
    @ManyToOne(fetch = FetchType.LAZY)
    private Request request;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "eventgroup", schema = "hib", joinColumns = { @JoinColumn(name = "eventid", referencedColumnName = "ID") },
            inverseJoinColumns = { @JoinColumn(name = "groupid", referencedColumnName = "ID") })
    Set<Group> groups;

    @JoinColumn(name = "eventtypeid")
    @ManyToOne(fetch = FetchType.LAZY)
    private EventType eventType;

    // TODO: should really move this to the eventgroup table
    @Column(name = "initial", columnDefinition = "BIT", length = 1)
    protected boolean initial; // Initial NDS event?

    @JoinColumn(name = "reviewid")
    @ManyToOne(fetch = FetchType.LAZY)
    protected Review review;

    Event() {
    }

    /**
     * Creates an Event with no associated Group.
     *
     * @param eventType
     *            the event type
     * @param request
     *            the request
     * @param createdBy
     *            the created by
     * @return the event
     * @throws ValidationException
     *             the validation exception
     */
    public static Event create(final EventType eventType, final Request request, final String createdBy)
            throws ValidationException {

        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Created By", createdBy);
        ValidationHelper.required("Event Type", eventType);

        Event result = new Event();
        result.createdBy = createdBy;
        result.createdOn = new Date();
        // TODO: use the name already stored for this EventType? (store the description?)
        result.name = eventType.getName();
        result.description = eventType.getDescription();
        result.request = request;

        result.eventType = eventType;
        result.initial = false; // default to NOT the initial NDS group

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    /**
     * Create an event with NO associated group. 
     * Only call this method if your EventType does not need a Group short name and
     * the event is not a stop/start event for a group.
     *
     * @param eventType
     *            the event type
     * @param isInitial
     *            the is initial
     * @param request
     *            the request
     * @param prefs
     *            the prefs
     * @return the event
     * @throws ValidationException
     *             the validation exception
     */
    public static Event create(final EventType eventType, final boolean isInitial, final Request request, UserPreferences prefs)
            throws ValidationException {

        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Type", eventType);
        ValidationHelper.required("User Preferences", prefs);
        ValidationHelper.required("Event Created By", prefs.getUserLoginId());

        Event result = new Event();
        result.createdBy = prefs.getUserLoginId();
        result.createdOn = new Date();
        result.name = eventType.getName();
        result.description = eventType.getEventDescription("");
        result.request = request;

        result.eventType = eventType;
        result.initial = isInitial; // initial NDS review?

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    /**
     * Creates the.
     *
     * @param name
     *            the name
     * @param description
     *            the description
     * @param eventType
     *            the event type
     * @param group
     *            the group
     * @param isInitial
     *            the is initial
     * @param request
     *            the request
     * @param createdBy
     *            the created by
     * @return the event
     * @throws ValidationException
     *             the validation exception
     */
    public static Event create(final String name, final String description, final EventType eventType, final Group group,
            final boolean isInitial, final Request request, final String createdBy) throws ValidationException {

        ValidationHelper.required("Event Name", name);
        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Created By", createdBy);
        ValidationHelper.required("Event Type", eventType);

        Event result = new Event();
        result.createdBy = createdBy;
        result.createdOn = new Date();
        // TODO: use the name already stored for this EventType? (store the description?)
        result.name = name;
        result.description = description;
        result.request = request;

        result.groups = new HashSet<Group>(); // create the linking table entry
        result.groups.add(group);

        result.eventType = eventType;
        result.initial = isInitial; // initial NDS review?

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    /**
     * Create an event with a single associated group.
     *
     * @param name
     *            the name
     * @param description
     *            the description
     * @param eventType
     *            the event type
     * @param group
     *            the group
     * @param isInitial
     *            the is initial
     * @param request
     *            the request
     * @param review
     *            the review
     * @param createdBy
     *            the created by
     * @return the event
     * @throws ValidationException
     *             the validation exception
     * @see Event create(final EventType eventType, final Group group, final boolean isInitial, final Request request,
     *      UserPreferences prefs) For consistency use the EventType to get the name and description for the event.
     */

    public static Event create(final String name, final String description, final EventType eventType, final Group group,
            final boolean isInitial, final Request request, Review review, final String createdBy) throws ValidationException {

        ValidationHelper.required("Event Name", name);
        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Created By", createdBy);
        ValidationHelper.required("Event Type", eventType);

        Event result = new Event();
        result.createdBy = createdBy;
        result.createdOn = new Date();
        // TODO: use the name already stored for this EventType? (store the description?)
        result.name = name;
        result.description = description;
        result.request = request;

        result.groups = new HashSet<Group>(); // create the linking table entry
        result.groups.add(group);

        result.eventType = eventType;
        result.initial = isInitial; // initial NDS review?
        result.review = review;

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    /**
     * Create an event with a single associated group.
     *
     * @param name
     *            the name
     * @param description
     *            the description
     * @param eventType
     *            the event type
     * @param request
     *            the request
     * @param createdBy
     *            the created by
     * @return the event
     * 
     * @throws ValidationException
     *             the validation exception
     */    
    public static Event create(final String name, final String description, final EventType eventType, 
                                    final Request request,final String createdBy) throws ValidationException{
        ValidationHelper.required("Event Name", name);
        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Created By", createdBy);
        ValidationHelper.required("Event Type", eventType);

        Event result = new Event();
        result.createdBy = createdBy;
        result.createdOn = new Date();
        result.name = name;
        result.description = description;
        result.request = request;

        result.eventType = eventType;
        result.initial = false; // not initial NDS review

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
 
    }
    
    /**
     * Create an event with a single associated group.
     *
     * @param eventType
     *            the event type
     * @param group
     *            the group
     * @param isInitial
     *            the is initial
     * @param request
     *            the request
     * @param prefs
     *            the prefs
     * @return the event
     * @throws ValidationException
     *             the validation exception
     */
    public static Event create(final EventType eventType, final Group group, final boolean isInitial, final Request request,
            final Review review, UserPreferences prefs) throws ValidationException {

        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Type", eventType);
        ValidationHelper.required("Event Group", group);
        ValidationHelper.required("User Prefereences", prefs);
        ValidationHelper.required("Event Created By", prefs.getUserLoginId());

        Event result = new Event();
        result.createdBy = prefs.getUserLoginId();
        result.createdOn = new Date();
        result.name = eventType.getEventName(group.getShortName());
        result.description = eventType.getEventDescription(group.getShortName());
        result.request = request;

        result.groups = new HashSet<Group>(); // create the linking table entry
        result.groups.add(group);

        result.eventType = eventType;
        result.initial = isInitial; // initial NDS review?
        result.review = review;

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    /**
     * Create an event with possibly many associated groups.
     * 
     * Events are used to calculate requester and reviewer elapsed time. This method looks to be used for old dart requests and
     * for multiple group events. Considerable consideration should be used to determine if it should continue to be used in the
     * future.
     * 
     * @param name
     * @param description
     * @param eventType
     * @param groupSet
     * @param isInitial
     * @param request
     * @param createdBy
     * @return
     * @throws ValidationException
     */
    public static Event create(final String name, final String description, final EventType eventType,
            final Set<Group> groupSet, final boolean isInitial, final Request request, final String createdBy)
            throws ValidationException {

        ValidationHelper.required("Event Name", name);
        ValidationHelper.required("Event Request", request);
        ValidationHelper.required("Event Created By", createdBy);
        ValidationHelper.required("Event Type", eventType);

        Event result = new Event();
        result.createdBy = createdBy;
        result.createdOn = new Date();
        // TODO: use the name already stored for this EventType? (store the description?)
        result.name = name;
        result.description = description;
        result.request = request;

        // TODO: should maybe group the initial and group so that each group has isInitial applied to it (for now, NDS will only
        // be in single-group events) -> this will change if the events cross workflows
        result.groups = new HashSet<Group>(); // create the linking table entry
        result.groups.addAll(groupSet); // add all groups to this event

        result.eventType = eventType;
        result.initial = isInitial; // initial NDS review?

        DartObjectFactory.getInstance().getEventDAO().save(result);

        return result;
    }

    public void delete() {
        DartObjectFactory.getInstance().getEventDAO().delete(this);
    }

    public Request getRequest() {
        return request;
    }

    public Set<Group> getGroups() {
        return groups;
    }

    public EventType getEventType() {
        return eventType;
    }

    public Review getReview() {
        return review;
    }

    public boolean hasGroup(final Group group) {
        boolean result = false;

        if (group == null || groups == null || groups.size() < 1) {
            return false;
        }

        for (Group grp : getGroups()) {
            if (grp.getId() == group.getId()) {
                result = true;
                break;
            }
        }

        return result;
    }

    public boolean isInitial() {
        return initial;
    }

    public static List<Event> listByActivityIdAndGroupOrder(final int activityId, final int groupId, final boolean isInitial) {
        return DartObjectFactory.getInstance().getEventDAO().listByActivityIdAndGroupOrder(activityId, groupId, isInitial);
    }

    public static List<Event> listByEventTypeAndActivityIdAndGroupId(final int eventTypeId, final int activityId,
            final int groupId) {
        return DartObjectFactory.getInstance().getEventDAO().listByEventTypeAndActivityIdAndGroupId(eventTypeId, activityId,
                groupId);
    }

    public static List<Event> listByEventTypeAndActivityIdAndGroupOrder(final int eventTypeId, final int activityId,
            final int groupId, final boolean isInitial) {
        return DartObjectFactory.getInstance().getEventDAO().listByEventTypeAndActivityIdAndGroupOrder(eventTypeId, activityId,
                groupId, isInitial);
    }

    public static List<Event> listByEventTypeAndRequestId(final int eventTypeId, final int requestId) {
        return DartObjectFactory.getInstance().getEventDAO().listByEventTypeAndRequestId(eventTypeId, requestId);
    }

    public static List<Event> listByRequestIdAndGroupId(final int requestId, final int groupId) {
        return DartObjectFactory.getInstance().getEventDAO().listByRequestIdAndGroupId(requestId, groupId);
    }

    public static List<Event> listByRequestIdAndGroupIdAndReviewId(final int requestId, final int groupId, final int reviewId) {
        return DartObjectFactory.getInstance().getEventDAO().listByRequestIdAndGroupIdAndReviewId(requestId, groupId, reviewId);
    }

    public static List<Event> listByRequestIdAndGroupOrder(final int requestId, final int groupId, final boolean isInitial) {
        return DartObjectFactory.getInstance().getEventDAO().listByRequestIdAndGroupOrder(requestId, groupId, isInitial);
    }

    public static List<Event> listByEventTypeAndRequestIdAndGroupOrder(final int eventTypeId, final int requestId,
            final int groupId, final boolean isInitial) {
        return DartObjectFactory.getInstance().getEventDAO().listByEventTypeAndRequestIdAndGroupOrder(eventTypeId, requestId,
                groupId, isInitial);
    }

    public static List<EventSummary> listSummaryByRequestId(final int requestId) {
        return DartObjectFactory.getInstance().getEventDAO().listSummaryByRequestId(requestId);
    }

    public static List<EventSummary> listSummaryByActivityId(final int activityId) {
        return DartObjectFactory.getInstance().getEventDAO().listSummaryByActivityId(activityId);

    }

    public static List<Event> listByGroup(final int groupId) {
        return DartObjectFactory.getInstance().getEventDAO().listByGroup(groupId);
    }

    
    public static void deleteEvents(final int eventTypeId, final int requestId){
        DartObjectFactory.getInstance().getEventDAO().deleteByEventAndRequest(eventTypeId, requestId);
    }
    
    // Requestor start event: changes requested by a review group
    public boolean isStartRequestorEvent(final Group group, final boolean initNDSReview) {

        if (group != null) {

            EventType eventType = getEventType();
            if (eventType != null) {

                final int eventTypeId = eventType.getId();

                Group.initialize();

                // Request start event: changes requested by a review group
                if (eventTypeId == EventType.CHANGE_REQUEST.getId() && hasGroup(group)) { // changes requested by this group

                    if (group.getId() == Group.NDS.getId()) {
                        if (initNDSReview == isInitial()) { // initial NDS or final NDS?
                            return true;
                        }

                        return false; // wrong NDS review
                    }

                    return true;
                }
            }
        }

        return false;
    }

    public boolean isStartRequestorEvent() {

        EventType eventType = getEventType();
        if (eventType != null) {

            final int eventTypeId = eventType.getId();

            if (eventTypeId == EventType.CHANGE_REQUEST.getId()) {

                return true;
            }

        }

        return false;
    }


    public boolean isEndRequestorEvent(final Group group, final boolean initNDSReview) {

        if (group != null) {

            EventType eventType = getEventType();
            if (eventType != null) {

                final int eventTypeId = eventType.getId();

                Group.initialize();

                // TODO: could use the "Request Sent for ... Review" as the end task for the requestor here (instead of the
                // change request submission) -> currently have a duplicate event
                // end event: submit with requested changes
                if ((hasGroup(group) && eventTypeId == EventType.SUBMIT_CHANGE_REQUEST.getId()) || // submitted changes
                                                                                                   // (requested by this group)
                        eventTypeId == EventType.CLOSE_REQUEST.getId()) {

                    if (group.getId() == Group.NDS.getId()) {
                        if (initNDSReview == isInitial()) { // initial NDS or final NDS?
                            return true;
                        }

                        return false; // wrong NDS review
                    }

                    return true;
                }
            }
        }

        return false;
    }

    public boolean isEndRequestorEvent() {

        EventType eventType = getEventType();
        if (eventType != null) {

            final int eventTypeId = eventType.getId();

            if (eventTypeId == EventType.SUBMIT_CHANGE_REQUEST.getId() || eventTypeId == EventType.CLOSE_REQUEST.getId() || eventTypeId == EventType.WITHDRAW_REVIEW.getId()) {

                return true;
            }
        }

        return false;
    }

    // Reviewer start event: sent for review, changes submitted
    public boolean isStartReviewEvent() {

        if (this.eventType != null) {

            if (this.eventType.getId() == EventType.SENT_FOR_REVIEW.getId()
                    || this.eventType.getId() == EventType.SUBMIT_CHANGE_REQUEST.getId()) {
                return true;
            }
        }

        return false;
    }

    // Reviewer end event: approve, deny, request changes, close
    public boolean isEndReviewEvent() {

        if (this.eventType != null) {

            if (this.eventType.getId() == EventType.APPROVE_REVIEW.getId()
                    || this.eventType.getId() == EventType.CHANGE_REQUEST.getId()
                    || this.eventType.getId() == EventType.DENY_REVIEW.getId()
                    || this.eventType.getId() == EventType.CLOSE_REQUEST.getId()
                    || this.eventType.getId() == EventType.WITHDRAW_REVIEW.getId()) {

                return true;
            }
        }

        return false;
    }

    // necessary to use (List<Event>).contains()
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        // if ( (Event.class.isAssignableFrom(obj.getClass())) == false &&
        // (obj.getClass().isAssignableFrom(Event.class)) == false ) { //class mis-match
        if ((obj instanceof Event) == false) { // class mis-match
            return false;
        }

        Event rs2 = (Event) obj;
        return rs2.getId() == this.getId();
    }

    /**
     * Sort the events, earliest first.
     * 
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static Comparator getComparator() {
        return new Comparator() {

            @Override
            public int compare(Object o1, Object o2) {
                if (o1 == null && o2 == null) {
                    return 0;
                }
                if ((o1 instanceof Event) == false) {
                    return 0;
                }
                if ((o2 instanceof Event) == false) {
                    return 0;
                }

                Event ev1 = (Event) o1;
                Event ev2 = (Event) o2;

                if (ev1.getCreatedOn() == null) {
                    if (ev2.getCreatedOn() == null) {
                        return 0;
                    } else {
                        return -1;
                    }
                } else {
                    if (ev2.getCreatedOn() == null) {
                        return 1;
                    }
                }

                if (ev1.getCreatedOn().equals(ev2.getCreatedOn())) { // same createdon time, so compare the eventID

                    if (ev1.getId() == ev2.getId())
                        return 0;
                    else if (ev1.getId() < ev2.getId())
                        return -1;
                    else
                        return 1;
                }

                return ev1.getCreatedOn().compareTo(ev2.getCreatedOn());
            }
        };
    }
}
