1<?php
2
3namespace LesserPHP\Functions;
4
5use Exception;
6use LesserPHP\Constants;
7use LesserPHP\Utils\Asserts;
8use LesserPHP\Utils\Util;
9
10/**
11 * Implements the math functions for LESS
12 *
13 * @link https://lesscss.org/functions/#math-functions
14 */
15class Math extends AbstractFunctionCollection
16{
17    /** @inheritdoc */
18    public function getFunctions(): array
19    {
20        return [
21            'ceil' => [$this, 'ceil'],
22            'floor' => [$this, 'floor'],
23            'percentage' => [$this, 'percentage'],
24            'round' => [$this, 'round'],
25            'sqrt' => [$this, 'sqrt'],
26            'abs' => [$this, 'abs'],
27            'sin' => [$this, 'sin'],
28            'asin' => [$this, 'asin'],
29            'cos' => [$this, 'cos'],
30            'acos' => [$this, 'acos'],
31            'tan' => [$this, 'tan'],
32            'atan' => [$this, 'atan'],
33            'pi' => [$this, 'pi'],
34            'pow' => [$this, 'pow'],
35            'mod' => [$this, 'mod'],
36            'min' => [$this, 'min'],
37            'max' => [$this, 'max'],
38        ];
39    }
40
41
42    /**
43     * Rounds up to the next highest integer
44     *
45     * @link https://lesscss.org/functions/#math-functions-ceil
46     * @throws Exception
47     */
48    public function ceil(array $arg): array
49    {
50        $value = Asserts::assertNumber($arg);
51        return ['number', ceil($value), $arg[2]];
52    }
53
54    /**
55     * Rounds down to the next lowest integer
56     *
57     * @link https://lesscss.org/functions/#math-functions-floor
58     * @throws Exception
59     */
60    public function floor(array $arg): array
61    {
62        $value = Asserts::assertNumber($arg);
63        return ['number', floor($value), $arg[2]];
64    }
65
66    /**
67     * Converts a floating point number into a percentage string
68     *
69     * @link https://lesscss.org/functions/#math-functions-percentage
70     * @throws Exception
71     */
72    public function percentage(array $arg): array
73    {
74        $num = Asserts::assertNumber($arg);
75        return ['number', $num * 100, '%'];
76    }
77
78    /**
79     * Applies rounding
80     *
81     * @link https://lesscss.org/functions/#math-functions-round
82     * @throws Exception
83     */
84    public function round(array $arg): array
85    {
86        if ($arg[0] != 'list') {
87            $value = Asserts::assertNumber($arg);
88            return ['number', round($value), $arg[2]];
89        } else {
90            $value = Asserts::assertNumber($arg[2][0]);
91            $precision = Asserts::assertNumber($arg[2][1]);
92            return ['number', round($value, $precision), $arg[2][0][2]];
93        }
94    }
95
96    /**
97     * Calculates square root of a number
98     *
99     * @link https://lesscss.org/functions/#math-functions-sqrt
100     * @throws Exception
101     */
102    public function sqrt(array $num): float
103    {
104        return sqrt(Asserts::assertNumber($num));
105    }
106
107    /**
108     * Calculates absolute value of a number. Keeps units as they are.
109     *
110     * @link https://lesscss.org/functions/#math-functions-abs
111     * @throws Exception
112     */
113    public function abs(array $num): array
114    {
115        return ['number', abs(Asserts::assertNumber($num)), $num[2]];
116    }
117
118    /**
119     * Calculates sine function
120     *
121     * @link https://lesscss.org/functions/#math-functions-sin
122     * @throws Exception
123     */
124    public function sin(array $num): float
125    {
126        return sin(Asserts::assertNumber($num));
127    }
128
129    /**
130     * Calculates arcsine function
131     *
132     * @link https://lesscss.org/functions/#math-functions-asin
133     * @throws Exception
134     */
135    public function asin(array $num): array
136    {
137        $num = asin(Asserts::assertNumber($num));
138        return ['number', $num, 'rad'];
139    }
140
141    /**
142     * Calculates cosine function
143     *
144     * @link https://lesscss.org/functions/#math-functions-cos
145     * @throws Exception
146     */
147    public function cos(array $num): float
148    {
149        return cos(Asserts::assertNumber($num));
150    }
151
152    /**
153     * Calculates arccosine function
154     *
155     * @link https://lesscss.org/functions/#math-functions-acos
156     * @throws Exception
157     */
158    public function acos(array $num): array
159    {
160        $num = acos(Asserts::assertNumber($num));
161        return ['number', $num, 'rad'];
162    }
163
164    /**
165     * Calculates tangent function
166     *
167     * @link https://lesscss.org/functions/#math-functions-tan
168     * @throws Exception
169     */
170    public function tan(array $num): float
171    {
172        return tan(Asserts::assertNumber($num));
173    }
174
175    /**
176     * Calculates arctangent function
177     *
178     * @link https://lesscss.org/functions/#math-functions-atan
179     * @throws Exception
180     */
181    public function atan(array $num): array
182    {
183        $num = atan(Asserts::assertNumber($num));
184        return ['number', $num, 'rad'];
185    }
186
187    /**
188     * Return the value of pi
189     *
190     * @link https://lesscss.org/functions/#math-functions-pi
191     */
192    public function pi(): float
193    {
194        return pi();
195    }
196
197    /**
198     * Returns the value of the first argument raised to the power of the second argument.
199     *
200     * @link https://lesscss.org/functions/#math-functions-pow
201     * @throws Exception
202     */
203    public function pow(array $args): array
204    {
205        [$base, $exp] = Asserts::assertArgs($args, 2, 'pow');
206        return ['number', Asserts::assertNumber($base) ** Asserts::assertNumber($exp), $args[2][0][2]];
207    }
208
209    /**
210     * Returns the value of the first argument modulus second argument.
211     *
212     * @link https://lesscss.org/functions/#math-functions-mod
213     * @throws Exception
214     */
215    public function mod(array $args): array
216    {
217        [$a, $b] = Asserts::assertArgs($args, 2, 'mod');
218        return ['number', Asserts::assertNumber($a) % Asserts::assertNumber($b), $args[2][0][2]];
219    }
220
221    /**
222     * Returns the lowest of one or more values
223     *
224     * @link https://lesscss.org/functions/#math-functions-min
225     * @throws Exception
226     */
227    public function min(array $args): array
228    {
229        $values = Asserts::assertMinArgs($args, 1, 'min');
230
231        $first_format = $values[0][2];
232
233        $min_index = 0;
234        $min_value = $values[0][1];
235
236        for ($a = 0; $a < sizeof($values); $a++) {
237            $converted = Util::convert($values[$a], $first_format);
238
239            if ($converted[1] < $min_value) {
240                $min_index = $a;
241                $min_value = $values[$a][1];
242            }
243        }
244
245        return $values[$min_index];
246    }
247
248    /**
249     * Returns the highest of one or more values
250     *
251     * @link https://lesscss.org/functions/#math-functions-max
252     * @throws Exception
253     */
254    public function max(array $args): array
255    {
256        $values = Asserts::assertMinArgs($args, 1, 'max');
257
258        $first_format = $values[0][2];
259
260        $max_index = 0;
261        $max_value = $values[0][1];
262
263        for ($a = 0; $a < sizeof($values); $a++) {
264            $converted = Util::convert($values[$a], $first_format);
265
266            if ($converted[1] > $max_value) {
267                $max_index = $a;
268                $max_value = $values[$a][1];
269            }
270        }
271
272        return $values[$max_index];
273    }
274}
275