/FileExtraction/Java_unseen/Asqatasun_Contrast-Finder/engine/hsv/src/main/java/org/asqatasun/contrastfinder/hsv/ColorFinderHsv.java
/*
 * Contrast Finder
 * Copyright (C) 2008-2019   Contrast-Finder.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact us by mail: asqatasun AT asqatasun DOT org
 */
package org.asqatasun.contrastfinder.hsv;

import java.awt.Color;
import java.util.Collection;
import java.util.HashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.asqatasun.contrastfinder.AbstractColorFinder;
import org.asqatasun.contrastfinder.result.factory.ColorCombinaisonFactoryImpl;
import org.asqatasun.contrastfinder.result.factory.ColorResultFactoryImpl;
import org.asqatasun.utils.colorconvertor.ColorConverter;
import org.asqatasun.utils.contrastchecker.ContrastChecker;

/**
 *
 * @author alingua
 */
public class ColorFinderHsv extends AbstractColorFinder {

    private static final Logger LOGGER = LoggerFactory.getLogger(ColorFinderHsv.class);
    private static final float STEP_BRIGHTNESS = 0.1f;
    private static final float STEP_SATURATION = 0.1f;
    private static final float STEP_HUE = 0.1f;
    private static final float NO_CHANGE_COMPONENT = 0.0f;
    private static final float MAX_POSSIBLE_VALUE = 1.0f;
    private static final float MIN_POSSIBLE_VALUE = 0.0f;
    private static final int MAX_RESULT_ONE_HUE = 20;
    private Collection<Color> testedColorsFromHueRound;
    private int testedColors;

    /**
     * Constructor
     */
    public ColorFinderHsv() {
        super(new ColorResultFactoryImpl(), new ColorCombinaisonFactoryImpl());
        LOGGER.debug("instanciation of ColorFinderHsv class");
    }

    /**
     * @param colorToChange
     * @param colorToKeep
     * @param coefficientLevel
     */
    @Override
    protected void findColors() {
        LOGGER.debug("call findColors, colors : " + getColorToKeep() + getColorResult().getSubmittedCombinaisonColor().getColor() + getColorResult().getNumberOfSuggestedColors());
        testedColorsFromHueRound = new HashSet<Color>();

        Color colorToModify = getColorResult().getSubmittedCombinaisonColor().getColor();

        testedColors = 0;
        changeHue(colorToModify, false);
        changeHue(colorToModify, true);

        getColorResult().setNumberOfTestedColors(testedColors);
        LOGGER.debug("Size of Color list : " + getColorResult().getSuggestedColors().size());
    }

    /**
     * @param colorToChange
     * @param increment
     */
    private void changeHue(final Color colorToChange, boolean increment) {
        float offset;
        if (increment) {
            offset = STEP_HUE;
        } else {
            offset = -STEP_HUE;
        }

        Color newColor = colorToChange;
        int initialResultSize = getColorResult().getNumberOfSuggestedColors();
        float currentHue = ColorConverter.getHue(newColor);
        boolean testNextColor = true;
        while (testNextColor) {
            testedColorsFromHueRound.add(newColor);
            LOGGER.debug("changeHue New Color  " + newColor.hashCode() + " " + newColor.toString());
            // the return is not used here, we just want to add the current color
            // in the result collection in case of its contrast is valid
            isNewColorValid(newColor);
            if (getColorResult().getNumberOfSuggestedColors() - initialResultSize < MAX_RESULT_ONE_HUE) {
                changeSaturation(newColor, false);
                changeSaturation(newColor, true);
                if (isNextColorBounded(currentHue, offset)) {
                    Color offsetColor = ColorConverter.offsetHsbColor(newColor,
                            offset,
                            NO_CHANGE_COMPONENT,
                            NO_CHANGE_COMPONENT);
                    currentHue = ColorConverter.getHue(offsetColor);
                    LOGGER.debug("offsetColor New Color  " + offsetColor.hashCode() + " " + offsetColor.toString());
                    if (!testedColorsFromHueRound.contains(offsetColor)) {
                        newColor = offsetColor;
                    } else {
                        // the offset never has impact on the color
                        testNextColor = false;
                    }
                } else {
                    // the next color is out of bound
                    testNextColor = false;
                }
            } else {
                // the maximum number of found color is reached for the current "changeHue" round
                testNextColor = false;
            }
        }
    }

    /**
     * @param colorToChange
     * @param increment
     */
    private void changeSaturation(final Color colorToChange, boolean increment) {
        LOGGER.debug("start saturation change : " + increment + " " + colorToChange);
        float offset;
        if (increment) {
            offset = STEP_SATURATION;
        } else {
            offset = -STEP_SATURATION;
        }
        Color newColor = colorToChange;
        float currentSaturation = ColorConverter.getSaturation(newColor);
        boolean testNextColor = true;
        while (testNextColor) {
            if (isNewColorValid(newColor)) {
                testNextColor = false;
            }
            if (isNextColorBounded(currentSaturation, offset)) {
                Color offsetColor = ColorConverter.offsetHsbColor(newColor,
                        NO_CHANGE_COMPONENT,
                        offset,
                        NO_CHANGE_COMPONENT);
                changeBrightness(newColor, false);
                changeBrightness(newColor, true);
                currentSaturation = ColorConverter.getSaturation(newColor);
                LOGGER.debug("offset color :  " + offsetColor);
                if (!newColor.equals(offsetColor)) {
                    newColor = offsetColor;
                } else {
                    testNextColor = false;
                }
            } else {
                testNextColor = false;
            }
        }
    }

    /**
     * @param colorToChange
     * @param colorToKeep
     * @param increment
     */
    private void changeBrightness(final Color colorToChange, boolean increment) {
        float offset;
        if (increment) {
            offset = STEP_BRIGHTNESS;
        } else {
            offset = -STEP_BRIGHTNESS;
        }
        Color newColor = colorToChange;
        float currentBrightness = ColorConverter.getBrightness(newColor);

        boolean testNextColor = true;
        while (testNextColor) {
            testedColors++;
            LOGGER.debug("(Direction : " + increment + ")    Color in change Brightness Loop : " + newColor.getRed() + " " + newColor.getGreen() + " " + newColor.getBlue() + " Contrast : " + ContrastChecker.getConstrastRatio(newColor, getColorToKeep()));
            if (isNewColorValid(newColor)) {
                testNextColor = false;
            }
            if (isNextColorBounded(currentBrightness, offset)) {
                newColor = ColorConverter.offsetHsbColor(newColor,
                        NO_CHANGE_COMPONENT,
                        NO_CHANGE_COMPONENT,
                        offset);
                currentBrightness = ColorConverter.getBrightness(newColor);
            } else {
                LOGGER.debug("Why do I return in brightness? (offset condition) ... Color : " + newColor.getRed() + " " + newColor.getGreen() + " " + newColor.getBlue() + " Contrast : " + ContrastChecker.getConstrastRatio(newColor, getColorToKeep()) + "  Brigtness" + ColorConverter.getBrightness(newColor));
                testNextColor = false;
            }
        }
    }

    /**
     * @param currentValue
     * @param offset
     * @return whether the next color is bounded
     */
    private boolean isNextColorBounded(float currentValue, float offset) {
        return (currentValue + offset >= MIN_POSSIBLE_VALUE
                && currentValue + offset <= MAX_POSSIBLE_VALUE);
    }

    /**
     * @return "HSV"
     */
    @Override
    public String getColorFinderKey() {
        return "HSV";
    }
}
