package info.bukova.isspst.services.workgroups;

import info.bukova.isspst.Constants;
import info.bukova.isspst.data.JobMapping;
import info.bukova.isspst.data.Member;
import info.bukova.isspst.data.Role;
import info.bukova.isspst.data.User;
import info.bukova.isspst.data.Workgroup;
import info.bukova.isspst.services.AbstractOwnedService;
import info.bukova.isspst.services.LazyLoader;
import org.hibernate.LazyInitializationException;
import org.hibernate.Query;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

public class WorkgroupServiceImpl extends AbstractOwnedService<Workgroup> implements WorkgroupService {
	
	@Override
	public void addMember(Workgroup workgroup, Member member, Role role) {
		if (!member.isPerson() && !role.getAuthority().equals(Constants.ROLE_USER)) {
			throw new WorkgroupException(WorkgroupException.NO_WORGROUP);
		}
		if (workgroup.isCentre() && !role.isCentre()) {
			throw new WorkgroupException(WorkgroupException.MSUT_HAS_CENTER);
		}
		if (!workgroup.isCentre() && !role.isWorkgroup()) {
			throw new WorkgroupException(WorkgroupException.MUST_HAS_WORKGROUP);
		}
		
		if (!checkGroups(workgroup, member)) {
			throw new WorkgroupException(WorkgroupException.CYCLIC_MEMBER);
		}
			
		JobMapping mapping = new JobMapping(member, role);
		if (!workgroup.getMembers().contains(mapping)) {
			workgroup.getMembers().add(mapping);
			member.addParent(workgroup);
		}
	}
	
	@Override
	public void removeMember(Workgroup workgroup, JobMapping jobMapping) {
		workgroup.removeMember(jobMapping);
		if (!isMember(workgroup, jobMapping.getMember())) {
			jobMapping.getMember().removeParent(workgroup);
			workgroup.getRemoved().add(jobMapping.getMember());
		}
	}

	@Override
	@Transactional
	public List<Workgroup> getWorkgroups() {
		return this.execQuery("from Workgroup where centre = false");
	}

	@Override
	@Transactional
	public List<Workgroup> getCentres() {
		return this.execQuery("from Workgroup where centre = true");
	}

	@Override
	@Transactional
	public boolean isMember(Workgroup workgroup, Member member) {
		loadParents(member);
		
		if (member.equals(getLoggedInUser()) && sessionData.getCurrentUser() != null) {
			return member.getParents().contains(workgroup);
		}
		
		loadMembers(workgroup);
		for (JobMapping jm : workgroup.getMembers()) {
			if (jm.getMember().equals(member)) {
				return true;
			}
		}
		
		return false;
	}
	
	// check, jestli nahodou nekdo z komise neni clemen strediska, do nehoz se komise pridava
	private boolean checkGroups(Workgroup workgroup, Member member) {
		if (member.equals(workgroup)) {
			return false;
		}
		
		if (member.isPerson()) {
			return checkParentsMembers(workgroup, (User)member);
		}
				
		Workgroup toAdd = Workgroup.class.cast(member);
		for (JobMapping jm : toAdd.getMembers()) {
			for (JobMapping wJm : workgroup.getMembers()) {
				if (jm.getMember().equals(wJm.getMember())) {
					return false;
				}
			}
			
		}
		
		return true;
	}
	
	//check, jestli uzivatel pridavany do komise neni zaroven clenem strediska, jehoz je komise clenem
	private boolean checkParentsMembers(Workgroup workgroup, User user) {
		if (workgroup.isCentre()) {
			return true;
		}
		
		for (Workgroup wg : workgroup.getParents()) {
			for (JobMapping jm : wg.getMembers()) {
				if (jm.getMember().equals(user)) {
					return false;
				}
			}
		}
		
		return true;
	}

	@Override
	@Transactional
	public List<Workgroup> getUserCentres(User user) {
		/*if (user.equals(getLoggedInUser()) && sessionData.getUserCentres() != null) {
			return sessionData.getUserCentres();
		}
		
		List<Workgroup> parents = getParents(user);
		
		if (parents == null) {
			return null;
		}
		
		List<Workgroup> centres = new ArrayList<Workgroup>();
		for (Workgroup w : parents) {
			if (w.isCentre()) {
				centres.add(w);
			}
		}
		
		for (Workgroup w : getUserWorkgroups(user)) {
			loadParents(w);
			for (Workgroup centre : w.getParents()) {
				if (!centres.contains(centre)) {
					centres.add(centre);
				}
			}
		}
		return centres;*/
		return getUserCentres(user, false);
	}

	@Override
	@Transactional
	public List<Workgroup> getUserCentres(User user, boolean userOnly) {
		if (!userOnly && user.equals(getLoggedInUser()) && sessionData.getUserCentres() != null) {
			return sessionData.getUserCentres();
		}

		if (userOnly && user.equals(getLoggedInUser()) && sessionData.getUserOnlyCentres() != null) {
			return sessionData.getUserOnlyCentres();
		}

		List<Workgroup> parents;

		if (userOnly) {
			parents = filterUserOnly(user, getParents(user));
		} else {
			parents = getParents(user);
		}

		if (parents == null) {
			return null;
		}

		List<Workgroup> centres = new ArrayList<Workgroup>();
		for (Workgroup w : parents) {
			if (w.isCentre()) {
				centres.add(w);
			}
		}

		List<Workgroup> workGroups;

		if (userOnly) {
			workGroups = filterUserOnly(user, getUserWorkgroups(user));
		} else {
			workGroups = getUserWorkgroups(user);
		}

		for (Workgroup w : workGroups) {
			loadParents(w);
			for (Workgroup centre : w.getParents()) {
				if (!centres.contains(centre)) {
					centres.add(centre);
				}
			}
		}
		return centres;
	}

	private List<Workgroup> filterUserOnly(User user, List<Workgroup> wgList) {
		List<Workgroup> centres = new ArrayList<Workgroup>();

		for (Workgroup wg : wgList) {
			List<Role> roles = getUserWorkgroupRoles(wg, user);
			if (roles == null) {
				continue;
			}

			for (Role r : roles) {
				if (r.getAuthority().equals(Constants.ROLE_USER)) {
					centres.add(wg);
				}
			}
		}

		return centres;
	}

	@Override
	@Transactional
	public List<Workgroup> getUserWorkgroups(User user) {
		if (user.equals(getLoggedInUser()) && sessionData.getUserWorkgroups() != null) {
			return sessionData.getUserWorkgroups();
		}
		
		List<Workgroup> parents = getParents(user);
		
		if (parents == null) {
			return null;
		}
		
		List<Workgroup> wg = new ArrayList<Workgroup>();
		for (Workgroup w: parents) {
			if (!w.isCentre()) {
				wg.add(w);
			}
		}
		
		return wg;
	}
	
	private List<Workgroup> getParents(User user) {
		loadParents(user);
			
		return user.getParents();
	}
	
	private User loadUser(User user) {
		Query q = dao.getQuery("from User where username = '" + user.getUsername() + "'");
		return (User) q.uniqueResult();
	}

	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_ADD')")
	public void add(Workgroup entity) {
		super.add(entity);
		updateMembers(entity);
	}

	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_EDIT')")
	public void update(Workgroup entity) {
		super.update(entity);
		updateMembers(entity);
	}
	
	private void updateMembers(Workgroup workgroup) {
		for (JobMapping jm : workgroup.getMembers()) {
			dao.getSession().merge(jm.getMember());			
		}
		
		for (Member m : workgroup.getRemoved()) {
			dao.getSession().merge(m);
		}
	}

	@Override
	@Transactional
	public List<Role> getUserWorkgroupRoles(Workgroup workgroup, User user) {
		if (user.equals(getLoggedInUser()) && sessionData.getWorkgroupRoles() != null) {
			return sessionData.getWorkgroupRoles().get(workgroup.getId());
		}
		
		List<Role> roles = new ArrayList<Role>();
		User u;
		
		if (user.getId() == 0) {
			u = loadUser(user);
		} else {
			u = user;
		}
		
		if (!isMember(workgroup, u)) {
			return null;
		}
		
		loadMembers(workgroup);
		
		for (JobMapping jm : workgroup.getMembers()) {
			if (jm.getMember().equals(u)) {
				roles.add(jm.getRole());
			}
		}
				
		return roles;
	}

	@Override
	@Transactional
	@PreAuthorize("hasPermission(this, 'PERM_DELETE')")
	public void delete(Workgroup entity) {
		Workgroup dbWg = dao.getById(entity.getId());
		if (!dbWg.getParents().isEmpty()) {
			throw new WorkgroupException(WorkgroupException.HAS_PARENT);
		}
		
		loadMembers(entity);
		for (JobMapping m : entity.getMembers()) {
			loadParents(m.getMember());
			m.getMember().removeParent(entity);
			dao.getSession().merge(m.getMember());
		}
		super.delete(entity);
	}

	@Override
	public List<Member> getMembers(Workgroup workgroup) {
		loadMembers(workgroup);
		List<JobMapping> jmList = workgroup.getMembers();
		List<Member> members = new ArrayList<Member>();
		
		for (JobMapping jm : jmList) {
			members.add(jm.getMember());
		}
		
		return members;
	}

	@Override
	@Transactional
	@LazyLoader({"grid", "form"})
	public void loadMembers(Workgroup workgroup) {
		try {
			if (workgroup.getMembers() == null) {
				throw new LazyInitializationException("");
			}
			workgroup.getMembers().size();
		} catch (LazyInitializationException e) {
			Workgroup w = dao.getById(workgroup.getId());
			w.getMembers().size();
			workgroup.setMembers(w.getMembers());
		}
	}

	@Override
	@Transactional
	@LazyLoader("form")
	public void loadParents(Member member) {
		try {
			if (member.getParents() == null) {
				throw new LazyInitializationException("");
			}
			member.getParents().size();
		} catch (LazyInitializationException e) {
			if (member.isPerson()) {
				User u = loadUser((User) member);
				u.getParents().size();
				member.setParents(u.getParents());
			} else {
				Workgroup w = dao.getById(member.getId());
				w.getParents().size();
				member.setParents(w.getParents());
			}
		}
	}
	
	@Override
	@Transactional
	public void loadParentsMembers(Workgroup workgroup) {
		loadParents(workgroup);
		
		for (Workgroup wg : workgroup.getParents()) {
			loadMembers(wg);
		}
	}

}
