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

package de.rcenvironment.components.doe.execution.validator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.databind.ObjectMapper;

import de.rcenvironment.components.doe.common.DOEAlgorithms;
import de.rcenvironment.components.doe.common.DOEConstants;
import de.rcenvironment.components.doe.execution.Messages;
import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.model.api.ComponentDescription;
import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription;
import de.rcenvironment.core.component.validation.api.ComponentValidationMessage;
import de.rcenvironment.core.component.validation.spi.AbstractLoopComponentValidator;
import de.rcenvironment.core.utils.common.JsonUtils;

/**
 * Validator for DOE component.
 * 
 * @author Sascha Zur
 * @author Jascha Riedel
 */
public class DOEComponentValidator extends AbstractLoopComponentValidator {

    @Override
    public String getIdentifier() {
        return DOEConstants.COMPONENT_ID;
    }

    @Override
    protected List<ComponentValidationMessage> validateLoopComponentSpecific(
        ComponentDescription componentDescription) {

        List<ComponentValidationMessage> messages = new ArrayList<>();

        final boolean hasOutputs = getOutputs(componentDescription).size() > 0;

        String methodName = getProperty(componentDescription, DOEConstants.KEY_METHOD);
        if (!hasOutputs) {
            final ComponentValidationMessage noInputMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, "", Messages.noOutputsDefinedLong,
                Messages.noOutputsDefinedLong);
            messages.add(noInputMessage);
        } else {
            int outputCount = getOutputs(componentDescription).size();
            int runNumber = Integer.parseInt(getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER));
            final ComponentValidationMessage tooManySamplesMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, "", Messages.tooManySamples, Messages.tooManySamples);

            if (methodName
                .equals(DOEConstants.DOE_ALGORITHM_FULLFACT)) {
                if (Math.pow(runNumber, outputCount) >= DOEAlgorithms.MAXMIMAL_RUNS) {
                    messages.add(tooManySamplesMessage);
                }
            } else {
                if (runNumber >= DOEAlgorithms.MAXMIMAL_RUNS) {
                    messages.add(tooManySamplesMessage);
                }
            }
        }
        if (!(methodName.equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE) || methodName.equals(DOEConstants.DOE_ALGORITHM_MONTE_CARLO)
            || methodName.equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE_INPUT))
            && getOutputs(componentDescription).size() < 2) {
            final ComponentValidationMessage outputTooLow = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_METHOD, Messages.numOutputsG2Long,
                Messages.numOutputsG2Long);
            messages.add(outputTooLow);

        }
        if (methodName.equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)) {
            checkStartAndEndSample(componentDescription, messages);
            checkTableDimensions(componentDescription, messages);
        }
        if (methodName.equals(DOEConstants.DOE_ALGORITHM_FULLFACT)) {
            if (getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER) == null
                || Integer.parseInt(getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER)) < 2) {
                final ComponentValidationMessage noInputMessage = new ComponentValidationMessage(
                    ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_RUN_NUMBER,
                    Messages.numLevelsInvalidLong, Messages.numLevelsInvalidLong);
                messages.add(noInputMessage);
            }
        }

        return messages;
    }

    @Override
    protected List<ComponentValidationMessage> validateOnWorkflowStartComponentSpecific(
        ComponentDescription componentDescription) {
        // TODO Auto-generated method stub
        return null;
    }

    private void checkTableDimensions(ComponentDescription componentDescription,
        List<ComponentValidationMessage> messages) {
        String table = getProperty(componentDescription, DOEConstants.KEY_TABLE);

        if (table == null || table.isEmpty() || table.equals("null")) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_TABLE, Messages.noTableLong,
                Messages.noTableLong);
            messages.add(startSampleErrorMessage);
        } else {
            ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
            try {
                Double[][] tableValues = mapper.readValue(table,
                    Double[][].class);
                if (tableValues != null && tableValues.length > 0 && (tableValues[0] != null
                    && tableValues[0].length < getOutputs(componentDescription).size())) {
                    final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                        ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_TABLE,
                        Messages.tableTooShortLong, Messages.tableTooShortLong);
                    messages.add(startSampleErrorMessage);
                }
                if (tableValues != null && tableValues.length > 0
                    && tableValues[0].length > getOutputs(componentDescription).size()) {
                    final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                        ComponentValidationMessage.Type.WARNING, DOEConstants.KEY_TABLE,
                        Messages.tableTooLongLong, Messages.tableTooLongLong);
                    messages.add(startSampleErrorMessage);
                }
                final ComponentValidationMessage noValueError = new ComponentValidationMessage(
                    ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_TABLE,
                    Messages.undefinedValues, Messages.undefinedValues);
                for (int i = 0; (i < Integer
                    .parseInt(getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER))
                    && (tableValues != null && i < tableValues.length)); i++) {
                    for (int j = 0; j < tableValues[i].length; j++) {
                        if (tableValues[i][j] == null) {
                            messages.add(noValueError);
                            break;
                        }
                    }
                    if (messages.contains(noValueError)) {
                        break;
                    }
                }
                if (getProperty(componentDescription, DOEConstants.KEY_METHOD)
                    .equals(DOEConstants.DOE_ALGORITHM_CUSTOM_TABLE)
                    && (getProperty(componentDescription, DOEConstants.KEY_END_SAMPLE) != null
                        && !getProperty(componentDescription, DOEConstants.KEY_END_SAMPLE).isEmpty()
                        && Double.parseDouble(getProperty(componentDescription,
                            DOEConstants.KEY_END_SAMPLE)) > Double.parseDouble(
                                getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER))
                                - 1)) {
                    final ComponentValidationMessage endSampleErrorMessage = new ComponentValidationMessage(
                        ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_END_SAMPLE,
                        Messages.endSampleTooHighLong, Messages.endSampleTooHighLong);
                    messages.add(endSampleErrorMessage);
                }
            } catch (IOException e) {
                final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                    ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_TABLE, Messages.noTableLong,
                    Messages.noTableLong);
                messages.add(startSampleErrorMessage);
            }

        }

    }

    private void checkStartAndEndSample(ComponentDescription componentDescription,
        final Collection<ComponentValidationMessage> messages) {
        String startSampleString = getProperty(componentDescription, DOEConstants.KEY_START_SAMPLE);
        Integer startSample = null;
        try {
            startSample = Integer.parseInt(startSampleString);
        } catch (NumberFormatException e) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_START_SAMPLE,
                Messages.startSampleNotInteger, Messages.startSampleNotInteger);
            messages.add(startSampleErrorMessage);
        }
        if (startSample != null && startSample < 0) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_START_SAMPLE, Messages.startSampleG0,
                Messages.startSampleG0);
            messages.add(startSampleErrorMessage);
        }
        if (startSample != null
            && startSample > Integer.parseInt(getProperty(componentDescription, DOEConstants.KEY_RUN_NUMBER))) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_START_SAMPLE, Messages.startSampleTooHigh,
                Messages.startSampleTooHigh);
            messages.add(startSampleErrorMessage);
        }
        String endSampleString = getProperty(componentDescription, DOEConstants.KEY_END_SAMPLE);
        Integer endSample = null;
        try {
            endSample = Integer.parseInt(endSampleString);
        } catch (NumberFormatException e) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_END_SAMPLE, Messages.endSampleNotInteger,
                Messages.endSampleNotInteger);
            messages.add(startSampleErrorMessage);
        }
        if (endSample != null && endSample < 0) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_END_SAMPLE, Messages.endSampleG0,
                Messages.endSampleG0);
            messages.add(startSampleErrorMessage);
        }
        if (startSample == null || endSample == null || startSample > endSample) {
            final ComponentValidationMessage startSampleErrorMessage = new ComponentValidationMessage(
                ComponentValidationMessage.Type.ERROR, DOEConstants.KEY_END_SAMPLE, Messages.endSampleGStart,
                Messages.endSampleGStart);
            messages.add(startSampleErrorMessage);
        }

    }

    @Override
    protected Set<EndpointDescription> getOutputs(ComponentDescription componentDescription) {
        Set<EndpointDescription> outputs = new HashSet<>(super.getOutputs(componentDescription));

        Iterator<EndpointDescription> outputsIterator = outputs.iterator();
        while (outputsIterator.hasNext()) {
            EndpointDescription next = outputsIterator.next();
            if (LoopComponentConstants.ENDPOINT_ID_TO_FORWARD.equals(next.getDynamicEndpointIdentifier())
                || LoopComponentConstants.ENDPOINT_NAME_LOOP_DONE.equals(next.getName())
                || DOEConstants.OUTPUT_NAME_NUMBER_OF_SAMPLES.equals(next.getName())) {
                outputsIterator.remove();
            }
        }
        return outputs;
    }

}
