Passed
Push — master ( d9efa6...1da82a )
by Alec
04:08
created

AllocationCalculator::computeTotal()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 1
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * User: alec
4
 * Date: 27.11.18
5
 * Time: 13:33
6
 */
7
8
namespace AlecRabbit\Money;
9
10
use AlecRabbit\Money\Contracts\CalculatorInterface;
11
12
class AllocationCalculator
13
{
14
    protected const PRECISION = 2;
15
16
    /** @var string */
17
    protected $amount;
18
19
    /** @var Currency */
20
    protected $currency;
21
22
    /** @var CalculatorInterface */
23
    protected $calculator;
24
25
    /** @var array */
26
    private $allocated = [];
27
28
    /** @var int */
29
    private $allocations;
30
31
    /** @var string */
32
    private $remainder;
33
34
    /** @var int */
35
    private $precision;
36
37
    /** @var float|int */
38
    private $total;
39
40
    /**
41
     * AllocationCalculator constructor.
42
     * @param Money $param
43
     */
44 37
    public function __construct(Money $param)
45
    {
46 37
        $this->amount = $param->getAmount();
47 37
        $this->currency = $param->getCurrency();
48 37
        $this->remainder = $this->amount;
49 37
        $this->calculator = CalculatorFactory::getCalculator();
50 37
    }
51
52 37
    public function compute(array $ratios, ?int $precision): array
53
    {
54 37
        $this->processArguments($ratios, $precision);
55
56 35
        $this->allocate($ratios);
57
58 34
        $this->allocateRemainder($ratios);
59
        return
60 34
            $this->prepareInstances();
61
    }
62
63
    /**
64
     * @param array $ratios
65
     * @param int|null $precision
66
     */
67 37
    private function processArguments(array $ratios, ?int $precision): void
68
    {
69 37
        $this->precision = $precision ?? self::PRECISION;
70 37
        $this->computeAllocations($ratios);
71 36
        $this->computeTotal($ratios);
72 35
    }
73
74
    /**
75
     * @param array $ratios
76
     */
77 37
    private function computeAllocations(array $ratios): void
78
    {
79 37
        if (0 === $this->allocations = \count($ratios)) {
80 1
            throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array.');
81
        }
82 36
    }
83
84
    /**
85
     * @param array $ratios
86
     */
87 36
    private function computeTotal(array $ratios): void
88
    {
89 36
        if (0 >= $this->total = array_sum($ratios)) {
90 1
            throw new \InvalidArgumentException('Sum of ratios must be greater than zero.');
91
        }
92 35
    }
93
94
    /**
95
     * @param array $ratios
96
     */
97 35
    private function allocate(array $ratios): void
98
    {
99 35
        foreach ($ratios as $ratio) {
100 35
            $this->checkRatio($ratio);
101
102 35
            $share = $this->calculator->share($this->amount, $ratio, $this->total, $this->precision);
103 35
            $this->allocated[] = $share;
104 35
            $this->remainder = $this->calculator->subtract($this->remainder, $share);
105
        }
106 34
    }
107
108
    /**
109
     * @param float|int $ratio
110
     */
111 35
    private function checkRatio($ratio): void
112
    {
113 35
        if ($ratio < 0) {
114 1
            throw new \InvalidArgumentException('Ratio must be zero or positive.');
115
        }
116 35
    }
117
118
    /**
119
     * @param array $ratios
120
     * @return array
121
     */
122 34
    private function allocateRemainder(array $ratios): array
123
    {
124 34
        switch ($this->calculator->compare($this->remainder, '0')) {
125
            case -1:
126 13
                for ($i = $this->allocations - 1; $i >= 0; $i--) {
127 13
                    if (!$ratios[$i]) {
128 1
                        continue;
129
                    }
130 13
                    $this->updateAllocated($i);
131 13
                    break;
132
                }
133 13
                break;
134 21
            case 1:
135 7
                for ($i = 0; $i < $this->allocations; $i++) {
136 7
                    if (!$ratios[$i]) {
137 1
                        continue;
138
                    }
139 7
                    $this->updateAllocated($i);
140 7
                    break;
141
                }
142 7
                break;
143
            default:
144 14
                break;
145
        }
146 34
        return $this->allocated;
147
    }
148
149
    /**
150
     * @return array
151
     */
152 34
    private function prepareInstances(): array
153
    {
154 34
        $computed = [];
155 34
        foreach ($this->allocated as $amount) {
156 34
            $computed[] = new Money($amount, $this->currency);
157
        }
158 34
        return $computed;
159
    }
160
161
    /**
162
     * @param int $index
163
     */
164 20
    private function updateAllocated(int $index): void
165
    {
166 20
        $this->allocated[$index] = $this->calculator->add($this->allocated[$index], $this->remainder);
167 20
    }
168
}
169