com.tysanclan.site.projectewok.beans.impl.DemocracyServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.tysanclan.site.projectewok.beans.impl.DemocracyServiceImpl.java

Source

/**
 * Tysan Clan Website
 * Copyright (C) 2008-2013 Jeroen Steenbeeke and Ties van de Ven
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.tysanclan.site.projectewok.beans.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.jeroensteenbeeke.hyperion.events.IEventDispatcher;
import com.tysanclan.rest.api.data.Rank;
import com.tysanclan.site.projectewok.beans.MailService;
import com.tysanclan.site.projectewok.entities.AcceptanceVote;
import com.tysanclan.site.projectewok.entities.AcceptanceVoteVerdict;
import com.tysanclan.site.projectewok.entities.ChancellorElection;
import com.tysanclan.site.projectewok.entities.CompoundVote;
import com.tysanclan.site.projectewok.entities.CompoundVoteChoice;
import com.tysanclan.site.projectewok.entities.Donation;
import com.tysanclan.site.projectewok.entities.Election;
import com.tysanclan.site.projectewok.entities.Group;
import com.tysanclan.site.projectewok.entities.GroupLeaderElection;
import com.tysanclan.site.projectewok.entities.Impeachment;
import com.tysanclan.site.projectewok.entities.ImpeachmentVote;
import com.tysanclan.site.projectewok.entities.JoinApplication;
import com.tysanclan.site.projectewok.entities.JoinVerdict;
import com.tysanclan.site.projectewok.entities.Regulation;
import com.tysanclan.site.projectewok.entities.RegulationChange;
import com.tysanclan.site.projectewok.entities.RegulationChange.ChangeType;
import com.tysanclan.site.projectewok.entities.RegulationChangeVote;
import com.tysanclan.site.projectewok.entities.SenateElection;
import com.tysanclan.site.projectewok.entities.UntenabilityVote;
import com.tysanclan.site.projectewok.entities.UntenabilityVoteChoice;
import com.tysanclan.site.projectewok.entities.User;
import com.tysanclan.site.projectewok.entities.dao.AcceptanceVoteDAO;
import com.tysanclan.site.projectewok.entities.dao.AcceptanceVoteVerdictDAO;
import com.tysanclan.site.projectewok.entities.dao.ChancellorElectionDAO;
import com.tysanclan.site.projectewok.entities.dao.CompoundVoteChoiceDAO;
import com.tysanclan.site.projectewok.entities.dao.CompoundVoteDAO;
import com.tysanclan.site.projectewok.entities.dao.DonationDAO;
import com.tysanclan.site.projectewok.entities.dao.GroupDAO;
import com.tysanclan.site.projectewok.entities.dao.GroupLeaderElectionDAO;
import com.tysanclan.site.projectewok.entities.dao.ImpeachmentDAO;
import com.tysanclan.site.projectewok.entities.dao.ImpeachmentVoteDAO;
import com.tysanclan.site.projectewok.entities.dao.JoinApplicationDAO;
import com.tysanclan.site.projectewok.entities.dao.JoinVerdictDAO;
import com.tysanclan.site.projectewok.entities.dao.RegulationChangeDAO;
import com.tysanclan.site.projectewok.entities.dao.RegulationChangeVoteDAO;
import com.tysanclan.site.projectewok.entities.dao.RegulationDAO;
import com.tysanclan.site.projectewok.entities.dao.SenateElectionDAO;
import com.tysanclan.site.projectewok.entities.dao.UntenabilityVoteChoiceDAO;
import com.tysanclan.site.projectewok.entities.dao.UntenabilityVoteDAO;
import com.tysanclan.site.projectewok.entities.dao.UserDAO;
import com.tysanclan.site.projectewok.entities.dao.filters.AcceptanceVoteFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.ChancellorElectionFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.DonationFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.GroupLeaderElectionFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.JoinApplicationFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.RegulationChangeFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.SenateElectionFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.UntenabilityVoteFilter;
import com.tysanclan.site.projectewok.entities.dao.filters.UserFilter;
import com.tysanclan.site.projectewok.event.MemberStatusEvent;
import com.tysanclan.site.projectewok.util.DateUtil;
import com.tysanclan.site.projectewok.util.MemberUtil;
import com.tysanclan.site.projectewok.util.bbcode.BBCodeUtil;

/**
 * @author Jeroen Steenbeeke
 */
@Component
@Scope("request")
class DemocracyServiceImpl implements com.tysanclan.site.projectewok.beans.DemocracyService {
    @Autowired
    private ChancellorElectionDAO chancellorElectionDAO;

    @Autowired
    private SenateElectionDAO senateElectionDAO;

    @Autowired
    private GroupLeaderElectionDAO groupLeaderElectionDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private GroupDAO groupDAO;

    @Autowired
    private AcceptanceVoteDAO acceptanceVoteDAO;

    @Autowired
    private AcceptanceVoteVerdictDAO acceptanceVoteVerdictDAO;

    @Autowired
    private CompoundVoteDAO compoundVoteDAO;
    @Autowired
    private CompoundVoteChoiceDAO compoundVoteChoiceDAO;

    @Autowired
    private JoinApplicationDAO joinApplicationDAO;

    @Autowired
    private JoinVerdictDAO joinVerdictDAO;

    @Autowired
    private ImpeachmentDAO impeachmentDAO;

    @Autowired
    private ImpeachmentVoteDAO impeachmentVoteDAO;

    @Autowired
    private UntenabilityVoteDAO untenabilityVoteDAO;

    @Autowired
    private UntenabilityVoteChoiceDAO untenabilityVoteChoiceDAO;

    @Autowired
    private RegulationDAO regulationDAO;

    @Autowired
    private RegulationChangeDAO regulationChangeDAO;

    @Autowired
    private RegulationChangeVoteDAO regulationChangeVoteDAO;

    @Autowired
    private DonationDAO donationDAO;

    @Autowired
    private com.tysanclan.site.projectewok.beans.MailService mailService;

    @Autowired
    private com.tysanclan.site.projectewok.beans.UserService userService;

    @Autowired
    private com.tysanclan.site.projectewok.beans.LogService logService;

    @Autowired
    private com.tysanclan.site.projectewok.beans.NotificationService notificationService;

    @Autowired
    private IEventDispatcher dispatcher;

    public void setDispatcher(IEventDispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    public void setJoinVerdictDAO(JoinVerdictDAO joinVerdictDAO) {
        this.joinVerdictDAO = joinVerdictDAO;
    }

    /**
     * @param donationDAO
     *            the donationDAO to set
     */
    public void setDonationDAO(DonationDAO donationDAO) {
        this.donationDAO = donationDAO;
    }

    public void setNotificationService(
            com.tysanclan.site.projectewok.beans.NotificationService notificationService) {
        this.notificationService = notificationService;
    }

    /**
     * @param regulationChangeVoteDAO
     *            the regulationChangeVoteDAO to set
     */
    public void setRegulationChangeVoteDAO(RegulationChangeVoteDAO regulationChangeVoteDAO) {
        this.regulationChangeVoteDAO = regulationChangeVoteDAO;
    }

    public void setMembershipService(com.tysanclan.site.projectewok.beans.UserService userService) {
        this.userService = userService;
    }

    /**
     * @param userDAO
     *            the userDAO to set
     */
    public void setUserDAO(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    /**
     * @param compoundVoteDAO
     *            the compoundVoteDAO to set
     */
    public void setCompoundVoteDAO(CompoundVoteDAO compoundVoteDAO) {
        this.compoundVoteDAO = compoundVoteDAO;
    }

    /**
     * @param compoundVoteChoiceDAO
     *            the compoundVoteChoiceDAO to set
     */
    public void setCompoundVoteChoiceDAO(CompoundVoteChoiceDAO compoundVoteChoiceDAO) {
        this.compoundVoteChoiceDAO = compoundVoteChoiceDAO;
    }

    /**
     * @param acceptanceVoteDAO
     *            the acceptanceVoteDAO to set
     */
    public void setAcceptanceVoteDAO(AcceptanceVoteDAO acceptanceVoteDAO) {
        this.acceptanceVoteDAO = acceptanceVoteDAO;
    }

    /**
     * @param joinApplicationDAO
     *            the joinApplicationDAO to set
     */
    public void setJoinApplicationDAO(JoinApplicationDAO joinApplicationDAO) {
        this.joinApplicationDAO = joinApplicationDAO;
    }

    /**
     * @param mailService
     *            the mailService to set
     */
    public void setMailService(MailService mailService) {
        this.mailService = mailService;
    }

    /**
     * @param groupDAO
     *            the groupDAO to set
     */
    public void setGroupDAO(GroupDAO groupDAO) {
        this.groupDAO = groupDAO;
    }

    /**
     * @param impeachmentDAO
     *            the impeachmentDAO to set
     */
    public void setImpeachmentDAO(ImpeachmentDAO impeachmentDAO) {
        this.impeachmentDAO = impeachmentDAO;
    }

    /**
     * @param impeachmentVoteDAO
     *            the impeachmentVoteDAO to set
     */
    public void setImpeachmentVoteDAO(ImpeachmentVoteDAO impeachmentVoteDAO) {
        this.impeachmentVoteDAO = impeachmentVoteDAO;
    }

    /**
     * @param logService
     *            the logService to set
     */
    public void setLogService(com.tysanclan.site.projectewok.beans.LogService logService) {
        this.logService = logService;
    }

    /**
     * @param acceptanceVoteVerdictDAO
     *            the acceptanceVoteVerdictDAO to set
     */
    public void setAcceptanceVoteVerdictDAO(AcceptanceVoteVerdictDAO acceptanceVoteVerdictDAO) {
        this.acceptanceVoteVerdictDAO = acceptanceVoteVerdictDAO;
    }

    /**
     * @param userService
     *            the userService to set
     */
    public void setUserService(com.tysanclan.site.projectewok.beans.UserService userService) {
        this.userService = userService;
    }

    /**
     * @param regulationDAO
     *            the regulationDAO to set
     */
    public void setRegulationDAO(RegulationDAO regulationDAO) {
        this.regulationDAO = regulationDAO;
    }

    /**
     * @param regulationChangeDAO
     *            the regulationChangeDAO to set
     */
    public void setRegulationChangeDAO(RegulationChangeDAO regulationChangeDAO) {
        this.regulationChangeDAO = regulationChangeDAO;
    }

    /**
     * @param untenabilityVoteChoiceDAO
     *            the untenabilityVoteChoiceDAO to set
     */
    public void setUntenabilityVoteChoiceDAO(UntenabilityVoteChoiceDAO untenabilityVoteChoiceDAO) {
        this.untenabilityVoteChoiceDAO = untenabilityVoteChoiceDAO;
    }

    /**
     * @param untenabilityVoteDAO
     *            the untenabilityVoteDAO to set
     */
    public void setUntenabilityVoteDAO(UntenabilityVoteDAO untenabilityVoteDAO) {
        this.untenabilityVoteDAO = untenabilityVoteDAO;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void removeAcceptanceVotes(User user) {
        AcceptanceVoteFilter filter = new AcceptanceVoteFilter();
        filter.setTrialMember(user);

        List<AcceptanceVote> votes = acceptanceVoteDAO.findByFilter(filter);
        for (AcceptanceVote vote : votes) {
            acceptanceVoteDAO.delete(vote);
        }
    }

    @Override
    public void resetSenateElectionIfUserIsParticipating(User user) {
        SenateElection election = getCurrentSenateElection();
        if (election != null) {
            if (election.isNominationOpen() && election.getCandidates().contains(user)) {
                election.getCandidates().remove(user);

            } else if (election.getCandidates().contains(user)) {
                // Reset election to nomination period
                election.setStart(new Date());
                for (CompoundVote vote : election.getVotes()) {
                    for (CompoundVoteChoice choice : vote.getChoices()) {
                        compoundVoteChoiceDAO.delete(choice);
                    }
                    compoundVoteDAO.delete(vote);

                    notificationService.notifyUser(vote.getCaster(),
                            "The Senate election was restarted due to a candidate's membership being terminated. You will need to vote again in a week");

                }

                logService.logSystemAction("Democracy",
                        "Senate election restarted due to candidate membership termination");
            }

            senateElectionDAO.update(election);
        }
    }

    @Override
    public void resetChancellorElectionIfUserIsParticipating(User user) {
        ChancellorElection election = getCurrentChancellorElection();
        if (election != null) {
            if (election.isNominationOpen() && election.getCandidates().contains(user)) {
                election.getCandidates().remove(user);

            } else if (election.getCandidates().contains(user)) {
                // Reset election to nomination period
                election.setStart(new Date());
                for (CompoundVote vote : election.getVotes()) {
                    for (CompoundVoteChoice choice : vote.getChoices()) {
                        compoundVoteChoiceDAO.delete(choice);
                    }
                    compoundVoteDAO.delete(vote);

                    notificationService.notifyUser(vote.getCaster(),
                            "The Chancellor election was restarted due to a candidate's membership being terminated. You will need to vote again in a week");

                }

                logService.logSystemAction("Democracy",
                        "Chancellor election restarted due to candidate membership termination");
            }

            chancellorElectionDAO.update(election);
        }
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#isEligibleChancellorCandidate(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean isEligibleChancellorCandidate(User _user) {
        User user = userDAO.load(_user.getId());

        if (user.isRetired()) {
            return false;
        }

        if (user.getEndorsedBy().size() >= getRequiredChancellorEndorsements()) {
            return true;
        }

        Calendar cal = DateUtil.getCalendarInstance();
        cal.add(Calendar.MONTH, -6);

        DonationFilter filter = new DonationFilter();
        filter.setFrom(cal.getTime());
        filter.setDonator(user);

        BigDecimal value = BigDecimal.ZERO;
        List<Donation> donations = donationDAO.findByFilter(filter);
        for (Donation donation : donations) {
            value = value.add(donation.getAmount());
        }

        if (value.compareTo(new BigDecimal(30)) >= 0) {
            return true;
        }

        return false;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#isEligibleSenateCandidate(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean isEligibleSenateCandidate(User _user) {
        User user = userDAO.load(_user.getId());

        if (user.isRetired()) {
            return false;
        }

        if (user.getEndorsedForSenateBy().size() >= getRequiredSenateEndorsements()) {
            return true;
        }

        Calendar cal = DateUtil.getCalendarInstance();
        cal.add(Calendar.MONTH, -6);

        DonationFilter filter = new DonationFilter();
        filter.setFrom(cal.getTime());
        filter.setDonator(user);

        BigDecimal value = BigDecimal.ZERO;
        List<Donation> donations = donationDAO.findByFilter(filter);
        for (Donation donation : donations) {
            value = value.add(donation.getAmount());
        }

        if (value.compareTo(new BigDecimal(20)) > 0) {
            return true;
        }

        return false;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createChancellorElection()
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public ChancellorElection createChancellorElection() {
        ChancellorElection election = new ChancellorElection();
        election.setStart(DateUtil.getCalendarInstance().getTime());

        chancellorElectionDAO.save(election);

        logService.logSystemAction("Democracy", "Chancellor election started");

        return election;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createGroupLeaderElection(com.tysanclan.site.projectewok.entities.Group)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public GroupLeaderElection createGroupLeaderElection(Group group) {
        GroupLeaderElection election = new GroupLeaderElection();
        election.setStart(DateUtil.getCalendarInstance().getTime());
        election.setGroup(group);
        groupLeaderElectionDAO.save(election);

        logService.logSystemAction("Groups", "Group leader election for group " + group.getName() + " started");

        return election;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createSenateElection()
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public SenateElection createSenateElection() {

        UserFilter filter = new UserFilter();
        filter.addRank(Rank.CHANCELLOR);
        filter.addRank(Rank.TRUTHSAYER);
        filter.addRank(Rank.SENATOR);
        filter.addRank(Rank.REVERED_MEMBER);
        filter.addRank(Rank.SENIOR_MEMBER);
        filter.addRank(Rank.FULL_MEMBER);
        filter.addRank(Rank.JUNIOR_MEMBER);
        filter.addRank(Rank.TRIAL);
        filter.setRetired(false);

        long memberCount = userDAO.countByFilter(filter);
        int seats = calculateSenateSeats(memberCount);

        SenateElection election = new SenateElection();
        election.setStart(DateUtil.getCalendarInstance().getTime());
        election.setSeats(seats);

        senateElectionDAO.save(election);

        logService.logSystemAction("Democracy", "Senate election started with " + seats + " available seats");

        return election;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveChancellorElection(com.tysanclan.site.projectewok.entities.ChancellorElection)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveChancellorElection(ChancellorElection _election) {
        ChancellorElection election = chancellorElectionDAO.load(_election.getId());

        Map<User, Integer> scores = determineUserScoreTotals(election);

        Set<User> winners = determineBordaWinners(scores, 1);
        if (winners.isEmpty()) {
            // Restart election
            election.setStart(new Date());
            chancellorElectionDAO.update(election);

            logService.logSystemAction("Democracy", "Chancellor election restarted due to lack of winners");
        } else {
            election.setWinner(winners.iterator().next());
            chancellorElectionDAO.update(election);

            UserFilter filter = new UserFilter();
            filter.addRank(Rank.CHANCELLOR);

            List<User> chancellors = userDAO.findByFilter(filter);
            // Should only be 0 or 1, but hey, who knows!
            for (User chancellor : chancellors) {
                chancellor.setRank(MemberUtil.determineRankByJoinDate(chancellor.getJoinDate()));
                if (!election.getWinner().equals(chancellor)) {
                    logService.logUserAction(chancellor, "Election",
                            "Has not been reelected as Chancellor, and has assumed the rank of "
                                    + chancellor.getRank().toString());
                    notificationService.notifyUser(chancellor, "You were not reelected as Chancellor");
                }

                userDAO.update(chancellor);

            }

            election.getWinner().setRank(Rank.CHANCELLOR);
            userDAO.update(election.getWinner());

            if (chancellors.contains(election.getWinner())) {
                logService.logUserAction(election.getWinner(), "Election", "Has been reelected as Chancellor");
                notificationService.notifyUser(election.getWinner(), "You were reelected as Chancellor");
            } else {
                logService.logUserAction(election.getWinner(), "Election", "Has been elected as Chancellor");
                notificationService.notifyUser(election.getWinner(), "You were elected as Chancellor");
            }
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveGroupLeaderElection(com.tysanclan.site.projectewok.entities.GroupLeaderElection)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveGroupLeaderElection(GroupLeaderElection _election) {
        GroupLeaderElection election = groupLeaderElectionDAO.load(_election.getId());

        Map<User, Integer> scores = determineUserScoreTotals(election);

        Set<User> winners = determineBordaWinners(scores, 1);

        if (winners.isEmpty()) {
            // Restart election
            election.setStart(new Date());
            groupLeaderElectionDAO.update(election);

            logService.logSystemAction("Democracy", "Group leader election for " + election.getGroup().getName()
                    + " restarted due to lack of winners");
        } else {
            election.setWinner(winners.iterator().next());

            groupLeaderElectionDAO.update(election);

            election.getGroup().setLeader(election.getWinner());

            logService.logUserAction(election.getWinner(), "Election",
                    "Has become the new leader of " + election.getGroup().getName());
            notificationService.notifyUser(election.getWinner(),
                    "You were elected as leader of " + election.getGroup().getName());

            groupDAO.update(election.getGroup());
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    protected Set<User> determineBordaWinners(final Map<User, Integer> scores, int amount) {
        List<User> winners = new ArrayList<User>(scores.size());
        Set<User> result = new HashSet<User>();

        winners.addAll(scores.keySet());

        Collections.sort(winners, new Comparator<User>() {

            @Override
            public int compare(User o1, User o2) {

                int diff = scores.get(o2).compareTo(scores.get(o1));

                if (diff == 0) {
                    return o1.getJoinDate().compareTo(o2.getJoinDate());
                }

                return diff;
            }

        });

        for (int i = 0; i < amount; i++) {
            if (i < winners.size()) {
                result.add(winners.get(i));
            }
        }

        return result;
    }

    /**
     * Counts the total score gained by each candidate in an election
     * 
     * @param election
     *            The election to count the score for
     * @return A data structure mapping each user to his total score
     */
    @Transactional(propagation = Propagation.REQUIRED)
    protected Map<User, Integer> determineUserScoreTotals(Election election) {
        // Election election = electionDAO.load(_election
        // .getId());

        Set<CompoundVote> votes = election.getVotes();
        Map<User, Integer> scores = new HashMap<User, Integer>();

        for (CompoundVote vote : votes) {
            for (CompoundVoteChoice choice : vote.getChoices()) {
                User votesFor = choice.getVotesFor();

                if (!election.getEligibility().isEligible(votesFor)) {
                    continue;
                }

                int score = choice.getScore();
                if (!scores.containsKey(votesFor)) {
                    scores.put(votesFor, score);
                } else {
                    scores.put(votesFor, score + scores.get(votesFor));
                }

            }
        }
        return scores;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveSenateElection(com.tysanclan.site.projectewok.entities.SenateElection)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveSenateElection(SenateElection _election) {
        SenateElection election = senateElectionDAO.load(_election.getId());

        if (!election.getWinners().isEmpty())
            return;

        int seats = election.getSeats();
        int candidateCount = election.getCandidates().size();

        if (seats >= candidateCount) {
            seats = Math.max(2, candidateCount - 1);

            logService.logSystemAction("Democracy",
                    "Available Senate seats adjusted to " + seats + " due to lack of candidates");
        }

        Map<User, Integer> scores = determineUserScoreTotals(election);

        Set<User> winners = determineBordaWinners(scores, seats);

        if (!winners.isEmpty()) {
            StringBuilder winnerString = new StringBuilder();
            winnerString.append("New Senate: ");
            int i = 0;
            for (User winner : winners) {
                if (i++ > 0) {
                    winnerString.append(", ");
                }

                winnerString.append(winner.getUsername());
            }

            election.setWinners(winners);
            senateElectionDAO.update(election);

            UserFilter filter = new UserFilter();
            filter.addRank(Rank.SENATOR);

            List<User> senators = userDAO.findByFilter(filter);
            for (User senator : senators) {
                senator.setRank(MemberUtil.determineRankByJoinDate(senator.getJoinDate()));
                if (!winners.contains(senator)) {
                    logService.logUserAction(senator, "Election",
                            "Has not been reelected as Senator, and has assumed the rank of "
                                    + senator.getRank().toString());
                    notificationService.notifyUser(senator, "You were not reelected as Senator");
                } else {
                    logService.logUserAction(senator, "Election", "Has been reelected as Senator");
                    notificationService.notifyUser(senator, "You were reelected as Senator");
                }

                userDAO.update(senator);
            }

            for (User senator : election.getWinners()) {
                senator.setRank(Rank.SENATOR);
                userDAO.update(senator);

                if (!senators.contains(senator)) {
                    logService.logUserAction(senator, "Election", "Has been elected as Senator");
                    notificationService.notifyUser(senator, "You were elected as Senator");
                }
            }
        } else {
            election.setStart(new Date());
            senateElectionDAO.update(election);

            logService.logSystemAction("Democracy", "Senate election restarted due to lack of winners");
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void checkAcceptanceVote(User user) {
        User _user = userDAO.load(user.getId());

        AcceptanceVoteFilter filter = new AcceptanceVoteFilter();
        filter.setTrialMember(user);

        long count = acceptanceVoteDAO.countByFilter(filter);

        if (count == 0) {
            AcceptanceVote acceptanceVote = new AcceptanceVote();
            acceptanceVote.setTrialMember(_user);
            acceptanceVote.setStart(new Date());
            acceptanceVoteDAO.save(acceptanceVote);

            logService.logUserAction(_user, "Membership", "Acceptance vote has started");
            notificationService.notifyUser(_user, "Your acceptance vote has started");
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveAcceptanceVote(AcceptanceVote _vote) {
        AcceptanceVote vote = acceptanceVoteDAO.load(_vote.getId());

        User user = vote.getTrialMember();

        int inFavor = 0, total = 0;

        for (AcceptanceVoteVerdict verdict : vote.getVerdicts()) {
            if (MemberUtil.isMember(verdict.getCaster())) {
                if (verdict.isInFavor())
                    inFavor++;
                total++;
            }
        }

        Integer factor = total > 0 ? (100 * inFavor) / total : null;

        boolean accepted = factor != null && factor >= MEMBERSHIP_ACCEPTANCE_VOTE_PERCENTAGE_REQUIRED;

        String body = mailService.getAcceptanceVoteNotificationMail(user, accepted, inFavor, total);

        mailService.sendHTMLMail(user.getEMail(), "Your Tysan Clan trial period is over", body);

        user.setRank(accepted ? Rank.JUNIOR_MEMBER : Rank.FORUM);

        if (accepted) {
            notificationService.notifyUser(user, "You are now a Junior Member");

            dispatcher.dispatchEvent(new MemberStatusEvent(
                    com.tysanclan.site.projectewok.entities.MembershipStatusChange.ChangeType.MEMBERSHIP_GRANTED,
                    user));

        } else {
            dispatcher.dispatchEvent(new MemberStatusEvent(
                    com.tysanclan.site.projectewok.entities.MembershipStatusChange.ChangeType.MEMBERSHIP_DENIED,
                    user));
        }

        user.setMentor(null);

        userDAO.update(user);

        acceptanceVoteDAO.delete(vote);

        logService.logUserAction(user, "Membership", "User has " + (accepted ? "passed" : "failed")
                + " his or her acceptance vote (" + (factor != null ? factor : 0) + "%)");

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveJoinApplication(JoinApplication _application) {
        JoinApplication application = joinApplicationDAO.load(_application.getId());
        if (application != null) {
            int inFavor = 0, total = 0;
            for (JoinVerdict verdict : application.getVerdicts()) {
                if (verdict.isInFavor()) {
                    inFavor++;
                }
                total++;
            }

            final User mentor = application.getMentor();
            final User applicant = application.getApplicant();
            Date startDate = application.getStartDate();
            LocalDate start = new LocalDate(startDate);
            LocalDate now = LocalDate.now();

            final boolean have3DaysPassed = start.plusDays(3).isBefore(now);

            boolean accepted = false;

            if (have3DaysPassed) {
                if (applicant.getActivations().isEmpty()) {
                    // If we managed to let 3 days pass without the member
                    // getting
                    // accepted,
                    // check for mentor is no longer relevant, all we need to
                    // know
                    // is if a Senator
                    // voted against
                    if (total == 0) {
                        accepted = true;
                    } else {
                        if (inFavor == 0) {
                            accepted = false;
                        } else {
                            accepted = true;
                        }
                    }
                } else {
                    accepted = false;
                }
            } else {
                // This really shouldn't happen, but don't resolve unactivated
                // users prior to 3 days
                if (!applicant.getActivations().isEmpty()) {
                    return;
                }

                // If this method gets invoked earlier, however, then member
                // needs a mentor and 1
                // vote in favor - otherwise do not resolve
                if (inFavor > 0 && mentor != null) {
                    accepted = true;
                } else {
                    return;
                }
            }

            if (accepted) {
                applicant.setMentor(application.getMentor());
                applicant.setRank(Rank.TRIAL);
                applicant.setJoinDate(new Date());
                applicant.setLoginCount(0);
                applicant.setLastAction(new Date());
                userDAO.update(applicant);

            }

            joinVerdictDAO.deleteForApplication(application);
            joinApplicationDAO.delete(application);

            mailService.sendHTMLMail(applicant.getEMail(), "Result of your Tysan Clan Application",
                    mailService.getJoinApplicationMail(application, accepted, inFavor, total));

            logService.logUserAction(applicant, "Membership",
                    "User has " + (accepted ? "been" : "not been") + " granted a trial membership");

            if (accepted) {
                notificationService.notifyUser(applicant, "You are now a Trial Member");

                dispatcher.dispatchEvent(new MemberStatusEvent(
                        com.tysanclan.site.projectewok.entities.MembershipStatusChange.ChangeType.TRIAL_GRANTED,
                        applicant));
            } else {
                dispatcher.dispatchEvent(new MemberStatusEvent(
                        com.tysanclan.site.projectewok.entities.MembershipStatusChange.ChangeType.TRIAL_DENIED,
                        applicant));
            }
        }

    }

    /**
     * @param chancellorElectionDAO
     *            the chancellorElectionDAO to set
     */
    public void setChancellorElectionDAO(ChancellorElectionDAO chancellorElectionDAO) {
        this.chancellorElectionDAO = chancellorElectionDAO;
    }

    /**
     * @param senateElectionDAO
     *            the senateElectionDAO to set
     */
    public void setSenateElectionDAO(SenateElectionDAO senateElectionDAO) {
        this.senateElectionDAO = senateElectionDAO;
    }

    /**
     * @param groupLeaderElectionDAO
     *            the groupLeaderElectionDAO to set
     */
    public void setGroupLeaderElectionDAO(GroupLeaderElectionDAO groupLeaderElectionDAO) {
        this.groupLeaderElectionDAO = groupLeaderElectionDAO;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#getCurrentChancellorElection()
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public ChancellorElection getCurrentChancellorElection() {
        ChancellorElectionFilter filter = new ChancellorElectionFilter();
        Calendar twoWeeksAgo = DateUtil.getCalendarInstance();
        twoWeeksAgo.add(Calendar.WEEK_OF_YEAR, -2);

        filter.setStartBefore(new Date());
        filter.setStartAfter(twoWeeksAgo.getTime());

        List<ChancellorElection> elections = chancellorElectionDAO.findByFilter(filter);

        if (elections.size() > 0) {
            return elections.get(0);
        }

        return null;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#getCurrentSenateElection()
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public SenateElection getCurrentSenateElection() {
        SenateElectionFilter filter = new SenateElectionFilter();
        Calendar twoWeeksAgo = DateUtil.getCalendarInstance();
        twoWeeksAgo.add(Calendar.WEEK_OF_YEAR, -2);

        filter.setStartBefore(new Date());
        filter.setStartAfter(twoWeeksAgo.getTime());

        List<SenateElection> elections = senateElectionDAO.findByFilter(filter);

        if (elections.size() > 0) {
            return elections.get(0);
        }

        return null;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#isElectionCandidate(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public boolean isElectionCandidate(User user) {
        SenateElection sElection = getCurrentSenateElection();

        boolean res = false;

        if (sElection != null) {
            res |= sElection.getCandidates().contains(user);
        }

        ChancellorElection cElection = getCurrentChancellorElection();

        if (cElection != null) {
            res |= cElection.getCandidates().contains(user);
        }

        return res;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#getRequiredChancellorEndorsements()
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public long getRequiredChancellorEndorsements() {
        long memberCount = userService.countMembers();
        long required = (5 * memberCount) / 100;

        if (required == 0) {
            required = 1;
        }

        return required;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#getRequiredSenateEndorsements()
     */
    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public long getRequiredSenateEndorsements() {
        long memberCount = userService.countMembers();
        long required = (3 * memberCount) / 100;

        if (required == 0) {
            required = 1;
        }

        return required;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void endorseUserForChancellor(User endorser, User target) {
        User _endorser = userDAO.load(endorser.getId());
        User _target = target != null ? userDAO.load(target.getId()) : null;

        User _current = _endorser.getEndorses();

        if (_current != null) {
            // Can not change endorsements of running candidates
            if (isElectionCandidate(_current)) {
                return;
            }
        }

        if (MemberUtil.canUserGrantEndorsement(_endorser)
                && (_target == null || MemberUtil.canUserGrantEndorsement(target))) {
            _endorser.setEndorses(_target);
            userDAO.update(_endorser);

            if (_target != null) {
                logService.logUserAction(_endorser, "Democracy",
                        "User has endorsed " + _target.getUsername() + " for Chancellor");
                notificationService.notifyUser(_target,
                        "You are endorsed for Chancellor by " + _endorser.getUsername());
            } else {
                logService.logUserAction(_endorser, "Democracy",
                        "User is no longer endorsing any member for Chancellor");
            }
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void endorseUserForSenate(User endorser, User target) {
        User _endorser = userDAO.load(endorser.getId());
        User _target = target != null ? userDAO.load(target.getId()) : null;

        User _current = _endorser.getEndorsesForSenate();

        if (_current != null) {
            // Can not change endorsements of running candidates
            if (isElectionCandidate(_current)) {
                return;
            }
        }

        if (MemberUtil.canUserGrantEndorsement(_endorser)
                && (_target == null || MemberUtil.canUserGrantEndorsement(target))) {
            _endorser.setEndorsesForSenate(_target);
            userDAO.update(_endorser);

            if (_target != null) {
                logService.logUserAction(_endorser, "Democracy",
                        "User has endorsed " + _target.getUsername() + " for Senator");
                notificationService.notifyUser(_target,
                        "You are endorsed for Senator by " + _endorser.getUsername());
            } else {
                logService.logUserAction(_endorser, "Democracy",
                        "User is no longer endorsing any member for Senator");
            }
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#addChancellorCandidate(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean addChancellorCandidate(User user) {
        ChancellorElection election = getCurrentChancellorElection();

        if (!user.isRetired() && election != null) {
            if (user.getEndorsedBy().size() >= getRequiredChancellorEndorsements()
                    || user.hasDonatedAtLeast(new BigDecimal(30))) {
                if (!isElectionCandidate(user) && election.isNominationOpen()) {
                    election.getCandidates().add(user);
                    chancellorElectionDAO.update(election);

                    logService.logUserAction(user, "Democracy", "Has decided to run for Chancellor!");

                    return true;
                }
            }
        }

        return false;

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#addSenateCandidate(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean addSenateCandidate(User user) {
        SenateElection election = getCurrentSenateElection();

        if (!user.isRetired() && election != null) {
            if (user.getEndorsedForSenateBy().size() >= getRequiredSenateEndorsements()
                    || user.hasDonatedAtLeast(new BigDecimal(20))) {
                if (!isElectionCandidate(user) && election.isNominationOpen()) {
                    election.getCandidates().add(user);
                    senateElectionDAO.update(election);

                    logService.logUserAction(user, "Democracy", "Has decided to run for Senator!");

                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#addGroupLeaderCandidate(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.GroupLeaderElection)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public boolean addGroupLeaderCandidate(User user, GroupLeaderElection election) {
        GroupLeaderElection _election = groupLeaderElectionDAO.load(election.getId());
        User _user = userDAO.load(user.getId());

        if (_election != null) {
            if (!_election.getCandidates().contains(_user) && _election.isNominationOpen()) {
                _election.getCandidates().add(_user);
                groupLeaderElectionDAO.update(_election);
                groupLeaderElectionDAO.flush();

                return true;
            }
        }

        return false;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createVote(com.tysanclan.site.projectewok.entities.User,
     *      java.util.List, com.tysanclan.site.projectewok.entities.Election)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public CompoundVote createVote(User caster, List<User> votesFor, Election election) {
        CompoundVote vote = new CompoundVote();
        vote.setCaster(caster);
        vote.setElection(election);
        compoundVoteDAO.save(vote);

        int score = votesFor.size() - 1;

        for (User user : votesFor) {
            CompoundVoteChoice choice = new CompoundVoteChoice();
            choice.setCompoundVote(vote);
            choice.setVotesFor(user);
            choice.setScore(score--);
            compoundVoteChoiceDAO.save(choice);

        }
        compoundVoteDAO.update(vote);

        return vote;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#castAcceptanceVote(com.tysanclan.site.projectewok.entities.AcceptanceVote,
     *      com.tysanclan.site.projectewok.entities.User, boolean)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void castAcceptanceVote(AcceptanceVote vote, User caster, boolean inFavor) {
        AcceptanceVote _vote = acceptanceVoteDAO.load(vote.getId());
        User _caster = userDAO.load(caster.getId());

        AcceptanceVoteVerdict myVerdict = null;

        for (AcceptanceVoteVerdict v : _vote.getVerdicts()) {
            if (v.getCaster().equals(_caster)) {
                myVerdict = v;
                break;
            }
        }

        if (myVerdict == null) {
            myVerdict = new AcceptanceVoteVerdict();
            myVerdict.setCaster(_caster);
            myVerdict.setVote(_vote);
            myVerdict.setInFavor(inFavor);

            acceptanceVoteVerdictDAO.save(myVerdict);
            acceptanceVoteDAO.evict(_vote);

        } else {
            myVerdict.setInFavor(inFavor);
            acceptanceVoteVerdictDAO.update(myVerdict);
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createImpeachmentVote(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void createImpeachmentVote(User initiator) {
        UserFilter filter = new UserFilter();
        filter.addRank(Rank.CHANCELLOR);
        List<User> chancellors = userDAO.findByFilter(filter);
        if (!chancellors.isEmpty()) {
            User chancellor = chancellors.get(0);

            Impeachment impeachment = new Impeachment();
            impeachment.setChancellor(chancellor);
            impeachment.setInitiator(initiator);
            impeachment.setStart(new Date());

            impeachmentDAO.save(impeachment);

            logService.logUserAction(initiator, "Democracy",
                    "An impeachment vote against the Chancellor has been started");
            notificationService.notifyUser(chancellor,
                    "An impeachment vote against you was started by " + initiator.getUsername());

        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#castImpeachmentVote(com.tysanclan.site.projectewok.entities.User,
     *      boolean)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void castImpeachmentVote(User voter, boolean inFavor) {
        User _voter = userDAO.load(voter.getId());

        List<Impeachment> impeachments = impeachmentDAO.findAll();

        if (!impeachments.isEmpty()) {
            Impeachment impeachment = impeachments.get(0);

            ImpeachmentVote vote = null;

            for (ImpeachmentVote next : impeachment.getVotes()) {
                if (next.getCaster().equals(_voter)) {
                    vote = next;
                    break;
                }
            }

            if (vote == null) {
                vote = new ImpeachmentVote();
                vote.setImpeachment(impeachment);
                vote.setInFavor(inFavor);
                vote.setCaster(_voter);

                impeachmentVoteDAO.save(vote);
            } else {
                vote.setInFavor(inFavor);

                impeachmentVoteDAO.update(vote);
            }
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveImpeachment()
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveImpeachment() {
        List<Impeachment> impeachments = impeachmentDAO.findAll();

        if (!impeachments.isEmpty()) {
            Calendar calendar = DateUtil.getCalendarInstance();
            calendar.add(Calendar.DAY_OF_MONTH, -4);

            Impeachment impeachment = impeachments.get(0);

            if (impeachment.getStart().before(calendar.getTime())) {

                int inFavor = 0;
                int total = impeachment.getVotes().size();

                User chancellor = impeachment.getChancellor();

                for (ImpeachmentVote vote : impeachment.getVotes()) {
                    if (vote.isInFavor()) {
                        inFavor++;
                    }
                }

                int percentage = (100 * inFavor) / total;

                if (percentage >= 66) {
                    notificationService.notifyUser(chancellor, "You have been impeached");

                    logService.logUserAction(chancellor, "Democracy",
                            "Chancellor has been impeached (" + percentage + ")");

                    chancellor.setRank(MemberUtil.determineRankByJoinDate(chancellor.getJoinDate()));
                    userDAO.update(chancellor);
                }

                impeachmentDAO.delete(impeachment);
            }
        }
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#stepDownAsChancellor(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void stepDownAsChancellor(User chancellor) {
        User user = userDAO.load(chancellor.getId());

        user.setRank(MemberUtil.determineRankByJoinDate(user.getJoinDate()));

        logService.logUserAction(user, "Democracy", "User has stepped down as Chancellor");

        userDAO.update(user);

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#stepDownAsSenator(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void stepDownAsSenator(User senator) {
        User user = userDAO.load(senator.getId());

        user.setRank(MemberUtil.determineRankByJoinDate(user.getJoinDate()));

        logService.logUserAction(user, "Democracy", "User has stepped down as Senator");

        userDAO.update(user);
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#stepDownAsTruthsayer(com.tysanclan.site.projectewok.entities.User)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void stepDownAsTruthsayer(User truthsayer) {
        User user = userDAO.load(truthsayer.getId());

        user.setRank(MemberUtil.determineRankByJoinDate(user.getJoinDate()));

        logService.logUserAction(user, "Democracy", "User has stepped down as Truthsayer");

        userDAO.update(user);
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#castUntenabilityVote(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.UntenabilityVote, boolean)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void castUntenabilityVote(User user, UntenabilityVote vote, boolean inFavor) {
        UntenabilityVote _vote = untenabilityVoteDAO.load(vote.getId());
        User _user = userDAO.load(user.getId());

        UntenabilityVoteChoice myChoice = null;

        for (UntenabilityVoteChoice choice : _vote.getChoices()) {
            if (choice.getCaster().equals(_user)) {
                myChoice = choice;
                break;
            }
        }

        if (myChoice == null) {
            myChoice = new UntenabilityVoteChoice();
            myChoice.setCaster(_user);
            myChoice.setInFavor(inFavor);
            myChoice.setVote(_vote);

            untenabilityVoteChoiceDAO.save(myChoice);
        } else {
            myChoice.setInFavor(inFavor);
            untenabilityVoteChoiceDAO.update(myChoice);
        }
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createUntenabilityVote(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.Regulation)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void createUntenabilityVote(User user, Regulation regulation) {
        Regulation _regulation = regulationDAO.load(regulation.getId());
        User _user = userDAO.load(user.getId());

        UntenabilityVoteFilter filter = new UntenabilityVoteFilter();
        filter.setRegulation(_regulation);

        if (untenabilityVoteDAO.countByFilter(filter) == 0) {
            UntenabilityVote vote = new UntenabilityVote();
            vote.setRegulation(_regulation);
            vote.setStart(new Date());

            untenabilityVoteDAO.save(vote);

            logService.logUserAction(_user, "Democracy",
                    "Regulation " + _regulation.getName() + " declared untenable, vote started");
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveUntenabilityVote(com.tysanclan.site.projectewok.entities.UntenabilityVote)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveUntenabilityVote(UntenabilityVote vote) {
        UntenabilityVote _vote = untenabilityVoteDAO.load(vote.getId());

        int inFavor = 0;
        int total = _vote.getChoices().size();

        Regulation regulation = _vote.getRegulation();

        for (UntenabilityVoteChoice choice : _vote.getChoices()) {
            if (choice.isInFavor()) {
                inFavor++;
            }
        }

        if (total > 0) {

            int percentage = (100 * inFavor) / total;

            if (percentage >= 51) {
                logService.logSystemAction("Democracy", "Regulation " + regulation.getName()
                        + " was repealled by untenability vote (" + percentage + "%)");

                regulationDAO.delete(regulation);

            } else {
                logService.logSystemAction("Democracy", "Regulation " + regulation.getName()
                        + " was maintained by untenability vote (" + percentage + "%)");
            }
        }

        untenabilityVoteDAO.delete(_vote);

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createAddRegulationVote(com.tysanclan.site.projectewok.entities.User,
     *      java.lang.String, java.lang.String)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public RegulationChange createAddRegulationVote(User user, String title, String description) {
        User _user = userDAO.load(user.getId());

        RegulationChange change = new RegulationChange();
        change.setChangeType(ChangeType.ADD);
        change.setTitle(BBCodeUtil.stripTags(title));
        change.setDescription(BBCodeUtil.stripTags(description));
        change.setStart(new Date());
        change.setVeto(false);
        change.setRegulation(null);
        change.setProposer(_user);

        regulationChangeDAO.save(change);

        return change;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createModifyRegulationVote(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.Regulation,
     *      java.lang.String, java.lang.String)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public RegulationChange createModifyRegulationVote(User user, Regulation regulation, String newTitle,
            String newDescription) {
        User _user = userDAO.load(user.getId());
        Regulation _regulation = regulationDAO.load(regulation.getId());

        RegulationChange change = new RegulationChange();
        change.setChangeType(ChangeType.MODIFY);
        change.setTitle(BBCodeUtil.stripTags(newTitle));
        change.setDescription(BBCodeUtil.stripTags(newDescription));
        change.setStart(new Date());
        change.setVeto(false);
        change.setRegulation(_regulation);
        change.setProposer(_user);

        regulationChangeDAO.save(change);

        return change;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#createRepealRegulationVote(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.Regulation)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public RegulationChange createRepealRegulationVote(User user, Regulation regulation) {
        User _user = userDAO.load(user.getId());
        Regulation _regulation = regulationDAO.load(regulation.getId());

        RegulationChange change = new RegulationChange();
        change.setChangeType(ChangeType.REPEAL);
        change.setVeto(false);
        change.setStart(new Date());
        change.setRegulation(_regulation);
        change.setProposer(_user);

        regulationChangeDAO.save(change);

        return change;
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#resolveRegulationVote(com.tysanclan.site.projectewok.entities.RegulationChange)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveRegulationVote(RegulationChange change) {
        RegulationChange _change = regulationChangeDAO.load(change.getId());

        UserFilter filter = new UserFilter();
        filter.addRank(Rank.SENATOR);

        long total = _change.isVeto() ? userDAO.countByFilter(filter) : _change.getVotes().size();
        int required_factor = _change.isVeto() ? 66 : 51;

        int inFavor = 0;

        for (RegulationChangeVote vote : _change.getVotes()) {
            if (vote.isInFavor()) {
                inFavor++;
            }
        }

        long factor = (100 * inFavor) / total;

        if (factor >= required_factor) {
            // Execute regulation change
            switch (_change.getChangeType()) {
            case ADD:
                createRegulation(_change, factor);
                break;
            case MODIFY:
                modifyRegulation(_change, factor);
                break;
            case REPEAL:
                repealRegulation(_change, factor);
                break;
            }
        } else {
            switch (_change.getChangeType()) {
            case ADD:
                logService.logSystemAction("Regulations",
                        "The proposed regulation " + _change.getTitle() + " did not pass (" + factor + "%)");
                break;
            case MODIFY:
                logService.logSystemAction("Regulations", "The proposed modifications to regulation "
                        + _change.getTitle() + " did not pass (" + factor + "%)");
                break;
            case REPEAL:
                logService.logSystemAction("Regulations", "The proposed repeal of regulation " + _change.getTitle()
                        + " did not pass (" + factor + "%)");
                break;
            }
        }

        for (RegulationChangeVote rcv : _change.getVotes()) {
            regulationChangeVoteDAO.delete(rcv);
        }

        regulationChangeDAO.delete(_change);

    }

    @Transactional(propagation = Propagation.REQUIRED)
    private void repealRegulation(RegulationChange _change, long factor) {
        Regulation regulation = _change.getRegulation();

        logService.logSystemAction("Regulations", "The Senate has voted to repeal the Regulation named "
                + regulation.getName() + " (" + factor + "%)");
        regulationDAO.delete(regulation);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    private void modifyRegulation(RegulationChange _change, long factor) {
        Regulation regulation = _change.getRegulation();

        logService.logSystemAction("Regulations", "The Senate has voted to modify the Regulation named "
                + regulation.getName() + " (" + factor + "%)");

        regulation.setContents(_change.getDescription());
        regulation.setName(_change.getTitle());
        regulationDAO.update(regulation);

    }

    @Transactional(propagation = Propagation.REQUIRED)
    private void createRegulation(RegulationChange _change, long factor) {
        Regulation regulation = new Regulation();

        logService.logSystemAction("Regulations", "The Senate has voted to create a new Regulation named "
                + _change.getTitle() + " (" + factor + "%)");

        regulation.setContents(_change.getDescription());
        regulation.setDrafter(_change.getProposer());
        regulation.setName(_change.getTitle());
        regulationDAO.save(regulation);
    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#voteForRegulation(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.RegulationChange, boolean)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void voteForRegulation(User senator, RegulationChange change, boolean inFavor) {
        RegulationChange _change = regulationChangeDAO.load(change.getId());

        RegulationChangeVote myVote = null;

        for (RegulationChangeVote vote : _change.getVotes()) {
            if (vote.getSenator().equals(senator)) {
                myVote = vote;
            }
        }

        if (myVote == null) {
            myVote = new RegulationChangeVote();
            myVote.setInFavor(inFavor);
            myVote.setRegulationChange(_change);
            myVote.setSenator(senator);
            regulationChangeVoteDAO.save(myVote);
            regulationChangeDAO.evict(_change);
        } else {
            myVote.setInFavor(inFavor);
            regulationChangeVoteDAO.update(myVote);
        }

    }

    /**
     * @see com.tysanclan.site.projectewok.beans.DemocracyService#vetoRegulationChange(com.tysanclan.site.projectewok.entities.User,
     *      com.tysanclan.site.projectewok.entities.RegulationChange)
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void vetoRegulationChange(User chancellor, RegulationChange change) {
        RegulationChange _change = regulationChangeDAO.load(change.getId());

        UserFilter filter = new UserFilter();
        filter.addRank(Rank.SENATOR);

        String status;

        if (userDAO.countByFilter(filter) < 2 || _change.getProposer().equals(chancellor)) {
            regulationChangeDAO.delete(_change);

            status = " was vetoed and automatically withdrawn";
        } else {
            _change.setVeto(true);

            regulationChangeDAO.update(_change);

            status = " was vetoed. A 66% Senate majority is now required";
        }

        switch (_change.getChangeType()) {
        case ADD:
            logService.logUserAction(chancellor, "Regulations",
                    "The proposed regulation " + _change.getTitle() + status);
            break;
        case MODIFY:
            logService.logUserAction(chancellor, "Regulations",
                    "The proposed modifications to regulation " + _change.getTitle() + status);
            break;
        case REPEAL:
            logService.logUserAction(chancellor, "Regulations",
                    "The proposed repeal of regulation " + _change.getTitle() + status);
            break;
        }

    }

    public static int calculateSenateSeats(long memberCount) {
        return (int) Math.max(2, 1 + (memberCount / 20));
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void checkAcceptanceVotes() {

        for (User user : userDAO.getTrialMembersReadyForVote()) {
            checkAcceptanceVote(user);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveAcceptanceVotes() {
        Calendar threeDaysAgo = DateUtil.getCalendarInstance();

        threeDaysAgo.add(Calendar.DAY_OF_YEAR, -3);

        AcceptanceVoteFilter filter = new AcceptanceVoteFilter();
        filter.setStartBefore(threeDaysAgo.getTime());

        List<AcceptanceVote> eligibles = acceptanceVoteDAO.findByFilter(filter);
        for (AcceptanceVote vote : eligibles) {
            resolveAcceptanceVote(vote);
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void checkChancellorElections() {
        Calendar calendar = DateUtil.getCalendarInstance();
        calendar.add(Calendar.MONTH, -6);

        ChancellorElectionFilter electionsLessThanSixMonthsAgo = new ChancellorElectionFilter();
        electionsLessThanSixMonthsAgo.setStartAfter(calendar.getTime());
        electionsLessThanSixMonthsAgo.addOrderBy("start", false);

        ChancellorElection current = getCurrentChancellorElection();

        UserFilter chancellorFilter = new UserFilter();
        chancellorFilter.addRank(Rank.CHANCELLOR);

        boolean noChancellor = userDAO.countByFilter(chancellorFilter) == 0;
        boolean noElectionInPastSixMonths = chancellorElectionDAO.countByFilter(electionsLessThanSixMonthsAgo) == 0;
        boolean noElectionCurrently = current == null;

        if ((noElectionCurrently && noChancellor) || (noElectionInPastSixMonths && noElectionCurrently)) {
            createChancellorElection();
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveChancellorElections() {
        ChancellorElectionFilter filter = new ChancellorElectionFilter();
        Calendar cal = DateUtil.getCalendarInstance();
        cal.add(Calendar.WEEK_OF_YEAR, -2);
        filter.setStartBefore(cal.getTime());
        filter.setNoWinner(true);
        List<ChancellorElection> elections = chancellorElectionDAO.findByFilter(filter);
        for (ChancellorElection election : elections) {
            resolveChancellorElection(election);
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void checkSenatorElections() {
        final SenateElection current = getCurrentSenateElection();

        if (current != null)
            return;

        LocalDate sixMonthsAgo = LocalDate.now().minusMonths(6);

        SenateElectionFilter electionsMoreThanSixMonthsAgo = new SenateElectionFilter();
        electionsMoreThanSixMonthsAgo.setStartBefore(sixMonthsAgo.toDate());
        electionsMoreThanSixMonthsAgo.addOrderBy("start", false);

        SenateElectionFilter electionsLessThanSixMonthsAgo = new SenateElectionFilter();
        electionsLessThanSixMonthsAgo.setStartAfter(sixMonthsAgo.toDate());
        electionsLessThanSixMonthsAgo.addOrderBy("start", false);

        final boolean thereWasAnElectionMoreThanSixMonthsAgo = senateElectionDAO
                .countByFilter(electionsMoreThanSixMonthsAgo) > 0;
        final boolean thereWasNoElectionLessThanSixMonthsAgo = senateElectionDAO
                .countByFilter(electionsLessThanSixMonthsAgo) == 0;
        final boolean thereHasNeverBeenAnElection = senateElectionDAO.countAll() == 0;
        if (thereHasNeverBeenAnElection
                || (thereWasAnElectionMoreThanSixMonthsAgo && thereWasNoElectionLessThanSixMonthsAgo)) {
            if (thereHasNeverBeenAnElection) {
                logService.logSystemAction("Democracy", "There has never been a Senate election");
            } else {
                logService.logSystemAction("Democracy", "Last Senate election more than six months ago");
            }
            createSenateElection();
        } else {

            SenateElectionFilter lastSenateElectionFilter = new SenateElectionFilter();
            lastSenateElectionFilter.addOrderBy("start", false);
            List<SenateElection> elections = senateElectionDAO.findByFilter(lastSenateElectionFilter, 0, 1);

            final long senators = userDAO.countByRank(Rank.SENATOR);

            if (senators <= 1) {
                logService.logSystemAction("Democracy", "Only one Senator left");
                createSenateElection();
                return;
            }

            for (SenateElection election : elections) {
                final int seats = election.getSeats();

                if (seats > 0) {

                    int fraction = (int) ((senators * 100) / seats);

                    if (fraction < 40) {
                        logService.logSystemAction("Democracy",
                                "Senate size less than 40% of last election's seats");
                        createSenateElection();
                    }
                }
                break;

            }
        }

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveSenatorElections() {
        SenateElectionFilter filter = new SenateElectionFilter();
        Calendar twoWeeksAgo = DateUtil.getCalendarInstance();
        Calendar threeWeeksAgo = DateUtil.getCalendarInstance();
        twoWeeksAgo.add(Calendar.WEEK_OF_YEAR, -2);
        threeWeeksAgo.add(Calendar.WEEK_OF_YEAR, -3);
        filter.setStartBefore(twoWeeksAgo.getTime());
        filter.setStartAfter(threeWeeksAgo.getTime());
        List<SenateElection> elections = senateElectionDAO.findByFilter(filter);
        for (SenateElection election : elections) {
            resolveSenateElection(election);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveGroupLeaderElections() {
        GroupLeaderElectionFilter filter = new GroupLeaderElectionFilter();
        Calendar twoWeeksAgo = DateUtil.getCalendarInstance();
        Calendar threeWeeksAgo = DateUtil.getCalendarInstance();
        twoWeeksAgo.add(Calendar.WEEK_OF_YEAR, -2);
        threeWeeksAgo.add(Calendar.WEEK_OF_YEAR, -3);
        filter.setStartBefore(twoWeeksAgo.getTime());
        filter.setStartAfter(threeWeeksAgo.getTime());

        List<GroupLeaderElection> elections = groupLeaderElectionDAO.findByFilter(filter);
        for (GroupLeaderElection election : elections) {
            resolveGroupLeaderElection(election);
        }

    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void resolveJoinApplications() {
        Calendar cal = DateUtil.getCalendarInstance();
        cal.add(Calendar.DAY_OF_YEAR, -3);

        JoinApplicationFilter filter = new JoinApplicationFilter();
        filter.setDateBefore(cal.getTime());

        List<JoinApplication> applications = joinApplicationDAO.findByFilter(filter);
        for (JoinApplication application : applications) {
            resolveJoinApplication(application);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void resolveRegulationVotes() {
        RegulationChangeFilter filter = new RegulationChangeFilter();
        Calendar cal = DateUtil.getMidnightCalendarInstance();
        cal.add(Calendar.WEEK_OF_YEAR, -1);
        filter.setStartBefore(cal.getTime());

        for (RegulationChange change : regulationChangeDAO.findByFilter(filter)) {
            resolveRegulationVote(change);
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void resolveUntenabilityVotes() {
        Calendar cal = DateUtil.getCalendarInstance();
        cal.add(Calendar.WEEK_OF_YEAR, 1);

        UntenabilityVoteFilter filter = new UntenabilityVoteFilter();
        filter.setStartBefore(cal.getTime());

        List<UntenabilityVote> votes = untenabilityVoteDAO.findByFilter(filter);

        for (UntenabilityVote vote : votes) {
            resolveUntenabilityVote(vote);
        }

    }
}