/** @module analyses/calculateFleschReading */

import stripNumbers from "../helpers/sanitize/stripNumbers.js";
import countSentences from "../helpers/sentence/countSentences.js";
import countWords from "../helpers/word/countWords.js";
import countSyllables from "../helpers/syllables/countSyllables.js";
import { clamp, inRange } from "lodash";

/**
 * Calculates an average from a total and an amount
 *
 * @param {number} total The total.
 * @param {number} amount The amount.
 * @returns {number} The average from the total and the amount.
 */
const getAverage = function( total, amount ) {
	return total / amount;
};

/**
 * The Flesch reading ease difficulty.
 * @readonly
 * @enum {number}
 */
export const DIFFICULTY = {
	NO_DATA: -1,
	VERY_EASY: 0,
	EASY: 1,
	FAIRLY_EASY: 2,
	OKAY: 3,
	FAIRLY_DIFFICULT: 4,
	DIFFICULT: 5,
	VERY_DIFFICULT: 6,
};

/**
 * Returns the Flesch reading ease difficulty based on the boundaries
 * defined in the score configuration.
 *
 * @param {number} score The Flesch reading ease score.
 * @param {Object} scoreConfiguration The score configuration.
 *
 * @returns {DIFFICULTY} The Flesch reading ease difficulty.
 */
function getDifficulty( score, scoreConfiguration ) {
	if ( score >= scoreConfiguration.borders.veryEasy ) {
		return DIFFICULTY.VERY_EASY;
	} else if ( inRange( score, scoreConfiguration.borders.easy, scoreConfiguration.borders.veryEasy ) ) {
		return DIFFICULTY.EASY;
	} else if ( inRange( score, scoreConfiguration.borders.fairlyEasy, scoreConfiguration.borders.easy ) ) {
		return DIFFICULTY.FAIRLY_EASY;
	} else if ( inRange( score, scoreConfiguration.borders.okay, scoreConfiguration.borders.fairlyEasy ) ) {
		return DIFFICULTY.OKAY;
	} else if ( inRange( score, scoreConfiguration.borders.fairlyDifficult, scoreConfiguration.borders.okay ) ) {
		return DIFFICULTY.FAIRLY_DIFFICULT;
	} else if ( inRange( score, scoreConfiguration.borders.difficult, scoreConfiguration.borders.fairlyDifficult ) ) {
		return DIFFICULTY.DIFFICULT;
	}

	return DIFFICULTY.VERY_DIFFICULT;
}

/**
 * Retrieves the scoring configuration defining the boundaries to use to
 * determine the Flesch reading ease difficulty.
 *
 * @param {Researcher} researcher The researcher.
 *
 * @returns {Object} The language specific scoring configuration, or the default configuration if not available.
 */
function getConfiguration( researcher ) {
	const languageSpecificConfig = researcher.getConfig( "fleschReadingEaseScores" );
	const defaultConfig = {
		borders: {
			veryEasy: 90,
			easy: 80,
			fairlyEasy: 70,
			okay: 60,
			fairlyDifficult: 50,
			difficult: 30,
			veryDifficult: 0,
		},
		scores: {
			veryEasy: 9,
			easy: 9,
			fairlyEasy: 9,
			okay: 9,
			fairlyDifficult: 6,
			difficult: 3,
			veryDifficult: 3,
		},
	};
	return languageSpecificConfig ? languageSpecificConfig : defaultConfig;
}

/**
 * This calculates the Flesch reading score for a given text.
 *
 * @param {Paper}       paper           The paper containing the text.
 * @param {Researcher}  researcher      The researcher.
 *
 * @returns {{ score: number, difficulty: DIFFICULTY }} The Flesch reading score.
 */
export default function( paper, researcher ) {
	const syllables = researcher.getConfig( "syllables" );
	const memoizedTokenizer = researcher.getHelper( "memoizedTokenizer" );
	const languageSpecificConfiguration = getConfiguration( researcher );

	let text = paper.getText();
	if ( text === "" ) {
		// A score of -1 signals to the code down the line that no valid FRE was calculated.
		return {
			score: -1,
			difficulty: DIFFICULTY.NO_DATA,
		};
	}

	text = stripNumbers( text );

	const numberOfSentences = countSentences( text, memoizedTokenizer );
	const numberOfWords = countWords( text );

	// Do not show the Flesch reading ease when there is not enough data for the FRE to make sense. Also used to prevent division by zero errors.
	// A score of -1 signals to the code down the line that no valid FRE was calculated.
	if ( numberOfSentences < 1 || numberOfWords <= 10 ) {
		return {
			score: -1,
			difficulty: DIFFICULTY.NO_DATA,
		};
	}

	const numberOfSyllables = countSyllables( text, syllables );
	const averageWordsPerSentence = getAverage( numberOfWords, numberOfSentences );
	const syllablesPer100Words = numberOfSyllables * ( 100 / numberOfWords );
	const statistics = {
		numberOfSentences,
		numberOfWords,
		numberOfSyllables,
		averageWordsPerSentence,
		syllablesPer100Words,
	};

	const getScore = researcher.getHelper( "fleschReadingScore" );

	const score = clamp( getScore( statistics ), 0, 100 );

	const difficulty = getDifficulty( score, languageSpecificConfiguration );

	return { score, difficulty };
}
