ResponseQueue::sortResponses()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
rs 10
1
<?php
2
/*
3
 * This file is part of Rivescript-php
4
 *
5
 * (c) Johnny Mast <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Axiom\Rivescript\Cortex\ResponseQueue;
12
13
use Axiom\Collections\Collection;
14
use Axiom\Rivescript\Cortex\Node;
15
use Axiom\Rivescript\Traits\Tags;
16
17
/**
18
 * ResponseQueue class
19
 *
20
 * The ResponseQueue releases the responses in order of sending them
21
 * back to the user.
22
 *
23
 * PHP version 7.4 and higher.
24
 *
25
 * @category Core
26
 * @package  Cortext\ResponseQueue
27
 * @author   Johnny Mast <[email protected]>
28
 * @license  https://opensource.org/licenses/MIT MIT
29
 * @link     https://github.com/axiom-labs/rivescript-php
30
 * @since    0.4.0
31
 */
32
class ResponseQueue extends Collection
33
{
34
35
    use Tags;
0 ignored issues
show
introduced by
The trait Axiom\Rivescript\Traits\Tags requires some properties which are not provided by Axiom\Rivescript\Cortex\...onseQueue\ResponseQueue: $memory, $input
Loading history...
36
37
    /**
38
     * A container with responses.
39
     *
40
     * @var Collection<ResponseQueueItem>
41
     */
42
    protected Collection $responses;
43
44
    /**
45
     * Store the local interpreter options
46
     * for this instance in time (since they can change).
47
     *
48
     * @var array<string, string>
49
     */
50
    protected array $options = [];
51
52
    /**
53
     * The trigger string this ResponseQueue belongs to.
54
     *
55
     * @var string
56
     */
57
    protected string $trigger = "";
58
59
    /**
60
     * ResponseQueue constructor.
61
     *
62
     * @param string $trigger the trigger this queue belongs to.
63
     */
64
    public function __construct(string $trigger = "")
65
    {
66
        parent::__construct();
67
68
        $this->responses = new Collection([]);
69
        $this->trigger = $trigger;
70
71
        $this->options = synapse()->memory->local()->all();
72
    }
73
74
    /**
75
     * Attach a response to the queue.
76
     *
77
     * @param Node $node The node contains information about the command.
78
     *
79
     * @return void
80
     */
81
    public function attach(Node $node): void
82
    {
83
        $type = $this->determineResponseType($node->source());
84
        $queueItem =  new ResponseQueueItem($node->command(), $type, 0, $this->options);
85
        $this->responses->put($node->value(), $queueItem);
86
    }
87
88
    /**
89
     * Sort the responses by order.
90
     *
91
     * @param Collection<ResponseQueueItem> $responses The array containing the resources.
92
     *
93
     * @return Collection<ResponseQueueItem>
94
     */
95
    private function sortResponses(Collection $responses): Collection
96
    {
97
        return $responses->sort(
98
            function ($current, $previous) {
99
                return ($current->order < $previous->order) ? -1 : 1;
100
            }
101
        )->reverse();
102
    }
103
104
    /**
105
     * Check if a response is allowed to be returned by the bot or not.
106
     *
107
     * @param string            $response The response to validate.
108
     * @param ResponseQueueItem $item     The ResponseQueueItem.
109
     *
110
     * @return false|mixed
111
     */
112
    private function validateResponse(string $response, ResponseQueueItem $item)
113
    {
114
        $response = $this->parseTags($response);
115
        $responses = synapse()->responses;
116
117
        foreach ($responses as $class) {
118
            if (class_exists("\\Axiom\\Rivescript\\Cortex\\Responses\\{$class}")) {
119
                $class = "\\Axiom\\Rivescript\\Cortex\\Responses\\{$class}";
120
                $class = new $class($response, $item);
121
122
                $result = $class->parse();
123
124
                if ($result !== false) {
125
                    return $result;
126
                }
127
            }
128
        }
129
130
131
        return false;
132
    }
133
134
    /**
135
     * Merge the ^ continue responses to the last - response.
136
     *
137
     * @param Collection<ResponseQueueItem> $responses The array containing the responses.
138
     *
139
     * @return Collection<ResponseQueueItem>
140
     */
141
    protected function concatContinues(Collection $responses): Collection
142
    {
143
        $lastData = $responses->first();
144
        $lastResponse = "";
145
146
        $continues = Collection::make($responses->all());
147
        $continues->each(
148
            function (ResponseQueueItem $data, $response) use (&$lastData, &$lastResponse, &$continues) {
149
150
                if ($data->type === 'continue') {
151
                    $continues->remove($lastResponse);
152
                    $continues->remove($response);
153
154
                    /**
155
                     * none -- the default, nothing is added when continuation lines are joined together.
156
                     * space -- continuation lines are joined by a space character (\s)
157
                     * newline -- continuation lines are joined by a line break character (\n)
158
                     */
159
                    $options = $lastData->options;
160
                    $method = $options['concat'];
161
162
                    switch ($method) {
163
                        case 'space':
164
                            $lastResponse .= " {$response}";
165
                            break;
166
167
                        case 'newline':
168
                            $lastResponse .= "\n{$response}";
169
                            break;
170
171
                        case 'none':
172
                        default:
173
                            $lastResponse .= $response;
174
                            break;
175
                    }
176
177
                    $continues->put($lastResponse, $lastData);
178
                }
179
180
                if ($data->command !== '^') {
181
                    $lastData = $data;
182
                    $lastResponse = $response;
183
                }
184
            }
185
        );
186
187
188
        return $continues;
189
    }
190
191
    /**
192
     * Determine the order of responses by type.
193
     *
194
     * @param Collection<ResponseQueueItem> $responses The responses to inspect.
195
     *
196
     * @return Collection<ResponseQueueItem>
197
     */
198
    private function determineResponseOrder(Collection $responses): Collection
199
    {
200
        return $responses->each(
201
            function (ResponseQueueItem $data, $response) use ($responses) {
202
                if (isset($data->type)) {
203
                    switch ($data->type) {
204
                        case 'condition':
205
                            $data->order += 3000000;
206
                            break;
207
                        case 'weighted':
208
                        case 'atomic':
209
                            $data->order += 1000000;
210
                            break;
211
                    }
212
213
                    $responses->put($response, $data);
214
                }
215
            }
216
        );
217
    }
218
219
    /**
220
     * Determine the response type.
221
     *
222
     * @param string $response
223
     *
224
     * @return string
225
     */
226
    public function determineResponseType(string $response): string
227
    {
228
        $wildcards = [
229
            'weighted' => '{weight=(.+?)}',
230
            'condition' => '/^\*/',
231
            'continue' => '/^\^/',
232
            'atomic' => '/-/',
233
        ];
234
235
        foreach ($wildcards as $type => $pattern) {
236
            if (@preg_match_all($pattern, $response, $matches)) {
237
                return $type;
238
            }
239
        }
240
241
        return 'atomic';
242
    }
243
244
    /**
245
     * Process the Response Queue.
246
     *
247
     * @return false|int|string|null
248
     */
249
    public function process()
250
    {
251
        $sortedResponses = $this->determineResponseOrder($this->responses);
252
253
        $validResponses = new Collection([]);
254
        foreach ($sortedResponses as $response => $item) {
255
            $result = $this->validateResponse($response, $item);
256
257
            if ($result !== false) {
258
                $validResponses->put($result, $item);
259
            }
260
        }
261
262
        $validResponses = $this->concatContinues($validResponses);
263
        $validResponses = $this->sortResponses($validResponses);
264
265
        if ($validResponses->count() > 0) {
266
            return $validResponses->keys()->first();
267
        }
268
269
        return false;
270
    }
271
}
272