1<?php
2
3namespace ILIAS\BackgroundTasks\Implementation\Tasks;
4
5use ILIAS\BackgroundTasks\Exceptions\InvalidArgumentException;
6use ILIAS\BackgroundTasks\Implementation\Tasks\UserInteraction\UserInteractionOption;
7use ILIAS\BackgroundTasks\Implementation\Values\PrimitiveValueWrapperFactory;
8use ILIAS\BackgroundTasks\Implementation\Values\ScalarValues\BasicScalarValueFactory;
9use ILIAS\BackgroundTasks\Implementation\Values\ThunkValue;
10use ILIAS\BackgroundTasks\Task;
11use ILIAS\BackgroundTasks\Value;
12
13/**
14 * Class AbstractTask
15 *
16 * @package ILIAS\BackgroundTasks\Implementation\Tasks
17 *
18 * @author  Oskar Truffer <ot@studer-raimann.ch>
19 */
20abstract class AbstractTask implements Task
21{
22    use BasicScalarValueFactory;
23    const MAIN_REMOVE = 'bt_main_remove';
24    const MAIN_ABORT = 'bt_main_abort';
25    /**
26     * @var Value[]
27     */
28    protected $input = [];
29    /**
30     * @var Value
31     */
32    protected $output;
33
34
35    /**
36     * @param $values (Value|Task)[]
37     *
38     * @return void
39     */
40    public function setInput(array $values)
41    {
42        $this->input = $this->getValues($values);
43        $this->checkTypes($this->input);
44    }
45
46
47    protected function checkTypes($values)
48    {
49        $expectedTypes = $this->getInputTypes();
50
51        for ($i = 0; $i < count($expectedTypes); $i++) {
52            $expectedType = $expectedTypes[$i];
53            $givenType = $this->extractType($values[$i]);
54            if (!$givenType->isExtensionOf($expectedType)) {
55                throw new InvalidArgumentException("Types did not match when setting input for "
56                    . get_called_class()
57                    . ". Expected type $expectedType given type $givenType.");
58            }
59        }
60    }
61
62
63    /**
64     * @param $value Value
65     *
66     * @return mixed
67     * @throws InvalidArgumentException
68     */
69    protected function extractType($value)
70    {
71        if (is_a($value, Value::class)) {
72            return $value->getType();
73        }
74        if (is_a($value, Task::class)) {
75            ;
76        }
77
78        return $value->getOutputType();
79
80        throw new InvalidArgumentException("Input values must be tasks or Values (extend BT\\Task or BT\\Value).");
81    }
82
83
84    /**
85     * @return Value Returns a thunk value (yet to be calculated). It's used for task composition
86     *               and type checks.
87     *
88     */
89    public function getOutput()
90    {
91        $thunk = new ThunkValue($this->getOutputType());
92        $thunk->setParentTask($this);
93
94        return $thunk;
95    }
96
97
98    /**
99     * @param $values (Value|Task)[]
100     *
101     * @return Value[]
102     */
103    private function getValues($values)
104    {
105        $inputs = [];
106
107        foreach ($values as $value) {
108            if ($value instanceof Task) {
109                $inputs[] = $value->getOutput();
110            } elseif ($value instanceof Value) {
111                $inputs[] = $value;
112            } else {
113                $inputs[] = $this->wrapScalar($value);
114            }
115        }
116
117        return $inputs;
118    }
119
120
121    /**
122     * @return Value[]
123     */
124    public function getInput()
125    {
126        return $this->input;
127    }
128
129
130    /**
131     * @return string
132     */
133    public function getType()
134    {
135        return get_called_class();
136    }
137
138
139    /**
140     * Unfold the task. If task A has dependency B and B' and B has dependency C, the resulting
141     * list will be [A, B, C, B'].
142     *
143     * @return Task[]
144     */
145    public function unfoldTask()
146    {
147        $list = [$this];
148        foreach ($this->getInput() as $input) {
149            if (is_a($input, ThunkValue::class)) {
150                $list = array_merge($list, $input->getParentTask()->unfoldTask());
151            }
152        }
153
154        return $list;
155    }
156
157
158    /**
159     * @inheritdoc
160     */
161    public function getRemoveOption()
162    {
163        return new UserInteractionOption('remove', self::MAIN_REMOVE);
164    }
165
166
167    /**
168     * @inheritdoc
169     */
170    public function getAbortOption()
171    {
172        return new UserInteractionOption('abort', self::MAIN_ABORT);
173    }
174}
175