<commit_before>/*
 *  Tanaguru - Automated webpage assessment
 *  Copyright (C) 2008-2013  Open-S Company
 * 
 *  This file is part of Tanaguru.
 * 
 *  Tanaguru 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: open-s AT open-s DOT com
 */

package org.opens.tanaguru.rules.elementchecker.contrast;

import java.text.DecimalFormat;
import java.util.*;
import javax.annotation.Nonnull;
import javax.persistence.NoResultException;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.select.Elements;
import org.opens.tanaguru.entity.audit.EvidenceElement;
import org.opens.tanaguru.entity.audit.TestSolution;
import org.opens.tanaguru.processor.SSPHandler;
import org.opens.tanaguru.ruleimplementation.TestSolutionHandler;
import org.opens.tanaguru.rules.elementchecker.ElementCheckerImpl;
import org.opens.tanaguru.rules.elementchecker.contrast.exception.ContrastCheckerParseResultException;
import static org.opens.tanaguru.rules.keystore.EvidenceStore.*;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.*;
import org.opens.tanaguru.rules.utils.ContrastHelper;
import static org.opens.tanaguru.service.ProcessRemarkService.DEFAULT_EVIDENCE;

/**
 * This checker tests whether the contrast between the colour of each textual 
 * element and their background colour is correct regarding a settable ratio
 * value. 
 * The ratio is computed during the loading phase and this test consists in
 * parsing the result in JSON format, create the result of the test and the
 * remarks.
 * 
 */
public class ContrastChecker extends ElementCheckerImpl {

    private static final Logger LOGGER = Logger.getLogger(ContrastChecker.class);
    
    private static final String COLOR_EXTRACTOR_PRE_PROCESS_RESULT_KEY = "colorExtractor";
    
    private static final String JSON_FONT_SIZE_KEY = "fontSize";
    private static final String JSON_ELEMENT_PATH_KEY = "path";
    private static final String JSON_FONT_WEIGHT_KEY = "fontWeight";
    private static final String JSON_FG_COLOR_KEY = "color";
    private static final String JSON_BG_COLOR_KEY = "bgColor";
    private static final String JSON_IS_HIDDEN_KEY = "isHidden";
    private static final String JSON_IS_TEXT_NODE_KEY = "isTextNode";

    private static final String NORMAL_WEIGHT_KEY = "normal";
    private static final String BOLD_WEIGHT_KEY = "bold";
    private static final String LIGHTER_WEIGHT_KEY = "lighter";
    private static final String BOLDER_WEIGHT_KEY = "bolder";
    
    private static final int NORMAL_FONT_SIZE_THRESHOLD = 18;
    private static final int BOLD_FONT_SIZE_THRESHOLD = 14;
    private static final int NORMAL_FONT_WEIGHT = 400;
    private static final int BOLD_FONT_WEIGHT = 700;
    
    /** The contrast ratio */
    private Float contrastRatio;
    
    /** is lower test */
    private boolean isLowerTest = true;
    
    /** check bold text */
    private boolean checkBoldText = true;
    
    /** check normal text */
    private boolean checkNormalText = true;

    /** */
    private boolean createSourceCodeRemarkForNmi = false;
    
    /** */
    private Set<String> notTreatedBackgroundColorValue = new HashSet<String>();
    
    /** The element counter */
    int elementCounter = 0;
    public int getElementCounter() {
        return elementCounter;
    }
    
    /**
     * Constructor 
     * 
     * @param contrastRatio
     * @param isLowerTest 
     */
    public ContrastChecker(
            @Nonnull Float contrastRatio, 
            @Nonnull boolean checkNormalText,
            @Nonnull boolean checkBoldText,
            @Nonnull boolean isLowerTest) {
        super();
        this.contrastRatio = contrastRatio;
        this.checkNormalText = checkNormalText;
        this.checkBoldText = checkBoldText;
        this.isLowerTest = isLowerTest;
    }
    
    @Override
    protected void doCheck(SSPHandler sspHandler, Elements elements, TestSolutionHandler testSolutionHandler) {
        try {

            String ppr = sspHandler.getPreProcessResult(
                    COLOR_EXTRACTOR_PRE_PROCESS_RESULT_KEY, 
                    sspHandler.getPage());
            // at this step, if the NoResultException hasn't be caught, that 
            // means that some elements are present. We initialise the solution
            // as passed, if some elements are invalid, we override it with a 
            // a failed solution
            parseJSONArray(sspHandler, new JSONArray(ppr), testSolutionHandler);
            if (elementCounter > 0) {
                testSolutionHandler.addTestSolution(TestSolution.PASSED);
            } else {
                testSolutionHandler.addTestSolution(TestSolution.NOT_APPLICABLE);
            }
        } catch (JSONException e) {
            resetCollectedDataOnException(testSolutionHandler, e);
        } catch (NoResultException nre) {
            // if the getPreProcessResult returns a noResultException, that 
            // means a problem occured when executing js. Nothing cannot be done.
            // the testResult is NOT_TESTED
            resetCollectedDataOnException(testSolutionHandler, nre);
        } catch (ContrastCheckerParseResultException ccpre) {
            // if any problem is encountered while analysing elements, the 
            // test result is set to NOT_TESTED and the processRemarkService is
            // reset
            resetCollectedDataOnException(testSolutionHandler, ccpre);
        }
    }

    /**
     * 
     * @param sspHandler
     * @param json
     * @param testSolutionHandler
     * <commit_msg>rules-commons : deal with contrast result when no element is on error, but when images are still in the page<commit_after>/*
 *  Tanaguru - Automated webpage assessment
 *  Copyright (C) 2008-2013  Open-S Company
 * 
 *  This file is part of Tanaguru.
 * 
 *  Tanaguru 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: open-s AT open-s DOT com
 */

package org.opens.tanaguru.rules.elementchecker.contrast;

import java.text.DecimalFormat;
import java.util.*;
import javax.annotation.Nonnull;
import javax.persistence.NoResultException;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.select.Elements;
import org.opens.tanaguru.entity.audit.EvidenceElement;
import org.opens.tanaguru.entity.audit.TestSolution;
import org.opens.tanaguru.processor.SSPHandler;
import org.opens.tanaguru.ruleimplementation.TestSolutionHandler;
import org.opens.tanaguru.rules.elementchecker.ElementCheckerImpl;
import org.opens.tanaguru.rules.elementchecker.contrast.exception.ContrastCheckerParseResultException;
import static org.opens.tanaguru.rules.keystore.EvidenceStore.BG_COLOR_EE;
import static org.opens.tanaguru.rules.keystore.EvidenceStore.CONTRAST_EE;
import static org.opens.tanaguru.rules.keystore.EvidenceStore.FG_COLOR_EE;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.BAD_CONTRAST_HIDDEN_ELEMENT_MSG;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.BAD_CONTRAST_MSG;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.CHECK_CONTRAST_MANUALLY_MSG;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.CHECK_CONTRAST_OF_IMAGE_MSG;
import static org.opens.tanaguru.rules.keystore.RemarkMessageStore.NOT_TREATED_BACKGROUND_COLOR_MSG;
import static org.opens.tanaguru.rules.keystore.CssLikeQueryStore.IMG_CSS_LIKE_QUERY;
import org.opens.tanaguru.rules.utils.ContrastHelper;
import static org.opens.tanaguru.service.ProcessRemarkService.DEFAULT_EVIDENCE;

/**
 * This checker tests whether the contrast between the colour of each textual 
 * element and their background colour is correct regarding a settable ratio
 * value. 
 * The ratio is computed during the loading phase and this test consists in
 * parsing the result in JSON format, create the result of the test and the
 * remarks.
 * 
 */
public class ContrastChecker extends ElementCheckerImpl {

    private static final Logger LOGGER = Logger.getLogger(ContrastChecker.class);
    
    private static final String COLOR_EXTRACTOR_PRE_PROCESS_RESULT_KEY = "colorExtractor";
    
    private static final String JSON_FONT_SIZE_KEY = "fontSize";
    private static final String JSON_ELEMENT_PATH_KEY = "path";
    private static final String JSON_FONT_WEIGHT_KEY = "fontWeight";
    private static final String JSON_FG_COLOR_KEY = "color";
    private static final String JSON_BG_COLOR_KEY = "bgColor";
    private static final String JSON_IS_HIDDEN_KEY = "isHidden";
    private static final String JSON_IS_TEXT_NODE_KEY = "isTextNode";

    private static final String NORMAL_WEIGHT_KEY = "normal";
    private static final String BOLD_WEIGHT_KEY = "bold";
    private static final String LIGHTER_WEIGHT_KEY = "lighter";
    private static final String BOLDER_WEIGHT_KEY = "bolder";
    
    private static final int NORMAL_FONT_SIZE_THRESHOLD = 18;
    private static final int BOLD_FONT_SIZE_THRESHOLD = 14;
    private static final int NORMAL_FONT_WEIGHT = 400;
    private static final int BOLD_FONT_WEIGHT = 700;
    
    /** The contrast ratio */
    private Float contrastRatio;
    
    /** is lower test */
    private boolean isLowerTest = true;
    
    /** check bold text */
    private boolean checkBoldText = true;
    
    /** check normal text */
    private boolean checkNormalText = true;

    /** */
    private boolean createSourceCodeRemarkForNmi = false;
    
    /** */
    private Set<String> notTreatedBackgroundColorValue = new HashSet<String>();
    
    /** The element counter */
    int elementCounter = 0;
    public int getElementCounter() {
        return elementCounter;
    }
    
    /**
     * Constructor 
     * 
     * @param contrastRatio
     * @param isLowerTest 
     */
    public ContrastChecker(
            @Nonnull Float contrastRatio, 
            @Nonnull boolean checkNormalText,
            @Nonnull boolean checkBoldText,
            @Nonnull boolean isLowerTest) {
        super();
        this.contrastRatio = contrastRatio;
        this.checkNormalText = checkNormalText;
        this.checkBoldText = checkBoldText;
        this.isLowerTest = isLowerTest;
    }
    
    @Override
    protected void doCheck(SSPHandler sspHandler, Elements elements, TestSolutionHandler testSolutionHandler) {
        try {

            String ppr = sspHandler.getPreProcessResult(
                    COLOR_EXTRACTOR_PRE_PROCESS_RESULT_KEY, 
                    sspHandler.getPage());
            // at this step, if the NoResultException hasn't be caught, that 
            // means that some elements are present. We initialise the solution
            // as passed, if some elements are invalid, we override it with a 
            // a failed solution
            parseJSONArray(sspHandler, new JSONArray(ppr), testSolutionHandler);
            if (elementCounter > 0) { 
//                 means that no element is on error or NMI
                if (testSolutionHandler.getTestSolution().equals(TestSolution.NOT_APPLICABLE)) {
                    LOGGER.debug("nothing to say about contrast");
                    if (sspHandler.domCssLikeSelectNodeSet(IMG_CSS_LIKE_QUERY).getSelectedElementNumber() == 0) {
                        testSolutionHandler.addTestSolution(TestSolution.PASSED);
                        LOGGER.debug("The page has no image, the result is Passed");
                    } else {
                        testSolutionHandler.addTestSolution(TestSolution.NEED_MORE_INFO);
                        addProcessRemark(TestSolution.NEED_MORE_INFO, 
                                         CHECK_CONTRAST_OF_IMAGE_MSG);
                        LOGGER.debug("The page has images, the result is NMI");
                    }
                } else {
                    LOGGER.debug("some Constrast remarks have been thrown");
                }
            } else {
                LOGGER.debug("test is not applicable");
            }
        } catch (JSONException e) {
            resetCollectedDataOnException(testSolutionHandler, e);
        } catch (NoResultException nre) {
            // if the getPreProcessResult returns a noResultException, that 
            // means a problem occured when executing js. Nothing cannot be done.
            // the testResult is NOT_TESTED
            resetCollectedDataOnException(testSolutionHandler, nre);
        } catch (ContrastCheckerParseResultException ccpre) {
            // if any problem is encountered while analysing elements, the 
            // test result is set to NOT_TESTED and the processRemarkService is
            // reset
            resetCollectedDataOnException(testSolutionHandler, ccpre);
        }
    }

    /**
     * 
     * @param sspHandler
     * @param json
     * @param testSolutionHandler
     * 
{'fraction_non_alphanumeric': 0.07051552184295547, 'fraction_numerical': 0.0033010254249192304, 'mean_word_length': 2.970719464584495, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1469995', 'n_tokens_mistral': 4014, 'n_tokens_neox': 3816, 'n_words': 1147}