SecretSanta::addCouple()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 9
ccs 6
cts 6
cp 1
rs 9.6666
c 1
b 0
f 0
cc 1
eloc 5
nc 1
nop 4
crap 1
1
<?php
2
3
namespace SecretSanta;
4
5
use SecretSanta\Exceptions\PlayerException;
6
use SecretSanta\Exceptions\SecretSantaException;
7
8
/**
9
 * Class SecretSanta
10
 * @package SecretSanta
11
 */
12
class SecretSanta
13
{
14
    /** @var PlayersCollection */
15
    private $players;
16
    /** @var  array */
17
    private $combination;
18
19
    /**
20
     * SecretSanta constructor.
21
     */
22 20
    public function __construct()
23
    {
24 20
        $this->players = new PlayersCollection();
25 20
    }
26
27
    /**
28
     * Add single player to the game
29
     * @param string $name
30
     * @param string $email
31
     * @return SecretSanta
32
     */
33 8
    public function addPlayer($name, $email)
34
    {
35 8
        $this->players->addPlayer(Player::create($name, $email));
36
37 8
        return $this;
38
    }
39
40
    /**
41
     * Add two excluded players to the game
42
     * @param string $name
43
     * @param string $email
44
     * @param string $coupleName
45
     * @param string $coupleEmail
46
     * @return SecretSanta
47
     */
48 12
    public function addCouple($name, $email, $coupleName, $coupleEmail)
49
    {
50 12
        $this->players->addCouple(
51 12
            Player::create($name, $email),
52 12
            Player::create($coupleName, $coupleEmail)
53 12
        );
54
55 11
        return $this;
56
    }
57
58
    /**
59
     * Add multiple exclusive players to the game
60
     * @param array $playersData
61
     * @return $this
62
     * @throws SecretSantaException
63
     */
64 7
    public function addExclusivePlayers(...$playersData)
65
    {
66 7
        if (count($playersData) < 2) {
67 2
            throw new SecretSantaException('Number of players must be grater or equal than two.');
68
        }
69
70
        try {
71 5
            $players = [];
72 5
            foreach ($playersData as $playerData) {
73 5
                list($name, $email) = array_values($playerData);
74 5
                $players[] = Player::create($name, $email);
75 4
            }
76
77 4
            $this->players->addExclusivePlayers($players);
78 5
        } catch (PlayerException $exception) {
79 1
            throw new SecretSantaException('One o more players are invalids.', $exception);
80 1
        } catch (\Exception $exception) {
81 1
            throw new SecretSantaException('An error has occurred.', $exception);
82
        }
83
84 3
        return $this;
85
    }
86
87
    /**
88
     * @return Player[]
89
     * @throws SecretSantaException
90
     */
91 13
    public function play()
92
    {
93
        try {
94 13
            $this->combinePlayers();
95
96 11
            return $this->associatePlayers();
97 2
        } catch (SecretSantaException $exception) {
98 2
            throw  $exception;
99
        } catch (\Exception $exception) {
100
            throw new SecretSantaException('Error during play, impossible to find secret santa, try again', $exception);
101
        }
102
    }
103
104
    /**
105
     * @throws SecretSantaException
106
     */
107 13
    private function combinePlayers()
108
    {
109 13
        if (count($this->players) < 3) {
110 1
            throw new SecretSantaException("Not enough players to play, at least 3 players are required");
111
        }
112
113 12
        $retry = count($this->players) + $this->players->countExclusivePlayers();
114
115 12
        while (!$this->tryMatchSecretSantaPlayers() && $retry > 0 ) {
116 1
            $retry--;
117 1
        }
118
119 12
        if (!$this->isValidCombination()) {
120 1
            throw new SecretSantaException("Not enough players to play");
121
        }
122 11
    }
123
124
    /**
125
     * @return bool
126
     */
127 12
    private function tryMatchSecretSantaPlayers()
128
    {
129 12
        $this->combination = [];
130 12
        $secretPlayers = $this->players->shufflePlayers();
131 12
        foreach ($this->players->players() as $playerId => $player) {
132 12
            foreach ($secretPlayers as $secretPlayer) {
133 12
                if ($this->isValidSecretSanta($player, $secretPlayer)) {
134 12
                    $this->combination[$player->id()] = $secretPlayer->id();
135 12
                    unset ($secretPlayers[$secretPlayer->id()]);
136 12
                    break;
137
                }
138 12
            }
139 12
        }
140
141 12
        return $this->isValidCombination();
142
    }
143
144
    /**
145
     * @param Player $player
146
     * @param Player $secretPlayer
147
     * @return bool
148
     */
149 12
    private function isValidSecretSanta(Player $player, Player $secretPlayer)
150
    {
151 12
        if ($player->id() != $secretPlayer->id() && !$this->players->areExclusive($player, $secretPlayer)) {
152 12
            if (!in_array($secretPlayer->id(), $this->combination)) {
153 12
                return true;
154
            }
155
        }
156
157 11
        return false;
158
    }
159
160
    /**
161
     * @return bool
162
     */
163 12
    private function isValidCombination()
164
    {
165 12
        return count($this->combination) > 0 && count($this->combination) == count($this->players);
166
    }
167
168 11
    private function associatePlayers()
169
    {
170 11
        foreach ($this->combination as $playerId => $secretSantaId) {
171 11
            $this->players->player($playerId)->setSecretSanta(
172 11
                $this->players->player($secretSantaId)
173 11
            );
174 11
        }
175
176 11
        return $this->players->players();
177
    }
178
}
179