Completed
Push — master ( 922505...49fe4c )
by Boudry
05:20
created

Election::parseCandidates()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 9.214

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 11
cts 15
cp 0.7332
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 20
nc 10
nop 2
crap 9.214
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 25 and the first side effect is on line 58.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/*
3
    Condorcet PHP Class, with Schulze Methods and others !
4
5
    By Julien Boudry - MIT LICENSE (Please read LICENSE.txt)
6
    https://github.com/julien-boudry/Condorcet
7
*/
8
declare(strict_types=1);
9
10
namespace Condorcet;
11
12
use Condorcet\Condorcet;
13
use Condorcet\CondorcetException;
14
use Condorcet\CondorcetVersion;
15
use Condorcet\Result;
16
use Condorcet\Vote;
17
use Condorcet\Algo\Pairwise;
18
use Condorcet\DataManager\VotesManager;
19
use Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface;
20
use Condorcet\Timer\Chrono as Timer_Chrono;
21
use Condorcet\Timer\Manager as Timer_Manager;
22
23
24
// Base Condorcet class
25
class Election
26
{
27
28
/////////// PROPERTIES ///////////
29
30
    public const MAX_LENGTH_CANDIDATE_ID = 30; // Max length for candidate identifiant string
31
32
    protected static $_maxParseIteration = null;
33
    protected static $_maxVoteNumber = null;
34
    protected static $_checksumMode = false;
35
36
/////////// STATICS METHODS ///////////
37
38
    // Change max parse iteration
39 1
    public static function setMaxParseIteration (?int $value) : ?int
40
    {
41 1
        self::$_maxParseIteration = $value;
42 1
        return self::$_maxParseIteration;
43
    }
44
45
    // Change max vote number
46 1
    public static function setMaxVoteNumber (?int $value) : ?int
47
    {
48 1
        self::$_maxVoteNumber = $value;
49 1
        return self::$_maxVoteNumber;
50
    }
51
52
53
    // Check JSON format
54
    public static function isJson (string $string) : bool
55
    {
56
        if (is_numeric($string) || $string === 'true' || $string === 'false' || $string === 'null' || empty($string)) :
57
            return false;
58
        endif;
59
60
        // try to decode string
61
        json_decode($string);
62
63
        // check if error occured
64
        return json_last_error() === JSON_ERROR_NONE;
65
    }
66
67
68
    // Generic action before parsing data from string input
69 70
    public static function prepareParse (string $input, bool $allowFile) : array
70
    {
71
        // Is string or is file ?
72 70
        if ($allowFile === true && is_file($input)) :
73
            $input = file_get_contents($input);
74
        endif;
75
76
        // Line
77 70
        $input = preg_replace("(\r\n|\n|\r)",';',$input);
78 70
        $input = explode(';', $input);
79
80
        // Delete comments
81 70
        foreach ($input as &$line) :
82
            // Delete comments
83 70
            $is_comment = mb_strpos($line, '#');
84 70
            if ($is_comment !== false) :
85 2
                $line = substr($line, 0, $is_comment);
86
            endif;
87
88
            // Trim
89 70
            $line = trim($line);
90
        endforeach;
91
92 70
        return $input;
93
    }
94
95
96
    public static function prepareJson (string $input)
97
    {
98
        if (!self::isJson($input)) :
99
            throw new CondorcetException(15);
100
        endif;
101
102
        return json_decode($input, true);
103
    }
104
105
106
/////////// CONSTRUCTOR ///////////
107
108
    use CondorcetVersion;
109
110
    // Data and global options
111
    protected $_Candidates = []; // Candidate list
112
    protected $_Votes; // Votes list
113
114
    // Mechanics
115
    protected $_i_CandidateId = 'A';
116
    protected $_State = 1; // 1 = Add Candidates / 2 = Voting / 3 = Some result have been computing
117
    protected $_timer;
118
    protected $_nextVoteTag = 0;
119
    protected $_ignoreStaticMaxVote = false;
120
121
    // Params
122
    protected $_ImplicitRanking = true;
123
    protected $_VoteWeightRule = false;
124
125
    // Result
126
    protected $_Pairwise;
127
    protected $_Calculator;
128
129
        //////
130
131 89
    public function __construct ()
132
    {
133 89
        $this->_Candidates = [];
134 89
        $this->_Votes = new VotesManager ($this);
135 89
        $this->_timer = new Timer_Manager;
136 89
    }
137
138 1
    public function __destruct ()
139
    {
140 1
        $this->destroyAllLink();
141 1
    }
142
143 1
    public function __sleep () : array
144
    {
145
        // Don't include others data
146
        $include = [
147 1
            '_Candidates',
148
            '_Votes',
149
150
            '_i_CandidateId',
151
            '_State',
152
            '_nextVoteTag',
153
            '_objectVersion',
154
            '_ignoreStaticMaxVote',
155
156
            '_ImplicitRanking',
157
            '_VoteWeightRule',
158
159
            '_Pairwise',
160
            '_Calculator',
161
        ];
162
163 1
        !self::$_checksumMode && array_push($include, '_timer');
164
165 1
        return $include;
166
    }
167
168
    public function __wakeup ()
169
    {
170
        if ( version_compare($this->getObjectVersion('MAJOR'),Condorcet::getVersion('MAJOR'),'!=') ) :
171
            throw new CondorcetException(11, 'Your object version is '.$this->getObjectVersion().' but the class engine version is '.Condorcet::getVersion('ENV'));
172
        endif;
173
    }
174
175
    public function __clone ()
176
    {
177
        $this->registerAllLinks();
178
    }
179
180
181
/////////// INTERNAL GENERIC REGULATION ///////////
182
183
184
    protected function registerAllLinks () : void
185
    {
186
        foreach ($this->_Candidates as $value) :
187
            $value->registerLink($this);
188
        endforeach;
189
190
        if ($this->_State > 1) :
191
            foreach ($this->_Votes as $value) :
192
                $value->registerLink($this);
193
            endforeach;
194
        endif;
195
    }
196
197 1
    protected function destroyAllLink () : void
198
    {
199 1
        foreach ($this->_Candidates as $value) :
200
            $value->destroyLink($this);
201
        endforeach;
202
203 1
        if ($this->_State > 1) :
204
            foreach ($this->_Votes as $value) :
205
                $value->destroyLink($this);
206
            endforeach;
207
        endif;
208 1
    }
209
210
        //////
211
212
213 2
    public function getGlobalTimer (bool $float = false) {
214 2
        return $this->_timer->getGlobalTimer($float);
215
    }
216
217 2
    public function getLastTimer (bool $float = false) {
218 2
        return $this->_timer->getLastTimer($float);
219
    }
220
221 67
    public function getTimerManager () : Timer_Manager {
222 67
        return $this->_timer;
223
    }
224
225 1
    public function getChecksum () : string
226
    {
227 1
        self::$_checksumMode = true;
228
229 1
        $r = hash_init('sha256');
230
231 1
        foreach ($this->_Candidates as $value) :
232 1
            hash_update($r, (string) $value);
233
        endforeach;
234
235 1
        foreach ($this->_Votes as $value) :
236 1
            hash_update($r, (string) $value);
237
        endforeach;
238
239 1
        $this->_Pairwise !== null
240 1
            && hash_update($r,serialize($this->_Pairwise->getExplicitPairwise()));
241
242 1
        hash_update($r, $this->getObjectVersion('major'));
243
244 1
        self::$_checksumMode = false;
245
246 1
        return hash_final($r);
247
    }
248
249
    public function ignoreMaxVote (bool $state = true) : bool
250
    {
251
        $this->_ignoreStaticMaxVote = $state;
252
        return $this->_ignoreStaticMaxVote;
253
    }
254
255 72
    public function getImplicitRankingRule () : bool
256
    {
257 72
        return $this->_ImplicitRanking;
258
    }
259
260 3
    public function setImplicitRanking (bool $rule = true) : bool
261
    {
262 3
        $this->_ImplicitRanking = $rule;
263 3
        $this->cleanupResult();
264 3
        return $this->getImplicitRankingRule();
265
    }
266
267 68
    public function isVoteWeightIsAllowed () : bool
268
    {
269 68
        return $this->_VoteWeightRule;
270
    }
271
272 1
    public function allowVoteWeight (bool $rule = true) : bool
273
    {
274 1
        $this->_VoteWeightRule = $rule;
275 1
        $this->cleanupResult();
276 1
        return $this->isVoteWeightIsAllowed();
277
    }
278
279
280
/////////// CANDIDATES ///////////
281
282
283
    // Add a vote candidate before voting
284 84
    public function addCandidate ($candidate_id = null) : Candidate
285
    {
286
        // only if the vote has not started
287 84
        if ( $this->_State > 1 ) :
288
            throw new CondorcetException(2);
289
        endif;
290
291
        // Filter
292 84
        if ( is_bool($candidate_id) || is_array($candidate_id) || (is_object($candidate_id) && !($candidate_id instanceof Candidate)) ) :
293
            throw new CondorcetException(1, $candidate_id);
294
        endif;
295
296
297
        // Process
298 84
        if ( empty($candidate_id) ) :
299 5
            while ( !$this->canAddCandidate($this->_i_CandidateId) ) :
300 5
                $this->_i_CandidateId++;
301
            endwhile;
302
303 5
            $newCandidate = new Candidate($this->_i_CandidateId);
304
        else : // Try to add the candidate_id
305 81
            $newCandidate = ($candidate_id instanceof Candidate) ? $candidate_id : new Candidate ((string) $candidate_id);
306
307 81
            if ( !$this->canAddCandidate($newCandidate) ) :
308
                throw new CondorcetException(3,$candidate_id);
309
            endif;
310
        endif;
311
312
        // Register it
313 84
        $this->_Candidates[] = $newCandidate;
314
315
        // Linking
316 84
        $newCandidate->registerLink($this);
317
318
        // Disallow other candidate object name matching 
319 84
        $newCandidate->setProvisionalState(false);
320
321 84
        return $newCandidate;
322
    }
323
324 84
        public function canAddCandidate ($candidate_id) : bool
325
        {
326 84
            return !$this->existCandidateId($candidate_id, false);
327
        }
328
329
330
    // Destroy a register vote candidate before voting
331 3
    public function removeCandidate ($list) : array
332
    {
333
        // only if the vote has not started
334 3
        if ( $this->_State > 1 ) :
335
            throw new CondorcetException(2);
336
        endif;
337
338
        
339 3
        if ( !is_array($list) ) :
340 3
            $list = [$list];
341
        endif;
342
343 3
        foreach ($list as &$candidate_id) :
344 3
            $candidate_key = $this->getCandidateKey($candidate_id);
345
346 3
            if ( $candidate_key === false ) :
347
                throw new CondorcetException(4,$candidate_id);
348
            endif;
349
350 3
            $candidate_id = $candidate_key;
351
        endforeach;
352
353 3
        $rem = [];
354 3
        foreach ($list as $candidate_key) :
355 3
            $this->_Candidates[$candidate_key]->destroyLink($this);
356
357 3
            $rem[] = $this->_Candidates[$candidate_key];
358
359 3
            unset($this->_Candidates[$candidate_key]);
360
        endforeach;
361
362 3
        return $rem;
363
    }
364
365
366
    public function jsonCandidates (string $input)
367
    {
368
        $input = self::prepareJson($input);
369
        if ($input === false) :
370
            return $input;
371
        endif;
372
373
            //////
374
375
        $adding = [];
376
        foreach ($input as $candidate) :
377
            try {
378
                $adding[] = $this->addCandidate($candidate);
379
            }
380
            catch (CondorcetException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
381
        endforeach;
382
383
        return $adding;
384
    }
385
386
387 6
    public function parseCandidates (string $input, bool $allowFile = true)
388
    {
389 6
        $input = self::prepareParse($input, $allowFile);
390 6
        if ($input === false) :
391
            return $input;
392
        endif;
393
394 6
        $adding = [];
395 6
        foreach ($input as $line) :
396
            // Empty Line
397 6
            if (empty($line)) :
398 1
                continue;
399
            endif;
400
401
            // addCandidate
402
            try {
403 6
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
404
                    throw new CondorcetException(12, self::$_maxParseIteration);
405
                endif;
406
407 6
                $adding[] = $this->addCandidate($line);
408
            } catch (CondorcetException $e) {
409
                if ($e->getCode() === 12)
410 6
                    {throw $e;}
411
            }
412
        endforeach;
413
414 6
        return $adding;
415
    }
416
417
418
        //:: CANDIDATES TOOLS :://
419
420
        // Count registered candidates
421 72
        public function countCandidates () : int
422
        {
423 72
            return count($this->_Candidates);
424
        }
425
426
        // Get the list of registered CANDIDATES
427 73
        public function getCandidatesList (bool $stringMode = false) : array
428
        {
429 73
            if (!$stringMode) :
430 72
                return $this->_Candidates;
431
            else :
432 3
                $result = [];
433
434 3
                foreach ($this->_Candidates as $candidateKey => &$oneCandidate) :
435 3
                    $result[$candidateKey] = $oneCandidate->getName();
436
                endforeach;
437
438 3
                return $result;
439
            endif;
440
        }
441
442 77
        public function getCandidateKey ($candidate_id)
443
        {
444 77
            if ($candidate_id instanceof Candidate) :
445 67
                return array_search($candidate_id, $this->_Candidates, true);
446
            else:
447 76
                return array_search(trim((string) $candidate_id), $this->_Candidates, false);
448
            endif;
449
        }
450
451 63
        public function getCandidateId (int $candidate_key, bool $onlyName = false)
452
        {
453 63
            if (!array_key_exists($candidate_key, $this->_Candidates)) :
454
                return false;
455
            else :
456 63
                return ($onlyName) ? $this->_Candidates[$candidate_key]->getName() : $this->_Candidates[$candidate_key];
457
            endif;
458
        }
459
460 84
        public function existCandidateId ($candidate_id, bool $strict = true) : bool
461
        {
462 84
            return ($strict) ? in_array($candidate_id, $this->_Candidates, true) : in_array((string) $candidate_id, $this->_Candidates);
463
        }
464
465 2
        public function getCandidateObjectByName (string $s)
466
        {
467 2
            foreach ($this->_Candidates as $oneCandidate) :
468
469 2
                if ($oneCandidate->getName() === $s) :
470 2
                    return $oneCandidate;
471
                endif;
472
            endforeach;
473
474
            return false;
475
        }
476
477
478
479
/////////// VOTING ///////////
480
481
482
    // Close the candidate config, be ready for voting (optional)
483 79
    public function setStateToVote () : bool
484
    {
485 79
        if ( $this->_State === 1 ) :
486 79
                if (empty($this->_Candidates)) :
487
                    throw new CondorcetException(20);
488
                endif;
489
490 79
                $this->_State = 2;
491
492
        // If voting continues after a first set of results
493 77
        elseif ( $this->_State > 2 ) :
494 6
                $this->cleanupResult();
495
        endif;
496
497 79
        return true;
498
    }
499
500
501
    // Add a single vote. Array key is the rank, each candidate in a rank are separate by ',' It is not necessary to register the last rank.
502 79
    public function addVote ($vote, $tag = null) : Vote
503
    {
504 79
        $this->prepareVoteInput($vote, $tag);
505
506
        // Check Max Vote Count
507 79
        if ( self::$_maxVoteNumber !== null && !$this->_ignoreStaticMaxVote && $this->countVotes() >= self::$_maxVoteNumber ) :
508 1
            throw new CondorcetException(16, self::$_maxVoteNumber);
509
        endif;
510
511
512
        // Register vote
513 79
        return $this->registerVote($vote, $tag); // Return the vote object
514
    }
515
516
        // return True or throw an Exception
517 5
        public function prepareModifyVote (Vote $existVote)
518
        {
519
            try {
520 5
                $this->prepareVoteInput($existVote);
521 5
                $this->setStateToVote();
522
            }
523
            catch (\Exception $e) {
524
                throw $e;
525
            }
526 5
        }
527
528
        // Return the well formated vote to use.
529 79
        protected function prepareVoteInput (&$vote, $tag = null) : void
530
        {
531 79
            if (!($vote instanceof Vote)) :
532 71
                $vote = new Vote ($vote, $tag);
533
            endif;
534
535
            // Check array format && Make checkVoteCandidate
536 79
            if ( !$this->checkVoteCandidate($vote) ) :
537
                throw new CondorcetException(5);
538
            endif;
539 79
        }
540
541
542 79
        public function checkVoteCandidate (Vote $vote) : bool
543
        {
544 79
            $linkCount = $vote->countLinks();
545 79
            $links = $vote->getLinks();
546
547 79
            $mirror = $vote->getRanking();
548 79
            $change = false;
549 79
            foreach ($vote as $rank => $choice) :
550 79
                foreach ($choice as $choiceKey => $candidate) :
551 79
                    if ( !$this->existCandidateId($candidate, true) ) :
552 76
                        if ($candidate->getProvisionalState() && $this->existCandidateId($candidate, false)) :
553 75
                            if ( $linkCount === 0 || ($linkCount === 1 && reset($links) === $this) ) :
554 75
                                $mirror[$rank][$choiceKey] = $this->_Candidates[$this->getCandidateKey((string) $candidate)];
555 75
                                $change = true;
556
                            else :
557 79
                                return false;
558
                            endif;
559
                        endif;
560
                    endif;
561
                endforeach;
562
            endforeach;
563
564 79
            if ($change) :
565 75
                $vote->setRanking(
566 75
                                    $mirror,
567 75
                                    ( abs($vote->getTimestamp() - microtime(true)) > 0.5 ) ? ($vote->getTimestamp() + 0.001) : false
0 ignored issues
show
Bug introduced by
It seems like abs($vote->getTimestamp(...stamp() + 0.001 : false can also be of type double; however, Condorcet\Vote::setRanking() does only seem to accept boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
568
                );
569
            endif;
570
571 79
            return true;
572
        }
573
574
        // Write a new vote
575 79
        protected function registerVote (Vote $vote, $tag = null) : Vote
576
        {
577
            // Vote identifiant
578 79
            $vote->addTags($tag);
579
            
580
            // Register
581
            try {
582 79
                $vote->registerLink($this);
583 79
                $this->_Votes[] = $vote;
584
            } catch (CondorcetException $e) {
585
                // Security : Check if vote object not already register
586
                throw new CondorcetException(6,'Vote object already registred');
587
            }
588
589 79
            return $vote;
590
        }
591
592
593 5
    public function removeVote ($in, bool $with = true) : array
594
    {    
595 5
        $rem = [];
596
597 5
        if ($in instanceof Vote) :
598 4
            $key = $this->getVoteKey($in);
599 4
            if ($key !== false) :
600 4
                $this->_Votes[$key]->destroyLink($this);
601
602 4
                $rem[] = $this->_Votes[$key];
603
604 4
                unset($this->_Votes[$key]);
605
            endif;
606
        else :
607
            // Prepare Tags
608 3
            $tag = Vote::tagsConvert($in);
609
610
            // Deleting
611 3
            foreach ($this->getVotesList($tag, $with) as $key => $value) :
612 3
                $this->_Votes[$key]->destroyLink($this);
613
614 3
                $rem[] = $this->_Votes[$key];
615
616 3
                unset($this->_Votes[$key]);
617
            endforeach;
618
619
        endif;
620
621 5
        return $rem;
622
    }
623
624
625
    public function jsonVotes (string $input)
626
    {
627
        $input = self::prepareJson($input);
628
        if ($input === false) :
629
            return $input;
630
        endif;
631
632
            //////
633
634
        $adding = [];
635
636
        foreach ($input as $record) :
637
            if (empty($record['vote'])) :
638
                continue;
639
            endif;
640
641
            $tags = (!isset($record['tag'])) ? null : $record['tag'];
642
            $multi = (!isset($record['multi'])) ? 1 : $record['multi'];
643
644
            for ($i = 0; $i < $multi; $i++) :
645
                if (self::$_maxParseIteration !== null && $this->countVotes() >= self::$_maxParseIteration) :
646
                    throw new CondorcetException(12, self::$_maxParseIteration);
647
                endif;
648
649
                try {
650
                    $adding[] = $this->addVote($record['vote'], $tags);
651
                } catch (\Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
652
            endfor;
653
        endforeach;
654
655
        return $adding;
656
    }
657
658 68
    public function parseVotes (string $input, bool $allowFile = true)
659
    {
660 68
        $input = self::prepareParse($input, $allowFile);
661 68
        if ($input === false) :
662
            return $input;
663
        endif;
664
665
        // Check each lines
666 68
        $adding = [];
667 68
        foreach ($input as $line) :
668
            // Empty Line
669 68
            if (empty($line)) :
670 61
                continue;
671
            endif;
672
673
            // Multiples
674 68
            $is_multiple = mb_strpos($line, '*');
675 68
            if ($is_multiple !== false) :
676 61
                $multiple = trim( substr($line, $is_multiple + 1) );
677
678
                // Errors
679 61
                if ( !is_numeric($multiple) ) :
680
                    throw new CondorcetException(13, null);
681
                endif;
682
683 61
                $multiple = intval($multiple);
684
685
                // Reformat line
686 61
                $line = substr($line, 0, $is_multiple);
687
            else :
688 9
                $multiple = 1;
689
            endif;
690
691
            // Vote Weight
692 68
            $is_voteWeight = mb_strpos($line, '^');
693 68
            if ($is_voteWeight !== false) :
694 2
                $weight = trim( substr($line, $is_voteWeight + 1) );
695
696
                // Errors
697 2
                if ( !is_numeric($weight) ) :
698
                    throw new CondorcetException(13, null);
699
                endif;
700
701 2
                $weight = intval($weight);
702
703
                // Reformat line
704 2
                $line = substr($line, 0, $is_voteWeight);
705
            else :
706 67
                $weight = 1;
707
            endif;
708
709
            // Tags + vote
710 68
            if (mb_strpos($line, '||') !== false) :
711 4
                $data = explode('||', $line);
712
713 4
                $vote = $data[1];
714 4
                $tags = $data[0];
715
            // Vote without tags
716
            else :
717 67
                $vote = $line;
718 67
                $tags = null;
719
            endif;
720
721
            // addVote
722 68
            for ($i = 0; $i < $multiple; $i++) :
723 68
                if (self::$_maxParseIteration !== null && count($adding) >= self::$_maxParseIteration) :
724 1
                    throw new CondorcetException(12, self::$_maxParseIteration);
725
                endif;
726
727
                try {
728 68
                    $adding[] = ($newVote = $this->addVote($vote, $tags));
729 68
                    $newVote->setWeight($weight);
730 1
                } catch (CondorcetException $e) {}
1 ignored issue
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
731
            endfor;
732
        endforeach;
733
734 68
        return $adding;
735
    }
736
737
738
    //:: LARGE ELECTION MODE :://
739
740 3
    public function setExternalDataHandler (DataHandlerDriverInterface $driver) : bool
741
    {
742 3
        if (!$this->_Votes->isUsingHandler()) :
743 3
            $this->_Votes->importHandler($driver);
744 3
            return true;
745
        else :
746
            throw new CondorcetException(24);
747
        endif;
748
    }
749
750 1
    public function removeExternalDataHandler () : bool
751
    {
752 1
        if ($this->_Votes->isUsingHandler()) :
753 1
            $this->_Votes->closeHandler();
754 1
            return true;
755
        else :
756
            throw new CondorcetException(23);
757
        endif;
758
    }
759
760
761
    //:: VOTING TOOLS :://
762
763
    // How many votes are registered ?
764 15
    public function countVotes ($tag = null, bool $with = true) : int
765
    {
766 15
        return $this->_Votes->countVotes(Vote::tagsConvert($tag),$with);
767
    }
768
769
    // Get the votes registered list
770 7
    public function getVotesList ($tag = null, bool $with = true) : array
771
    {
772 7
        return $this->_Votes->getVotesList(Vote::tagsConvert($tag), $with);
773
    }
774
775 3
    public function getVotesListAsString () : string
776
    {
777 3
        return $this->_Votes->getVotesListAsString();
778
    }
779
780 68
    public function getVotesManager () : VotesManager {
781 68
        return $this->_Votes;
782
    }
783
784 4
    public function getVoteKey (Vote $vote) {
785 4
        return $this->_Votes->getVoteKey($vote);
786
    }
787
788
    public function getVoteByKey (int $key) {
789
        if (!isset($this->_Votes[$key])) :
790
            return false;
791
        else :
792
            return $this->_Votes[$key];
793
        endif;
794
    }
795
796
797
/////////// RESULTS ///////////
798
799
800
    //:: PUBLIC FUNCTIONS :://
801
802
    // Generic function for default result with ability to change default object method
803 61
    public function getResult ($method = true, array $options = []) : Result
804
    {
805 61
        $options = $this->formatResultOptions($options);
806
807
        // Filter if tag is provided & return
808 61
        if ($options['%tagFilter']) :
809 2
            $chrono = new Timer_Chrono ($this->_timer, 'GetResult with filter');
810
811 2
            $filter = new self;
812
813 2
            foreach ($this->getCandidatesList() as $candidate) :
814 2
                $filter->addCandidate($candidate);
815
            endforeach;
816
817 2
            foreach ($this->getVotesList($options['tags'], $options['withTag']) as $vote) :
818 2
                $filter->addVote($vote);
819
            endforeach;
820
821 2
            unset($chrono);
822
823 2
            return $filter->getResult($method);
824
        endif;
825
826
            ////// Start //////
827
828
        // Prepare
829 61
        $this->prepareResult();
830
831
            //////
832
833 61
        $chrono = new Timer_Chrono ($this->_timer);
834
835 61
        if ($method === true) :
836 3
            $this->initResult(Condorcet::getDefaultMethod());
837 3
            $result = $this->_Calculator[Condorcet::getDefaultMethod()]->getResult();
838 61
        elseif ($method = Condorcet::isAuthMethod((string) $method)) :
839 61
            $this->initResult($method);
840 59
            $result = $this->_Calculator[$method]->getResult();
841
        else :
842
            throw new CondorcetException(8,$method);
843
        endif;
844
845 58
        $chrono->setRole('GetResult for '.$method);
846
847 58
        return $result;
848
    }
849
850
851 65
    public function getWinner (?string $substitution = null)
852
    {
853 65
        $algo = $this->condorcetBasicSubstitution($substitution);
854
855
            //////
856
857 65
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
858 63
            new Timer_Chrono ($this->_timer, 'GetWinner for CondorcetBasic');
859 63
            $this->initResult($algo);
860 63
            $result = $this->_Calculator[$algo]->getWinner();
861
862 63
            return ($result === null) ? null : $this->getCandidateId($result);
863
        else :
864 49
            return $this->getResult($algo)->getWinner();
865
        endif;
866
    }
867
868
869 61
    public function getLoser (?string $substitution = null)
870
    {
871 61
        $algo = $this->condorcetBasicSubstitution($substitution);
872
873
            //////
874
875 61
        if ($algo === Condorcet::CONDORCET_BASIC_CLASS) :
876 61
            new Timer_Chrono ($this->_timer, 'GetLoser for CondorcetBasic');
877 61
            $this->initResult($algo);
878 61
            $result = $this->_Calculator[$algo]->getLoser();
879
880 61
            return ($result === null) ? null : $this->getCandidateId($result);
881
        else :
882 2
            return $this->getResult($algo)->getLoser();
883
        endif;
884
    }
885
886 66
        protected function condorcetBasicSubstitution ($substitution) : string {
887 66
            if ( $substitution ) :
888 49
                if ($substitution === true) :
889
                    $substitution = Condorcet::getDefaultMethod();
890
                endif;
891
                
892 49
                if ( Condorcet::isAuthMethod($substitution) ) :
893 49
                    $algo = $substitution;
894
                else :
895 49
                    throw new CondorcetException(9,$substitution);
896
                endif;
897
            else :
898 64
                $algo = Condorcet::CONDORCET_BASIC_CLASS;
899
            endif;
900
901 66
            return $algo;
902
        }
903
904
905
    public function computeResult ($method = true) : void
906
    {
907
        $this->getResult($method);
908
    }
909
910
911
    //:: TOOLS FOR RESULT PROCESS :://
912
913
914
    // Prepare to compute results & caching system
915 67
    protected function prepareResult () : bool
916
    {
917 67
        if ($this->_State > 2) :
918 60
            return false;
919 67
        elseif ($this->_State === 2) :
920 67
            $this->cleanupResult();
921
922
            // Do Pairewise
923 67
            $this->_Pairwise = new Pairwise ($this);
924
925
            // Change state to result
926 67
            $this->_State = 3;
927
928
            // Return
929 67
            return true;
930
        else :
931
            throw new CondorcetException(6);
932
        endif;
933
    }
934
935
936 67
    protected function initResult (string $class) : void
937
    {
938 67
        if ( !isset($this->_Calculator[$class]) ) :
939 67
            $this->_Calculator[$class] = new $class($this);
940
        endif;
941 65
    }
942
943
944
    // Cleanup results to compute again with new votes
945 67
    protected function cleanupResult () : void
946
    {
947
        // Reset state
948 67
        if ($this->_State > 2) : 
949 8
            $this->_State = 2;
950
        endif;
951
952
            //////
953
954
        // Clean pairwise
955 67
        $this->_Pairwise = null;
956
957
        // Algos
958 67
        $this->_Calculator = null;
959 67
    }
960
961
962 61
    protected function formatResultOptions (array $arg) : array
963
    {
964
        // About tag filter
965 61
        if (isset($arg['tags'])):
966 2
            $arg['%tagFilter'] = true;
967
968 2
            if ( !isset($arg['withTag']) || !is_bool($arg['withTag']) ) :
969 2
                $arg['withTag'] = true;
970
            endif;
971
        else:
972 61
            $arg['%tagFilter'] = false;
973
        endif;
974
975 61
        return $arg;
976
    }
977
978
979
    //:: GET RAW DATA :://
980
981 64
    public function getPairwise (bool $explicit = true)
982
    {
983 64
        $this->prepareResult();
984
985 64
        return (!$explicit) ? $this->_Pairwise : $this->_Pairwise->getExplicitPairwise();
986
    }
987
988
}
989