<?php

namespace IMATHUZH\Qfq\Core\Report;

use IMATHUZH\Qfq\Core\Exception\Thrower;
use IMATHUZH\Qfq\Core\Helper\KeyValueStringParser;
use IMATHUZH\Qfq\Core\Helper\Path;
use IMATHUZH\Qfq\Core\Store\Store;


/**
 * Class ColumnScript
 * @package IMATHUZH\Qfq\Core\Report
 *
 * Executes a function of an external script. Column parameters:
 *  F: path to script
 *  call: function name
 *  arg: arguments as key value pairs: myArg1=hello&myArg2=world
 *
 * The arguments (arg) are unpacked and passed together with the other parameters to the function as the first argument.
 * The second argument passed is an instance of ScriptFunctions which acts as an interface to QFQ functionality.
 */
class ColumnScript {

    /**
     * @param string $columnValue
     * @return false|string
     * @throws \CodeException
     * @throws \UserFormException
     * @throws \UserReportException
     */
    public static function render(string $columnValue) {

        // Parse arguments
        $param = KeyValueStringParser::parse($columnValue, PARAM_TOKEN_DELIMITER, PARAM_DELIMITER);
        foreach ($param as $key => $value) {
            if (!in_array($key, [TOKEN_SCRIPT, TOKEN_FUNCTION_CALL, TOKEN_ARGUMENT])) {
                throw new \UserReportException("Token '$key' not recognized for column _script.", ERROR_IO_READ_FILE);
            }
        }
        if (empty($param[TOKEN_SCRIPT])) {
            throw new \UserReportException("Missing script path.", ERROR_MISSING_VALUE);
        }
        if (empty($param[TOKEN_FUNCTION_CALL])) {
            throw new \UserReportException("Missing function name.", ERROR_MISSING_VALUE);
        }
        if (!empty($param[TOKEN_ARGUMENT])) {
            // parse arg: token
            $arg = KeyValueStringParser::parse($param[TOKEN_ARGUMENT], '=', '&');
            $param = array_merge($arg, $param);
        }

        // include script
        $absoluteScriptPath = Path::absoluteApp($param[TOKEN_SCRIPT]);
        if (!is_readable($absoluteScriptPath)) {
            Thrower::userReportException("Can't read script file.", "Can't read file: $absoluteScriptPath");
        }
        try {
            ob_start();
            $success = include_once $absoluteScriptPath;
            ob_end_clean();
            if ($success === false) {
                // this will be caught bellow
                throw new \Exception('Include failed.');
            }
        } catch (\Exception|\Error $e) {
            Thrower::userReportException('Error during reading script file.', "Error message:\n" . $e->getMessage());
        }

        // execute function, write output to buffer
        if (!function_exists($param[TOKEN_FUNCTION_CALL])) {
            Thrower::userReportException("Function doesn't exist.", "In file '$absoluteScriptPath' there is no function " . $param[TOKEN_FUNCTION_CALL]);
        }
        ob_start();
        try {
            $return = call_user_func_array($param[TOKEN_FUNCTION_CALL], [$param, new ScriptFunctions()]);
            $output = ob_get_clean();
        } catch (\Exception|\Error $e) {
            ob_end_clean();
            Thrower::userReportException('Function execution failed.',
                "Error message:\n" . $e->getMessage() . "\n\nFunction: " . $param[TOKEN_FUNCTION_CALL] . "\n\nParameters:\n" . print_r($param, true));
        }

        // return buffer output and fill store
        if (is_array($return)) {
            $returnNoArrays = array_filter($return, function ($value) {
                return !is_array($value);
            });
            Store::appendToStore($returnNoArrays, STORE_VAR);
        }
        return $output;
    }
}

/**
 * Class ScriptFunctions
 * @package IMATHUZH\Qfq\Core\Report
 *
 * An instance of this class is passed as the last argument to every external function call.
 *
 * ** WARNING ** : Be aware that this class acts as a "public" interface to QFQ. All changes made in here might break existing applications.
 */
class ScriptFunctions {
    public static function apiCall(string $method, string $url, string $data = '', array $header = [], int $timeout = 5) {
        list($http_code, $answer) = RestClient::callApiStream($method, $url, $data, $header, $timeout);
        return [$http_code, $answer];

    }

    public static function getVar($key, $useStores = STORE_USE_DEFAULT, $sanitizeClass = '', &$foundInStore = '',
                                  $typeMessageViolate = SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS) {
        return Store::getInstance()->getVar($key, $useStores, $sanitizeClass, $foundInStore,
            $typeMessageViolate);
    }
}