package de.jottyfan.camporganizer.module.admin;

import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMP;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPDOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_CAMPPROFILE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENT;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_DOCUMENTROLE;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_LOCATION;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PERSON;
import static de.jottyfan.camporganizer.db.jooq.Tables.T_PROFILE;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.DeleteConditionStep;
import org.jooq.Field;
import org.jooq.InsertResultStep;
import org.jooq.InsertReturningStep;
import org.jooq.InsertValuesStep16;
import org.jooq.InsertValuesStep3;
import org.jooq.Record4;
import org.jooq.Record5;
import org.jooq.SelectConditionStep;
import org.jooq.SelectSeekStep1;
import org.jooq.SelectSeekStep2;
import org.jooq.SelectWhereStep;
import org.jooq.UpdateConditionStep;
import org.jooq.UpdateSetMoreStep;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import de.jottyfan.camporganizer.db.jooq.enums.EnumCamprole;
import de.jottyfan.camporganizer.db.jooq.enums.EnumDocument;
import de.jottyfan.camporganizer.db.jooq.enums.EnumFiletype;
import de.jottyfan.camporganizer.db.jooq.enums.EnumModule;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampdocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TCampprofileRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TDocumentroleRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TLocationRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TPersonRecord;
import de.jottyfan.camporganizer.db.jooq.tables.records.TProfileRecord;
import de.jottyfan.camporganizer.module.admin.model.CampBean;
import de.jottyfan.camporganizer.module.admin.model.CampDocumentBean;
import de.jottyfan.camporganizer.module.admin.model.DocumentBean;
import de.jottyfan.camporganizer.module.admin.model.IntKeyValueBean;
import de.jottyfan.camporganizer.module.admin.model.LocationBean;
import de.jottyfan.camporganizer.module.admin.model.PrivilegesContainerBean;
import de.jottyfan.camporganizer.module.admin.model.ProfileBean;
import de.jottyfan.camporganizer.module.camplist.model.LambdaResultWrapper;
import jakarta.validation.Valid;

/**
 *
 * @author jotty
 *
 */
@Repository
@Transactional(transactionManager = "transactionManager")
public class AdminRepository {

    private final GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak;

	private static final Logger LOGGER = LogManager.getLogger(AdminRepository.class);

	@Autowired
	private DSLContext jooq;

    AdminRepository(GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak) {
        this.userAuthoritiesMapperForKeycloak = userAuthoritiesMapperForKeycloak;
    }

	/**
	 * get the document with that ID
	 *
	 * @param id the ID of the document
	 * @return the document
	 */
	public DocumentBean getDocument(Integer id) {
		Field<EnumCamprole[]> ROLES = DSL.field("roles", EnumCamprole[].class);
		SelectSeekStep1<Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]>, Integer> sql = jooq
		// @formatter:off
			.select(T_DOCUMENT.PK,
					    T_DOCUMENT.NAME,
					    T_DOCUMENT.DOCTYPE,
					    T_DOCUMENT.FILETYPE,
					    DSL.arrayAgg(T_DOCUMENTROLE.CAMPROLE).as(ROLES))
			.from(T_DOCUMENT)
			.leftJoin(T_DOCUMENTROLE).on(T_DOCUMENTROLE.FK_DOCUMENT.eq(T_DOCUMENT.PK))
			.where(T_DOCUMENT.PK.eq(id))
			.groupBy(T_DOCUMENT.PK, T_DOCUMENT.NAME, T_DOCUMENT.DOCTYPE, T_DOCUMENT.FILETYPE)
			.orderBy(T_DOCUMENT.PK);
		// @formatter:on
		LOGGER.trace(sql);
		Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]> r = sql.fetchOne();
		if (r != null) {
			DocumentBean bean = new DocumentBean();
			bean.setPk(r.get(T_DOCUMENT.PK));
			bean.setName(r.get(T_DOCUMENT.NAME));
			bean.setDoctype(r.get(T_DOCUMENT.DOCTYPE));
			bean.setFiletype(r.get(T_DOCUMENT.FILETYPE));
			bean.setRoles(r.get(ROLES));
			return bean;
		}
		return null;
	}

	/**
	 * get all documents from the database
	 *
	 * @return all documents
	 */
	public List<DocumentBean> getAllDocuments() {
		Field<EnumCamprole[]> ROLES = DSL.field("roles", EnumCamprole[].class);
		SelectSeekStep1<Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]>, Integer> sql = jooq
		// @formatter:off
			.select(T_DOCUMENT.PK,
					    T_DOCUMENT.NAME,
					    T_DOCUMENT.DOCTYPE,
					    T_DOCUMENT.FILETYPE,
					    DSL.arrayAgg(T_DOCUMENTROLE.CAMPROLE).as(ROLES))
			.from(T_DOCUMENT)
			.leftJoin(T_DOCUMENTROLE).on(T_DOCUMENTROLE.FK_DOCUMENT.eq(T_DOCUMENT.PK))
			.groupBy(T_DOCUMENT.PK, T_DOCUMENT.NAME, T_DOCUMENT.DOCTYPE, T_DOCUMENT.FILETYPE)
			.orderBy(T_DOCUMENT.PK);
		// @formatter:on
		LOGGER.trace(sql.toString());
		List<DocumentBean> list = new ArrayList<>();
		Iterator<Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record5<Integer, String, EnumDocument, EnumFiletype, EnumCamprole[]> r = i.next();
			DocumentBean bean = new DocumentBean();
			bean.setPk(r.get(T_DOCUMENT.PK));
			bean.setName(r.get(T_DOCUMENT.NAME));
			bean.setDoctype(r.get(T_DOCUMENT.DOCTYPE));
			bean.setFiletype(r.get(T_DOCUMENT.FILETYPE));
			bean.setRoles(r.get(ROLES));
			list.add(bean);
		}
		return list;
	}

	/**
	 * get all documents from the database that fit to condition
	 *
	 * @param condition the where condition, e.g. to filter for doctype
	 *
	 * @return all documents
	 */
	public List<DocumentBean> getAllDocumentsWith(Condition condition) {
		SelectConditionStep<Record4<Integer, String, EnumDocument, EnumFiletype>> sql = jooq
		// @formatter:off
			.select(T_DOCUMENT.PK,
					    T_DOCUMENT.NAME,
					    T_DOCUMENT.DOCTYPE,
					    T_DOCUMENT.FILETYPE)
			.from(T_DOCUMENT)
			.where(condition);
		// @formatter:on
		LOGGER.trace(sql);
		List<DocumentBean> list = new ArrayList<>();
		Iterator<Record4<Integer, String, EnumDocument, EnumFiletype>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, String, EnumDocument, EnumFiletype> r = i.next();
			DocumentBean bean = new DocumentBean();
			bean.setPk(r.get(T_DOCUMENT.PK));
			bean.setName(r.get(T_DOCUMENT.NAME));
			bean.setDoctype(r.get(T_DOCUMENT.DOCTYPE));
			bean.setFiletype(r.get(T_DOCUMENT.FILETYPE));
			list.add(bean);
		}
		return list;
	}

	/**
	 * upsert document in t_document
	 *
	 * @param document
	 * @throws DataAccessException
	 */
	public Integer upsert(DocumentBean bean) throws DataAccessException {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(c -> {
			Integer pk = bean.getPk();
			if (bean.getPk() != null) {
				UpdateSetMoreStep<TDocumentRecord> sqlPart = DSL.using(c)
				// @formatter:off
					.update(T_DOCUMENT)
					.set(T_DOCUMENT.NAME, bean.getName())
					.set(T_DOCUMENT.DOCTYPE, bean.getDoctype());
			  // @formatter:on
				if (bean.getDocument() != null) {
					sqlPart = sqlPart.set(T_DOCUMENT.DOCUMENT, bean.getDocument());
				}
				// @formatter:off
				UpdateConditionStep<TDocumentRecord> sql = sqlPart
				  .set(T_DOCUMENT.FILETYPE, bean.getFiletype())
				  .where(T_DOCUMENT.PK.eq(bean.getPk()));
			  // @formatter:on
				LOGGER.trace(sql);
				lrw.add(sql.execute());
			} else {
				InsertResultStep<TDocumentRecord> sql = DSL.using(c)
				// @formatter:off
				  .insertInto(T_DOCUMENT,
				  		        T_DOCUMENT.NAME,
				  		        T_DOCUMENT.DOCTYPE,
				  		        T_DOCUMENT.DOCUMENT,
				  		        T_DOCUMENT.FILETYPE)
				  .values(bean.getName(), bean.getDoctype(), bean.getDocument(), bean.getFiletype())
				  .returning(T_DOCUMENT.PK);
		  	// @formatter:on
				LOGGER.trace(sql);
				pk = sql.fetchOne().get(T_DOCUMENT.PK);
				lrw.add(1);
			}
			List<EnumCamprole> allEnums = Arrays.asList(EnumCamprole.values());
			Set<EnumCamprole> removeCandidates = new HashSet<>();
			removeCandidates.addAll(allEnums);
			for (EnumCamprole role : bean.getRoles()) {
				try {
					InsertReturningStep<TDocumentroleRecord> sql = DSL.using(c)
					// @formatter:off
				  	.insertInto(T_DOCUMENTROLE,
				  			        T_DOCUMENTROLE.FK_DOCUMENT,
				  			        T_DOCUMENTROLE.CAMPROLE)
				  	.values(pk, role)
				  	.onConflict(T_DOCUMENTROLE.FK_DOCUMENT, T_DOCUMENTROLE.CAMPROLE)
				  	.doNothing();
				  // @formatter:on
					LOGGER.trace(sql);
					lrw.add(sql.execute());
					removeCandidates.remove(role);
				} catch (IllegalArgumentException e) {
					LOGGER.error(e);
				}
			}
			DeleteConditionStep<TDocumentroleRecord> sql = DSL.using(c)
			// @formatter:off
				.deleteFrom(T_DOCUMENTROLE)
				.where(T_DOCUMENTROLE.FK_DOCUMENT.eq(pk))
				.and(T_DOCUMENTROLE.CAMPROLE.in(removeCandidates));
			// @formatter:on
			LOGGER.trace(sql);
			lrw.add(sql.execute());
		});
		return lrw.getCounter();
	}

	/**
	 * delete entry from t_document where pk = ?
	 *
	 * @param pk to be used as reference
	 * @return number of affected database lines
	 * @throws DataAccessException
	 */
	public Integer deleteDocument(Integer pk) throws DataAccessException {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(t -> {
			UpdateConditionStep<TCampRecord> sql = DSL.using(t)
			// @formatter:off
			  .update(T_CAMP)
			  .set(T_CAMP.FK_DOCUMENT, (Integer) null)
			  .where(T_CAMP.FK_DOCUMENT.eq(pk));
		  // @formatter:on
			LOGGER.trace(sql);
			lrw.add(sql.execute());

			UpdateConditionStep<TLocationRecord> sql1 = DSL.using(t)
			// @formatter:off
			  .update(T_LOCATION)
			  .set(T_LOCATION.FK_DOCUMENT, (Integer) null)
			  .where(T_LOCATION.FK_DOCUMENT.eq(pk));
		  // @formatter:on
			LOGGER.trace(sql1);
			lrw.add(sql1.execute());

			DeleteConditionStep<TDocumentroleRecord> sql2 = DSL.using(t)
			// @formatter:off
				.deleteFrom(T_DOCUMENTROLE)
				.where(T_DOCUMENTROLE.FK_DOCUMENT.eq(pk));
			// @formatter:on
			LOGGER.trace(sql2);
			lrw.add(sql2.execute());

			DeleteConditionStep<TDocumentRecord> sql3 = DSL.using(t)
			// @formatter:off
				.deleteFrom(T_DOCUMENT)
				.where(T_DOCUMENT.PK.eq(pk));
			// @formatter:on
			LOGGER.trace(sql3);
			lrw.add(sql3.execute());
		});
		return lrw.getCounter();
	}

	/**
	 * get all locations from the database
	 *
	 * @return the locations
	 */
	public List<LocationBean> getLocations() {
		SelectWhereStep<TLocationRecord> sql = jooq.selectFrom(T_LOCATION);
		LOGGER.trace(sql);
		List<LocationBean> list = new ArrayList<>();
		for (TLocationRecord r : sql.fetch()) {
			list.add(LocationBean.of(r));
		}
		return list;
	}

	/**
	 * get the location from the database
	 *
	 * @param id the ID
	 * @return the location bean or null
	 */
	public LocationBean getLocation(Integer id) {
		SelectConditionStep<TLocationRecord> sql = jooq.selectFrom(T_LOCATION).where(T_LOCATION.PK.eq(id));
		LOGGER.trace(sql);
		return LocationBean.of(sql.fetchOne());
	}

	/**
	 * delete the location from the database
	 *
	 * @param id the ID of the location
	 * @return number of affected database rows; should be 1
	 */
	public Integer deleteLocation(Integer id) {
		DeleteConditionStep<TLocationRecord> sql = jooq.deleteFrom(T_LOCATION).where(T_LOCATION.PK.eq(id));
		LOGGER.trace(sql);
		return sql.execute();
	}

	/**
	 * upsert the bean; if pk is null, do an insert, an upsert else
	 *
	 * @param bean the bean
	 * @return number of affected database rows; should be 1
	 */
	public Integer upsertLocation(@Valid LocationBean bean) {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(t -> {
			if (bean.getPk() == null) {
				InsertValuesStep3<TLocationRecord, String, Integer, String> sql = DSL.using(t)
				// @formatter:off
					.insertInto(T_LOCATION,
							    T_LOCATION.NAME,
							    T_LOCATION.FK_DOCUMENT,
							    T_LOCATION.URL)
					.values(bean.getName(), bean.getFkDocument(), bean.getUrl());
				// @formatter:on
				LOGGER.trace(sql);
				lrw.add(sql.execute());
			} else {
				UpdateConditionStep<TLocationRecord> sql = DSL.using(t)
				// @formatter:off
					.update(T_LOCATION)
					.set(T_LOCATION.NAME, bean.getName())
					.set(T_LOCATION.FK_DOCUMENT, bean.getFkDocument())
					.set(T_LOCATION.URL, bean.getUrl())
					.where(T_LOCATION.PK.eq(bean.getPk()));
				// @formatter:on
				LOGGER.trace(sql);
				lrw.add(sql.execute());
			}
		});
		return lrw.getCounter();
	}

	/**
	 * get all camps from the database
	 *
	 * @return a list of camps; an empty list at least
	 */
	public List<CampBean> getAllCamps() {
		SelectWhereStep<TCampRecord> sql = jooq.selectFrom(T_CAMP);
		LOGGER.trace(sql);
		List<CampBean> list = new ArrayList<>();
		for (TCampRecord r : sql.fetch()) {
			list.add(CampBean.of(r));
		}
		return list;
	}

	/**
	 * get the camp of id
	 *
	 * @param id the ID of the camp
	 * @return the camp or null
	 */
	public CampBean getCamp(Integer id) {
		SelectConditionStep<TCampRecord> sql = jooq.selectFrom(T_CAMP).where(T_CAMP.PK.eq(id));
		LOGGER.trace(sql);
		return CampBean.of(sql.fetchOne());
	}

	/**
	 * delete the camp and all of its dependencies
	 *
	 * @param id the ID of the camp
	 * @return error message
	 */
	public String deleteCamp(Integer id) {
		LambdaResultWrapper lrw = new LambdaResultWrapper();
		jooq.transaction(t -> {
			SelectConditionStep<TPersonRecord> sql1 = DSL.using(t).selectFrom(T_PERSON).where(T_PERSON.FK_CAMP.eq(id));
			LOGGER.trace(sql1);
			Integer registrations = sql1.fetch().size();
			if (registrations < 1) {
				DeleteConditionStep<TCampRecord> sql2 = DSL.using(t).deleteFrom(T_CAMP).where(T_CAMP.PK.eq(id));
				LOGGER.trace(sql2);
				sql2.execute();
			} else {
				lrw.putString("error", String
						.format("Es gibt bereits %d Anmeldungen. Die Freizeit kann daher nicht gelöscht werden.", registrations));
			}
		});
		return lrw.getString("error");
	}

	/**
	 * upsert the camp
	 *
	 * @param bean the bean
	 * @return an error message, if any
	 */
	public String upsertCamp(@Valid CampBean bean) {
		jooq.transaction(t -> {
			LocalDate arriveDate = bean.getArrive();
			LocalDate departDate = bean.getDepart();
			LocalDateTime arrive = arriveDate == null ? null : arriveDate.atStartOfDay();
			LocalDateTime depart = departDate == null ? null : departDate.atStartOfDay();
			if (bean.getPk() == null) {
				InsertValuesStep16<TCampRecord, LocalDateTime, String, LocalDateTime, Integer, Integer, Integer, Boolean, Integer, Integer, String, String, Integer, Integer, Integer, Integer, LocalDateTime> sql = DSL
						.using(t)
						// @formatter:off
					.insertInto(T_CAMP,
							        T_CAMP.ARRIVE,
							        T_CAMP.COUNTRIES,
							        T_CAMP.DEPART,
							        T_CAMP.FK_DOCUMENT,
							        T_CAMP.FK_LOCATION,
							        T_CAMP.FK_PROFILE,
							        T_CAMP.LOCK_SALES,
							        T_CAMP.MAX_AGE,
							        T_CAMP.MIN_AGE,
							        T_CAMP.NAME,
							        T_CAMP.PRICE,
							        T_CAMP.BEDS_FEMALE,
							        T_CAMP.BEDS_MALE,
							        T_CAMP.BLOCKED_BEDS_FEMALE,
							        T_CAMP.BLOCKED_BEDS_MALE,
							        T_CAMP.START_BOOKING)
					.values(arrive, bean.getCountries(), depart, bean.getFkDocument(), bean.getFkLocation(), bean.getFkProfile(),
							bean.getLockSales() != null ? bean.getLockSales() : false, bean.getMaxAge(), bean.getMinAge(), bean.getName(), bean.getPrice(),
						  bean.getBedsFemale(), bean.getBedsMale(), bean.getBlockedBedsFemale(), bean.getBlockedBedsMale(), bean.getStartBooking());
				// @formatter:on
				LOGGER.trace(sql);
				sql.execute();
			} else {
				UpdateConditionStep<TCampRecord> sql = DSL.using(t)
				// @formatter:off
					.update(T_CAMP)
					.set(T_CAMP.ARRIVE, arrive)
					.set(T_CAMP.COUNTRIES, bean.getCountries())
					.set(T_CAMP.DEPART, depart)
					.set(T_CAMP.FK_DOCUMENT, bean.getFkDocument())
					.set(T_CAMP.FK_LOCATION, bean.getFkLocation())
					.set(T_CAMP.FK_PROFILE, bean.getFkProfile())
					.set(T_CAMP.LOCK_SALES, bean.getLockSales() != null ? bean.getLockSales() : false)
					.set(T_CAMP.MAX_AGE, bean.getMaxAge())
					.set(T_CAMP.MIN_AGE, bean.getMinAge())
					.set(T_CAMP.NAME, bean.getName())
					.set(T_CAMP.PRICE, bean.getPrice())
					.set(T_CAMP.BEDS_FEMALE, bean.getBedsFemale())
					.set(T_CAMP.BEDS_MALE, bean.getBedsMale())
					.set(T_CAMP.BLOCKED_BEDS_FEMALE, bean.getBlockedBedsFemale())
					.set(T_CAMP.BLOCKED_BEDS_MALE, bean.getBlockedBedsMale())
					.set(T_CAMP.START_BOOKING, bean.getStartBooking())
					.where(T_CAMP.PK.eq(bean.getPk()));
				// @formatter:on
				LOGGER.trace(sql);
				sql.execute();
			}
		});
		return null;
	}

	/**
	 * get all profiles from the db
	 *
	 * @return the profiles
	 */
	public List<ProfileBean> getProfiles() {
		SelectWhereStep<TProfileRecord> sql = jooq.selectFrom(T_PROFILE);
		LOGGER.trace(sql);
		List<ProfileBean> list = new ArrayList<>();
		for (TProfileRecord r : sql.fetch()) {
			list.add(ProfileBean.of(r));
		}
		return list;
	}

	/**
	 * get all modules
	 *
	 * @return all modules
	 */
	public List<String> getAllModules() {
		List<String> list = new ArrayList<>();
		for (EnumModule r : EnumModule.values()) {
			list.add(r.getLiteral());
		}
		return list;
	}

	/**
	 * get the mapping that contains the form bean for a selected module
	 *
	 * @param module the module
	 * @return the container
	 */
	public PrivilegesContainerBean getPersonCampMappingByModule(EnumModule module) {
		SelectSeekStep2<Record4<Integer, String, LocalDateTime, String>, LocalDateTime, Integer> sql = jooq
		// @formatter:off
			.select(T_CAMPPROFILE.PK,
					    T_CAMP.NAME,
					    T_CAMP.ARRIVE,
					    T_PROFILE.FORENAME.concat(" ").concat(T_PROFILE.SURNAME).as(T_PROFILE.USERNAME))
			.from(T_CAMPPROFILE)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_CAMPPROFILE.FK_CAMP))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(T_CAMPPROFILE.MODULE.eq(module))
			.orderBy(T_CAMP.ARRIVE, T_PROFILE.PK);
		// @formatter:on
		LOGGER.trace(sql);
		PrivilegesContainerBean pcb = new PrivilegesContainerBean();
		Iterator<Record4<Integer, String, LocalDateTime, String>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, String, LocalDateTime, String> r = i.next();
			Integer fkCampProfile = r.get(T_CAMPPROFILE.PK);
			String username = r.get(T_PROFILE.USERNAME);
			String campname = r.get(T_CAMP.NAME);
			LocalDateTime ldt = r.get(T_CAMP.ARRIVE);
			String year = ldt != null ? String.valueOf(ldt.getYear()) : "?";
			campname = campname == null ? year : campname.concat(" ").concat(year);
			if (pcb.get(username) == null) {
				pcb.addKey(username);
			}
			pcb.add(username, IntKeyValueBean.of(fkCampProfile, campname));
		}
		return pcb;
	}

	/**
	 * get the mapping that contains the form bean for a selected user
	 *
	 * @param user the user ID
	 * @return the container
	 */
	public PrivilegesContainerBean getModuleCampMappingByPerson(Integer user) {
		SelectSeekStep1<Record4<Integer, String, LocalDateTime, EnumModule>, LocalDateTime> sql = jooq
		// @formatter:off
			.select(T_CAMPPROFILE.PK,
					    T_CAMP.NAME,
					    T_CAMP.ARRIVE,
					    T_CAMPPROFILE.MODULE)
			.from(T_CAMPPROFILE)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_CAMPPROFILE.FK_CAMP))
			.where(T_CAMPPROFILE.FK_PROFILE.eq(user))
			.orderBy(T_CAMP.ARRIVE);
		// @formatter:on
		LOGGER.trace(sql);
		PrivilegesContainerBean pcb = new PrivilegesContainerBean();
		Iterator<Record4<Integer, String, LocalDateTime, EnumModule>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, String, LocalDateTime, EnumModule> r = i.next();
			Integer fkCampProfile = r.get(T_CAMPPROFILE.PK);
			EnumModule moduleEnum = r.get(T_CAMPPROFILE.MODULE);
			String module = moduleEnum == null ? null : moduleEnum.getLiteral();
			String campname = r.get(T_CAMP.NAME);
			LocalDateTime ldt = r.get(T_CAMP.ARRIVE);
			String year = ldt != null ? String.valueOf(ldt.getYear()) : "?";
			campname = campname == null ? year : campname.concat(" ").concat(year);
			if (pcb.get(module) == null) {
				pcb.addKey(module);
			}
			pcb.add(module, IntKeyValueBean.of(fkCampProfile, campname));
		}
		return pcb;
	}

	/**
	 * get the mapping that contains the form bean for a selected camp
	 *
	 * @param camp the camp ID
	 * @return the container
	 */
	public PrivilegesContainerBean getPersonModuleMappingByCamp(Integer camp) {
		SelectSeekStep2<Record4<Integer, String, String, EnumModule>, String, String> sql = jooq
		// @formatter:off
			.select(T_CAMPPROFILE.PK,
					    T_PROFILE.FORENAME,
					    T_PROFILE.SURNAME,
					    T_CAMPPROFILE.MODULE)
			.from(T_CAMPPROFILE)
			.leftJoin(T_CAMP).on(T_CAMP.PK.eq(T_CAMPPROFILE.FK_CAMP))
			.leftJoin(T_PROFILE).on(T_PROFILE.PK.eq(T_CAMPPROFILE.FK_PROFILE))
			.where(T_CAMPPROFILE.FK_CAMP.eq(camp))
			.orderBy(T_PROFILE.SURNAME, T_PROFILE.FORENAME);
		// @formatter:on
		LOGGER.trace(sql);
		PrivilegesContainerBean pcb = new PrivilegesContainerBean();
		Iterator<Record4<Integer, String, String, EnumModule>> i = sql.fetch().iterator();
		while (i.hasNext()) {
			Record4<Integer, String, String, EnumModule> r = i.next();
			Integer fkCampProfile = r.get(T_CAMPPROFILE.PK);
			EnumModule moduleEnum = r.get(T_CAMPPROFILE.MODULE);
			String forename = r.get(T_PROFILE.FORENAME);
			String surname = r.get(T_PROFILE.SURNAME);
			String person = new StringBuilder().append(forename).append(" ").append(surname).toString();
			String module = moduleEnum == null ? null : moduleEnum.getLiteral();
			if (pcb.get(person) == null) {
				pcb.addKey(person);
			}
			pcb.add(person, IntKeyValueBean.of(fkCampProfile, module));
		}
		return pcb;
	}

	/**
	 * delete entry from camp profile
	 *
	 * @param id the pk
	 */
	public void deleteFromCampProfile(Integer id) {
		DeleteConditionStep<TCampprofileRecord> sql = jooq.deleteFrom(T_CAMPPROFILE).where(T_CAMPPROFILE.PK.eq(id));
		LOGGER.trace(sql);
		sql.execute();
	}

	/**
	 * add entry to database
	 *
	 * @param fkCamp    the camp ID
	 * @param fkProfile the profile ID
	 * @param module    the module
	 */
	public void addToCampProfile(Integer fkCamp, Integer fkProfile, EnumModule module) {
		InsertReturningStep<TCampprofileRecord> sql = jooq
		// @formatter:off
			.insertInto(T_CAMPPROFILE,
								  T_CAMPPROFILE.FK_CAMP,
								  T_CAMPPROFILE.FK_PROFILE,
								  T_CAMPPROFILE.MODULE)
			.values(fkCamp, fkProfile, module)
			.onConflict(T_CAMPPROFILE.FK_CAMP,
				  T_CAMPPROFILE.FK_PROFILE,
				  T_CAMPPROFILE.MODULE)
			.doNothing();
		// @formatter:on
		LOGGER.trace(sql);
		sql.execute();
	}

	/**
	 * get all camp document beans
	 *
	 * @return a list of camp document beans; an empty list at least
	 */
	public List<CampDocumentBean> getAllCampDocuments() {
		SelectWhereStep<TCampdocumentRecord> sql = jooq.selectFrom(T_CAMPDOCUMENT);
		LOGGER.trace(sql);
		return sql.fetchInto(CampDocumentBean.class);
	}

	/**
	 * add a camp document combination; if the combination already exists, ignore this
	 *
	 * @param fkCamp the camp id
	 * @param fkDocument the document id
	 */
	public void insertCampDocument(Integer fkCamp, Integer fkDocument) {
		InsertReturningStep<TCampdocumentRecord> sql = jooq
		// @formatter:off
			.insertInto(T_CAMPDOCUMENT,
					        T_CAMPDOCUMENT.FK_CAMP,
					        T_CAMPDOCUMENT.FK_DOCUMENT)
			.values(fkCamp, fkDocument)
			.onConflict(T_CAMPDOCUMENT.FK_CAMP, T_CAMPDOCUMENT.FK_DOCUMENT)
			.doNothing();
		// @formatter:off
		LOGGER.trace(sql);
		sql.execute();
	}

	/**
	 * update the camp document combination
	 *
	 * @param pk the ID of the combination
	 * @param fkCamp the ID of the camp
	 * @param fkDocument the ID of the document
	 */
	public void updateCampDocument(Integer pk, Integer fkCamp, Integer fkDocument) {
		UpdateConditionStep<TCampdocumentRecord> sql = jooq
		// @formatter:off
			.update(T_CAMPDOCUMENT)
			.set(T_CAMPDOCUMENT.FK_CAMP, fkCamp)
			.set(T_CAMPDOCUMENT.FK_DOCUMENT, fkDocument)
			.where(T_CAMPDOCUMENT.PK.eq(pk));
		// @formatter:on
		LOGGER.trace(sql);
		sql.execute();
	}

	/**
	 * delete from camp document
	 * @param id the ID of the camp document
	 */
	public void deleteCampDocument(Integer id) {
		DeleteConditionStep<TCampdocumentRecord> sql = jooq.deleteFrom(T_CAMPDOCUMENT).where(T_CAMPDOCUMENT.PK.eq(id));
		LOGGER.trace(sql);
		sql.execute();
	}
}
