Passed
Push — master ( 7c3e69...689c3c )
by Alec
04:17
created

AllocationCalculator::allocateRemainder()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 8
ccs 6
cts 6
cp 1
crap 3
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 41
    public function __construct(Money $param)
45
    {
46 41
        $this->amount = $param->getAmount();
47 41
        $this->currency = $param->getCurrency();
48 41
        $this->remainder = $this->amount;
49 41
        $this->calculator = CalculatorFactory::getCalculator();
50 41
    }
51
52 41
    public function compute(array $ratios, ?int $precision): array
53
    {
54 41
        $this->processArguments($ratios, $precision);
55
56 39
        $this->allocate($ratios);
57
58 38
        $this->allocateRemainder($ratios);
59
        return
60 38
            $this->prepareInstances();
61
    }
62
63
    /**
64
     * @param array $ratios
65
     * @param int|null $precision
66
     */
67 41
    private function processArguments(array $ratios, ?int $precision): void
68
    {
69 41
        $this->precision = $precision ?? self::PRECISION;
70 41
        $this->computeAllocations($ratios);
71 40
        $this->computeTotal($ratios);
72 39
    }
73
74
    /**
75
     * @param array $ratios
76
     */
77 41
    private function computeAllocations(array $ratios): void
78
    {
79 41
        if (0 === $this->allocations = \count($ratios)) {
80 1
            throw new \InvalidArgumentException('Cannot allocate to none, ratios cannot be an empty array.');
81
        }
82 40
    }
83
84
    /**
85
     * @param array $ratios
86
     */
87 40
    private function computeTotal(array $ratios): void
88
    {
89 40
        if (0 >= $this->total = array_sum($ratios)) {
90 1
            throw new \InvalidArgumentException('Sum of ratios must be greater than zero.');
91
        }
92 39
    }
93
94
    /**
95
     * @param array $ratios
96
     */
97 39
    private function allocate(array $ratios): void
98
    {
99 39
        foreach ($ratios as $ratio) {
100 39
            $this->checkRatio($ratio);
101
102 39
            $share = $this->calculator->share($this->amount, $ratio, $this->total, $this->precision);
103 39
            $this->allocated[] = $share;
104 39
            $this->remainder = $this->calculator->subtract($this->remainder, $share);
105
        }
106 38
    }
107
108
    /**
109
     * @param float|int $ratio
110
     */
111 39
    private function checkRatio($ratio): void
112
    {
113 39
        if ($ratio < 0) {
114 1
            throw new \InvalidArgumentException('Ratio must be zero or positive.');
115
        }
116 39
    }
117
118
    /**
119
     * @param array $ratios
120
     */
121 38
    private function allocateRemainder(array $ratios): void
122
    {
123 38
        foreach ($this->indexRange() as $index) {
124 38
            if (!$ratios[$index]) {
125 4
                continue;
126
            }
127 38
            $this->updateAllocated($index);
128 38
            break;
129
        }
130 38
    }
131
132
    /**
133
     * @return array
134
     */
135 38
    private function indexRange(): array
136
    {
137
        return
138 38
            $this->calculator->compare($this->remainder, '0') < 0 ?
139 38
                range($this->allocations - 1, 0) : range(0, $this->allocations);
140
    }
141
142
    /**
143
     * @param int $index
144
     */
145 38
    private function updateAllocated(int $index): void
146
    {
147 38
        $this->allocated[$index] = $this->calculator->add($this->allocated[$index], $this->remainder);
148 38
    }
149
150
    /**
151
     * @return array
152
     */
153 38
    private function prepareInstances(): array
154
    {
155 38
        $computed = [];
156 38
        foreach ($this->allocated as $amount) {
157 38
            $computed[] = new Money($amount, $this->currency);
158
        }
159 38
        return $computed;
160
    }
161
}
162