AllocationCalculator   A
last analyzed

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