/**
 * 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.List;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.patrikdufresne.managers.DatabaseUpdateHelper;
import com.patrikdufresne.managers.IManager;
import com.patrikdufresne.managers.ManagerException;
import com.patrikdufresne.managers.Managers;

/**
 * Used to easily access one set of manager using the same session factory.
 * 
 * @author Patrik Dufresne
 * 
 */
public class PlanimodManagers extends Managers {

    /**
     * Constant for version 1.0
     */
    public static final String VERSION_10 = "1.0";

    /**
     * Determine the database version
     * 
     * @param factory
     *            the session factory
     * @return the database version or null if it can be determined
     */
    private static String determineVersion(SessionFactory factory) {
        if (isVersion10(factory)) {
            return VERSION_10;
        }
        return null;
    }

    /**
     * Check if the database is version 10.
     * 
     * @param factory
     * @return True if the database metadata matches version10.
     */
    private static boolean isVersion10(SessionFactory factory) {
        String schema = DatabaseUpdateHelper.getSchema(factory);
        String catalog = DatabaseUpdateHelper.getCatalog(factory);
        List<String> expectedTables = Arrays.asList(
                "ApplicationSetting",
                "Employee",
                "EmployeePreference",
                "NonAvailability",
                "Position",
                "ProductionEvent",
                "Product",
                "ProductPosition",
                "Qualification",
                "Section",
                "Shift",
                "Team");
        for (String expectedTable : expectedTables) {
            if (DatabaseUpdateHelper.getTableMetadata(factory, expectedTable, schema, catalog) == null) {
                return false;
            }
        }
        return true;
    }

    /**
     * The application setting manager in this sets of managers.
     */
    private ApplicationSettingManager applicationSettingManager;
    /**
     * True if the database is a copy.
     */
    private boolean copy;
    /**
     * The employee manager in this sets of managers.
     */
    private EmployeeManager employeeManager;
    /**
     * The employee preference manager in this sets of managers.
     */
    private EmployeePreferenceManager employeePreferenceManager;

    /**
     * The calendar event manager in this sets of managers.
     */
    private NonAvailabilityManager nonAvailabilityManager;

    /**
     * The Position manager in this sets of managers.
     */
    private PositionManager positionManager;

    private ProductionEventManager productionEventManager;

    /**
     * The product manager in this sets of managers.
     */
    private ProductManager productManager;

    /**
     * The product position manager in this sets of managers.
     */
    private ProductPositionManager productPositionManager;

    /**
     * The Qualification manager in this sets of managers.
     */
    private QualificationManager qualificationManager;

    /**
     * The Section manager in this sets of managers.
     */
    private SectionManager sectionManager;

    private ShiftManager shiftManager;

    /**
     * This planif event manager in this sets of managers.
     */
    private TaskManager taskManager;

    /**
     * The non-availability manager in this sets of managers.
     */
    private TeamManager teamManager;

    /**
     * Create a new managers with the given session factory.
     * 
     * @param config
     *            the database configuration to be open
     * @throws ManagerException
     *             If the database can't be started or created
     */
    public PlanimodManagers(Configuration config) throws ManagerException {
        super(config);
    }

    @Override
    protected void configure(Configuration cfg) {
        super.configure(cfg);
        // Identify persistent class
        cfg.addAnnotatedClass(ApplicationSetting.class);
        cfg.addAnnotatedClass(Employee.class);
        cfg.addAnnotatedClass(EmployeePreference.class);
        cfg.addAnnotatedClass(NonAvailability.class);
        cfg.addAnnotatedClass(Task.class);
        cfg.addAnnotatedClass(Position.class);
        cfg.addAnnotatedClass(Product.class);
        cfg.addAnnotatedClass(ProductionEvent.class);
        cfg.addAnnotatedClass(ProductPosition.class);
        cfg.addAnnotatedClass(Qualification.class);
        cfg.addAnnotatedClass(Section.class);
        cfg.addAnnotatedClass(Team.class);
        cfg.addAnnotatedClass(Shift.class);
    }

    public ApplicationSettingManager getApplicationSettingManager() {
        if (this.applicationSettingManager == null) {
            this.applicationSettingManager = new ApplicationSettingManager(this);
        }
        return this.applicationSettingManager;
    }

    /**
     * Return the employee manager in this sets of managers.
     * 
     * @return
     */
    public EmployeeManager getEmployeeManager() {
        if (this.employeeManager == null) {
            this.employeeManager = new EmployeeManager(this);
        }
        return this.employeeManager;
    }

    public EmployeePreferenceManager getEmployeePreferenceManager() {
        if (this.employeePreferenceManager == null) {
            this.employeePreferenceManager = new EmployeePreferenceManager(this);
        }
        return this.employeePreferenceManager;
    }

    @Override
    public IManager getManagerForClass(Class clazz) {
        IManager m[] = new IManager[] {
                this.getApplicationSettingManager(),
                this.getTeamManager(),
                this.getNonAvailabilityManager(),
                this.getEmployeeManager(),
                this.getEmployeePreferenceManager(),
                this.getTaskManager(),
                this.getPositionManager(),
                this.getProductManager(),
                this.getProductPositionManager(),
                this.getQualificationManager(),
                this.getSectionManager(),
                this.getShiftManager(),
                this.getProductionEventManager() };
        IManager match = null;
        IManager perfectMatch = null;
        for (IManager n : m) {
            if (n.objectClass().equals(clazz)) {
                perfectMatch = n;
            } else if (n.objectClass().isAssignableFrom(clazz)) {
                match = n;
            }
        }
        if (perfectMatch != null) {
            return perfectMatch;
        }
        return match;
    }

    public NonAvailabilityManager getNonAvailabilityManager() {
        if (this.nonAvailabilityManager == null) {
            this.nonAvailabilityManager = new NonAvailabilityManager(this);
        }
        return this.nonAvailabilityManager;
    }

    /**
     * Return the position manager in this sets of managers.
     * 
     * @return
     */
    public PositionManager getPositionManager() {
        if (this.positionManager == null) {
            this.positionManager = new PositionManager(this);
        }
        return this.positionManager;
    }

    public ProductionEventManager getProductionEventManager() {
        if (this.productionEventManager == null) {
            this.productionEventManager = new ProductionEventManager(this);
        }
        return this.productionEventManager;
    }

    /**
     * Return the shift manager in this sets of managers.
     * 
     * @return
     */
    public ProductManager getProductManager() {
        if (this.productManager == null) {
            this.productManager = new ProductManager(this);
        }
        return this.productManager;
    }

    /**
     * Return the product-position manager in this sets of managers.
     * 
     * @return
     */
    public ProductPositionManager getProductPositionManager() {
        if (this.productPositionManager == null) {
            this.productPositionManager = new ProductPositionManager(this);
        }
        return this.productPositionManager;
    }

    /**
     * Return the qualification manager from this sets of managers.
     * 
     * @return
     */
    public QualificationManager getQualificationManager() {
        if (this.qualificationManager == null) {
            this.qualificationManager = new QualificationManager(this);
        }
        return this.qualificationManager;
    }

    /**
     * Return the section manager from this sets of managers.
     * 
     * @return
     */
    public SectionManager getSectionManager() {
        if (this.sectionManager == null) {
            this.sectionManager = new SectionManager(this);
        }
        return this.sectionManager;
    }

    public ShiftManager getShiftManager() {
        if (this.shiftManager == null) {
            this.shiftManager = new ShiftManager(this);
        }
        return this.shiftManager;
    }

    /**
     * Return the planif event manager in this sets of managers.
     * 
     * @return
     */
    public TaskManager getTaskManager() {
        if (this.taskManager == null) {
            this.taskManager = new TaskManager(this);
        }
        return this.taskManager;
    }

    /**
     * Return the non-availability manager in this sets of managers.
     * 
     * @return
     */
    public TeamManager getTeamManager() {
        if (this.teamManager == null) {
            this.teamManager = new TeamManager(this);
        }
        return this.teamManager;
    }

    /**
     * Return true if the database is a copy.
     * 
     * @return
     */
    public boolean isCopy() {
        return this.copy;
    }

    /**
     * Set the copy mode.
     * 
     * @param copy
     */
    public void setIsCopy(boolean copy) {
        this.copy = copy;
    }

    /**
     * This implementation check if the database is valid and update it.
     */
    @Override
    protected void updateDatabase(SessionFactory factory) {
        String version = determineVersion(factory);
        if (version == null) {
            throw new HibernateException("Database is invalid.");
        }
        if (version.equals(VERSION_10)) {
            return;
        }
    }

    /**
     * This implementation check the database version.
     */
    @Override
    protected void validateDatabase(SessionFactory factory) {
        String version = determineVersion(factory);
        if (version == null) {
            throw new HibernateException("Database is invalid.");
        }
        if (!version.equals(VERSION_10)) {
            throw new HibernateException("Database should be updated.");
        }
    }

}
