/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package com.planimod.core;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import com.patrikdufresne.managers.AbstractManager;
import com.patrikdufresne.managers.Exec;
import com.patrikdufresne.managers.ManagerContext;
import com.patrikdufresne.managers.ManagerException;
import com.patrikdufresne.managers.Query;
import com.patrikdufresne.util.BidiMultiHashMap;
import com.patrikdufresne.util.BidiMultiMap;

//TODO is a qualification is removed, make sure to un-assigned the preferred position.

/**
 * Object manager for qualification.
 * 
 * @author Patrik Dufresne
 * 
 */
public class QualificationManager extends AbstractManager<Qualification> {

    public QualificationManager(PlanimodManagers managers) {
        super(managers);
    }

    /**
     * This function is used to set the qualification for a given pair of employee-position.
     * 
     * @param position
     *            the position
     * @param employee
     *            the employee
     * @param qualify
     *            True if the employee is qualify, false otherwise
     * @throws ManagerException
     */
    public void setQualification(final Position position, final Employee employee, final boolean qualify) throws ManagerException {
        getManagers().exec(new Exec() {
            @Override
            public void run() throws ManagerException {

                Qualification q = listByPositionAndEmployee(position, employee);
                if (q == null && qualify) {
                    Qualification qualif = new Qualification();
                    qualif.setEmployee(employee);
                    qualif.setPosition(position);
                    add(Arrays.asList(qualif));
                } else if (q != null && !qualify) {
                    remove(Arrays.asList(q));
                }

            }
        });
    }

    /**
     * Check if the qualification already exists.
     */
    @Override
    protected void preAddObject(Qualification q) throws ManagerException {
        if (listByPositionAndEmployee(q.getPosition(), q.getEmployee()) != null) {
            throw new IntegrityException("Qualification already exists.");
        }
    }

    /**
     * List qualification objects matching the given position and employee.
     * 
     * @param position
     *            the position
     * @param employee
     *            the employee
     * @throws ManagerException
     */
    protected Qualification listByPositionAndEmployee(final Position position, final Employee employee) throws ManagerException {
        return getManagers().query(new Query<Qualification>() {
            @Override
            public Qualification run() throws ManagerException {
                return (Qualification) ManagerContext
                        .getDefaultSession()
                        .createCriteria(objectClass())
                        .add(Restrictions.eq(Qualification.POSITION, position))
                        .add(Restrictions.eq(Qualification.EMPLOYEE, employee))
                        .uniqueResult();
            }
        });
    }

    /**
     * List of position for which the given employees are qualify for.
     * 
     * @param employee
     *            the employees to check
     * @return list of position
     * @throws ManagerException
     */
    public List<Position> listQualifyPositionForEmployee(final Collection<Employee> employees) throws ManagerException {
        return getManagers().query(new Query<List<Position>>() {
            @Override
            public List<Position> run() throws ManagerException {
                return ManagerContext
                        .getDefaultSession()
                        .createCriteria(Qualification.class)
                        .add(Restrictions.in(Qualification.EMPLOYEE, employees))
                        .setProjection(Projections.distinct(Projections.property(Qualification.POSITION)))
                        .list();
            }
        });
    }

    /**
     * Create a BidiMultiMap to represent the qualification of the requested employees and positions.
     * <p>
     * The table contains only not archived employee.
     * 
     * @param employees
     *            the employees
     * @param position
     *            the positions
     * @return the map
     * @throws ManagerException
     */
    public BidiMultiMap<Employee, Position> getQualificationTableByPositions(final Collection<Position> positions) throws ManagerException {

        if (positions.size() == 0) {
            return BidiMultiMap.EMPTY;
        }

        return getManagers().query(new Query<BidiMultiMap<Employee, Position>>() {
            @SuppressWarnings("unchecked")
            @Override
            public BidiMultiMap<Employee, Position> run() throws ManagerException {

                org.hibernate.Query namedQuery = ManagerContext.getDefaultSession().getNamedQuery("getQualificationTableByPositions");
                namedQuery.setParameterList("positions", positions);

                BidiMultiMap<Employee, Position> table = new BidiMultiHashMap<Employee, Position>();
                for (Object[] obj : (Collection<Object[]>) namedQuery.list()) {
                    table.put((Employee) obj[0], (Position) obj[1]);
                }
                return table;

            }
        });

    }

    @Override
    public Class<Qualification> objectClass() {
        return Qualification.class;
    }

    /**
     * This function is used to remove any qualification catching the given pair of employee-position.
     * 
     * @param position
     *            the position
     * @param employee
     *            the employee
     * @throws ManagerException
     */
    public void removeQualification(final Position position, final Employee employee) throws ManagerException {
        getManagers().exec(new Exec() {
            @Override
            public void run() throws ManagerException {
                Qualification qualif = listByPositionAndEmployee(position, employee);
                remove(Arrays.asList(qualif));
            }
        });
    }

}
