package gov.va.vinci.dart.biz;

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

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

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.OneToMany;
import javax.persistence.Table;

import com.googlecode.ehcache.annotations.Cacheable;

@Entity
@Table(name="person",schema="hib")
public class Person extends ReferenceObject implements Comparable {

	@OneToMany(fetch=FetchType.LAZY,mappedBy="personOwner")
	Set<PersonTask> tasks;
	
	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="locationId")
	Location location;

	@Column(name="fullname")
	private String fullName;
	
	@ManyToMany(fetch=FetchType.LAZY)
	@JoinTable(
		      name="persongroup", schema="hib",
		      joinColumns={@JoinColumn(name="personid", referencedColumnName="ID")},
		      inverseJoinColumns={@JoinColumn(name="groupid", referencedColumnName="ID")})
	Set<Group> groups;
	
	@ManyToMany(fetch=FetchType.LAZY)
	@JoinTable(
		      name="personrole", schema="hib",
		      joinColumns={@JoinColumn(name="personid", referencedColumnName="ID")},
		      inverseJoinColumns={@JoinColumn(name="roleid", referencedColumnName="ID")})
	Set<Role> roles;

	@OneToMany(fetch=FetchType.LAZY,mappedBy="person")
	Set<PersonGroup> personGroups;


	public static Person findById(final int personId) throws ObjectNotFoundException {
		return DartObjectFactory.getInstance().getPersonDAO().findById(personId);
	}
	
	public static List<Person> listAll() {
		return DartObjectFactory.getInstance().getPersonDAO().listAll();
	}
	
	public static List<Person> list(final String term) {
		return DartObjectFactory.getInstance().getPersonDAO().list(term);
	}


	@Cacheable(cacheName = "Person")
	public static Person findByName(final String name) throws ObjectNotFoundException, ValidationException {
		ValidationHelper.required("Person Name", name);
		ValidationHelper.validateSize("Person Name", name, 1, 64);
		
		return DartObjectFactory.getInstance().getPersonDAO().findByName(name.toUpperCase());
	}
	
	
	public static List<Person> listAllWithRole(final int roleId) {
		return DartObjectFactory.getInstance().getPersonDAO().listAllWithRole(roleId);
	}
	
	public static List<Person> listByGroup(final int groupId) {
		return DartObjectFactory.getInstance().getPersonDAO().listByGroup(groupId);
	}
	
	public static List<Person> listAllWithRoleAndGroup(final int roleId, final int groupId) {
		return DartObjectFactory.getInstance().getPersonDAO().listAllWithRoleAndGroup(roleId, groupId);
	}

	public static List<Person> listByGroupAndNotifications(final int groupId) {
		return DartObjectFactory.getInstance().getPersonDAO().listByGroupAndNotifications(groupId);
	}
	
	public static List<Person> listAllWithRoleAndGroupAndNotifications(final int roleId, final int groupId) {
		return DartObjectFactory.getInstance().getPersonDAO().listAllWithRoleAndGroupAndNotifications(roleId, groupId);
	}	
	
	
	public static Person create(final String name, final String fullName, final Location location)  throws ValidationException  {
	
		ValidationHelper.required("Person Location", location);
		
		Person result = new Person();
		
		result.modify(name, fullName);
		
		result.location = location;
		
		DartObjectFactory.getInstance().getPersonDAO().save(result);
		
		return result;
	}
	
	public void modify(final String name, final String fullName) throws ValidationException {
		validateModify(name, fullName);
		
		this.name = name;
		this.fullName = fullName;
	}
	
	private void validateModify(final String name, final String fullName) throws ValidationException {
		ValidationHelper.required("Person Name", name);
		ValidationHelper.validateSize("Person Name", name, 1, 64);
		ValidationHelper.required("Person Full Name", fullName);
		ValidationHelper.validateSize("Person Full Name", fullName, 1, 256);
	}
	
	public void modifyRoles(final Set<Role> newRoles) {
		if( roles == null ) {
			roles = new HashSet<Role>();
		}
		
		roles.clear();
		roles.addAll(newRoles);
		
		DartObjectFactory.getInstance().getPersonDAO().save(this);
	}

	public void modifyGroups(final Set<Group> newGroups) {
		if( groups == null ) {
			groups = new HashSet<Group>();
		}
		
		groups.clear();
		groups.addAll(newGroups);
		
		DartObjectFactory.getInstance().getPersonDAO().save(this);
	}
	
	
	// no setter for a person's location.  this is managed in the database until further notice.
	public Location getLocation() {
		return location;
	}

	// TESTING ONLY
	public void setLocation(final Location location) {
		this.location = location;
	}
	
	public Set<PersonTask> getTasks() {
		if( tasks == null ) {
			tasks = new HashSet<PersonTask>();
		}

		return tasks;
	}
	
	// TESTING ONLY
	public void setTasks(final Set<PersonTask> tasks) {
		this.tasks = tasks;
	}
	
	public Set<Group> getGroups() {
		if( groups == null ) {
			groups = new HashSet<Group>();
		}
		
		return groups;
	}

	// TESTING ONLY
	public void setGroups(final Set<Group> groups) {
		this.groups = groups;
	}
	
	public Set<Role> getRoles() {
		if( roles == null ) {
			roles = new HashSet<Role>();
		}
		
		return roles;
	}

	// TESTING ONLY
	public void setRoles(final Set<Role> roles) {
		this.roles = roles;
	}

	public Set<Role> listRoles() {
		Set<Role> result = new HashSet<Role>();
		
		result.addAll(roles);

//		for (Group group : groups) {
//			result.addAll(group.getRoles());
//		}
		
		return result;
	}
	
	
	public Set<PersonGroup> getPersonGroups() {
		return personGroups;
	}
	
	public String getFullName() {
		return fullName;
	}
	
	public void setFullName(String fullName) {
		this.fullName = fullName;
	}

	// necessary to use (List<Person>).contains()
	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		
		if ((obj instanceof Person) == false) {
			return false;
		}
		
		Person rs2 = (Person)obj;
		return rs2.getId() == this.getId();
	}
	
	@Override public int hashCode() {
        return this.id;
    }

	public static Set<Person> findAllWithRole(Role role) {
		Set<Person> result = new HashSet<Person>();
		
		if (role != null) {
			for (Person pers : listAll()) {
				if (pers.hasRole(role)) {
					result.add(pers);
				}
			}
		}
		
		return result;
	}
	
	public boolean hasRole(final Role role) {
		boolean result = false;
		
		if (hasPersonalRole(role)) {
			return true;
		}

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

//		for (Group grp : getGroups()) {
//			if (grp.hasRole(role)) {
//				result = true;
//				break;
//			}
//		}
		
		return result;
	}
	
	private boolean hasPersonalRole(final Role role) {
		if (role == null  || roles == null || roles.size() < 1) {
			return false;
		}
		
		for (Role r : roles) {
			if (r.getId() == role.getId()) {
				return true;
			}
		}
		return false;
	}
	
	public boolean inGroup(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;
	}
	
	@Override
	public int compareTo(Object o) {
		if (o == null) {
			return -1;
		}
		if (Person.class.isAssignableFrom(o.getClass()) == false) {
			return -1;
		}
		
		Person pers2 = (Person)o;
		if (getFullName() == null) {
			return pers2.getFullName() == null ? 0 : -1;
		}
		if (pers2.getFullName() == null) {
			return 1;
		}
		return getFullName().compareTo(pers2.getFullName());
	}
}
