<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    ecommerce
 */

/**
 * Hook class.
 */
class Hook_ecommerce_gambling
{
    /**
     * Get the overall categorisation for the products handled by this eCommerce hook.
     *
     * @return ?array A map of product categorisation details (null: disabled)
     */
    public function get_product_category() : ?array
    {
        return [
            'category_name' => do_lang('GAMBLING'),
            'category_description' => do_lang_tempcode('GAMBLING_DESCRIPTION'),
            'category_image_url' => find_theme_image('icons/features'),
        ];
    }

    /**
     * Get the products handled by this eCommerce hook.
     *
     * IMPORTANT NOTE TO PROGRAMMERS: This function may depend only on the database, and not on get_member() or any GET/POST values.
     *  Such dependencies will break IPN, which works via a Guest and no dependable environment variables. It would also break manual transactions from the Admin Zone.
     *
     * @param  ?ID_TEXT $search Product being searched for (passed by reference as it may be modified for special cases) (null: none)
     * @return array A map of product name to list of product details
     */
    public function get_products(?string &$search = null) : array
    {
        $products = [];

        $min = intval(get_option('minimum_gamble_amount'));
        $max = intval(get_option('maximum_gamble_amount'));
        if ($max > $min) {
            $_min = $min;
            $min = $max;
            $max = $_min;
        }
        $spread = intval(round(floatval($max - $min) / 4.0));
        $amounts = [];
        $amounts[] = $min;
        $amounts[] = $min + $spread * 1;
        $amounts[] = $min + $spread * 2;
        $amounts[] = $min + $spread * 3;
        $amounts[] = $max;
        $amounts = array_unique($amounts);

        foreach ($amounts as $amount) {
            $products['GAMBLING_' . strval($amount)] = [
                'item_name' => do_lang('GAMBLE_THIS', integer_format($amount, 0)),
                'item_description' => new Tempcode(),
                'item_image_url' => '',

                'type' => PRODUCT_PURCHASE,
                'type_special_details' => [],

                'price' => null,
                'currency' => get_option('currency'),
                'price_points' => $amount,
                'discount_points__num_points' => null,
                'discount_points__price_reduction' => null,
                'discount_points__percentile' => null,

                'tax_code' => '0.0',
                'shipping_cost' => 0.00,
                'product_weight' => null,
                'product_length' => null,
                'product_width' => null,
                'product_height' => null,
                'needs_shipping_address' => false,
            ];
        }

        return $products;
    }

    /**
     * Check whether the product codename is available for purchase by the member.
     *
     * @param  ID_TEXT $type_code The product codename
     * @param  MEMBER $member_id The member we are checking against
     * @param  integer $req_quantity The number required
     * @param  boolean $must_be_listed Whether the product must be available for public listing
     * @return integer The availability code (a ECOMMERCE_PRODUCT_* constant)
     */
    public function is_available(string $type_code, int $member_id, int $req_quantity = 1, bool $must_be_listed = false) : int
    {
        if (!addon_installed('points')) {
            return ECOMMERCE_PRODUCT_INTERNAL_ERROR;
        }

        if (get_option('is_on_gambling_buy') == '0') {
            return ECOMMERCE_PRODUCT_DISABLED;
        }

        if (is_guest($member_id)) {
            return ECOMMERCE_PRODUCT_NO_GUESTS;
        }

        return ECOMMERCE_PRODUCT_AVAILABLE;
    }

    /**
     * Get the message for use in the purchasing module.
     *
     * @param  ID_TEXT $type_code The product in question
     * @return ?Tempcode The message (null: no message)
     */
    public function get_message(string $type_code) : ?object
    {
        return do_lang_tempcode('GAMBLE_WARNING');
    }

    /**
     * Get fields that need to be filled in in the purchasing module.
     *
     * @param  ID_TEXT $type_code The product codename
     * @param  boolean $from_admin Whether this is being called from the Admin Zone. If so, optionally different fields may be used, including a purchase_id field for direct purchase ID input.
     * @return array A triple: The fields (use null for none), Hidden fields (use null for none), The text (use null for none), array of JavaScript function calls
     */
    public function get_needed_fields(string $type_code, bool $from_admin = false) : array
    {
        return [null, null, null, []];
    }

    /**
     * Handling of a product purchase change state.
     *
     * @param  ID_TEXT $type_code The product codename
     * @param  ID_TEXT $purchase_id The purchase ID
     * @param  array $details Details of the product, with added keys: TXN_ID, STATUS, ORDER_STATUS
     * @return boolean Whether the product was automatically dispatched (if not then hopefully this function sent a staff notification)
     */
    public function actualiser(string $type_code, string $purchase_id, array $details) : bool
    {
        if ($details['STATUS'] != 'Completed') {
            return false;
        }

        $amount = intval(preg_replace('#^GAMBLING_#', '', $type_code));

        $member_id = intval($purchase_id);

        // Calculate
        $average_gamble_multiplier = floatval(get_option('average_gamble_multiplier')) / 100.0;
        $maximum_gamble_multiplier = floatval(get_option('maximum_gamble_multiplier')) / 100.0;
        $above_average = (mt_rand(0, 10) < 5);
        if ($above_average) {
            //$winnings = round($average_gamble_multiplier * $amount + mt_rand(0, round($maximum_gamble_multiplier * $amount - $average_gamble_multiplier * $amount)));   Even distribution is NOT wise
            $peak = $maximum_gamble_multiplier * $amount;
            $under = 0.0;
            $number = intval(round($average_gamble_multiplier * $amount + mt_rand(0, intval(round($maximum_gamble_multiplier * $amount - $average_gamble_multiplier * $amount)))));
            for ($x = 1; $x < intval($peak); $x++) { // Perform some discrete calculus: we need to find when we've reached the proportional probability area equivalent to our number
                $p = $peak * (1.0 / pow(floatval($x) + 0.4, 2.0) - (1.0 / pow($maximum_gamble_multiplier * floatval($amount), 2.0))); // Using a 1/x^2 curve. 0.4 is a bit of a magic number to get the averaging right
                $under += $p;
                if ($under > floatval($number)) {
                    break;
                }
            }
            $winnings = intval(round($average_gamble_multiplier * $amount + $x * 1.1)); // 1.1 is a magic number to make it seem a bit fairer
        } else {
            $winnings = mt_rand(0, intval(round($average_gamble_multiplier * $amount)));
        }

        $GLOBALS['SITE_DB']->query_insert('ecom_sales', ['date_and_time' => time(), 'member_id' => $member_id, 'details' => do_lang('GAMBLING', null, null, null, get_site_default_lang()), 'details2' => strval($amount) . ' --> ' . strval($winnings), 'txn_id' => $details['TXN_ID']]);

        // Actuate
        if ($winnings > 0) {
            require_code('points2');
            points_credit_member($member_id, do_lang('GAMBLING_WINNINGS'), $winnings, 0, null, 0, 'ecommerce', 'gamble_win', '', null, false); // No ranking for gambling; that is unfair
        }

        // Show an instant message so the member knows how it worked out (plus buying via points, so will definitely be seen)
        if ($winnings > $amount) {
            $result = do_lang_tempcode('GAMBLE_CONGRATULATIONS', escape_html(integer_format($winnings - $amount, 0)), escape_html(integer_format($amount, 0)));
        } else {
            $result = do_lang_tempcode('GAMBLE_COMMISERATIONS', escape_html(integer_format($amount - $winnings, 0)), escape_html(integer_format($amount, 0)));
        }
        global $ECOMMERCE_SPECIAL_SUCCESS_MESSAGE;
        $ECOMMERCE_SPECIAL_SUCCESS_MESSAGE = $result;

        return true;
    }

    /**
     * Get the member who made the purchase.
     *
     * @param  ID_TEXT $type_code The product codename
     * @param  ID_TEXT $purchase_id The purchase ID
     * @return ?MEMBER The member ID (null: none)
     */
    public function member_for(string $type_code, string $purchase_id) : ?int
    {
        return intval($purchase_id);
    }
}
