de.appsolve.padelcampus.admin.controller.events.AdminEventsController.java Source code

Java tutorial

Introduction

Here is the source code for de.appsolve.padelcampus.admin.controller.events.AdminEventsController.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package de.appsolve.padelcampus.admin.controller.events;

import de.appsolve.padelcampus.admin.controller.AdminBaseController;
import de.appsolve.padelcampus.constants.Constants;
import de.appsolve.padelcampus.constants.EventType;
import de.appsolve.padelcampus.constants.Gender;
import de.appsolve.padelcampus.constants.PaymentMethod;
import de.appsolve.padelcampus.data.EventGroups;
import de.appsolve.padelcampus.data.GameData;
import de.appsolve.padelcampus.data.GameList;
import de.appsolve.padelcampus.data.ScoreEntry;
import de.appsolve.padelcampus.db.dao.*;
import de.appsolve.padelcampus.db.dao.generic.BaseEntityDAOI;
import de.appsolve.padelcampus.db.model.*;
import de.appsolve.padelcampus.spring.*;
import de.appsolve.padelcampus.utils.BookingUtil;
import de.appsolve.padelcampus.utils.EventsUtil;
import de.appsolve.padelcampus.utils.GameUtil;
import de.appsolve.padelcampus.utils.RankingUtil;
import org.apache.commons.lang.StringUtils;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.joda.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.*;

import static de.appsolve.padelcampus.utils.FormatUtils.DATE_HUMAN_READABLE_PATTERN;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

/**
 * @author dominik
 */
@Controller()
@RequestMapping("/admin/events")
public class AdminEventsController extends AdminBaseController<Event> {

    private static final Integer ROUND_NONE = null;
    private static final Integer ROUND_ONE = 0;

    @Autowired
    EventDAOI eventDAO;

    @Autowired
    GameDAOI gameDAO;

    @Autowired
    BaseEntityDAOI<Participant> participantDAO;

    @Autowired
    CalendarConfigDAOI calendarConfigDAO;

    @Autowired
    EventGroupDAOI eventGroupDAO;

    @Autowired
    RankingUtil rankingUtil;

    @Autowired
    EventsUtil eventsUtil;

    @Autowired
    GameUtil gameUtil;

    @Autowired
    BookingUtil bookingUtil;

    @Autowired
    PlayerDAOI playerDAO;

    @Autowired
    ParticipantCollectionEditor participantCollectionEditor;

    @Autowired
    CommunitiesCollectionEditor communitiesCollectionEditor;

    @Autowired
    CalendarConfigPropertyEditor calendarConfigPropertyEditor;

    @Autowired
    EventGroupPropertyEditor eventGroupPropertyEditor;

    @Autowired
    PlayerCollectionEditor playerCollectionEditor;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new LocalDateEditor(DATE_HUMAN_READABLE_PATTERN, false));
        binder.registerCustomEditor(Set.class, "communities", communitiesCollectionEditor);
        binder.registerCustomEditor(Set.class, "participants", participantCollectionEditor);
        binder.registerCustomEditor(Set.class, "groupParticipants", participantCollectionEditor);
        binder.registerCustomEditor(Set.class, "team1", playerCollectionEditor);
        binder.registerCustomEditor(Set.class, "team2", playerCollectionEditor);
        binder.registerCustomEditor(CalendarConfig.class, calendarConfigPropertyEditor);
        binder.registerCustomEditor(EventGroup.class, eventGroupPropertyEditor);
    }

    @Override
    public ModelAndView showIndex(HttpServletRequest request, Pageable pageable,
            @RequestParam(required = false, name = "search") String search) {
        return new ModelAndView(getModuleName() + "/index");
    }

    @RequestMapping(method = GET, value = "/{status}")
    public ModelAndView showEvents(HttpServletRequest request, Pageable pageable,
            @RequestParam(required = false, name = "search") String search, @PathVariable("status") String status) {
        Set<Criterion> criterions = new HashSet<>();
        LocalDate today = new LocalDate(Constants.DEFAULT_TIMEZONE);
        if (status.equals("current")) {
            criterions.add(Restrictions.ge("endDate", today));
        } else {
            criterions.add(Restrictions.lt("endDate", today));
        }
        Page<Event> page;
        if (!StringUtils.isEmpty(search)) {
            page = eventDAO.findAllByFuzzySearch(search, criterions, "participants", "participants.players");
        } else {
            page = eventDAO.findAllFetchWithParticipantsAndPlayers(pageable, criterions);
        }
        ModelAndView mav = new ModelAndView(getModuleName() + "/events");
        mav.addObject("Page", page);
        mav.addObject("Models", page.getContent());
        mav.addObject("moduleName", getModuleName());
        mav.addObject("status", status);
        return mav;
    }

    @Override
    protected Event findById(Long modelId) {
        return getDAO().findByIdFetchWithParticipants(modelId);
    }

    @Override
    @RequestMapping(value = { "add", "edit/{modelId}" }, method = POST)
    public ModelAndView postEditView(@ModelAttribute("Model") Event model, HttpServletRequest request,
            BindingResult result) {
        ModelAndView editView = getEditView(model);
        validator.validate(model, result);
        if (result.hasErrors()) {
            return editView;
        }
        try {
            if (model.getId() != null) {

                //prevent removal of a team if it has already played a game
                Event existingEvent = eventDAO.findByIdFetchWithParticipants(model.getId());
                if (!existingEvent.getParticipants().equals(model.getParticipants())) {
                    for (Participant participant : existingEvent.getParticipants()) {
                        if (!model.getParticipants().contains(participant)) {
                            List<Game> existingGames = gameDAO.findByParticipantAndEventWithScoreOnly(participant,
                                    model);
                            if (!existingGames.isEmpty()) {
                                throw new Exception(
                                        msg.get("TeamHasAlreadyPlayedInEvent", new Object[] {
                                                participant.toString(), existingGames.size(), model.toString() }),
                                        null);
                            }
                        }
                    }
                }

                //prevent switching from one event type to another
                if (!existingEvent.getEventType().equals(model.getEventType())) {
                    throw new Exception(msg.get("CannotModifyEventTypeOfExistingEvent"));
                }
            }

            //make sure end date is not before start date
            if (model.getEndDate().isBefore(model.getStartDate())) {
                model.setEndDate(model.getStartDate());
            }

            //if participants can sign up online, make sure price and payment methods are set
            if (model.getAllowSignup()) {
                if (model.getPaymentMethods() == null || model.getPaymentMethods().isEmpty()) {
                    throw new Exception(msg.get("SelectAPaymentMethod"));
                }
                if (model.getPrice() == null) {
                    throw new Exception(msg.get("SetAPrice"));
                }
            }

            switch (model.getEventType()) {

            case SingleRoundRobin:
                model = getDAO().saveOrUpdate(model);

                //remove games that have not been played yet
                gameUtil.removeObsoleteGames(model);

                gameUtil.createMissingGames(model, model.getParticipants());
                return redirectToIndex(request);

            case GroupTwoRounds:
                //remove games that have not been played yet
                gameUtil.removeObsoleteGames(model);
                //no break
            case GroupKnockout:
                model = getDAO().saveOrUpdate(model);
                if (!model.getParticipants().isEmpty()) {
                    return redirectToGroupDraws(model);
                } else {
                    return redirectToIndex(request);
                }

            case Knockout:
                model = getDAO().saveOrUpdate(model);
                if (!model.getParticipants().isEmpty()) {
                    return redirectToDraws(model);
                } else {
                    return redirectToIndex(request);
                }
            case CommunityRoundRobin:
            case PullRoundRobin:
            case FriendlyGames:
                getDAO().saveOrUpdate(model);
                return redirectToIndex(request);
            default:
                throw new Exception("Unsupported event type " + model.getEventType());
            }
        } catch (Exception e) {
            result.addError(new ObjectError("id", e.getMessage()));
            return editView;
        }
    }

    @RequestMapping(value = { "edit/{eventId}/draws" }, method = GET)
    public ModelAndView getDraws(@PathVariable("eventId") Long eventId, HttpServletRequest request) {
        ModelAndView mav = getDrawsView(eventId);
        return mav;
    }

    @RequestMapping(value = { "edit/{eventId}/draws" }, method = POST)
    public ModelAndView postDraws(@PathVariable("eventId") Long eventId) {
        try {
            Event model = eventDAO.findByIdFetchWithParticipants(eventId);

            //prevent udpate operations for existing events
            List<Game> eventGames = gameDAO.findByEvent(model);
            if (!eventGames.isEmpty()) {
                throw new Exception(msg.get("DrawsAlreadyExist"));
            }

            //check min number of participants
            if (model.getParticipants().size() < 3) {
                throw new Exception(msg.get("PleaseSelectAtLeast3Participants"));
            }

            //determine ranking
            List<Ranking> ranking = rankingUtil.getRankedParticipants(model);
            List<Participant> rankedParticipants = new ArrayList<>();
            ranking.stream().forEach(r -> {
                rankedParticipants.add(r.getParticipant());
            });

            eventsUtil.createKnockoutGames(model, rankedParticipants);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            ModelAndView mav = getDrawsView(eventId);
            mav.addObject("error", e.getMessage());
            return mav;
        }

        return new ModelAndView("redirect:/admin/events/edit/" + eventId + "/draws");
    }

    @RequestMapping(value = { "edit/{eventId}/draws/game/{gameId}" }, method = GET)
    public ModelAndView getDrawsGame(@PathVariable("eventId") Long eventId, @PathVariable("gameId") Long gameId,
            HttpServletRequest request) {
        Game game = gameDAO.findById(gameId);
        GameData gameData = new GameData(game);
        ModelAndView mav = getDrawsGameView(eventId, gameData);
        return mav;
    }

    @RequestMapping(value = { "edit/{eventId}/draws/game/{gameId}" }, method = POST)
    public ModelAndView postDrawsGame(@PathVariable("eventId") Long eventId,
            @RequestParam(value = "id", required = true) Long id,
            @RequestParam(value = "participant1", required = true) Long participant1,
            @RequestParam(value = "participant2", required = false) Long participant2) {
        Game game = gameDAO.findByIdFetchWithNextGame(id);

        Game nextGame = game.getNextGame();
        if (nextGame != null) {
            nextGame.getParticipants().removeAll(game.getParticipants());
            gameDAO.saveOrUpdate(nextGame);
        }

        game.getParticipants().clear();
        Participant p1 = participantDAO.findById(participant1);
        game.getParticipants().add(p1);
        if (participant2 != null) {
            Participant p2 = participantDAO.findById(participant2);
            game.getParticipants().add(p2);
        }

        if (nextGame != null && game.getParticipants().size() == 1) {
            nextGame.getParticipants().add(p1);
            gameDAO.saveOrUpdate(nextGame);
        }

        gameDAO.saveOrUpdate(game);
        return new ModelAndView("redirect:/admin/events/edit/" + eventId + "/draws");
    }

    @RequestMapping(value = { "edit/{eventId}/groupdraws" }, method = GET)
    public ModelAndView getGroupDraws(@PathVariable("eventId") Long eventId) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        return getGroupDrawsView(event, getDefaultEventGroups(event));
    }

    @RequestMapping(value = { "edit/{eventId}/groupdraws" }, method = POST)
    public ModelAndView postGroupDraws(@PathVariable("eventId") Long eventId,
            @ModelAttribute("Model") @Valid EventGroups eventGroups, BindingResult bindingResult,
            HttpServletRequest request) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        Iterator<Map.Entry<Integer, Set<Participant>>> iterator = eventGroups.getGroupParticipants().entrySet()
                .iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, Set<Participant>> entry = iterator.next();
            if (entry.getValue() == null) {
                bindingResult.reject("PleaseSelectParticipantsForEachGroup");
            }
        }

        //prevent modification of group draws if knockout round games have already begun
        if (event.getEventType().equals(EventType.Knockout)) {
            SortedMap<Integer, List<Game>> roundGames = eventsUtil.getRoundGameMap(event);
            if (!roundGames.isEmpty()) {
                bindingResult.reject("CannotModifyEventAfterGroupPhaseHasEnded");
            }
        }

        if (bindingResult.hasErrors()) {
            return getGroupDrawsView(event, eventGroups);
        }

        //remove games that have not been played yet
        gameUtil.removeObsoleteGames(event);

        //remove games with teams that are no longer part of a group
        Iterator<Game> gameIterator = event.getGames().iterator();
        while (gameIterator.hasNext()) {
            Game game = gameIterator.next();
            Integer groupNumber = game.getGroupNumber();
            if (groupNumber != null) {
                Set<Participant> groupParticipants = eventGroups.getGroupParticipants().get(groupNumber);
                if (!groupParticipants.containsAll(game.getParticipants())) {
                    gameDAO.deleteById(game.getId());
                    gameIterator.remove();
                }
            }
        }

        //create missing games
        for (int groupNumber = 0; groupNumber < event.getNumberOfGroups(); groupNumber++) {
            Set<Participant> groupParticipants = eventGroups.getGroupParticipants().get(groupNumber);
            Integer roundNumber = null;
            switch (event.getEventType()) {
            case GroupKnockout:
                //for GroupKnockout events, only knockout games have a round
                roundNumber = ROUND_NONE;
                break;
            case GroupTwoRounds:
                //for GroupTwoRound events, every round has a number
                roundNumber = ROUND_ONE;
                break;
            }
            gameUtil.createMissingGames(event, groupParticipants, groupNumber, roundNumber);
        }

        return new ModelAndView("redirect:/admin/events/edit/" + eventId + "/groupschedule");
    }

    @RequestMapping(value = { "edit/{eventId}/groupdraws/{round}" }, method = GET)
    public ModelAndView getGroupDrawsForRound(@PathVariable Long eventId, @PathVariable Integer round) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        try {
            int maxParticipantsPerGroup = (int) Math
                    .ceil(event.getParticipants().size() / event.getNumberOfGroups());
            SortedMap<Integer, List<Game>> groupGames = eventsUtil.getGroupGameMap(event, round - 1);
            List<Participant> rankedParticipants = getRankedGroupParticipants(groupGames, event.getNumberOfGroups(),
                    maxParticipantsPerGroup);
            EventGroups eventGroupsRoundTwo = getEventGroupsRoundTwo(event, rankedParticipants);
            return getGroupDrawsView(event, eventGroupsRoundTwo, round);
        } catch (Exception e) {
            return getGroupDrawsView(event, null, round);
        }
    }

    @RequestMapping(value = { "edit/{eventId}/groupdraws/{round}" }, method = POST)
    public ModelAndView postGroupDrawsForRound(@PathVariable Long eventId, @PathVariable Integer round,
            @ModelAttribute("Model") @Valid EventGroups eventGroups, BindingResult bindingResult,
            HttpServletRequest request) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        Iterator<Map.Entry<Integer, Set<Participant>>> iterator = eventGroups.getGroupParticipants().entrySet()
                .iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, Set<Participant>> entry = iterator.next();
            if (entry.getValue() == null) {
                bindingResult.reject("PleaseSelectParticipantsForEachGroup");
            }
        }

        if (bindingResult.hasErrors()) {
            return getGroupDrawsView(event, eventGroups, round);
        }

        //remove games of players who no longer participate
        gameUtil.removeObsoleteGames(event);

        //remove games with teams that are no longer part of a group in the current round
        Iterator<Game> gameIterator = event.getGames().iterator();
        while (gameIterator.hasNext()) {
            Game game = gameIterator.next();
            Integer groupNumber = game.getGroupNumber();
            if (groupNumber != null && game.getRound().equals(round)) {
                Set<Participant> groupParticipants = eventGroups.getGroupParticipants().get(groupNumber);
                if (!groupParticipants.containsAll(game.getParticipants())) {
                    gameIterator.remove();
                    gameDAO.deleteById(game.getId());
                }
            }
        }

        //create missing games
        for (int groupNumber = 0; groupNumber < event.getNumberOfGroupsSecondRound(); groupNumber++) {
            Set<Participant> groupParticipants = eventGroups.getGroupParticipants().get(groupNumber);
            gameUtil.createMissingGames(event, groupParticipants, groupNumber, round);
        }

        return new ModelAndView("redirect:/events/event/" + eventId + "/groupgames/" + round);
    }

    @RequestMapping(value = { "edit/{eventId}/groupschedule" }, method = GET)
    public ModelAndView getGroupSchedule(@PathVariable("eventId") Long eventId) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        return getGroupScheduleView(event);
    }

    @RequestMapping(value = { "edit/{eventId}/gameschedule" }, method = GET)
    public ModelAndView getGameSchedule(@PathVariable("eventId") Long eventId) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        return getGameScheduleView(event);
    }

    @RequestMapping(value = { "edit/{eventId}/schedule/{scheduleName}" }, method = POST)
    public ModelAndView postGroupSchedule(@PathVariable("eventId") Long eventId,
            @PathVariable("scheduleName") String scheduleName, @ModelAttribute("Model") GameList gameList,
            BindingResult bindingResult, HttpServletRequest request) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        if (bindingResult.hasErrors()) {
            switch (scheduleName) {
            case "groupschedule":
                return getGroupScheduleView(event);
            case "gameschedule":
            default:
                return getGameScheduleView(event);
            }
        }
        if (gameList.getList() != null) {
            for (Game game : gameList.getList()) {
                Game existingGame = gameDAO.findById(game.getId());
                existingGame.setStartDate(game.getStartDate());
                existingGame.setStartTimeHour(game.getStartTimeHour());
                existingGame.setStartTimeMinute(game.getStartTimeMinute());
                gameDAO.saveOrUpdate(existingGame);
            }
        }
        return redirectToIndex(request);
    }

    @Override
    protected ModelAndView getDeleteView(Event model) {
        return new ModelAndView(getModuleName() + "/delete", "Model", model);
    }

    @Override
    @RequestMapping(value = "/{id}/delete", method = POST)
    public ModelAndView postDelete(HttpServletRequest request, @PathVariable("id") Long id) {
        try {
            Event event = eventDAO.findByIdFetchWithGames(id);
            if (event == null) {
                return getNotFoundView();
            }
            for (Game game : event.getGames()) {
                game.getGameSets().clear();
            }
            event.getGames().clear();
            eventDAO.saveOrUpdate(event);

            eventDAO.deleteById(event.getId());
        } catch (DataIntegrityViolationException e) {
            Event model = (Event) getDAO().findById(id);
            LOG.warn("Attempt to delete " + model + " failed due to " + e);
            ModelAndView deleteView = getDeleteView(model);
            deleteView.addObject("error", msg.get("CannotDeleteDueToRefrence", new Object[] { model.toString() }));
            return deleteView;
        }
        return redirectToIndex(request);
    }

    @RequestMapping(method = GET, value = "event/{eventId}/groupgamesend")
    public ModelAndView getEventGroupGamesEnd(@PathVariable("eventId") Long eventId) {
        Event event = eventDAO.findById(eventId);
        return getGroupGamesEndView(event);
    }

    @RequestMapping(method = POST, value = "event/{eventId}/groupgamesend")
    public ModelAndView saveEventGroupGamesEnd(@PathVariable("eventId") Long eventId,
            @ModelAttribute("Model") Event dummy, BindingResult result) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        try {
            SortedMap<Integer, List<Game>> roundGames = eventsUtil.getRoundGameMap(event);
            if (!roundGames.isEmpty()) {
                throw new Exception(msg.get("GroupGamesAlreadyEnded"));
            }

            SortedMap<Integer, List<Game>> groupGames = eventsUtil.getGroupGameMap(event);
            if (groupGames.isEmpty()) {
                throw new Exception(msg.get("NoGroupGames"));
            }

            List<Participant> rankedParticipants = getRankedGroupParticipants(groupGames, event.getNumberOfGroups(),
                    event.getNumberOfWinnersPerGroup());

            eventsUtil.createKnockoutGames(event, rankedParticipants);
            return redirectToDraws(event);
        } catch (Exception e) {
            result.addError(new ObjectError("*", e.getMessage()));
            return getGroupGamesEndView(dummy);
        }
    }

    @RequestMapping(method = GET, value = "event/{eventId}/groupgamesend/{round}")
    public ModelAndView getEventGroupGamesEndForRound(@PathVariable() Long eventId, @PathVariable Integer round) {
        Event event = eventDAO.findById(eventId);
        return getGroupGamesEndView(event, round);
    }

    @RequestMapping(method = POST, value = "event/{eventId}/groupgamesend/{round}")
    public ModelAndView saveEventGroupGamesEndForRound(@PathVariable() Long eventId, @PathVariable Integer round,
            @ModelAttribute("Model") Event dummy, BindingResult result) {
        Event event = eventDAO.findByIdFetchWithParticipantsAndGames(eventId);
        try {
            SortedMap<Integer, List<Game>> roundGames = eventsUtil.getRoundGameMap(event, round + 1);
            if (!roundGames.isEmpty()) {
                throw new Exception(msg.get("GroupGamesAlreadyEnded"));
            }

            SortedMap<Integer, List<Game>> groupGames = eventsUtil.getGroupGameMap(event, round);
            if (groupGames.isEmpty()) {
                throw new Exception(msg.get("NoGroupGames"));
            }
            getRankedGroupParticipants(groupGames, event.getNumberOfGroups(), event.getNumberOfWinnersPerGroup());

            return redirectToGroupDrawsRound(event, round + 1);
        } catch (Exception e) {
            result.addError(new ObjectError("*", e.getMessage()));
            return getGroupGamesEndView(dummy, round);
        }
    }

    @RequestMapping(method = GET, value = "/{eventId}/game/{gameId}/delete")
    public ModelAndView getDeleteGameView(@PathVariable("eventId") Long eventId,
            @PathVariable("gameId") Long gameId) {
        Game game = gameDAO.findById(gameId);
        return getDeleteGameView(game);
    }

    @RequestMapping(method = POST, value = "/{eventId}/game/{gameId}/delete")
    public ModelAndView postDeleteGameView(HttpServletRequest request, @PathVariable("gameId") Long gameId,
            @RequestParam(value = "redirectUrl", required = false) String redirectUrl) {
        try {
            gameDAO.deleteById(gameId);
        } catch (DataIntegrityViolationException e) {
            Game model = gameDAO.findById(gameId);
            LOG.warn("Attempt to delete " + model + " failed due to " + e);
            ModelAndView deleteView = getDeleteGameView(model);
            deleteView.addObject("error", msg.get("CannotDeleteDueToRefrence", new Object[] { model.toString() }));
            return deleteView;
        }
        if (!StringUtils.isEmpty(redirectUrl)) {
            return new ModelAndView("redirect:/" + redirectUrl);
        }
        return redirectToIndex(request);
    }

    @RequestMapping(method = GET, value = "/{eventId}/addguests")
    public ModelAndView getAddGuests(@PathVariable("eventId") Long eventId) {
        Event event = eventDAO.findById(eventId);
        return getAddGuestsView(event, new Player(), 1L);
    }

    @RequestMapping(method = POST, value = "/{eventId}/addguests")
    public ModelAndView postAddGuests(@PathVariable("eventId") Long eventId,
            @ModelAttribute("Player") Player player, @RequestParam("NumberOfGuests") Long numberOfGuests,
            HttpServletRequest request, BindingResult result) {
        Event event = eventDAO.findByIdFetchWithParticipants(eventId);
        try {
            Player primaryPlayer;
            if (StringUtils.isEmpty(player.getUUID())) {
                validator.validate(player, result);
                if (result.hasErrors()) {
                    return getAddGuestsView(event, player, numberOfGuests);
                }
                if (playerDAO.findByEmail(player.getEmail()) != null) {
                    throw new Exception(msg.get("EmailAlreadyRegistered"));
                }
                player.setAllowEmailContact(false);
                primaryPlayer = playerDAO.saveOrUpdate(player);
            } else {
                primaryPlayer = playerDAO.findByUUID(player.getUUID());
            }
            if (event.getParticipants().contains(primaryPlayer)) {
                throw new Exception(msg.get("AlreadyParticipatesInThisEvent", new Object[] { primaryPlayer }));
            }
            for (long guestNumber = 1L; guestNumber <= numberOfGuests; guestNumber++) {
                String domain = request.getServerName().equals("localhost") ? "localhost.local"
                        : request.getServerName();
                String guestEmail = String.format("%s-%s-Gast-%s@%s", primaryPlayer.getFirstName(),
                        primaryPlayer.getLastName(), guestNumber, domain).replace(" ", "-");
                Player guest = playerDAO.findByEmail(guestEmail);
                if (guest == null) {
                    guest = new Player();
                    guest.setFirstName(primaryPlayer.getFirstName());
                    guest.setLastName(String.format("%s Gast %s", primaryPlayer.getLastName(), guestNumber));
                    guest.setAllowEmailContact(false);
                    guest.setEmail(guestEmail);
                    guest.setPhone(primaryPlayer.getPhone());
                    guest.setGender(primaryPlayer.getGender());
                    guest = playerDAO.saveOrUpdate(guest);
                }
                event.getParticipants().add(guest);
            }
            event.getParticipants().add(primaryPlayer);
            eventDAO.saveOrUpdate(event);
            return new ModelAndView("redirect:/admin/events/edit/" + eventId);
        } catch (Exception e) {
            LOG.error(e.getMessage(), e);
            result.addError(new ObjectError("id", e.getMessage()));
            return getAddGuestsView(event, player, numberOfGuests);
        }
    }

    @Override
    protected ModelAndView getEditView(Event event) {
        ModelAndView mav = new ModelAndView("admin/events/edit", "Model", event);
        mav.addObject("EventTypes", EventType.values());
        mav.addObject("Genders", Gender.values());
        mav.addObject("CalendarConfigs", calendarConfigDAO.findAll());
        mav.addObject("EventGroups", eventGroupDAO.findAll());
        List<PaymentMethod> activePaymentMethods = bookingUtil.getActivePaymentMethods();
        activePaymentMethods.remove(PaymentMethod.Voucher);
        mav.addObject("PaymentMethods", activePaymentMethods);
        return mav;
    }

    @Override
    public EventDAOI getDAO() {
        return eventDAO;
    }

    @Override
    public String getModuleName() {
        return "admin/events";
    }

    @Override
    public Page<Event> findAll(Pageable pageable) {
        Page<Event> page = eventDAO.findAllFetchWithParticipantsAndPlayers(pageable);
        return page;
    }

    private ModelAndView redirectToDraws(Event model) {
        return new ModelAndView("redirect:/admin/events/edit/" + model.getId() + "/draws");
    }

    private ModelAndView getDrawsView(Long eventId) {
        Event event = eventDAO.findByIdFetchWithGames(eventId);
        SortedMap<Integer, List<Game>> roundGames = eventsUtil.getRoundGameMap(event);
        ModelAndView mav = new ModelAndView("admin/events/draws", "Model", event);
        mav.addObject("RoundGameMap", roundGames);
        return mav;
    }

    private ModelAndView getDrawsGameView(Long eventId, GameData gameData) {
        Event event = eventDAO.findByIdFetchWithParticipants(eventId);
        ModelAndView mav = new ModelAndView("admin/events/game");
        mav.addObject("Event", event);
        mav.addObject("Model", gameData);
        return mav;
    }

    private ModelAndView getGroupDrawsView(Event event, EventGroups eventGroups) {
        return getGroupDrawsView(event, eventGroups, null);
    }

    private ModelAndView getGroupDrawsView(Event event, EventGroups eventGroups, Integer round) {
        ModelAndView mav = new ModelAndView("admin/events/groupdraws");
        mav.addObject("Event", event);
        mav.addObject("Model", eventGroups);
        mav.addObject("Round", round);
        return mav;
    }

    private ModelAndView getGroupGamesEndView(Event event) {
        return getGroupGamesEndView(event, null);
    }

    private ModelAndView getGroupGamesEndView(Event event, Integer round) {
        ModelAndView mav = new ModelAndView("admin/events/groupgamesend", "Model", event);
        mav.addObject("Round", round);
        return mav;
    }

    private ModelAndView getGroupScheduleView(Event event) {
        ModelAndView mav = new ModelAndView("admin/events/groupschedule");
        mav.addObject("Event", event);
        SortedMap<Integer, List<Game>> groupGameMap = eventsUtil.getGroupGameMap(event);
        mav.addObject("GroupGameMap", groupGameMap);
        List<Game> games = new ArrayList<>();
        for (List<Game> list : groupGameMap.values()) {
            games.addAll(list);
        }
        GameList formList = new GameList();
        formList.setList(games);
        mav.addObject("Model", formList);
        return mav;
    }

    private ModelAndView getGameScheduleView(Event event) {
        ModelAndView mav = new ModelAndView("admin/events/gameschedule");
        mav.addObject("Event", event);
        List<Game> games = new ArrayList<>();
        games.addAll(event.getGames());
        GameList formList = new GameList();
        formList.setList(games);
        mav.addObject("Model", formList);
        return mav;
    }

    private ModelAndView getAddGuestsView(Event event, Player player, Long numberOfGuests) {
        ModelAndView mav = new ModelAndView("admin/events/addguests");
        mav.addObject("Event", event);
        mav.addObject("Player", player);
        mav.addObject("NumberOfGuests", numberOfGuests);
        return mav;
    }

    private ModelAndView redirectToGroupDraws(Event model) {
        return new ModelAndView("redirect:/admin/events/edit/" + model.getId() + "/groupdraws");
    }

    private ModelAndView redirectToGroupDrawsRound(Event model, Integer round) {
        return new ModelAndView("redirect:/admin/events/edit/" + model.getId() + "/groupdraws/" + round);
    }

    private EventGroups getDefaultEventGroups(Event event) {
        //initialize participant map
        Map<Integer, Set<Participant>> participantMap = new TreeMap<>();
        for (int i = 0; i < event.getNumberOfGroups(); i++) {
            participantMap.put(i, new TreeSet<Participant>());
        }

        //fill participant map from existing games if possible
        for (Game game : event.getGames()) {
            //ROUND_NONE for EventType.GroupKnockout, ROUND_ONE for EventType.GroupTwoRounds
            if (Objects.equals(game.getRound(), ROUND_NONE) || game.getRound().equals(ROUND_ONE)) {
                Integer groupNumber = game.getGroupNumber();
                if (groupNumber != null) {
                    Set<Participant> groupParticipants = participantMap.get(groupNumber);
                    groupParticipants.addAll(game.getParticipants());
                    participantMap.put(groupNumber, groupParticipants);
                }
            }
        }
        EventGroups eventGroups = new EventGroups();
        eventGroups.setGroupParticipants(participantMap);
        return eventGroups;
    }

    private EventGroups getEventGroupsRoundTwo(Event event, List<Participant> rankedGroupParticipants) {

        //initialize participant map
        Map<Integer, Set<Participant>> participantMap = new TreeMap<>();

        int grp = 0;
        for (Participant rankedGroupParticipant : rankedGroupParticipants) {
            Set<Participant> participants = participantMap.get(grp);
            if (participants == null) {
                participants = new TreeSet<>();
            }
            participants.add(rankedGroupParticipant);
            participantMap.put(grp, participants);

            if (grp + 1 >= event.getNumberOfGroupsSecondRound()) {
                grp = 0;
            } else {
                grp++;
            }
        }

        //fill participant map from existing games if possible
        for (Game game : event.getGames()) {
            if (game.getRound() != null && game.getRound() == 1) {
                Integer groupNumber = game.getGroupNumber();
                if (groupNumber != null) {
                    Set<Participant> groupParticipants = participantMap.get(groupNumber);
                    groupParticipants.addAll(game.getParticipants());
                    participantMap.put(groupNumber, groupParticipants);
                }
            }
        }
        EventGroups eventGroups = new EventGroups();
        eventGroups.setGroupParticipants(participantMap);
        return eventGroups;
    }

    private ModelAndView getDeleteGameView(Game game) {
        ModelAndView mav = new ModelAndView("include/delete");
        mav.addObject("Model", game);
        return mav;
    }

    private List<Participant> getRankedGroupParticipants(SortedMap<Integer, List<Game>> groupGames,
            Integer numberOfGroups, Integer maxWinnersPerGroup) throws Exception {
        Map<Integer, List<Participant>> rankedGroupParticipants = new TreeMap<>();
        Iterator<Map.Entry<Integer, List<Game>>> iterator = groupGames.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Integer, List<Game>> entry = iterator.next();
            Integer groupNumber = entry.getKey();
            List<Game> games = entry.getValue();

            //determine participant based on games to filter out participants who did not play
            Set<Participant> participants = new HashSet<>();
            List<Game> playedGames = new ArrayList<>();
            for (Game game : games) {
                if (!game.getGameSets().isEmpty()) {
                    participants.addAll(game.getParticipants());
                    playedGames.add(game);
                }
            }

            if (participants.isEmpty() || playedGames.isEmpty()) {
                throw new Exception(msg.get("CannotEndGroupGames"));
            }

            //get list of score entries sorted by rank
            List<ScoreEntry> scoreEntries = rankingUtil.getScores(participants, playedGames);
            for (int groupPos = 0; groupPos < maxWinnersPerGroup; groupPos++) {
                List<Participant> rankedParticipants = rankedGroupParticipants.get(groupNumber);
                if (rankedParticipants == null) {
                    rankedParticipants = new ArrayList<>();
                }
                Participant p = null;
                try {
                    p = scoreEntries.get(groupPos).getParticipant();
                } catch (IndexOutOfBoundsException e) {
                    //could happen when not enough games were played in one group
                }
                rankedParticipants.add(p);
                rankedGroupParticipants.put(groupNumber, rankedParticipants);
            }
        }

        //sort participants so that group winners are first
        List<Participant> rankedParticipants = new ArrayList<>();
        for (int groupPos = 0; groupPos < maxWinnersPerGroup; groupPos++) {
            for (int group = 0; group < numberOfGroups; group++) {
                rankedParticipants.add(rankedGroupParticipants.get(group).get(groupPos));
            }
        }
        return rankedParticipants;
    }
}