/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.config.score.director;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.apache.commons.lang3.BooleanUtils;
import org.kie.api.KieBase;
import org.kie.api.KieBaseConfiguration;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.io.KieResources;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.builder.conf.PropertySpecificOption;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.AbstractConfig;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.score.definition.ScoreDefinitionType;
import org.optaplanner.core.config.score.trend.InitializingScoreTrendLevel;
import org.optaplanner.core.config.solver.EnvironmentMode;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.config.util.KeyAsElementMapConverter;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.score.buildin.bendable.BendableScoreDefinition;
import org.optaplanner.core.impl.score.buildin.bendablebigdecimal.BendableBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.bendablelong.BendableLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardmediumsoft.HardMediumSoftScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardmediumsoftlong.HardMediumSoftLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoft.HardSoftScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftbigdecimal.HardSoftBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftdouble.HardSoftDoubleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.hardsoftlong.HardSoftLongScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simple.SimpleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simplebigdecimal.SimpleBigDecimalScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simpledouble.SimpleDoubleScoreDefinition;
import org.optaplanner.core.impl.score.buildin.simplelong.SimpleLongScoreDefinition;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.optaplanner.core.impl.score.director.AbstractScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.InnerScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.drools.DroolsScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.drools.LegacyDroolsScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.drools.testgen.TestGenDroolsScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.drools.testgen.TestGenLegacyDroolsScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.easy.EasyScoreCalculator;
import org.optaplanner.core.impl.score.director.easy.EasyScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.incremental.IncrementalScoreCalculator;
import org.optaplanner.core.impl.score.director.incremental.IncrementalScoreDirectorFactory;
import org.optaplanner.core.impl.score.trend.InitializingScoreTrend;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@XStreamAlias("scoreDirectorFactory")
public class ScoreDirectorFactoryConfig extends AbstractConfig<ScoreDirectorFactoryConfig> {

    private static final Logger logger = LoggerFactory.getLogger(ScoreDirectorFactoryConfig.class);

    @Deprecated protected Class<? extends ScoreDefinition> scoreDefinitionClass = null;
    @Deprecated protected ScoreDefinitionType scoreDefinitionType = null;
    @Deprecated protected Integer bendableHardLevelsSize = null;
    @Deprecated protected Integer bendableSoftLevelsSize = null;

    protected Class<? extends EasyScoreCalculator> easyScoreCalculatorClass = null;
    @XStreamConverter(KeyAsElementMapConverter.class)
    protected Map<String, String> easyScoreCalculatorCustomProperties = null;

    protected Class<? extends IncrementalScoreCalculator> incrementalScoreCalculatorClass = null;
    @XStreamConverter(KeyAsElementMapConverter.class)
    protected Map<String, String> incrementalScoreCalculatorCustomProperties = null;

    protected String ksessionName = null;
    @XStreamOmitField
    @Deprecated // TODO remove in 8.0
    protected KieBase kieBase = null;
    @XStreamImplicit(itemFieldName = "scoreDrl")
    protected List<String> scoreDrlList = null;
    @XStreamImplicit(itemFieldName = "scoreDrlFile")
    protected List<File> scoreDrlFileList = null;
    @XStreamConverter(KeyAsElementMapConverter.class)
    protected Map<String, String> kieBaseConfigurationProperties = null;
    protected Boolean generateDroolsTestOnError = null;

    protected String initializingScoreTrend = null;

    @XStreamAlias("assertionScoreDirectorFactory")
    protected ScoreDirectorFactoryConfig assertionScoreDirectorFactory = null;

    // ************************************************************************
    // Constructors and simple getters/setters
    // ************************************************************************

    /**
     * @return sometimes null
     * @deprecated Use {@link PlanningScore#scoreDefinitionClass()} instead. Will be removed in 8.0.
     */
    @Deprecated public Class<? extends ScoreDefinition> getScoreDefinitionClass() {
        return scoreDefinitionClass;
    }

    /**
     * @param scoreDefinitionClass sometimes null
     * @deprecated Use {@link PlanningScore#scoreDefinitionClass()} instead. Will be removed in 8.0.
     */
    @Deprecated public void setScoreDefinitionClass(Class<? extends ScoreDefinition> scoreDefinitionClass) {
        this.scoreDefinitionClass = scoreDefinitionClass;
    }

    /**
     * @return sometimes null
     * @deprecated Use {@link PlanningScore} instead. Will be removed in 8.0.
     */
    @Deprecated public ScoreDefinitionType getScoreDefinitionType() {
        return scoreDefinitionType;
    }

    /**
     * @param scoreDefinitionType sometimes null
     * @deprecated Use {@link PlanningScore} instead. Will be removed in 8.0.
     */
    @Deprecated public void setScoreDefinitionType(ScoreDefinitionType scoreDefinitionType) {
        this.scoreDefinitionType = scoreDefinitionType;
    }

    /**
     * @return sometimes null
     * @deprecated Use {@link PlanningScore#bendableHardLevelsSize()} instead. Will be removed in 8.0.
     */
    @Deprecated public Integer getBendableHardLevelsSize() {
        return bendableHardLevelsSize;
    }

    /**
     * @param bendableHardLevelsSize sometimes null
     * @deprecated Use {@link PlanningScore#bendableHardLevelsSize()} instead. Will be removed in 8.0.
     */
    @Deprecated public void setBendableHardLevelsSize(Integer bendableHardLevelsSize) {
        this.bendableHardLevelsSize = bendableHardLevelsSize;
    }

    /**
     * @return sometimes null
     * @deprecated Use {@link PlanningScore#bendableSoftLevelsSize()} instead. Will be removed in 8.0.
     */
    @Deprecated public Integer getBendableSoftLevelsSize() {
        return bendableSoftLevelsSize;
    }

    /**
     * @param bendableSoftLevelsSize sometimes null
     * @deprecated Use {@link PlanningScore#bendableSoftLevelsSize()} instead. Will be removed in 8.0.
     */
    @Deprecated public void setBendableSoftLevelsSize(Integer bendableSoftLevelsSize) {
        this.bendableSoftLevelsSize = bendableSoftLevelsSize;
    }

    public Class<? extends EasyScoreCalculator> getEasyScoreCalculatorClass() {
        return easyScoreCalculatorClass;
    }

    public void setEasyScoreCalculatorClass(Class<? extends EasyScoreCalculator> easyScoreCalculatorClass) {
        this.easyScoreCalculatorClass = easyScoreCalculatorClass;
    }

    public Map<String, String> getEasyScoreCalculatorCustomProperties() {
        return easyScoreCalculatorCustomProperties;
    }

    public void setEasyScoreCalculatorCustomProperties(Map<String, String> easyScoreCalculatorCustomProperties) {
        this.easyScoreCalculatorCustomProperties = easyScoreCalculatorCustomProperties;
    }

    public Class<? extends IncrementalScoreCalculator> getIncrementalScoreCalculatorClass() {
        return incrementalScoreCalculatorClass;
    }

    public void setIncrementalScoreCalculatorClass(Class<? extends IncrementalScoreCalculator> incrementalScoreCalculatorClass) {
        this.incrementalScoreCalculatorClass = incrementalScoreCalculatorClass;
    }

    public Map<String, String> getIncrementalScoreCalculatorCustomProperties() {
        return incrementalScoreCalculatorCustomProperties;
    }

    public void setIncrementalScoreCalculatorCustomProperties(Map<String, String> incrementalScoreCalculatorCustomProperties) {
        this.incrementalScoreCalculatorCustomProperties = incrementalScoreCalculatorCustomProperties;
    }

    public String getKsessionName() {
        return ksessionName;
    }

    public void setKsessionName(String ksessionName) {
        this.ksessionName = ksessionName;
    }

    /**
     * @return sometimes null
     * @deprecated Use {@link #setKsessionName(String)} and {@link SolverFactory#createFromKieContainerXmlResource(KieContainer, String)} instead. Might be removed in 8.0.
     */
    @Deprecated public KieBase getKieBase() {
        return kieBase;
    }

    /**
     * @param kieBase sometimes null
     * @deprecated Use {@link #setKsessionName(String)} and {@link SolverFactory#createFromKieContainerXmlResource(KieContainer, String)} instead. Might be removed in 8.0.
     */
    @Deprecated public void setKieBase(KieBase kieBase) {
        this.kieBase = kieBase;
    }

    public List<String> getScoreDrlList() {
        return scoreDrlList;
    }

    public void setScoreDrlList(List<String> scoreDrlList) {
        this.scoreDrlList = scoreDrlList;
    }

    public List<File> getScoreDrlFileList() {
        return scoreDrlFileList;
    }

    public void setScoreDrlFileList(List<File> scoreDrlFileList) {
        this.scoreDrlFileList = scoreDrlFileList;
    }

    public Map<String, String> getKieBaseConfigurationProperties() {
        return kieBaseConfigurationProperties;
    }

    public void setKieBaseConfigurationProperties(Map<String, String> kieBaseConfigurationProperties) {
        this.kieBaseConfigurationProperties = kieBaseConfigurationProperties;
    }

    public String getInitializingScoreTrend() {
        return initializingScoreTrend;
    }

    public void setInitializingScoreTrend(String initializingScoreTrend) {
        this.initializingScoreTrend = initializingScoreTrend;
    }

    public ScoreDirectorFactoryConfig getAssertionScoreDirectorFactory() {
        return assertionScoreDirectorFactory;
    }

    public void setAssertionScoreDirectorFactory(ScoreDirectorFactoryConfig assertionScoreDirectorFactory) {
        this.assertionScoreDirectorFactory = assertionScoreDirectorFactory;
    }

    public Boolean isGenerateDroolsTestOnError() {
        return generateDroolsTestOnError;
    }

    public void setGenerateDroolsTestOnError(Boolean generateDroolsTestOnError) {
        this.generateDroolsTestOnError = generateDroolsTestOnError;
    }

    // ************************************************************************
    // With methods
    // ************************************************************************

    public ScoreDirectorFactoryConfig withEasyScoreCalculatorClass(Class<? extends EasyScoreCalculator> easyScoreCalculatorClass) {
        this.easyScoreCalculatorClass = easyScoreCalculatorClass;
        return this;
    }

    public ScoreDirectorFactoryConfig withEasyScoreCalculatorCustomProperties(Map<String, String> easyScoreCalculatorCustomProperties) {
        this.easyScoreCalculatorCustomProperties = easyScoreCalculatorCustomProperties;
        return this;
    }

    public ScoreDirectorFactoryConfig withIncrementalScoreCalculatorClass(Class<? extends IncrementalScoreCalculator> incrementalScoreCalculatorClass) {
        this.incrementalScoreCalculatorClass = incrementalScoreCalculatorClass;
        return this;
    }

    public ScoreDirectorFactoryConfig withIncrementalScoreCalculatorCustomProperties(Map<String, String> incrementalScoreCalculatorCustomProperties) {
        this.incrementalScoreCalculatorCustomProperties = incrementalScoreCalculatorCustomProperties;
        return this;
    }

    public ScoreDirectorFactoryConfig withScoreDrlList(List<String> scoreDrlList) {
        this.scoreDrlList = scoreDrlList;
        return this;
    }

    public ScoreDirectorFactoryConfig withScoreDrls(String... scoreDrls) {
        this.scoreDrlList = Arrays.asList(scoreDrls);
        return this;
    }

    public ScoreDirectorFactoryConfig withScoreDrlFileList(List<File> scoreDrlFileList) {
        this.scoreDrlFileList = scoreDrlFileList;
        return this;
    }

    public ScoreDirectorFactoryConfig withScoreDrlFiles(File... scoreDrlFiles) {
        this.scoreDrlFileList = Arrays.asList(scoreDrlFiles);
        return this;
    }

    public ScoreDirectorFactoryConfig withInitializingScoreTrend(String initializingScoreTrend) {
        this.initializingScoreTrend = initializingScoreTrend;
        return this;
    }

    public ScoreDirectorFactoryConfig withAssertionScoreDirectorFactory(ScoreDirectorFactoryConfig assertionScoreDirectorFactory) {
        this.assertionScoreDirectorFactory = assertionScoreDirectorFactory;
        return this;
    }

    // ************************************************************************
    // Builder methods
    // ************************************************************************

    public ScoreDefinition buildDeprecatedScoreDefinition() {
        if (scoreDefinitionType != ScoreDefinitionType.BENDABLE
                && scoreDefinitionType != ScoreDefinitionType.BENDABLE_LONG
                && scoreDefinitionType != ScoreDefinitionType.BENDABLE_BIG_DECIMAL
                && (bendableHardLevelsSize != null || bendableSoftLevelsSize != null)) {
            throw new IllegalArgumentException("A bendableHardLevelsSize (" + bendableHardLevelsSize
                    + ") or bendableSoftLevelsSize (" + bendableSoftLevelsSize
                    + ") needs a scoreDefinitionType (" + scoreDefinitionType + ") that is bendable.");
        }
        if ((scoreDefinitionType == ScoreDefinitionType.BENDABLE
                || scoreDefinitionType == ScoreDefinitionType.BENDABLE_LONG
                || scoreDefinitionType == ScoreDefinitionType.BENDABLE_BIG_DECIMAL)
                && (bendableHardLevelsSize == null || bendableSoftLevelsSize == null)) {
            throw new IllegalArgumentException("With scoreDefinitionType (" + scoreDefinitionType
                    + ") there must be a bendableHardLevelsSize (" + bendableHardLevelsSize
                    + ") and a bendableSoftLevelsSize (" + bendableSoftLevelsSize + ").");
        }
        if (scoreDefinitionClass != null) {
            if (scoreDefinitionType != null) {
                throw new IllegalArgumentException("With scoreDefinitionClass (" + scoreDefinitionClass
                        + ") there must be no scoreDefinitionType (" + scoreDefinitionType + ").");
            }
            return ConfigUtils.newInstance(this, "scoreDefinitionClass", scoreDefinitionClass);
        }
        if (scoreDefinitionType != null) {
            switch (scoreDefinitionType) {
                case SIMPLE:
                    return new SimpleScoreDefinition();
                case SIMPLE_LONG:
                    return new SimpleLongScoreDefinition();
                case SIMPLE_DOUBLE:
                    return new SimpleDoubleScoreDefinition();
                case SIMPLE_BIG_DECIMAL:
                    return new SimpleBigDecimalScoreDefinition();
                case HARD_SOFT:
                    return new HardSoftScoreDefinition();
                case HARD_SOFT_LONG:
                    return new HardSoftLongScoreDefinition();
                case HARD_SOFT_DOUBLE:
                    return new HardSoftDoubleScoreDefinition();
                case HARD_SOFT_BIG_DECIMAL:
                    return new HardSoftBigDecimalScoreDefinition();
                case HARD_MEDIUM_SOFT:
                    return new HardMediumSoftScoreDefinition();
                case HARD_MEDIUM_SOFT_LONG:
                    return new HardMediumSoftLongScoreDefinition();
                case BENDABLE:
                    return new BendableScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
                case BENDABLE_LONG:
                    return new BendableLongScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
                case BENDABLE_BIG_DECIMAL:
                    return new BendableBigDecimalScoreDefinition(bendableHardLevelsSize, bendableSoftLevelsSize);
                default:
                    throw new IllegalStateException("The scoreDefinitionType (" + scoreDefinitionType
                            + ") is not implemented.");
            }
        } else {
            return null;
        }
    }

    public <Solution_> InnerScoreDirectorFactory<Solution_> buildScoreDirectorFactory(
            SolverConfigContext configContext, EnvironmentMode environmentMode,
            SolutionDescriptor<Solution_> solutionDescriptor) {
        AbstractScoreDirectorFactory<Solution_> easyScoreDirectorFactory = buildEasyScoreDirectorFactory(solutionDescriptor);
        AbstractScoreDirectorFactory<Solution_> incrementalScoreDirectorFactory = buildIncrementalScoreDirectorFactory(solutionDescriptor);
        AbstractScoreDirectorFactory<Solution_> droolsScoreDirectorFactory = buildDroolsScoreDirectorFactory(configContext, solutionDescriptor);
        AbstractScoreDirectorFactory<Solution_> scoreDirectorFactory;
        if (easyScoreDirectorFactory != null) {
            if (incrementalScoreDirectorFactory != null) {
                throw new IllegalArgumentException("The scoreDirectorFactory cannot have "
                        + "both an easyScoreDirectorFactory and an incrementalScoreDirectorFactory.");
            }
            if (droolsScoreDirectorFactory != null) {
                throw new IllegalArgumentException("The scoreDirectorFactory cannot have "
                        + "both an easyScoreDirectorFactory and an droolsScoreDirectorFactory.");
            }
            if (BooleanUtils.isTrue(generateDroolsTestOnError)) {
                throw new IllegalArgumentException("The <generateDroolsTestOnError> option can only be set to true "
                        + "when used together with droolsScoreDirectorFactory, not with easyScoreDirectorFactory.");
            }
            scoreDirectorFactory = easyScoreDirectorFactory;
        } else if (incrementalScoreDirectorFactory != null) {
            if (droolsScoreDirectorFactory != null) {
                throw new IllegalArgumentException("The scoreDirectorFactory cannot have "
                        + "both an incrementalScoreDirectorFactory and an droolsScoreDirectorFactory.");
            }
            if (BooleanUtils.isTrue(generateDroolsTestOnError)) {
                throw new IllegalArgumentException("The <generateDroolsTestOnError> option can only be set to true "
                        + "when used together with droolsScoreDirectorFactory, not with incrementalScoreDirectorFactory.");
            }
            scoreDirectorFactory = incrementalScoreDirectorFactory;
        } else if (droolsScoreDirectorFactory != null) {
            scoreDirectorFactory = droolsScoreDirectorFactory;
        } else {
            throw new IllegalArgumentException("The scoreDirectorFactory lacks a configuration for an "
                    + "easyScoreDirectorFactory, an incrementalScoreDirectorFactory or a droolsScoreDirectorFactory.");
        }
        if (assertionScoreDirectorFactory != null) {
            if (assertionScoreDirectorFactory.getAssertionScoreDirectorFactory() != null) {
                throw new IllegalArgumentException("A assertionScoreDirectorFactory ("
                        + assertionScoreDirectorFactory + ") cannot have a non-null assertionScoreDirectorFactory ("
                        + assertionScoreDirectorFactory.getAssertionScoreDirectorFactory() + ").");
            }
            if (assertionScoreDirectorFactory.getScoreDefinitionClass() != null
                    || assertionScoreDirectorFactory.getScoreDefinitionType() != null) {
                throw new IllegalArgumentException("A assertionScoreDirectorFactory ("
                        + assertionScoreDirectorFactory + ") must reuse the scoreDefinition of its parent." +
                        " It cannot have a non-null scoreDefinition* property.");
            }
            if (environmentMode.compareTo(EnvironmentMode.FAST_ASSERT) > 0) {
                throw new IllegalArgumentException("A non-null assertionScoreDirectorFactory ("
                        + assertionScoreDirectorFactory + ") requires an environmentMode ("
                        + environmentMode + ") of " + EnvironmentMode.FAST_ASSERT + " or lower.");
            }
            scoreDirectorFactory.setAssertionScoreDirectorFactory(
                    assertionScoreDirectorFactory.buildScoreDirectorFactory(configContext,
                            EnvironmentMode.NON_REPRODUCIBLE, solutionDescriptor));
        }
        scoreDirectorFactory.setInitializingScoreTrend(InitializingScoreTrend.parseTrend(
                initializingScoreTrend == null ? InitializingScoreTrendLevel.ANY.name() : initializingScoreTrend,
                solutionDescriptor.getScoreDefinition().getLevelsSize()));
        if (environmentMode.isNonIntrusiveFullAsserted()) {
            scoreDirectorFactory.setAssertClonedSolution(true);
        }
        return scoreDirectorFactory;
    }

    protected <Solution_> AbstractScoreDirectorFactory<Solution_> buildEasyScoreDirectorFactory(
            SolutionDescriptor<Solution_> solutionDescriptor) {
        if (easyScoreCalculatorClass != null) {
            if (!EasyScoreCalculator.class.isAssignableFrom(easyScoreCalculatorClass)) {
                throw new IllegalArgumentException(
                        "The easyScoreCalculatorClass (" + easyScoreCalculatorClass
                                + ") does not implement " + EasyScoreCalculator.class.getSimpleName() + ".");
            }
            EasyScoreCalculator<Solution_> easyScoreCalculator = ConfigUtils.newInstance(this,
                    "easyScoreCalculatorClass", easyScoreCalculatorClass);
            ConfigUtils.applyCustomProperties(easyScoreCalculator, "easyScoreCalculatorClass",
                    easyScoreCalculatorCustomProperties, "easyScoreCalculatorCustomProperties");
            return new EasyScoreDirectorFactory<>(solutionDescriptor, easyScoreCalculator);
        } else {
            if (easyScoreCalculatorCustomProperties != null) {
                throw new IllegalStateException("If there is no easyScoreCalculatorClass (" + easyScoreCalculatorClass
                        + "), then there can be no easyScoreCalculatorCustomProperties ("
                        + easyScoreCalculatorCustomProperties + ") either.");
            }
            return null;
        }
    }

    protected <Solution_> AbstractScoreDirectorFactory<Solution_> buildIncrementalScoreDirectorFactory(
            SolutionDescriptor<Solution_> solutionDescriptor) {
        if (incrementalScoreCalculatorClass != null) {
            if (!IncrementalScoreCalculator.class.isAssignableFrom(incrementalScoreCalculatorClass)) {
                throw new IllegalArgumentException(
                        "The incrementalScoreCalculatorClass (" + incrementalScoreCalculatorClass
                        + ") does not implement " + IncrementalScoreCalculator.class.getSimpleName() + ".");
            }
            return new IncrementalScoreDirectorFactory<>(solutionDescriptor, incrementalScoreCalculatorClass,
                    incrementalScoreCalculatorCustomProperties);
        } else {
            if (incrementalScoreCalculatorCustomProperties != null) {
                throw new IllegalStateException("If there is no incrementalScoreCalculatorClass (" + incrementalScoreCalculatorClass
                        + "), then there can be no incrementalScoreCalculatorCustomProperties ("
                        + incrementalScoreCalculatorCustomProperties + ") either.");
            }
            return null;
        }
    }

    protected <Solution_> AbstractScoreDirectorFactory<Solution_> buildDroolsScoreDirectorFactory(
            SolverConfigContext configContext, SolutionDescriptor<Solution_> solutionDescriptor) {
        KieContainer kieContainer = configContext.getKieContainer();
        if (kieContainer != null || ksessionName != null) {
            if (kieContainer == null) {
                throw new IllegalArgumentException("If ksessionName (" + ksessionName
                        + ") is not null, then the kieContainer (" + kieContainer
                        + ") must not be null."); // TODO improve error message with "maybe fix it like this"
            }
            if (!ConfigUtils.isEmptyCollection(scoreDrlList) || !ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
                throw new IllegalArgumentException("If kieContainer or ksessionName (" + ksessionName
                        + ") is not null, then the scoreDrlList (" + scoreDrlList
                        + ") and the scoreDrlFileList (" + scoreDrlFileList + ") must be empty.\n"
                        + "Maybe this is running in a kjar in kie-server, in which case the DRL's are located"
                        + " by the META-INF/kmodule.xml, so only ksessionName is allowed.");
            }
            if (kieBase != null) {
                throw new IllegalArgumentException("If kieContainer or ksessionName (" + ksessionName
                        + ") is not null, then the kieBase must be null.");
            }
            if (kieBaseConfigurationProperties != null) {
                throw new IllegalArgumentException("If kieContainer or ksessionName (" + ksessionName
                        + ") is not null, then the kieBaseConfigurationProperties ("
                        + kieBaseConfigurationProperties + ") must be null.");
            }
            if (BooleanUtils.isTrue(generateDroolsTestOnError)) {
                return new TestGenDroolsScoreDirectorFactory<>(solutionDescriptor, kieContainer, ksessionName);
            } else {
                return new DroolsScoreDirectorFactory<>(solutionDescriptor, kieContainer, ksessionName);
            }
        } else if (kieBase != null) {
            if (!ConfigUtils.isEmptyCollection(scoreDrlList) || !ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
                throw new IllegalArgumentException("If kieBase is not null, then the scoreDrlList (" + scoreDrlList
                        + ") and the scoreDrlFileList (" + scoreDrlFileList + ") must be empty.");
            }
            if (kieBaseConfigurationProperties != null) {
                throw new IllegalArgumentException("If kieBase is not null, then the kieBaseConfigurationProperties ("
                        + kieBaseConfigurationProperties + ") must be null.");
            }
            if (BooleanUtils.isTrue(generateDroolsTestOnError)) {
                return new TestGenLegacyDroolsScoreDirectorFactory<>(solutionDescriptor, kieBase, null, null);
            } else {
                return new LegacyDroolsScoreDirectorFactory<>(solutionDescriptor, kieBase);
            }
        } else if (!ConfigUtils.isEmptyCollection(scoreDrlList) || !ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
            KieServices kieServices = KieServices.Factory.get();
            KieResources kieResources = kieServices.getResources();
            KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
            if (!ConfigUtils.isEmptyCollection(scoreDrlList)) {
                ClassLoader actualClassLoader = configContext.determineActualClassLoader();
                for (String scoreDrl : scoreDrlList) {
                    if (scoreDrl == null) {
                        throw new IllegalArgumentException("The scoreDrl (" + scoreDrl + ") cannot be null.");
                    }
                    URL scoreDrlURL = actualClassLoader.getResource(scoreDrl);
                    if (scoreDrlURL == null) {
                        String errorMessage = "The scoreDrl (" + scoreDrl + ") does not exist as a classpath resource"
                                + " in the classLoader (" + actualClassLoader + ").";
                        if (scoreDrl.startsWith("/")) {
                            errorMessage += "\nAs from 6.1, a classpath resource should not start with a slash (/)."
                                    + " A scoreDrl now adheres to ClassLoader.getResource(String)."
                                    + " Remove the leading slash from the scoreDrl if you're upgrading from 6.0.";
                        }
                        throw new IllegalArgumentException(errorMessage);
                    }
                    kieFileSystem.write(kieResources.newClassPathResource(scoreDrl, "UTF-8", actualClassLoader));
                }
            }
            if (!ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
                for (File scoreDrlFile : scoreDrlFileList) {
                    if (scoreDrlFile == null) {
                        throw new IllegalArgumentException("The scoreDrlFile (" + scoreDrlFile + ") cannot be null.");
                    }
                    if (!scoreDrlFile.exists()) {
                        throw new IllegalArgumentException("The scoreDrlFile (" + scoreDrlFile
                                + ") does not exist.");
                    }
                    kieFileSystem.write(kieResources.newFileSystemResource(scoreDrlFile, "UTF-8"));
                }
            }

            // Can be overwritten by kieBaseConfigurationProperties
            KieModuleModel kmodel = kieServices.newKieModuleModel()
                    .setConfigurationProperty(PropertySpecificOption.PROPERTY_NAME,
                            PropertySpecificOption.ALLOWED.toString());
            kieFileSystem.writeKModuleXML(kmodel.toXML());

            KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
            kieBuilder.buildAll();
            Results results = kieBuilder.getResults();
            if (results.hasMessages(Message.Level.ERROR)) {
                throw new IllegalStateException("There are errors in a score DRL:\n"
                        + results.toString());
            } else if (results.hasMessages(Message.Level.WARNING)) {
                logger.warn("There are warning in a score DRL:\n{}", results);
            }
            kieContainer = kieServices.newKieContainer(kieBuilder.getKieModule().getReleaseId());

            KieBaseConfiguration kieBaseConfiguration = kieServices.newKieBaseConfiguration();
            if (kieBaseConfigurationProperties != null) {
                for (Map.Entry<String, String> entry : kieBaseConfigurationProperties.entrySet()) {
                    kieBaseConfiguration.setProperty(entry.getKey(), entry.getValue());
                }
            }
            KieBase kieBase = kieContainer.newKieBase(kieBaseConfiguration);
            if (BooleanUtils.isTrue(generateDroolsTestOnError)) {
                return new TestGenLegacyDroolsScoreDirectorFactory<>(solutionDescriptor, kieBase, scoreDrlList, scoreDrlFileList);
            } else {
                return new LegacyDroolsScoreDirectorFactory<>(solutionDescriptor, kieBase);
            }
        } else {
            if (kieBaseConfigurationProperties != null) {
                throw new IllegalArgumentException(
                        "If kieBaseConfigurationProperties (" + kieBaseConfigurationProperties
                        + ") is not null, the scoreDrlList (" + scoreDrlList
                        + ") and the scoreDrlFileList (" + scoreDrlFileList + ") must not be empty.");
            }
            return null;
        }
    }

    @Override
    public void inherit(ScoreDirectorFactoryConfig inheritedConfig) {
        if (scoreDefinitionClass == null && scoreDefinitionType == null
                && bendableHardLevelsSize == null && bendableSoftLevelsSize == null) {
            scoreDefinitionClass = inheritedConfig.getScoreDefinitionClass();
            scoreDefinitionType = inheritedConfig.getScoreDefinitionType();
            bendableHardLevelsSize = inheritedConfig.getBendableHardLevelsSize();
            bendableSoftLevelsSize = inheritedConfig.getBendableSoftLevelsSize();
        }
        easyScoreCalculatorClass = ConfigUtils.inheritOverwritableProperty(
                easyScoreCalculatorClass, inheritedConfig.getEasyScoreCalculatorClass());
        easyScoreCalculatorCustomProperties = ConfigUtils.inheritMergeableMapProperty(
                easyScoreCalculatorCustomProperties, inheritedConfig.getEasyScoreCalculatorCustomProperties());
        incrementalScoreCalculatorClass = ConfigUtils.inheritOverwritableProperty(
                incrementalScoreCalculatorClass, inheritedConfig.getIncrementalScoreCalculatorClass());
        incrementalScoreCalculatorCustomProperties = ConfigUtils.inheritMergeableMapProperty(
                incrementalScoreCalculatorCustomProperties, inheritedConfig.getIncrementalScoreCalculatorCustomProperties());
        ksessionName = ConfigUtils.inheritOverwritableProperty(
                ksessionName, inheritedConfig.getKsessionName());
        kieBase = ConfigUtils.inheritOverwritableProperty(
                kieBase, inheritedConfig.getKieBase());
        scoreDrlList = ConfigUtils.inheritMergeableListProperty(
                scoreDrlList, inheritedConfig.getScoreDrlList());
        scoreDrlFileList = ConfigUtils.inheritMergeableListProperty(
                scoreDrlFileList, inheritedConfig.getScoreDrlFileList());
        kieBaseConfigurationProperties = ConfigUtils.inheritMergeableMapProperty(
                kieBaseConfigurationProperties, inheritedConfig.getKieBaseConfigurationProperties());
        initializingScoreTrend = ConfigUtils.inheritOverwritableProperty(
                initializingScoreTrend, inheritedConfig.getInitializingScoreTrend());

        assertionScoreDirectorFactory = ConfigUtils.inheritOverwritableProperty(
                assertionScoreDirectorFactory, inheritedConfig.getAssertionScoreDirectorFactory());
        generateDroolsTestOnError = ConfigUtils.inheritOverwritableProperty(
                generateDroolsTestOnError, inheritedConfig.isGenerateDroolsTestOnError());
    }

}


/*
 * Copyright 2011 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.benchmark.config;

import java.util.ArrayList;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import org.optaplanner.benchmark.impl.result.PlannerBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SolverBenchmarkResult;
import org.optaplanner.core.config.AbstractConfig;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;

@XStreamAlias("solverBenchmark")
public class SolverBenchmarkConfig<Solution_> extends AbstractConfig<SolverBenchmarkConfig> {

    private String name = null;

    @XStreamAlias("solver")
    private SolverConfig solverConfig = null;

    @XStreamAlias("problemBenchmarks")
    private ProblemBenchmarksConfig problemBenchmarksConfig = null;

    private Integer subSingleCount = null;

    // ************************************************************************
    // Constructors and simple getters/setters
    // ************************************************************************

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SolverConfig getSolverConfig() {
        return solverConfig;
    }

    public void setSolverConfig(SolverConfig solverConfig) {
        this.solverConfig = solverConfig;
    }

    public ProblemBenchmarksConfig getProblemBenchmarksConfig() {
        return problemBenchmarksConfig;
    }

    public void setProblemBenchmarksConfig(ProblemBenchmarksConfig problemBenchmarksConfig) {
        this.problemBenchmarksConfig = problemBenchmarksConfig;
    }

    public Integer getSubSingleCount() {
        return subSingleCount;
    }

    public void setSubSingleCount(Integer subSingleCount) {
        this.subSingleCount = subSingleCount;
    }

    // ************************************************************************
    // Builder methods
    // ************************************************************************

    public void buildSolverBenchmark(SolverConfigContext solverConfigContext, PlannerBenchmarkResult plannerBenchmark,
            Solution_[] extraProblems) {
        validate();
        SolverBenchmarkResult solverBenchmarkResult = new SolverBenchmarkResult(plannerBenchmark);
        solverBenchmarkResult.setName(name);
        solverBenchmarkResult.setSubSingleCount(ConfigUtils.inheritOverwritableProperty(subSingleCount, 1));
        solverBenchmarkResult.setSolverConfig(solverConfig);
        SolutionDescriptor<Object> solutionDescriptor = solverConfig.buildSolutionDescriptor(solverConfigContext);
        for (Solution_ extraProblem : extraProblems) {
            if (!solutionDescriptor.getSolutionClass().isInstance(extraProblem)) {
                throw new IllegalArgumentException("The solverBenchmark name (" + name
                        + ") for solution class (" + solutionDescriptor.getSolutionClass()
                        + ") cannot solve a problem (" + extraProblem
                        + ") of class (" + (extraProblem == null ? null : extraProblem.getClass()) + ").");
            }
        }
        solverBenchmarkResult.setScoreDefinition(
                solutionDescriptor.getScoreDefinition());
        solverBenchmarkResult.setSingleBenchmarkResultList(new ArrayList<>());
        ProblemBenchmarksConfig problemBenchmarksConfig_
                = problemBenchmarksConfig == null ? new ProblemBenchmarksConfig()
                : problemBenchmarksConfig;
        plannerBenchmark.getSolverBenchmarkResultList().add(solverBenchmarkResult);
        problemBenchmarksConfig_.buildProblemBenchmarkList(solverConfigContext, solverBenchmarkResult, extraProblems);
    }

    protected void validate() {
        if (!PlannerBenchmarkConfig.VALID_NAME_PATTERN.matcher(name).matches()) {
            throw new IllegalStateException("The solverBenchmark name (" + name
                    + ") is invalid because it does not follow the nameRegex ("
                    + PlannerBenchmarkConfig.VALID_NAME_PATTERN.pattern() + ")" +
                    " which might cause an illegal filename.");
        }
        if (!name.trim().equals(name)) {
            throw new IllegalStateException("The solverBenchmark name (" + name
                    + ") is invalid because it starts or ends with whitespace.");
        }
        if (subSingleCount != null && subSingleCount < 1) {
            throw new IllegalStateException("The solverBenchmark name (" + name
                    + ") is invalid because the subSingleCount (" + subSingleCount + ") must be greater than 1.");
        }
    }

    @Override
    public void inherit(SolverBenchmarkConfig inheritedConfig) {
        solverConfig = ConfigUtils.inheritConfig(solverConfig, inheritedConfig.getSolverConfig());
        problemBenchmarksConfig = ConfigUtils.inheritConfig(problemBenchmarksConfig,
                inheritedConfig.getProblemBenchmarksConfig());
        subSingleCount = ConfigUtils.inheritOverwritableProperty(subSingleCount, inheritedConfig.getSubSingleCount());
    }

}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.impl.localsearch.decider.acceptor.tabu;

import java.util.Collection;
import java.util.Collections;

import org.optaplanner.core.impl.heuristic.move.Move;
import org.optaplanner.core.impl.localsearch.scope.LocalSearchMoveScope;
import org.optaplanner.core.impl.localsearch.scope.LocalSearchStepScope;

public class MoveTabuAcceptor extends AbstractTabuAcceptor {

    protected boolean useUndoMoveAsTabuMove = true;

    public MoveTabuAcceptor(String logIndentation) {
        super(logIndentation);
    }

    public void setUseUndoMoveAsTabuMove(boolean useUndoMoveAsTabuMove) {
        this.useUndoMoveAsTabuMove = useUndoMoveAsTabuMove;
    }

    // ************************************************************************
    // Worker methods
    // ************************************************************************

    @Override
    protected Collection<? extends Object> findTabu(LocalSearchMoveScope moveScope) {
        return Collections.singletonList(moveScope.getMove());
    }

    @Override
    protected Collection<? extends Object> findNewTabu(LocalSearchStepScope stepScope) {
        Move<?> tabuMove;
        if (useUndoMoveAsTabuMove) {
            tabuMove = stepScope.getUndoStep();
        } else {
            tabuMove = stepScope.getStep();
        }
        return Collections.singletonList(tabuMove);
    }

}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.examples.nqueens.app;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig;
import org.optaplanner.core.config.constructionheuristic.ConstructionHeuristicType;
import org.optaplanner.core.config.heuristic.selector.common.SelectionOrder;
import org.optaplanner.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig;
import org.optaplanner.core.config.localsearch.LocalSearchPhaseConfig;
import org.optaplanner.core.config.localsearch.decider.acceptor.AcceptorConfig;
import org.optaplanner.core.config.phase.PhaseConfig;
import org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.termination.TerminationConfig;
import org.optaplanner.examples.common.app.CommonApp;
import org.optaplanner.examples.nqueens.domain.NQueens;
import org.optaplanner.examples.nqueens.domain.Queen;
import org.optaplanner.examples.nqueens.swingui.NQueensPanel;
import org.optaplanner.persistence.common.api.domain.solution.SolutionFileIO;
import org.optaplanner.persistence.xstream.impl.domain.solution.XStreamSolutionFileIO;

/**
 * For an easy example, look at {@link NQueensHelloWorld} instead.
 */
public class NQueensApp extends CommonApp<NQueens> {

    public static final String SOLVER_CONFIG
            = "org/optaplanner/examples/nqueens/solver/nqueensSolverConfig.xml";

    public static final String DATA_DIR_NAME = "nqueens";

    public static void main(String[] args) {
        prepareSwingEnvironment();
        new NQueensApp().init();
    }

    public NQueensApp() {
        super("N queens",
                "Place queens on a chessboard.\n\n" +
                        "No 2 queens must be able to attack each other.",
                SOLVER_CONFIG, DATA_DIR_NAME,
                NQueensPanel.LOGO_PATH);
    }

    @Override
    protected Solver<NQueens> createSolver() {
        return createSolverByXml();
    }

    /**
     * Normal way to create a {@link Solver}.
     * @return never null
     */
    protected Solver<NQueens> createSolverByXml() {
        SolverFactory<NQueens> solverFactory = SolverFactory.createFromXmlResource(SOLVER_CONFIG);
        return solverFactory.buildSolver();
    }

    /**
     * Unused alternative. Abnormal way to create a {@link Solver}.
     * <p>
     * Not recommended! It is recommended to use {@link #createSolverByXml()} instead.
     * @return never null
     */
    protected Solver<NQueens> createSolverByApi() {
        SolverFactory<NQueens> solverFactory = SolverFactory.createEmpty();
        SolverConfig solverConfig = solverFactory.getSolverConfig();

        solverConfig.setSolutionClass(NQueens.class);
        solverConfig.setEntityClassList(Collections.<Class<?>>singletonList(Queen.class));

        ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig();
        scoreDirectorFactoryConfig.setScoreDrlList(
                Arrays.asList("org/optaplanner/examples/nqueens/solver/nQueensScoreRules.drl"));
        solverConfig.setScoreDirectorFactoryConfig(scoreDirectorFactoryConfig);

        solverConfig.setTerminationConfig(new TerminationConfig().withBestScoreLimit("0"));
        List<PhaseConfig> phaseConfigList = new ArrayList<>();

        ConstructionHeuristicPhaseConfig constructionHeuristicPhaseConfig = new ConstructionHeuristicPhaseConfig();
        constructionHeuristicPhaseConfig.setConstructionHeuristicType(
                ConstructionHeuristicType.FIRST_FIT_DECREASING);
        phaseConfigList.add(constructionHeuristicPhaseConfig);

        LocalSearchPhaseConfig localSearchPhaseConfig = new LocalSearchPhaseConfig();
        ChangeMoveSelectorConfig changeMoveSelectorConfig = new ChangeMoveSelectorConfig();
        changeMoveSelectorConfig.setSelectionOrder(SelectionOrder.ORIGINAL);
        localSearchPhaseConfig.setMoveSelectorConfig(changeMoveSelectorConfig);
        localSearchPhaseConfig.setAcceptorConfig(new AcceptorConfig().withEntityTabuSize(5));
        phaseConfigList.add(localSearchPhaseConfig);

        solverConfig.setPhaseConfigList(phaseConfigList);
        return solverFactory.buildSolver();
    }

    @Override
    protected NQueensPanel createSolutionPanel() {
        return new NQueensPanel();
    }

    @Override
    public SolutionFileIO<NQueens> createSolutionFileIO() {
        return new XStreamSolutionFileIO<>(NQueens.class);
    }

}


/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.impl.localsearch.decider;

import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.impl.heuristic.move.Move;
import org.optaplanner.core.impl.heuristic.selector.move.MoveSelector;
import org.optaplanner.core.impl.localsearch.decider.acceptor.Acceptor;
import org.optaplanner.core.impl.localsearch.decider.forager.LocalSearchForager;
import org.optaplanner.core.impl.localsearch.scope.LocalSearchMoveScope;
import org.optaplanner.core.impl.localsearch.scope.LocalSearchPhaseScope;
import org.optaplanner.core.impl.localsearch.scope.LocalSearchStepScope;
import org.optaplanner.core.impl.score.director.InnerScoreDirector;
import org.optaplanner.core.impl.solver.scope.DefaultSolverScope;
import org.optaplanner.core.impl.solver.termination.Termination;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
 */
public class LocalSearchDecider<Solution_> {

    protected final transient Logger logger = LoggerFactory.getLogger(getClass());

    protected final String logIndentation;
    protected final Termination termination;
    protected final MoveSelector moveSelector;
    protected final Acceptor acceptor;
    protected final LocalSearchForager forager;

    protected boolean assertMoveScoreFromScratch = false;
    protected boolean assertExpectedUndoMoveScore = false;

    public LocalSearchDecider(String logIndentation,
            Termination termination, MoveSelector moveSelector, Acceptor acceptor, LocalSearchForager forager) {
        this.logIndentation = logIndentation;
        this.termination = termination;
        this.moveSelector = moveSelector;
        this.acceptor = acceptor;
        this.forager = forager;
    }

    public Termination getTermination() {
        return termination;
    }

    public MoveSelector getMoveSelector() {
        return moveSelector;
    }

    public Acceptor getAcceptor() {
        return acceptor;
    }

    public LocalSearchForager getForager() {
        return forager;
    }

    public void setAssertMoveScoreFromScratch(boolean assertMoveScoreFromScratch) {
        this.assertMoveScoreFromScratch = assertMoveScoreFromScratch;
    }

    public void setAssertExpectedUndoMoveScore(boolean assertExpectedUndoMoveScore) {
        this.assertExpectedUndoMoveScore = assertExpectedUndoMoveScore;
    }

    // ************************************************************************
    // Worker methods
    // ************************************************************************

    public void solvingStarted(DefaultSolverScope<Solution_> solverScope) {
        moveSelector.solvingStarted(solverScope);
        acceptor.solvingStarted(solverScope);
        forager.solvingStarted(solverScope);
    }

    public void phaseStarted(LocalSearchPhaseScope<Solution_> phaseScope) {
        moveSelector.phaseStarted(phaseScope);
        acceptor.phaseStarted(phaseScope);
        forager.phaseStarted(phaseScope);
    }

    public void stepStarted(LocalSearchStepScope<Solution_> stepScope) {
        moveSelector.stepStarted(stepScope);
        acceptor.stepStarted(stepScope);
        forager.stepStarted(stepScope);
    }

    public void decideNextStep(LocalSearchStepScope<Solution_> stepScope) {
        InnerScoreDirector<Solution_> scoreDirector = stepScope.getScoreDirector();
        scoreDirector.setAllChangesWillBeUndoneBeforeStepEnds(true);
        int moveIndex = 0;
        for (Move<Solution_> move : moveSelector) {
            LocalSearchMoveScope<Solution_> moveScope = new LocalSearchMoveScope<>(stepScope, moveIndex, move);
            moveIndex++;
            // TODO use Selector filtering to filter out not doable moves
            if (!move.isMoveDoable(scoreDirector)) {
                logger.trace("{}        Move index ({}) not doable, ignoring move ({}).",
                        logIndentation, moveScope.getMoveIndex(), move);
            } else {
                doMove(moveScope);
                if (forager.isQuitEarly()) {
                    break;
                }
            }
            stepScope.getPhaseScope().getSolverScope().checkYielding();
            if (termination.isPhaseTerminated(stepScope.getPhaseScope())) {
                break;
            }
        }
        scoreDirector.setAllChangesWillBeUndoneBeforeStepEnds(false);
        pickMove(stepScope);
    }

    protected void doMove(LocalSearchMoveScope<Solution_> moveScope) {
        InnerScoreDirector<Solution_> scoreDirector = moveScope.getScoreDirector();
        scoreDirector.doAndProcessMove(moveScope.getMove(), assertMoveScoreFromScratch, score -> {
                    moveScope.setScore(score);
                    boolean accepted = acceptor.isAccepted(moveScope);
                    moveScope.setAccepted(accepted);
                    forager.addMove(moveScope);
                });
        if (assertExpectedUndoMoveScore) {
            scoreDirector.assertExpectedUndoMoveScore(moveScope.getMove(),
                    moveScope.getStepScope().getPhaseScope().getLastCompletedStepScope().getScore());
        }
        logger.trace("{}        Move index ({}), score ({}), accepted ({}), move ({}).",
                logIndentation,
                moveScope.getMoveIndex(), moveScope.getScore(), moveScope.getAccepted(),
                moveScope.getMove());
    }

    protected void pickMove(LocalSearchStepScope<Solution_> stepScope) {
        LocalSearchMoveScope<Solution_> pickedMoveScope = forager.pickMove(stepScope);
        if (pickedMoveScope != null) {
            Move<Solution_> step = pickedMoveScope.getMove();
            stepScope.setStep(step);
            if (logger.isDebugEnabled()) {
                stepScope.setStepString(step.toString());
            }
            stepScope.setScore(pickedMoveScope.getScore());
        }
    }

    public void stepEnded(LocalSearchStepScope<Solution_> stepScope) {
        moveSelector.stepEnded(stepScope);
        acceptor.stepEnded(stepScope);
        forager.stepEnded(stepScope);
    }

    public void phaseEnded(LocalSearchPhaseScope<Solution_> phaseScope) {
        moveSelector.phaseEnded(phaseScope);
        acceptor.phaseEnded(phaseScope);
        forager.phaseEnded(phaseScope);
    }

    public void solvingEnded(DefaultSolverScope<Solution_> solverScope) {
        moveSelector.solvingEnded(solverScope);
        acceptor.solvingEnded(solverScope);
        forager.solvingEnded(solverScope);
    }

}


/*
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.impl.domain.variable.listener.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.domain.variable.listener.StatefulVariableListener;
import org.optaplanner.core.impl.domain.variable.listener.VariableListener;
import org.optaplanner.core.impl.domain.variable.supply.Demand;
import org.optaplanner.core.impl.domain.variable.supply.Supply;
import org.optaplanner.core.impl.domain.variable.supply.SupplyManager;
import org.optaplanner.core.impl.score.director.InnerScoreDirector;
import org.optaplanner.core.impl.score.director.ScoreDirector;

/**
 * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
 */
public class VariableListenerSupport<Solution_> implements SupplyManager {

    protected final InnerScoreDirector<Solution_> scoreDirector;

    protected final List<VariableListenerNotifiable> notifiableList;
    protected final Map<VariableDescriptor, List<VariableListenerNotifiable>> sourceVariableToNotifiableMap;
    protected final Map<EntityDescriptor<Solution_>, List<VariableListenerNotifiable>> sourceEntityToNotifiableMap;
    protected final Map<Demand, Supply> supplyMap;
    protected int nextGlobalOrder = 0;

    protected boolean notificationQueuesAreEmpty;

    public VariableListenerSupport(InnerScoreDirector<Solution_> scoreDirector) {
        this.scoreDirector = scoreDirector;
        notifiableList = new ArrayList<>();
        sourceVariableToNotifiableMap = new LinkedHashMap<>();
        sourceEntityToNotifiableMap = new LinkedHashMap<>();
        supplyMap = new LinkedHashMap<>();
    }

    public void linkVariableListeners() {
        notificationQueuesAreEmpty = true;
        for (EntityDescriptor<Solution_> entityDescriptor : scoreDirector.getSolutionDescriptor().getEntityDescriptors()) {
            for (VariableDescriptor variableDescriptor : entityDescriptor.getDeclaredVariableDescriptors()) {
                sourceVariableToNotifiableMap.put(variableDescriptor, new ArrayList<>());
            }
            sourceEntityToNotifiableMap.put(entityDescriptor, new ArrayList<>());
        }
        for (EntityDescriptor<Solution_> entityDescriptor : scoreDirector.getSolutionDescriptor().getEntityDescriptors()) {
            for (ShadowVariableDescriptor<Solution_> shadowVariableDescriptor : entityDescriptor.getDeclaredShadowVariableDescriptors()) {
                if (shadowVariableDescriptor.hasVariableListener(scoreDirector)) {
                    VariableListener variableListener = shadowVariableDescriptor.buildVariableListener(scoreDirector);
                    supplyMap.put(shadowVariableDescriptor.getProvidedDemand(), variableListener);
                    int globalOrder = shadowVariableDescriptor.getGlobalShadowOrder();
                    if (nextGlobalOrder <= globalOrder) {
                        nextGlobalOrder = globalOrder + 1;
                    }
                    VariableListenerNotifiable notifiable = new VariableListenerNotifiable(variableListener, globalOrder);
                    for (VariableDescriptor<Solution_> source : shadowVariableDescriptor.getSourceVariableDescriptorList()) {
                        List<VariableListenerNotifiable> variableNotifiableList = sourceVariableToNotifiableMap.get(source);
                        variableNotifiableList.add(notifiable);
                        List<VariableListenerNotifiable> entityNotifiableList = sourceEntityToNotifiableMap.get(source.getEntityDescriptor());
                        if (!entityNotifiableList.contains(notifiable)) {
                            entityNotifiableList.add(notifiable);
                        }
                    }
                    notifiableList.add(notifiable);
                }
            }
        }
        Collections.sort(notifiableList);
    }

    @Override
    public <S extends Supply> S demand(Demand<S> demand) {
        S supply = (S) supplyMap.get(demand);
        if (supply == null) {
            supply = demand.createExternalizedSupply(scoreDirector);
            if (supply instanceof StatefulVariableListener) {
                StatefulVariableListener variableListener = (StatefulVariableListener) supply;
                // An external ScoreDirector can be created before the working solution is set
                if (scoreDirector.getWorkingSolution() != null) {
                    variableListener.resetWorkingSolution(scoreDirector);
                }
                VariableDescriptor<Solution_> source = variableListener.getSourceVariableDescriptor();
                VariableListenerNotifiable notifiable = new VariableListenerNotifiable(variableListener, nextGlobalOrder);
                nextGlobalOrder++;
                List<VariableListenerNotifiable> variableNotifiableList = sourceVariableToNotifiableMap.get(source);
                variableNotifiableList.add(notifiable);
                List<VariableListenerNotifiable> entityNotifiableList = sourceEntityToNotifiableMap.get(source.getEntityDescriptor());
                if (!entityNotifiableList.contains(notifiable)) {
                    entityNotifiableList.add(notifiable);
                }
                notifiableList.add(notifiable);
                // No need to sort notifiableList again because notifiable's globalOrder is highest
            }
            supplyMap.put(demand, supply);
        }
        return supply;
    }

    // ************************************************************************
    // Lifecycle methods
    // ************************************************************************

    public void resetWorkingSolution() {
        for (VariableListenerNotifiable notifiable : notifiableList) {
            VariableListener variableListener = notifiable.getVariableListener();
            if (variableListener instanceof StatefulVariableListener) {
                ((StatefulVariableListener) variableListener).resetWorkingSolution(scoreDirector);
            }
        }
    }

    public void clearWorkingSolution() {
        for (VariableListenerNotifiable notifiable : notifiableList) {
            VariableListener variableListener = notifiable.getVariableListener();
            if (variableListener instanceof StatefulVariableListener) {
                ((StatefulVariableListener) variableListener).clearWorkingSolution(scoreDirector);
            }
        }
    }

    public void beforeEntityAdded(EntityDescriptor<Solution_> entityDescriptor, Object entity) {
        List<VariableListenerNotifiable> notifiableList = sourceEntityToNotifiableMap.get(entityDescriptor);
        for (VariableListenerNotifiable notifiable : notifiableList) {
            Collection<VariableListenerNotification> notificationQueue = notifiable.getNotificationQueue();
            boolean added = notificationQueue.add(
                    new VariableListenerNotification(entity, VariableListenerNotificationType.ENTITY_ADDED));
            if (added) {
                notifiable.getVariableListener().beforeEntityAdded(scoreDirector, entity);
            }
        }
        notificationQueuesAreEmpty = false;
    }

    public void afterEntityAdded(EntityDescriptor<Solution_> entityDescriptor, Object entity) {
        // beforeEntityAdded() has already added it to the notificationQueue
    }

    public void beforeVariableChanged(VariableDescriptor<Solution_> variableDescriptor, Object entity) {
        List<VariableListenerNotifiable> notifiableList = sourceVariableToNotifiableMap.get(variableDescriptor);
        for (VariableListenerNotifiable notifiable : notifiableList) {
            Collection<VariableListenerNotification> notificationQueue = notifiable.getNotificationQueue();
            boolean added = notificationQueue.add(
                    new VariableListenerNotification(entity, VariableListenerNotificationType.VARIABLE_CHANGED));
            if (added) {
                notifiable.getVariableListener().beforeVariableChanged(scoreDirector, entity);
            }
        }
        notificationQueuesAreEmpty = false;
    }

    public void afterVariableChanged(VariableDescriptor<Solution_> variableDescriptor, Object entity) {
        // beforeVariableChanged() has already added it to the notificationQueue
    }

    public void beforeEntityRemoved(EntityDescriptor<Solution_> entityDescriptor, Object entity) {
        List<VariableListenerNotifiable> notifiableList = sourceEntityToNotifiableMap.get(entityDescriptor);
        for (VariableListenerNotifiable notifiable : notifiableList) {
            Collection<VariableListenerNotification> notificationQueue = notifiable.getNotificationQueue();
            boolean added = notificationQueue.add(
                    new VariableListenerNotification(entity, VariableListenerNotificationType.ENTITY_REMOVED));
            if (added) {
                notifiable.getVariableListener().beforeEntityRemoved(scoreDirector, entity);
            }
        }
        notificationQueuesAreEmpty = false;
    }

    public void afterEntityRemoved(EntityDescriptor<Solution_> entityDescriptor, Object entity) {
        // beforeEntityRemoved() has already added it to the notificationQueue
    }

    public void triggerVariableListenersInNotificationQueues() {
        for (VariableListenerNotifiable notifiable : notifiableList) {
            Collection<VariableListenerNotification> notificationQueue = notifiable.getNotificationQueue();
            int notifiedCount = 0;
            VariableListener variableListener = notifiable.getVariableListener();
            for (VariableListenerNotification notification : notificationQueue) {
                Object entity = notification.getEntity();
                switch (notification.getType()) {
                    case ENTITY_ADDED:
                        variableListener.afterEntityAdded(scoreDirector, entity);
                        break;
                    case VARIABLE_CHANGED:
                        variableListener.afterVariableChanged(scoreDirector, entity);
                        break;
                    case ENTITY_REMOVED:
                        variableListener.afterEntityRemoved(scoreDirector, entity);
                        break;
                    default:
                        throw new IllegalStateException("The variableListenerNotificationType ("
                                + notification.getType() + ") is not implemented.");
                }
                notifiedCount++;
            }
            if (notifiedCount != notificationQueue.size()) {
                throw new IllegalStateException("The variableListener (" + variableListener.getClass()
                        + ") has been notified with notifiedCount (" + notifiedCount
                        + ") but after notification it has different size (" + notificationQueue.size() + ").\n"
                        + "Maybe that variableListener (" + variableListener.getClass()
                        + ") changed an upstream shadow variable (which is illegal).");
            }
            notificationQueue.clear();
        }
        notificationQueuesAreEmpty = true;
    }

    public void triggerAllVariableListeners() {
        SolutionDescriptor<Solution_> solutionDescriptor = scoreDirector.getSolutionDescriptor();
        List<Object> entityList = scoreDirector.getWorkingEntityList();
        for (Object entity : entityList) {
            EntityDescriptor<Solution_> entityDescriptor = solutionDescriptor.findEntityDescriptorOrFail(entity.getClass());
            for (GenuineVariableDescriptor<Solution_> variableDescriptor : entityDescriptor.getGenuineVariableDescriptors()) {
                beforeVariableChanged(variableDescriptor, entity);
                // No change
                afterVariableChanged(variableDescriptor, entity);
            }
        }
        triggerVariableListenersInNotificationQueues();
    }

    public void assertNotificationQueuesAreEmpty() {
        if (!notificationQueuesAreEmpty) {
            throw new IllegalStateException("The notificationQueues might not be empty (" + notificationQueuesAreEmpty
                    + ") so any shadow variables might be stale so score calculation is unreliable.\n"
                    + "Maybe a " + ScoreDirector.class.getSimpleName() + ".before*() method was called"
                    + " without calling " + ScoreDirector.class.getSimpleName() + ".triggerVariableListeners(),"
                    + " before calling " + ScoreDirector.class.getSimpleName() + ".calculateScore().");
        }
    }

}


/*
 * Copyright 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.impl.score.director.incremental;

import java.util.Map;

import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.score.director.AbstractScoreDirectorFactory;
import org.optaplanner.core.impl.score.director.ScoreDirectorFactory;

/**
 * Incremental implementation of {@link ScoreDirectorFactory}.
 * @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
 * @see IncrementalScoreDirector
 * @see ScoreDirectorFactory
 */
public class IncrementalScoreDirectorFactory<Solution_> extends AbstractScoreDirectorFactory<Solution_> {

    private final Class<? extends IncrementalScoreCalculator> incrementalScoreCalculatorClass;
    private final Map<String, String> incrementalScoreCalculatorCustomProperties;

    public IncrementalScoreDirectorFactory(SolutionDescriptor<Solution_> solutionDescriptor,
            Class<? extends IncrementalScoreCalculator> incrementalScoreCalculatorClass,
            Map<String, String> incrementalScoreCalculatorCustomProperties) {
        super(solutionDescriptor);
        this.incrementalScoreCalculatorClass = incrementalScoreCalculatorClass;
        this.incrementalScoreCalculatorCustomProperties = incrementalScoreCalculatorCustomProperties;
    }

    // ************************************************************************
    // Complex methods
    // ************************************************************************

    @Override
    public IncrementalScoreDirector<Solution_> buildScoreDirector(
            boolean lookUpEnabled, boolean constraintMatchEnabledPreference) {
        IncrementalScoreCalculator<Solution_> incrementalScoreCalculator = ConfigUtils.newInstance(this,
                "incrementalScoreCalculatorClass", incrementalScoreCalculatorClass);
        ConfigUtils.applyCustomProperties(incrementalScoreCalculator, "incrementalScoreCalculatorClass",
                incrementalScoreCalculatorCustomProperties, "incrementalScoreCalculatorCustomProperties");
        return new IncrementalScoreDirector<>(this,
                lookUpEnabled, constraintMatchEnabledPreference, incrementalScoreCalculator);
    }

}


/*
 * Copyright 2012 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.core.config.heuristic.selector.move.generic.chained;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.config.heuristic.policy.HeuristicConfigPolicy;
import org.optaplanner.core.config.heuristic.selector.common.SelectionCacheType;
import org.optaplanner.core.config.heuristic.selector.common.SelectionOrder;
import org.optaplanner.core.config.heuristic.selector.move.MoveSelectorConfig;
import org.optaplanner.core.config.heuristic.selector.value.ValueSelectorConfig;
import org.optaplanner.core.config.heuristic.selector.value.chained.SubChainSelectorConfig;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor;
import org.optaplanner.core.impl.heuristic.selector.move.MoveSelector;
import org.optaplanner.core.impl.heuristic.selector.move.generic.chained.SubChainChangeMoveSelector;
import org.optaplanner.core.impl.heuristic.selector.value.EntityIndependentValueSelector;
import org.optaplanner.core.impl.heuristic.selector.value.ValueSelector;
import org.optaplanner.core.impl.heuristic.selector.value.chained.SubChainSelector;

import static org.apache.commons.lang3.ObjectUtils.*;

@XStreamAlias("subChainChangeMoveSelector")
public class SubChainChangeMoveSelectorConfig extends MoveSelectorConfig<SubChainChangeMoveSelectorConfig> {

    private Class<?> entityClass = null;
    @XStreamAlias("subChainSelector")
    private SubChainSelectorConfig subChainSelectorConfig = null;
    @XStreamAlias("valueSelector")
    private ValueSelectorConfig valueSelectorConfig = null;

    private Boolean selectReversingMoveToo = null;

    public Class<?> getEntityClass() {
        return entityClass;
    }

    public void setEntityClass(Class<?> entityClass) {
        this.entityClass = entityClass;
    }

    public SubChainSelectorConfig getSubChainSelectorConfig() {
        return subChainSelectorConfig;
    }

    public void setSubChainSelectorConfig(SubChainSelectorConfig subChainSelectorConfig) {
        this.subChainSelectorConfig = subChainSelectorConfig;
    }

    public ValueSelectorConfig getValueSelectorConfig() {
        return valueSelectorConfig;
    }

    public void setValueSelectorConfig(ValueSelectorConfig valueSelectorConfig) {
        this.valueSelectorConfig = valueSelectorConfig;
    }

    public Boolean getSelectReversingMoveToo() {
        return selectReversingMoveToo;
    }

    public void setSelectReversingMoveToo(Boolean selectReversingMoveToo) {
        this.selectReversingMoveToo = selectReversingMoveToo;
    }

    // ************************************************************************
    // Builder methods
    // ************************************************************************

    @Override
    public MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy,
            SelectionCacheType minimumCacheType, boolean randomSelection) {
        EntityDescriptor entityDescriptor = deduceEntityDescriptor(
                configPolicy.getSolutionDescriptor(), entityClass);
        SubChainSelectorConfig subChainSelectorConfig_ = subChainSelectorConfig == null ? new SubChainSelectorConfig()
                : subChainSelectorConfig;
        SubChainSelector subChainSelector = subChainSelectorConfig_.buildSubChainSelector(configPolicy,
                entityDescriptor,
                minimumCacheType, SelectionOrder.fromRandomSelectionBoolean(randomSelection));
        ValueSelectorConfig valueSelectorConfig_ = valueSelectorConfig == null ? new ValueSelectorConfig()
                : valueSelectorConfig;
        ValueSelector valueSelector = valueSelectorConfig_.buildValueSelector(configPolicy,
                entityDescriptor,
                minimumCacheType, SelectionOrder.fromRandomSelectionBoolean(randomSelection));
        if (!(valueSelector instanceof EntityIndependentValueSelector)) {
            throw new IllegalArgumentException("The moveSelectorConfig (" + this
                    + ") needs to be based on an EntityIndependentValueSelector (" + valueSelector + ")."
                    + " Check your @" + ValueRangeProvider.class.getSimpleName() + " annotations.");
        }
        return new SubChainChangeMoveSelector(subChainSelector, (EntityIndependentValueSelector) valueSelector,
                randomSelection, defaultIfNull(selectReversingMoveToo, true));
    }

    @Override
    public void inherit(SubChainChangeMoveSelectorConfig inheritedConfig) {
        super.inherit(inheritedConfig);
        entityClass = ConfigUtils.inheritOverwritableProperty(entityClass, inheritedConfig.getEntityClass());
        subChainSelectorConfig = ConfigUtils.inheritConfig(subChainSelectorConfig, inheritedConfig.getSubChainSelectorConfig());
        valueSelectorConfig = ConfigUtils.inheritConfig(valueSelectorConfig, inheritedConfig.getValueSelectorConfig());
        selectReversingMoveToo = ConfigUtils.inheritOverwritableProperty(selectReversingMoveToo,
                inheritedConfig.getSelectReversingMoveToo());
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "(" + subChainSelectorConfig + ", " + valueSelectorConfig + ")";
    }

}


/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.benchmark.impl.result;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.optaplanner.benchmark.impl.report.BenchmarkReport;
import org.optaplanner.benchmark.impl.statistic.ProblemStatistic;
import org.optaplanner.benchmark.impl.statistic.PureSubSingleStatistic;
import org.optaplanner.benchmark.impl.statistic.StatisticType;
import org.optaplanner.benchmark.impl.statistic.SubSingleStatistic;
import org.optaplanner.core.api.score.FeasibilityScore;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.solver.Solver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Represents 1 benchmark run for 1 Single Benchmark configuration for 1 {@link Solver} configuration for 1 problem
 * instance (data set).
 */
@XStreamAlias("subSingleBenchmarkResult")
public class SubSingleBenchmarkResult implements BenchmarkResult {

    private static final Logger logger = LoggerFactory.getLogger(SubSingleBenchmarkResult.class);

    @XStreamOmitField // Bi-directional relationship restored through BenchmarkResultIO
    private SingleBenchmarkResult singleBenchmarkResult;
    private final int subSingleBenchmarkIndex;

    @XStreamImplicit(itemFieldName = "pureSubSingleStatistic")
    private List<PureSubSingleStatistic> pureSubSingleStatisticList = null;

    @XStreamOmitField // Lazily restored when read through ProblemStatistic and CSV files
    private Map<StatisticType, SubSingleStatistic> effectiveSubSingleStatisticMap;

    private Long usedMemoryAfterInputSolution = null;

    private Boolean succeeded = null;
    private Score score = null;
    private long timeMillisSpent = -1L;
    private long scoreCalculationCount = -1L;

    // ************************************************************************
    // Report accumulates
    // ************************************************************************

    // Ranking starts from 0
    private Integer ranking = null;

    // ************************************************************************
    // Constructors and simple getters/setters
    // ************************************************************************

    public SubSingleBenchmarkResult(SingleBenchmarkResult singleBenchmarkResult, int subSingleBenchmarkIndex) {
        this.singleBenchmarkResult = singleBenchmarkResult;
        this.subSingleBenchmarkIndex = subSingleBenchmarkIndex;
    }

    public List<PureSubSingleStatistic> getPureSubSingleStatisticList() {
        return pureSubSingleStatisticList;
    }

    public void setPureSubSingleStatisticList(List<PureSubSingleStatistic> pureSubSingleStatisticList) {
        this.pureSubSingleStatisticList = pureSubSingleStatisticList;
    }

    public void initSubSingleStatisticMap() {
        List<ProblemStatistic> problemStatisticList = singleBenchmarkResult.getProblemBenchmarkResult().getProblemStatisticList();
        effectiveSubSingleStatisticMap = new HashMap<>(
                problemStatisticList.size() + pureSubSingleStatisticList.size());
        for (ProblemStatistic problemStatistic : problemStatisticList) {
            SubSingleStatistic subSingleStatistic = problemStatistic.createSubSingleStatistic(this);
            effectiveSubSingleStatisticMap.put(subSingleStatistic.getStatisticType(), subSingleStatistic);
        }
        for (PureSubSingleStatistic pureSubSingleStatistic : pureSubSingleStatisticList) {
            effectiveSubSingleStatisticMap.put(pureSubSingleStatistic.getStatisticType(), pureSubSingleStatistic);
        }
    }

    public SingleBenchmarkResult getSingleBenchmarkResult() {
        return singleBenchmarkResult;
    }

    public void setSingleBenchmarkResult(SingleBenchmarkResult singleBenchmarkResult) {
        this.singleBenchmarkResult = singleBenchmarkResult;
    }

    public int getSubSingleBenchmarkIndex() {
        return subSingleBenchmarkIndex;
    }

    public Map<StatisticType, SubSingleStatistic> getEffectiveSubSingleStatisticMap() {
        return effectiveSubSingleStatisticMap;
    }

    /**
     * @return null if {@link PlannerBenchmarkResult#hasMultipleParallelBenchmarks()} return true
     */
    public Long getUsedMemoryAfterInputSolution() {
        return usedMemoryAfterInputSolution;
    }

    public void setUsedMemoryAfterInputSolution(Long usedMemoryAfterInputSolution) {
        this.usedMemoryAfterInputSolution = usedMemoryAfterInputSolution;
    }

    public Boolean getSucceeded() {
        return succeeded;
    }

    public void setSucceeded(Boolean succeeded) {
        this.succeeded = succeeded;
    }

    public Score getScore() {
        return score;
    }

    public void setScore(Score score) {
        this.score = score;
    }

    public long getTimeMillisSpent() {
        return timeMillisSpent;
    }

    public void setTimeMillisSpent(long timeMillisSpent) {
        this.timeMillisSpent = timeMillisSpent;
    }

    public long getScoreCalculationCount() {
        return scoreCalculationCount;
    }

    public void setScoreCalculationCount(long scoreCalculationCount) {
        this.scoreCalculationCount = scoreCalculationCount;
    }

    public Integer getRanking() {
        return ranking;
    }

    public void setRanking(Integer ranking) {
        this.ranking = ranking;
    }

    // ************************************************************************
    // Smart getters
    // ************************************************************************

    /**
     * @return never null, filename safe
     */
    @Override
    public String getName() {
        return singleBenchmarkResult.getName() + "_" + subSingleBenchmarkIndex;
    }

    @Override
    public boolean hasAllSuccess() {
        return succeeded != null && succeeded.booleanValue();
    }

    public boolean isInitialized() {
        return score != null && score.isSolutionInitialized();
    }

    @Override
    public boolean hasAnyFailure() {
        return succeeded != null && !succeeded.booleanValue();
    }

    public boolean isScoreFeasible() {
        if (score instanceof FeasibilityScore) {
            return ((FeasibilityScore) score).isFeasible();
        } else {
            return true;
        }
    }

    public Long getScoreCalculationSpeed() {
        long timeMillisSpent = this.timeMillisSpent;
        if (timeMillisSpent == 0L) {
            // Avoid divide by zero exception on a fast CPU
            timeMillisSpent = 1L;
        }
        return scoreCalculationCount * 1000L / timeMillisSpent;
    }

    public boolean isWinner() {
        return ranking != null && ranking.intValue() == 0;
    }

    public SubSingleStatistic getSubSingleStatistic(StatisticType statisticType) {
        return effectiveSubSingleStatisticMap.get(statisticType);
    }

    @Override
    public Score getAverageScore() {
        return getScore();
    }

    // ************************************************************************
    // Accumulate methods
    // ************************************************************************

    @Override
    public String getResultDirectoryName() {
        return "sub" + subSingleBenchmarkIndex;
    }

    @Override
    public File getResultDirectory() {
        return new File(singleBenchmarkResult.getResultDirectory(), getResultDirectoryName());
    }

    public void makeDirs() {
        File subSingleReportDirectory = getResultDirectory();
        subSingleReportDirectory.mkdirs();
    }

    public void accumulateResults(BenchmarkReport benchmarkReport) {

    }

    // ************************************************************************
    // Merger methods
    // ************************************************************************

    protected static SubSingleBenchmarkResult createMerge(
            SingleBenchmarkResult singleBenchmarkResult, SubSingleBenchmarkResult oldResult,
            int subSingleBenchmarkIndex) {
        SubSingleBenchmarkResult newResult = new SubSingleBenchmarkResult(singleBenchmarkResult, subSingleBenchmarkIndex);
        newResult.pureSubSingleStatisticList = new ArrayList<>(oldResult.pureSubSingleStatisticList.size());
        for (PureSubSingleStatistic oldSubSingleStatistic : oldResult.pureSubSingleStatisticList) {
            newResult.pureSubSingleStatisticList.add(
                    oldSubSingleStatistic.getStatisticType().buildPureSubSingleStatistic(newResult));
        }

        newResult.initSubSingleStatisticMap();
        for (SubSingleStatistic newSubSingleStatistic : newResult.effectiveSubSingleStatisticMap.values()) {
            SubSingleStatistic oldSubSingleStatistic = oldResult.getSubSingleStatistic(newSubSingleStatistic.getStatisticType());
            if (!oldSubSingleStatistic.getCsvFile().exists()) {
                if (oldResult.hasAnyFailure()) {
                    newSubSingleStatistic.initPointList();
                    logger.debug("Old result ({}) is a failure, skipping merge of its sub single statistic ({}).",
                            oldResult, oldSubSingleStatistic);
                    continue;
                } else {
                    throw new IllegalStateException("Could not find old result's (" + oldResult
                            + ") sub single statistic's (" + oldSubSingleStatistic + ") CSV file.");
                }
            }
            oldSubSingleStatistic.unhibernatePointList();
            newSubSingleStatistic.setPointList(oldSubSingleStatistic.getPointList());
            oldSubSingleStatistic.hibernatePointList();
        }
        // Skip oldResult.reportDirectory
        // Skip oldResult.usedMemoryAfterInputSolution
        newResult.succeeded = oldResult.succeeded;
        newResult.score = oldResult.score;
        newResult.timeMillisSpent = oldResult.timeMillisSpent;
        newResult.scoreCalculationCount = oldResult.scoreCalculationCount;

        singleBenchmarkResult.getSubSingleBenchmarkResultList().add(newResult);
        return newResult;
    }

    @Override
    public String toString() {
        return getName();
    }

}


/*
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.benchmark.impl.statistic.bestsolutionmutation;

import java.util.List;

import org.optaplanner.benchmark.config.statistic.ProblemStatisticType;
import org.optaplanner.benchmark.impl.result.SubSingleBenchmarkResult;
import org.optaplanner.benchmark.impl.statistic.ProblemBasedSubSingleStatistic;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import org.optaplanner.core.api.solver.event.SolverEventListener;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.domain.solution.mutation.MutationCounter;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.optaplanner.core.impl.score.director.InnerScoreDirectorFactory;

public class BestSolutionMutationSubSingleStatistic<Solution_>
        extends ProblemBasedSubSingleStatistic<Solution_, BestSolutionMutationStatisticPoint> {

    private BestSolutionMutationSubSingleStatisticListener listener;

    public BestSolutionMutationSubSingleStatistic(SubSingleBenchmarkResult subSingleBenchmarkResult) {
        super(subSingleBenchmarkResult, ProblemStatisticType.BEST_SOLUTION_MUTATION);
        listener = new BestSolutionMutationSubSingleStatisticListener();
    }

    // ************************************************************************
    // Lifecycle methods
    // ************************************************************************

    @Override
    public void open(Solver<Solution_> solver) {
        InnerScoreDirectorFactory<Solution_> scoreDirectorFactory
                = (InnerScoreDirectorFactory<Solution_>) solver.getScoreDirectorFactory();
        SolutionDescriptor<Solution_> solutionDescriptor = scoreDirectorFactory.getSolutionDescriptor();
        listener.setMutationCounter(new MutationCounter<>(solutionDescriptor));
        solver.addEventListener(listener);
    }

    @Override
    public void close(Solver<Solution_> solver) {
        solver.removeEventListener(listener);
    }

    private class BestSolutionMutationSubSingleStatisticListener implements SolverEventListener<Solution_> {

        private MutationCounter<Solution_> mutationCounter;

        private Solution_ oldBestSolution = null;

        private void setMutationCounter(MutationCounter<Solution_> mutationCounter) {
            if (this.mutationCounter != null) {
                throw new IllegalStateException("Impossible state: mutationCounter (" + this.mutationCounter
                        + ") is not null.");
            }
            this.mutationCounter = mutationCounter;
        }

        @Override
        public void bestSolutionChanged(BestSolutionChangedEvent<Solution_> event) {
            int mutationCount;
            Solution_ newBestSolution = event.getNewBestSolution();
            if (oldBestSolution == null) {
                mutationCount = 0;
            } else {
                mutationCount = mutationCounter.countMutations(oldBestSolution, newBestSolution);
            }
            pointList.add(new BestSolutionMutationStatisticPoint(
                    event.getTimeMillisSpent(), mutationCount));
            oldBestSolution = newBestSolution;
        }

    }

    // ************************************************************************
    // CSV methods
    // ************************************************************************

    @Override
    protected String getCsvHeader() {
        return BestSolutionMutationStatisticPoint.buildCsvLine("timeMillisSpent", "mutationCount");
    }

    @Override
    protected BestSolutionMutationStatisticPoint createPointFromCsvLine(ScoreDefinition scoreDefinition,
            List<String> csvLine) {
        return new BestSolutionMutationStatisticPoint(Long.parseLong(csvLine.get(0)),
                Integer.parseInt(csvLine.get(1)));
    }

}