/*
* Copyright (C) 2018, 2019 Andrew Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application 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/gpl.html>.
 */

/*
	Change log
	2.1.0   support multiple Trugs and multiple servers per Trug
 */

package uk.co.gardennotebook.spi;

//import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.prefs.Preferences;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;

/**
 * Obtains an instance of a Trug using the Java Service Loader allowing for
 * pluggable Trug services.
 * 
 * @author Andrew Gegg
*	@version	2.1.0
*	@since	1.0
 */
public final class TrugServer {
    
	private static final Logger LOGGER = LogManager.getLogger();
    
    private static TrugServer server;
    private static ITrug trug;
	
	private final ServiceLoader<ITrug> loader;
    
    private TrugServer()
    {
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("private constructor");
		loader = ServiceLoader.load(ITrug.class);
        LOGGER.debug("loader: {}", loader);
////        load the pluggable services
//        trug = new MySQLTrug();
//		if (!trug.isAvailable())
//		{
////			System.out.println("notebook.TrugServer.<init>(): no MySQL available");
//			LOGGER.trace("notebook.TrugServer.<init>(): no MySQL available");
//		}
    }

	/**
	 * Obtain a singleton instance of a TrugServer.
	 * 
	 * @return	a TrugServer 
	 */
    public static TrugServer getTrugServer()
    {
        if (server == null)
		{
            server = new TrugServer();
		}
        return server;
    }

	/**
	 * Obtain a list of available Trugs (services).<BR>
	 * 
	 * Each Trug has a service type (e.g. SQL) and a list of servers.<BR>
	 * The service type is the type of service offered, e.g for a SQL based RDBMS 'SQL'.<BR>
     * Each Trug may offer access to more than one server, e.g. a SQL Trug may provide access to
     * several different RDBMSs, e.g. MySQL, MariaDB. Each such server is included in a Map keyed
     * on the name of the RDBMS (e.g. 'MySQL') with a list of properties including at least
     * the name and whether or not it's supported (i.e. was a driver found for it).
	 * 
	 * @return	a map of (name, service_details) pairs
	 */
	public Map<String, Map<String, Properties>> getServices()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("getServices()");
		HashMap<String, Map<String, Properties>> services = new HashMap<>();
        
        for (ITrug d : loader)
        {// this loops over the Trug services available, currently only the SQL based Trugs
            LOGGER.debug("getServices(); in loop");
            services.put(d.getServiceType(), d.getServers());
        }
        return 	LOGGER.traceExit(log4jEntryMsg, services);
	}

	/**
	 * Check that the selected Trug (service) is actually available.
	 * 
	 * The presence of the relevant package in the class path does <B>not</B> mean the DB service is actually running.
	 * 
	 * @param prefs	a pre-populated list of Preferences.  Those used here are:<UL>
     *              <LI>selectedTrug    - the type of service - see {@link #getServices() getServices()}
	 *				<LI>selectedServer  - the actual server within the Trug - see {@link #getServices() getServices()}
	 * </UL>
	 * @return <UL><LI>OK - the requested service is available
	 *			<LI>FAILED - the requested service is <B>not</B> available to be loaded from the class path
	 *			<LI>a service error message if the underlying DB service cannot be accessed
	 * </UL>
	 */
	public String checkTrug(Preferences prefs)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("checkTrug()");
		if (trug != null)
		{
			return "OK";
		}
		try 
		{
			Iterator<ITrug> trugs = loader.iterator();
			LOGGER.debug("checkTrug(); iterator: {}: prefs.selectedserver: {}", trugs, prefs.get("selectedServer", ""));
			while (trug == null && trugs.hasNext())
			{
				LOGGER.debug("checkTrug(); in loop");
				ITrug d = trugs.next();
                d.getServers();
                LOGGER.trace("trug.serviceType: {}, trug.name: {}", d.getServiceType(), d.getName());
				if (!d.getServiceType().equals(prefs.get("selectedTrug", "")))
				{
					continue;
				}
				LOGGER.debug("checkTrug(); in loop: selected service: {}", d.getName());
				if (d.isAvailable(prefs.node(prefs.get("selectedTrug", ""))))
				{
					trug = d;
				}
			}
		} catch (ServiceConfigurationError serviceError) {
			trug = null;
			LOGGER.catching(serviceError);
			return serviceError.getLocalizedMessage();
		} catch (GNDBException sqlError) {
			trug = null;
			return sqlError.getLocalizedMessage();
		}
		return LOGGER.traceExit(log4jEntryMsg, (trug == null) ? "FAILED" : "OK");
	}

	/**
	 * Obtain a Trug which can be used to access the persisted Garden Notebook
	 * 
	 * @return	a Trug
	 */
    public ITrug getTrug()
    {
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("getTrug()");
		if (trug == null)
		{
//			try 
//			{
//				Iterator<ITrug> trugs = loader.iterator();
//				LOGGER.debug("getTrug(); iterator: {}", trugs);
//				while (trug == null && trugs.hasNext())
//				{
//					LOGGER.debug("getTrug(); in loop");
//					ITrug d = trugs.next();
//					if (d.isAvailable())
//					{
//						trug = d;
//					}
//				}
//			} catch (ServiceConfigurationError serviceError) {
//				trug = null;
//				serviceError.printStackTrace();
//			}
		}
        return 	LOGGER.traceExit(log4jEntryMsg, trug);
    }
}
