Passed
Push — master ( 1f5936...ef809d )
by Alec
05:25
created

AllocationCalculator   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 148
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 46
dl 0
loc 148
ccs 53
cts 53
cp 1
rs 10
c 0
b 0
f 0
wmc 19

11 Methods

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