<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Timothy Chow
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\models;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\MediaConstants;
use seekquarry\yioop\library\VersionManager;
use seekquarry\yioop\library\WikiParser;
use seekquarry\yioop\library\processors\ImageProcessor;
use seekquarry\yioop\models\ImpressionModel;

/**
 * This is class is used to handle
 * db results related to Group Administration. Groups are collections of
 * users who might access a common blog/news feed and set of pages. This
 * method also controls adding and deleting entries to a group feed and
 * does limited access control checks of these operations.
 *
 * @author Timothy Chow
 */
class TrendingModel extends Model implements MediaConstants
{
    /**
     * Computes an array of the top $limit to $limit+$num terms from feed items
     * for the update periods listed as an array. If no periods are
     * provided, then the period hour, day, week are used.
     *
     * @param string $locale_tag language to get top terms for
     * @param array $update_periods as array of update periods.
     *      Items in this array should be from intervals pre-defined in
     *      Config.php such as ONE_HOUR, ONE_DAY, or ONE_WEEK.
     * @param string $category used to compute the trending terms with
     *      respect to
     * @param array $order_by for either of the two fields TERM,
     *  SCORE (or OCCURRENCES) lets you specify a sort direction on the
     *  tending values. For example, ["TERM" => "ASC"] or ["SCORE" => "DESC"]
     * @param int $limit the rank of the first trending term tooutput
     * @param int $num number of trending terms desired
     * @return array consists of one array/update period, each of these
     *      arrays consist of a rank => [term, occurrences] for the
     *      top NUM_TRENDING results for that period.
     */
    public function topTermsForUpdatePeriods($locale_tag, $update_periods,
        $category, $order_by = [], $limit = 0, $num = C\NUM_TRENDING)
    {
        $db = $this->db;
        if (empty($update_periods)) {
            $update_periods = [C\ONE_HOUR, C\ONE_DAY, C\ONE_WEEK,
                C\ONE_MONTH, C\ONE_YEAR];
        }
        $default_order_string = "ORDER BY OCCURRENCES DESC";
        $order_string = "ORDER BY ";
        $comma = "";
        foreach ($order_by as $field => $values) {
            $field = mb_strtoupper($field);
            $field = ($field == "SCORE") ? "OCCURRENCES" :
                $field;
            $values = mb_strtoupper($values);
            if (in_array($values, ['ASC', 'DESC'])) {
                $order_string .= $comma . $field . " " . $values;
                $comma = ", ";
            }
        }
        if ($order_string == "ORDER BY ") {
            $order_string = $default_order_string;
        }
        $sql = "SELECT TERM, OCCURRENCES FROM TRENDING_TERM ".
            "WHERE LANGUAGE= ? AND UPDATE_PERIOD = ? AND CATEGORY =? AND ".
            "TIMESTAMP = (SELECT MAX(TIMESTAMP) " .
            "FROM TRENDING_TERM WHERE LANGUAGE= ? AND UPDATE_PERIOD = ?) " .
            "$order_string ".
            $db->limitOffset($limit, $num);
        $top_results = [];
        foreach($update_periods as $period) {
            $result = $db->execute($sql, [$locale_tag, $period, $category,
                $locale_tag, $period]);
            $term_occurrences = [];
            if($result) {
                while($term_occurrence = $db->fetchArray($result)) {
                    $term_occurrences[] = $term_occurrence;
                }
            }
            $top_results[$period] = $term_occurrences;
        }
        return $top_results;
    }
    /**
     * Returns a list of $num_term many random tending terms from the
     * sets of trending terms across all tracked time periods with respect
     * to category supplied.
     *
     * @param string $locale_tag language to get random trending terms for
     * @param string $category category to compute trending terms for
     * @param int $num_terms number of trending terms to return
     * @return array terms which are trending
     */
    public function randomTrends($locale_tag, $category = 'news', $num_terms =
        C\NUM_RESULTS_PER_PAGE)
    {
        $trend_data = $this->topTermsForUpdatePeriods($locale_tag, [],
            $category);
        $trending = [];
        foreach ($trend_data as $period => $trend_data) {
            $trending = array_merge($trending, $trend_data);
        }
        usort($trending, function($a, $b) {
            if ($a['OCCURRENCES'] == $b['OCCURRENCES']) {
                return 0;
            }
            return ($a['OCCURRENCES'] > $b['OCCURRENCES']) ? -1 : 1;
        });
        $total = array_sum(array_column($trending, "OCCURRENCES"));
        if (count($trending) > $num_terms && $total >= 1) {
            $trending_terms = [];
            while (count($trending_terms) < $num_terms) {
                $random_pick = random_int(0, intval($total) * 1000)/1000;
                $prob = 0;
                foreach ($trending as $trend) {
                    $next_prob = $prob + $trend['OCCURRENCES'];
                    if ($next_prob >= $random_pick &&
                        empty($trending_terms[$trend['TERM']])) {
                        $trending_terms[$trend['TERM']] = $trend['TERM'];
                        break;
                    }
                    $prob = $next_prob;
                }
            }
            $trending_terms = array_values($trending_terms);
        } else {
            $trending_terms = array_column($trending, "TERM");
        }
        return $trending_terms;
    }
    /**
     * Return trending scores for a term with respect to a locale
     * over the appropriate sub-period for the last period. For example,
     * Donald Trump in en-US locale, for the last day. Here the subinterval
     * would be hourly.
     *
     * @param string $term to find time => score data for
     * @param int $period time period to get subinterval data for
     * @param string $locale_tag which locale to get trending data for
     * @param bool $raw whether to keep as linux timestamps or to format into
     *      appropriate date units for subinterval
     * @return array associative array of time => scores
     */
    public function termScoresForPeriod($term, $period, $locale_tag,
        $raw = false)
    {
        $db = $this->db;
        $subintervals = [ C\ONE_HOUR => C\ONE_MINUTE,
            C\ONE_DAY => C\ONE_HOUR,  C\ONE_WEEK => C\ONE_DAY,
            C\ONE_MONTH => C\ONE_WEEK, C\ONE_YEAR => C\ONE_MONTH
        ];
        $dates = [ C\ONE_HOUR => 'i', C\ONE_DAY => 'H', C\ONE_WEEK => 'D',
            C\ONE_MONTH => 'W', C\ONE_YEAR => 'm'
        ];
        $sub_period = (empty($subintervals[$period])) ? C\ONE_HOUR :
            $subintervals[$period];
        $num_results = (empty($subintervals[$period])) ?
            C\ONE_HOUR/C\ONE_MINUTE : ceil($period / $sub_period);
        $date_format = (empty($dates[$period])) ? 'r' : $dates[$period];
        $sql = "SELECT OCCURRENCES, TIMESTAMP FROM TRENDING_TERM ".
            "WHERE TERM = ? AND UPDATE_PERIOD = ? AND LANGUAGE= ? " .
            "ORDER BY TIMESTAMP DESC ".
            $db->limitOffset($num_results);
        $result = $db->execute($sql, [$term, $sub_period, $locale_tag]);
        $pre_occurrences = [];
        if($result) {
            $i = 0;
            while($row = $db->fetchArray($result)) {
                $pre_occurrences[$row['TIMESTAMP']] = $row['OCCURRENCES'];
            }
        }
        ksort($pre_occurrences);
        if ($raw) {
            return $pre_occurrences;
        }
        $timestamp_occurrences = [];
        foreach ($pre_occurrences as $timestamp => $occurrences) {
            $date = date($date_format, $timestamp);
            $timestamp_occurrences[$date] = $occurrences;
        }
        return $timestamp_occurrences;
    }
}
