<?PHP
/**
 * Contains class NodaValidationHelper.
 *
 * @author Joshua Ramon Enslin <joshua@museum-digital.de>
 */
declare(strict_types = 1);

/**
 * A class of static functions for validating single fields of noda entities.
 */
final class NodaValidationHelper {

    private const ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS = 3;

    /**
     * Validates an actor description for completeness. Of course, only an informed
     * guess based on the length and character composition of the description can be
     * made.
     *
     * @param string $description Input descrition.
     * @param string $name        Names of the actor. Optional. Setting this enables
     *                            checks e.g. to prevent duplicating the actor name
     *                            as a description.
     *
     * @return void
     */
    public static function validateTagDescription(string $description, string $name = ""):void {

        // For nodac, empty tag descriptions are to be allowed. Thus, a
        // dedicated check for fully empty ones with a dedicated exception
        // is needed.
        if (empty($description)) {
            throw new MDInvalidEmptyInputException("No tag name is provided");
        }

        // Throw error on descriptions that are too short
        if (\mb_strlen($description) < 10) {
            throw new MDgenericInvalidInputsException("Tag description is too short");
        }

        // Validate tag description based on character composition.

        // Ensure more than 3 distinct characters are used.
        $chars = \str_split($description);

        $uniqueChars = array_unique($chars);
        if (count($uniqueChars) <= self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS) {
            throw new MDgenericInvalidInputsException("There need to be more than " . self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS . " distinct characters.");
        }

        // Ensure more than the actor name is used.
        $clearedChars = ['  ' => ' ', ',' => ' ', ';' => ' ', '.' => ' '];

        $uniqueNames = array_unique(array_diff(explode(' ', strtr($name, $clearedChars)), ['']));
        sort($uniqueNames);

        $descCleared = strtr($description, $clearedChars);
        $descWords = array_unique(array_diff(explode(' ', $descCleared), ['']));
        sort($descWords);

        if ($uniqueNames === $descWords) {
            throw new MDgenericInvalidInputsException("The tag name was simply repeated in the description. This is not enough.");
        }

    }

    /**
     * Validates an actor description for completeness. Of course, only an informed
     * guess based on the length and character composition of the description can be
     * made.
     *
     * @param string   $description Input descrition.
     * @param string[] $names       Names of the actor. Optional. Setting this enables
     *                              checks e.g. to prevent duplicating the actor name
     *                              as a description.
     *
     * @return void
     */
    public static function validateActorDescription(string $description, array $names = []):void {

        // For nodac, empty actor descriptions are to be allowed. Thus, a
        // dedicated check for fully empty ones with a dedicated exception
        // is needed.
        if (empty($description)) {
            throw new MDInvalidEmptyInputException("No author name is provided");
        }

        // Throw error on descriptions that are too short
        if (\mb_strlen($description) < 10) {
            throw new MDgenericInvalidInputsException("Author description is too short");
        }

        // Validate actor description based on character composition.

        $chars = \str_split($description);

        $uniqueChars = array_unique($chars);
        if (count($uniqueChars) <= self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS) {
            throw new MDgenericInvalidInputsException("There need to be more than " . self::ACTOR_DESCRIPTION_REQUIRED_DISTINCT_CHARS . " distinct characters.");
        }

        if (!empty($names)) {

            $clearedChars = ['  ' => ' ', ',' => ' ', ';' => ' ', '.' => ' '];

            $namesMerged = implode(' ', $names);
            $namesMerged = strtr($namesMerged, $clearedChars);
            $uniqueNames = array_unique(array_diff(explode(' ', $namesMerged), ['']));
            sort($uniqueNames);

            $descCleared = strtr($description, $clearedChars);
            $descWords = array_unique(array_diff(explode(' ', $descCleared), ['']));
            sort($descWords);

            if ($uniqueNames === $descWords) {
                throw new MDgenericInvalidInputsException("The actor name was simply repeated in the description. This is not enough.");
            }

        }

    }

    /**
     * Checks if an actor name is valid.
     * Returns 0 if the actor can be added as a new one / actor name can be used as a new one.
     * Returns the ID for already known actors.
     * Throws an exception if the actor is blacklisted.
     *
     * @param MDMysqli $mysqli_noda          DB connection to vocabulary.
     * @param string   $lang                 User language.
     * @param string   $persinst_name        Actor name.
     * @param string   $persinst_name_en     Actor name (short).
     * @param string   $persinst_geburtsjahr Birth year.
     * @param string   $persinst_sterbejahr  Death year.
     * @param string   $context_identifier   Context / instance identifier.
     * @param integer  $institution_id       Institution ID.
     *
     * @return integer
     */
    public static function checkPersinstNameIsUsable(MDMysqli $mysqli_noda, string $lang, string $persinst_name, string $persinst_name_en, string $persinst_geburtsjahr, string $persinst_sterbejahr, string $context_identifier, int $institution_id):int {

        if ($storedType = NodaDistinctlyTypedStrings::lookup($mysqli_noda, $lang, $persinst_name_en)) {

            if ($storedType !== 'persinst') {
                throw new MDpageParameterMissingException("This term is marked to be distinctly a " . $storedType);
            }

        }

        // Special case: ? or "unbekannt" as a given name.
        if (str_starts_with($persinst_name_en, '? ') || str_starts_with(strtolower($persinst_name_en), 'unbekannt ') ) {
            throw new MDBlacklistedInputException("Term is blacklisted");
        }

        // Check if the place name is in the current language's blacklist.
        if (NodaBlacklistedTerms::checkPersinstBlacklistedInDb($mysqli_noda, $lang, $persinst_name) === true) {
            throw new MDBlacklistedInputException("Term is blacklisted");
        }
        if (NodaBlacklistedTerms::checkPersinstBlacklistedInDb($mysqli_noda, $lang, $persinst_name_en) === true) {
            throw new MDBlacklistedInputException("Term is blacklisted");
        }

        // Check rewrites

        if ($existingPersinstId = NodaIDGetter::getPersinstIDByNamesAndRewrites($mysqli_noda, $lang, $persinst_name_en, $persinst_geburtsjahr, $persinst_sterbejahr, $context_identifier, $institution_id)) {
            return $existingPersinstId;
        }

        if ($persinst_name_en !== $persinst_name) {
            if ($existingPersinstId = NodaIDGetter::getPersinstIDByNamesAndRewrites($mysqli_noda, $lang, $persinst_name, $persinst_geburtsjahr, $persinst_sterbejahr, $context_identifier, $institution_id)) {
                return $existingPersinstId;
            }
        }

        // Return 0, no ID found
        return 0;

    }

    /**
     * Checks if an place name is valid.
     * Returns 0 if the place can be added as a new one / place name can be used as a new one.
     * Returns the ID for already known places.
     * Throws an exception if the place is blacklisted.
     *
     * @param MDMysqli $mysqli_noda        DB connection to vocabulary.
     * @param string   $lang               User language.
     * @param string   $place_name         Place name.
     * @param string   $context_identifier Context / instance identifier.
     * @param integer  $institution_id     Institution ID.
     *
     * @return integer
     */
    public static function checkPlaceNameIsUsable(MDMysqli $mysqli_noda, string $lang, string $place_name, string $context_identifier, int $institution_id):int {

        if ($storedType = NodaDistinctlyTypedStrings::lookup($mysqli_noda, $lang, $place_name)) {

            if ($storedType !== 'orte') {
                throw new MDpageParameterMissingException("This term is marked to be distinctly a " . $storedType);
            }

        }

        // Check if the place name is in the current language's blacklist.
        if (NodaBlacklistedTerms::checkPlaceBlacklistedInDb($mysqli_noda, $lang, $place_name) === true) {
            throw new MDBlacklistedInputException("Term is blacklisted");
        }

        if ($existingPlaceId = NodaIDGetter::getPlaceIDByNamesAndRewrites($mysqli_noda, $lang, $place_name, $context_identifier, $institution_id)) {
            return $existingPlaceId;
        }

        return 0;

    }
}
