Passed
Push — develop ( 7909c1...ec92a4 )
by Johnny
07:21
created

ResponseQueue::concatContinues()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 49
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
nc 1
nop 1
dl 0
loc 49
rs 8.8817
c 0
b 0
f 0
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
        $this->responses->put($node->value(), new ResponseQueueItem($node->command(), $type, 0, $this->options));
85
    }
86
87
    /**
88
     * Sort the responses by order.
89
     *
90
     * @param Collection<ResponseQueueItem> $responses The array containing the resources.
91
     *
92
     * @return Collection<ResponseQueueItem>
93
     */
94
    private function sortResponses(Collection $responses): Collection
95
    {
96
        return $responses->sort(
97
            function ($current, $previous) {
98
                return ($current->order < $previous->order) ? -1 : 1;
99
            }
100
        )->reverse();
101
    }
102
103
    /**
104
     * Check if a response is allowed to be returned by the bot or not.
105
     *
106
     * @param string            $response The response to validate.
107
     * @param ResponseQueueItem $item     The ResponseQueueItem.
108
     *
109
     * @return false|mixed
110
     */
111
    private function validateResponse(string $response, ResponseQueueItem $item)
112
    {
113
        $response = $this->parseTags($response);
114
115
        foreach (synapse()->responses as $class) {
116
            if (class_exists("\\Axiom\\Rivescript\\Cortex\\Responses\\{$class}")) {
117
                $class = "\\Axiom\\Rivescript\\Cortex\\Responses\\{$class}";
118
                $class = new $class($response, $item);
119
120
                $result = $class->parse();
121
122
                if ($result) {
123
                    return $result;
124
                }
125
            }
126
        }
127
128
        return false;
129
    }
130
131
    /**
132
     * Merge the ^ continue responses to the last - response.
133
     *
134
     * @param Collection<ResponseQueueItem> $responses The array containing the responses.
135
     *
136
     * @return Collection<ResponseQueueItem>
137
     */
138
    protected function concatContinues(Collection $responses): Collection
139
    {
140
        $lastData = $responses->first();
141
        $lastResponse = "";
142
143
        $continues = Collection::make($responses->all());
144
        $continues->each(
145
            function (ResponseQueueItem $data, $response) use (&$lastData, &$lastResponse, &$continues) {
146
147
                if ($data->type === 'continue') {
148
                    $continues->remove($lastResponse);
149
                    $continues->remove($response);
150
151
                    /**
152
                     * none -- the default, nothing is added when continuation lines are joined together.
153
                     * space -- continuation lines are joined by a space character (\s)
154
                     * newline -- continuation lines are joined by a line break character (\n)
155
                     */
156
157
                    $options = $lastData->options;
158
                    $method = $options['concat'];
159
160
                    switch ($method) {
161
                        case 'space':
162
                            $lastResponse .= " {$response}";
163
                            break;
164
165
                        case 'newline':
166
                            $lastResponse .= "\n{$response}";
167
                            break;
168
169
                        case 'none':
170
                        default:
171
                            $lastResponse .= $response;
172
                            break;
173
                    }
174
175
                    $continues->put($lastResponse, $lastData);
176
                }
177
178
                if ($data->command !== '^') {
179
                    $lastData = $data;
180
                    $lastResponse = $response;
181
                }
182
            }
183
        );
184
185
186
        return $continues;
187
    }
188
189
    /**
190
     * Determine the order of responses by type.
191
     *
192
     * @param Collection<ResponseQueueItem> $responses The responses to inspect.
193
     *
194
     * @return Collection<ResponseQueueItem>
195
     */
196
    private function determineResponseOrder(Collection $responses): Collection
197
    {
198
        return $responses->each(
199
            function (ResponseQueueItem $data, $response) use ($responses) {
200
                if (isset($data->type)) {
201
                    switch ($data->type) {
202
                        case 'condition':
203
                            $data->order += 3000000;
204
                            break;
205
                        case 'weighted':
206
                        case 'atomic':
207
                            $data->order += 1000000;
208
                            break;
209
                    }
210
211
                    $responses->put($response, $data);
212
                }
213
            }
214
        );
215
    }
216
217
    /**
218
     * Determine the response type.
219
     *
220
     * @param string $response
221
     *
222
     * @return string
223
     */
224
    public function determineResponseType(string $response): string
225
    {
226
        $wildcards = [
227
            'weighted' => '{weight=(.+?)}',
228
            'condition' => '/^\*/',
229
            'continue' => '/^\^/',
230
            'atomic' => '/-/',
231
        ];
232
233
        foreach ($wildcards as $type => $pattern) {
234
            if (@preg_match_all($pattern, $response, $matches)) {
235
                return $type;
236
            }
237
        }
238
239
        return 'atomic';
240
    }
241
242
243
    /**
244
     * Process the Response Queue.
245
     *
246
     * @return false|int|string|null
247
     */
248
    public function process()
249
    {
250
        $sortedResponses = $this->determineResponseOrder($this->responses);
251
252
        $validResponses = new Collection([]);
253
        foreach ($sortedResponses as $response => $item) {
254
            $result = $this->validateResponse($response, $item);
255
256
            if ($result !== false) {
257
                $validResponses->put($result, $item);
258
            }
259
        }
260
261
        $validResponses = $this->concatContinues($validResponses);
262
        $validResponses = $this->sortResponses($validResponses);
263
264
        if ($validResponses->count() > 0) {
265
            return $validResponses->keys()->first();
266
        }
267
268
        return false;
269
    }
270
}
271