/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.components.optimizer.gui.view;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.LogFactory;

import de.rcenvironment.components.optimizer.common.Dimension;
import de.rcenvironment.components.optimizer.common.Measure;
import de.rcenvironment.components.optimizer.common.OptimizerReceiver;
import de.rcenvironment.components.optimizer.common.OptimizerResultService;
import de.rcenvironment.components.optimizer.common.OptimizerResultSet;
import de.rcenvironment.components.optimizer.common.ResultSet;
import de.rcenvironment.components.optimizer.common.ResultStructure;
import de.rcenvironment.core.communication.common.ResolvableNodeId;
import de.rcenvironment.core.utils.common.rpc.RemoteOperationException;

/**
 * Study holding values generated by the {@link ParametricStudyComponent}.
 * 
 * @author Sascha Zur
 */
public final class OptimizerDatastore extends ResultSet {

    private static final long serialVersionUID = 990775937058384209L;

    // needed to hold reference otherwise it is can not be called back
    private static List<DatasetNotificationSubscriber> notificationSubscriber;

    private final List<OptimizerResultSet> datasets = Collections.synchronizedList(new LinkedList<OptimizerResultSet>());

    private final List<OptimizerResultSetAddListener> listeners = new LinkedList<OptimizerResultSetAddListener>();

    private final Map<String, Double> minValues = new HashMap<String, Double>();

    private final Map<String, Double> maxValues = new HashMap<String, Double>();

    public OptimizerDatastore(final String identifier, final String title, final ResultStructure structure) {
        super(identifier, title, structure);
    }

    /**
     * @param dataset new values.
     */
    public void addDataset(final OptimizerResultSet dataset) {
        datasets.add(dataset);
        fireDatasetAdd(dataset);
        for (final Dimension dimension : getStructure().getDimensions()) {
            adjustMinMaxRange(dataset, dimension.getName());
        }
        for (final Measure measure : getStructure().getMeasures()) {
            adjustMinMaxRange(dataset, measure.getName());
        }
    }

    private void adjustMinMaxRange(final OptimizerResultSet dataset,
        final String key) {
        final double value = dataset.getValue(key);
        if (minValues.get(key) == null
            || minValues.get(key) > value) {
            minValues.put(key, value);
        }
        if (maxValues.get(key) == null
            || maxValues.get(key) < value) {
            maxValues.put(key, value);
        }
    }

    public Collection<OptimizerResultSet> getDatasets() {
        return Collections.unmodifiableCollection(datasets);
    }

    public int getDatasetCount() {
        return datasets.size();
    }

    /**
     * @param key of the relevant value chain.
     * @return the minimum value of the chain.
     */
    public Double getMinValue(final String key) {
        return minValues.get(key);
    }

    /**
     * @param key of the relevant value chain.
     * @return the maximum value of the chain.
     */
    public Double getMaxValue(final String key) {
        return maxValues.get(key);
    }

    /**
     * Connects a {@link ResultReceiver} to the {@link StudyPublisher}.
     * 
     * @param identifier the unique identifier
     * @param platform the platform to receive updates from
     * @param optimizerResultService instance of {@link OptimizerResultService}
     * @return created {@link OptimizerDatastore}.
     */
    public static OptimizerDatastore connect(final String identifier, final ResolvableNodeId platform,
        OptimizerResultService optimizerResultService) {
        OptimizerReceiver receiver;
        try {
            receiver = optimizerResultService.createReceiver(identifier, platform);
            if (receiver == null) {
                LogFactory.getLog(OptimizerDatastore.class).error("Failed to get values from node: " + platform);
                return null;
            }
            final OptimizerDatastore datastore = new OptimizerDatastore(identifier,
                receiver.getStudy().getTitle(), receiver.getStudy().getStructure());
            if (notificationSubscriber == null) {
                notificationSubscriber = new LinkedList<DatasetNotificationSubscriber>();
            }

            DatasetNotificationSubscriber newNotificationSubscriber = new DatasetNotificationSubscriber(datastore);
            receiver.setNotificationSubscriber(newNotificationSubscriber);
            receiver.initialize();
            notificationSubscriber.add(newNotificationSubscriber);
            return datastore;
        } catch (RemoteOperationException e) {
            LogFactory.getLog(OptimizerDatastore.class).error("Failed to get values from remote node: " + platform);
        }
        return null;
    }

    /**
     * @param listener for {@link StudyDataset}to add.
     */
    public void addDatasetAddListener(final OptimizerResultSetAddListener listener) {
        listeners.add(listener);
    }

    /**
     * @param listener for {@link StudyDataset} to remove.
     */
    public void removeDatasetAddListener(final OptimizerResultSetAddListener listener) {
        listeners.remove(listener);
    }

    private void fireDatasetAdd(final OptimizerResultSet dataset) {
        final OptimizerResultSetAddListener[] listenersArray = listeners.toArray(new OptimizerResultSetAddListener[0]);
        for (final OptimizerResultSetAddListener listener : listenersArray) {
            try {
                listener.handleStudyDatasetAdd(dataset);
            } catch (RuntimeException e) {
                e = null; // ignore
            }
        }
    }

    /**
     * Needs to be implemented by classes which are interested in {@link StudyDataset}.
     * 
     * @author Doreen Seider
     */
    public interface OptimizerResultSetAddListener {

        /**
         * @param dataset the new {@link StudyDataset}.
         */
        void handleStudyDatasetAdd(final OptimizerResultSet dataset);

    }

}
