/**
 * Copyright © CENRA (2016)
 *
 * remi.clement@espaces-naturels.fr
 *
 * This software is a multi-platform and portable application based on
 * the scientifical toolbox to monitor wetlands. With this toolbox it’s
 * possible to calculate indicators «RhoMéO» to monitor quality,
 * fonctionalities and urban and agricultural pressures of wetlands.
 * Indicators are calculated using naturalist data entered and/or imported,
 * and geographic data downloaded from a geographic server.
 *
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use,
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info".
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability.
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or
 * data to be ensured and,  more generally, to use and operate it in the
 * same conditions as regards security.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
*/
package fr.cenra.rhomeo.core.util;

import fr.cenra.rhomeo.api.Version;
import fr.cenra.rhomeo.api.data.DataContext;
import fr.cenra.rhomeo.api.data.Protocol;
import fr.cenra.rhomeo.api.data.Reference;
import fr.cenra.rhomeo.api.data.Site;
import fr.cenra.rhomeo.core.RhomeoCore;
import fr.cenra.rhomeo.core.Session;
import fr.cenra.rhomeo.core.data.site.SiteRepository;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import javafx.collections.ObservableMap;

/**
 * A simple POJO writable using Jackson. The aim is to persist information of a
 * {@link  DataContext}.
 *
 * @author Alexis Manin (Geomatys)
 */
public class SerializableDataContext {

    /**
     * Name of the target site.
     */
    public String siteName;

    /**
     * Person in charge of the pointed site.
     */
    public String referent;

    /**
     * Organisation in charge of the site management.
     */
    public String organisation;

    /**
     * Name of the target protocol.
     */
    public String protocolName;

    /**
     * ISO Zoned date and time of the creation of the context.
     */
    public String date;

    /**
     * Contains versions used for reference list inside application. The key is
     * the canonical name of the pointed {@link Reference} class, and value is
     * the string representation of the {@link Version}.
     */
    public Map<String, String> referenceVersions = new HashMap<>();

    public Map<String, Object> userData = new HashMap<>();

    /**
     * Try to set the session data context from the current object.
     * @param session
     * @return True if we succeeded to set session data context according to
     * current object. False if there's missing information which prevent us
     * from doing so.
     */
    public boolean restoreDataContext(final Session session) {
        final Site site = session.getBean(SiteRepository.class).findOne(siteName);
        if (site == null)
            return false;

        final Optional<Protocol> protocolOptional = session.getProtocols().stream()
                .filter(protocolCandidate -> protocolName.equals(protocolCandidate.getName()))
                .findFirst();
        if (!protocolOptional.isPresent()) {
            return false;
        }

        final Protocol protocol = protocolOptional.get();
        session.startEdition(site, protocol);
        if (referenceVersions != null && !referenceVersions.isEmpty()) {
            ObservableMap<Class<Reference>, Version> refs = session.getDataContext().getReferences();
            for (final Map.Entry<String, String> entry : referenceVersions.entrySet()) {
                // Using protocol class loader should be good, as it is sensed to reference the reference class it use.
                try {
                    refs.put((Class) Class.forName(entry.getKey(), true, protocol.getClass().getClassLoader()), new Version(entry.getValue()));
                } catch (ClassNotFoundException e) {
                    RhomeoCore.LOGGER.log(Level.WARNING, "Cannot restore a reference version preference.", e);
                }
            }
        }

        session.getDataContext().getUserData().putAll(userData);

        return true;
    }

    /**
     * Converts into a {@linkplain DataContext data context}.
     *
     * @param session
     * @return A data context representing this instance, or {@code null} if no site or protocol defined in the session.
     */
    public DataContext toDataContext(final Session session) {
        if (session == null) {
            return null;
        }
        final Site site = session.getBean(SiteRepository.class).findOne(siteName);
        if (site == null)
            return null;

        final Optional<Protocol> protocolOptional = session.getProtocols().stream()
                .filter(protocolCandidate -> protocolName.equals(protocolCandidate.getName()))
                .findFirst();
        if (!protocolOptional.isPresent()) {
            return null;
        }

        final Protocol protocol = protocolOptional.get();
        final DataContext dataContext = new DataContext(site, protocol);
        if (referenceVersions == null || referenceVersions.isEmpty()) {
            return dataContext;
        }

        ObservableMap<Class<Reference>, Version> refs = dataContext.getReferences();
        for (final Map.Entry<String, String> entry : referenceVersions.entrySet()) {
            // Using protocol class loader should be good, as it is sensed to reference the reference class it use.
            try {
                refs.put((Class) Class.forName(entry.getKey(), true, protocol.getClass().getClassLoader()), new Version(entry.getValue()));
            } catch (ClassNotFoundException e) {
                RhomeoCore.LOGGER.log(Level.WARNING, "Cannot restore a reference version preference.", e);
            }
        }

        dataContext.getUserData().putAll(userData);

        return dataContext;
    }

    public static SerializableDataContext fromDataContext(final DataContext context) {
        final SerializableDataContext ctx = new SerializableDataContext();
        ctx.protocolName = context.getProtocol().getName();
        ctx.siteName = context.getSite().getName();
        ctx.referent = context.getSite().getReferent();
        ctx.organisation = context.getSite().getOrganization();
        ctx.date = context.getDate().toString();
        for (final Map.Entry<Class<Reference>, Version> entry : context.getReferences().entrySet()) {
            ctx.referenceVersions.put(entry.getKey().getCanonicalName(), entry.getValue().toString());
        }

        ctx.userData.putAll(context.getUserData());

        return ctx;
    }

}
