<?php

/*
 * @component UserPoints
 * @copyright Copyright (C) 2015-2022 Bernard Gilly - Adrien Roussel - Martin Brampton
 * @license : GNU/GPL
 * @Website : https://remository.com
 */

namespace BlackSheepResearch\Component\UserPoints\Site\Problems;

defined('_JEXEC') or die;

require_once JPATH_SITE . '/components/com_userpoints/userpoints_setup.php';

use Joomla\CMS\Factory;

abstract class ProblemBase {

    protected $problem_object = null;
    private $savobject = null;

    public function __construct() {
        if (0 > static::$count) {
            $this->initialise();
            $this->loadFields();
        }
    }

    public function __toString(): string {
        $name = (new \ReflectionClass($this))->getShortName();
        $key = static::$keyname;
        return sprintf("[UserPoints Problem %s, key %s, contains: %s]\n", $name, $key, (is_object($this->problem_object) ? json_encode(get_object_vars($this->problem_object)) : 'empty'));
    }

    public static function makeProblems(array $objects, string $keyname=''): array {
        foreach ($objects as $object) {
            $result[] = self::makeOneProblem($object, $keyname);
        }
        return isset($result) ? $result : [];
    }

    public static function makeOneProblem($object, string $keyname='') {
        $problem = $keyname ? new static($object->$keyname) : new static();
        $problem->problem_object = $object;
        return $problem;
    }

    public static function countAll(): int {
        new static();
        return static::$count;
    }

    public static function countByFieldvalue(string $fieldname, int $value): int {
        $db = SQL::getInstance();
        $sql = sprintf("SELECT COUNT(*) FROM `%s` WHERE `%s` = %d", static::$tablename, $db->escape($fieldname), $value);
        $db->setQuery($sql);
        return $db->loadResult();
    }

    public static function getLimitsFromRequest(int $limit = 0): array {
        $app = Factory::getApplication();
        // Get the pagination request variables
        if (!$limit) {
            $limit = $app->getUserStateFromRequest('com_userpoints.limit', 'limit', $app->getCfg('list_limit'), 'int');
        }
        // In case limit has been changed, adjust limitstart accordingly
        $limitstart = ( $limit != 0 ? (floor($app->input->get('limitstart', 0, 'int') / $limit) * $limit) : 0);
        return [intval($limitstart), intval($limit)];
    }

    public static function datetimeSQL() {
        return SQL::datetimeSQL();
    }

    public static function dateSQL() {
        return SQL::dateSQL();
    }

    public static function nulldateSQL() {
        return SQL::nulldateSQL();
    }

    public static function nulldatetimeSQL() {
        return SQL::nulldatetimeSQL();
    }

    public static function getRecordN(int $n) { // Counting from zero
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SELECT * FROM `%s` ORDER BY `%s` LIMIT 1 OFFSET %d", static::$tablename, static::$keyname, $n-1));
        return $db->loadObject();
    }

    protected function initialise() {
        static::$count = $this->countData();
    }

    protected function loadFields() {
        foreach ($this->updatableFields() as $sub => $field) {
            static::$fieldnames[$sub] = $field->Field;
            static::$fieldtypes[$sub] = $field->Type;
        }
    }

    protected function getAllRecords(): array {
        $db = SQL::getInstance();
        $sql = isset(static::$fetchAllSQL) ? static::$fetchAllSQL : 'SELECT * FROM `%s`';
        $db->setQuery(sprintf($sql, static::$tablename));
        return (array) $db->loadObjectList();
    }

    protected function countData(): int {
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SELECT COUNT(*) FROM `%s`", static::$updatetable));
        return $db->loadResult();
    }

    protected function getOneRow(int $id) {
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SELECT * FROM `%s` WHERE `%s` = %d", static::$tablename, static::$keyname, $id));
        $row = $db->loadObject();
        if (is_object($row)) {
            $this->problem_object = $row;
        }
    }

    protected function updatableFields(): array {
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SHOW FIELDS FROM `%s`", static::$updatetable));
        return (array) $db->loadObjectList();
    }

    public function __get($name) {
        if (is_object($this->problem_object) AND isset($this->problem_object->$name)) {
            return $this->problem_object->$name;
        }
        return null;
    }

    public function __set(string $name, $value) {
        if (!is_object($this->problem_object)) {
            $this->problem_object = new \stdClass();
        }
        $this->problem_object->$name = $value;
    }

    public static function getRange(int $limitstart, int $limit): array {
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SELECT * FROM `%s` LIMIT %d, %d", static::$tablename, $limitstart, $limit));
        return self::makeProblems((array) $db->loadObjectList());
    }

    public static function getLimitedRange(int $limitstart, int $limit, string $fieldname, int $value): array {
        $db = SQL::getInstance();
        $db->setQuery(sprintf("SELECT * FROM `%s` WHERE `%s` = %d LIMIT %d, %d", static::$tablename, $db->escape($fieldname), $value, $limitstart, $limit));
        return self::makeProblems((array) $db->loadObjectList());
    }

    public static function getMany(array $ids): array {
        if (count($ids)) {
            $db = SQL::getInstance();
            $db->setQuery(sprintf("SELECT * FROM `%s` WHERE `%s` IN (%s)", static::$tablename, static::$keyname, self::listIDs($ids)));
            return self::makeProblems((array) $db->loadObjectList());
        }
        return [];
    }

    public function save($post = null): bool {
        $this->savobject = new \stdClass();
        if (!is_object($this->problem_object)) {
            $this->problem_object = new \stdClass();
        }
        foreach (static::$fieldnames as $sub => $name) {
            if (is_object($this->problem_object)) {
                $this->mapFieldByType($name, $this->problem_object, static::$fieldtypes[$sub]);
            }
            if ($post) {
                $this->mapFieldByType($name, (object) $post, static::$fieldtypes[$sub]);
            } elseif (!is_object($this->problem_object)) {
                die('Save a problem has no data');
            }
        }
        $this->setTextDefaults();
        $keyname = static::$keyname;
        $table = isset(static::$updatetable) ? static::$updatetable : static::$tablename;
        if (is_object($this->savobject)) {
            if (isset($this->savobject->$keyname) AND $this->savobject->$keyname) {
                if (!SQL::getInstance()->updateObject($table, $this->savobject, static::$keyname)) {
                    die('Failed to update problem object');
                }
            } else {
                if (!SQL::getInstance()->insertObject($table, $this->savobject, static::$keyname)) {
                    die('Failed to insert problem object');
                }
                $this->problem_object->$keyname = $this->savobject->$keyname;
            }
        } else {
            die('Attempt to save an problem object that has no entries');
        }
        return true;
    }

    public function delete() {
        if (is_object($this->problem_object)) {
            $db = SQL::getInstance();
            $key = static::$keyname;
            $db->setQuery(sprintf("DELETE FROM `%s` WHERE `$key` = {$db->quote($this->$key)}", static::$updatetable));
            $db->execute();
            static::reset();
        }
    }

    protected static function reset() {

    }

    private function mapFieldByType($name, $postobject, $type) {
        if (isset($postobject->$name)) {
            if (false !== strpos($type, 'int')) {
                $this->savobject->$name = (int) $postobject->$name;
            } elseif (false !== strpos($type, 'float')) {
                $this->savobject->$name = (float) $postobject->$name;
            } else {
                $this->savobject->$name = (string) $postobject->$name;
            }
        }
    }

    private function setTextDefaults() {
        foreach (static::$fieldnames as $sub => $name) {
            $type = static::$fieldtypes[$sub];
            if (false !== strpos($type, 'text') AND !isset($this->savobject->$name)) {
                $this->savobject->$name = '';
            }
        }
    }

    public static function deleteMany(array $ids, string $fieldname = '') {
        if (count($ids)) {
            $db = SQL::getInstance();
            $keyname = $fieldname ? $fieldname : static::$keyname;
            $db->setQuery(sprintf("DELETE FROM `%s` WHERE `%s` IN (%s)", static::$tablename, $db->escape($keyname), self::listIDs($ids)));
            $db->execute();
            static::reset();
        }
    }

    public static function toggleProperties(array $ids, bool $setting, string $tablename, string $fieldname) {
        if (count($ids)) {
            $db = SQL::getInstance();
            $db->setQuery(sprintf("UPDATE `%s` SET `%s` = %d WHERE `id` IN (%s)",
                            $db->escape($tablename), $db->escape($fieldname), ($setting ? 1 : 0), self::listIDs($ids)));
            $db->execute();
        }
    }

    protected static function listIDs(array $ids): string {
        return implode(',', array_map('\\intval', (array) $ids));
    }
}
