Passed
Push — master ( 55b2d7...d9efa6 )
by Alec
03:49
created

AllocationCalculator::prepareInstances()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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